1/***********************************************************************
2*                                                                      *
3*               This software is part of the ast package               *
4*          Copyright (c) 1985-2010 AT&T Intellectual Property          *
5*                      and is licensed under the                       *
6*                  Common Public License, Version 1.0                  *
7*                    by AT&T Intellectual Property                     *
8*                                                                      *
9*                A copy of the License is available at                 *
10*            http://www.opensource.org/licenses/cpl1.0.txt             *
11*         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
12*                                                                      *
13*              Information and Software Systems Research               *
14*                            AT&T Research                             *
15*                           Florham Park NJ                            *
16*                                                                      *
17*                 Glenn Fowler <gsf@research.att.com>                  *
18*                  David Korn <dgk@research.att.com>                   *
19*                   Phong Vo <kpv@research.att.com>                    *
20*                                                                      *
21***********************************************************************/
22#pragma prototyped
23/*
24 * Glenn Fowler
25 * AT&T Research
26 *
27 * Time_t conversion support
28 */
29
30#include <tmx.h>
31#include <ctype.h>
32
33#define warped(t,n)	((t)<((n)-tmxsns(6L*30L*24L*60L*60L,0))||(t)>((n)+tmxsns(24L*60L*60L,0)))
34
35/*
36 * format n with padding p into s
37 * return end of s
38 *
39 * p:	<0	blank padding
40 *	 0	no padding
41 *	>0	0 padding
42 */
43
44static char*
45number(register char* s, register char* e, register long n, register int p, int w, int pad)
46{
47	char*	b;
48
49	if (w)
50	{
51		if (p > 0 && (pad == 0 || pad == '0'))
52			while (w > p)
53			{
54				p++;
55				n *= 10;
56			}
57		else if (w > p)
58			p = w;
59	}
60	switch (pad)
61	{
62	case '-':
63		p = 0;
64		break;
65	case '_':
66		if (p > 0)
67			p = -p;
68		break;
69	case '0':
70		if (p < 0)
71			p = -p;
72		break;
73	}
74	b = s;
75	if (p > 0)
76		s += sfsprintf(s, e - s, "%0*lu", p, n);
77	else if (p < 0)
78		s += sfsprintf(s, e - s, "%*lu", -p, n);
79	else
80		s += sfsprintf(s, e - s, "%lu", n);
81	if (w && (s - b) > w)
82		*(s = b + w) = 0;
83	return s;
84}
85
86typedef struct Stack_s
87{
88	char*		format;
89	int		delimiter;
90} Stack_t;
91
92/*
93 * format t into buf of length len
94 * end of buf is returned
95 */
96
97char*
98tmxfmt(char* buf, size_t len, const char* format, Time_t t)
99{
100	register char*	cp;
101	register char*	ep;
102	register char*	p;
103	register int	n;
104	int		c;
105	int		i;
106	int		flags;
107	int		alt;
108	int		pad;
109	int		delimiter;
110	int		width;
111	int		prec;
112	int		parts;
113	char*		arg;
114	char*		f;
115	const char*	oformat;
116	Tm_t*		tm;
117	Tm_zone_t*	zp;
118	Time_t		now;
119	Stack_t*	sp;
120	Stack_t		stack[8];
121	Tm_t		ts;
122	char		argbuf[256];
123	char		fmt[32];
124
125	tmlocale();
126	tm = tmxtm(&ts, t, NiL);
127	if (!format || !*format)
128		format = tm_info.deformat;
129	oformat = format;
130	flags = tm_info.flags;
131	sp = &stack[0];
132	cp = buf;
133	ep = buf + len;
134	delimiter = 0;
135	for (;;)
136	{
137		if ((c = *format++) == delimiter)
138		{
139			delimiter = 0;
140			if (sp <= &stack[0])
141				break;
142			sp--;
143			format = sp->format;
144			delimiter = sp->delimiter;
145			continue;
146		}
147		if (c != '%')
148		{
149			if (cp < ep)
150				*cp++ = c;
151			continue;
152		}
153		alt = 0;
154		arg = 0;
155		pad = 0;
156		width = 0;
157		prec = 0;
158		parts = 0;
159		for (;;)
160		{
161			switch (c = *format++)
162			{
163			case '_':
164			case '-':
165				pad = c;
166				continue;
167			case 'E':
168			case 'O':
169				if (!isalpha(*format))
170					break;
171				alt = c;
172				continue;
173			case '0':
174				if (!parts)
175				{
176					pad = c;
177					continue;
178				}
179				/*FALLTHROUGH*/
180			case '1':
181			case '2':
182			case '3':
183			case '4':
184			case '5':
185			case '6':
186			case '7':
187			case '8':
188			case '9':
189				switch (parts)
190				{
191				case 0:
192					parts++;
193					/*FALLTHROUGH*/
194				case 1:
195					width = width * 10 + (c - '0');
196					break;
197				case 2:
198					prec = prec * 10 + (c - '0');
199					break;
200				}
201				continue;
202			case '.':
203				if (!parts++)
204					parts++;
205				continue;
206			case '(':
207				i = 1;
208				arg = argbuf;
209				for (;;)
210				{
211					if (!(c = *format++))
212					{
213						format--;
214						break;
215					}
216					else if (c == '(')
217						i++;
218					else if (c == ')' && !--i)
219						break;
220					else if (arg < &argbuf[sizeof(argbuf) - 1])
221						*arg++ = c;
222				}
223				*arg = 0;
224				arg = argbuf;
225				continue;
226			default:
227				break;
228			}
229			break;
230		}
231		switch (c)
232		{
233		case 0:
234			format--;
235			continue;
236		case '%':
237			if (cp < ep)
238				*cp++ = '%';
239			continue;
240		case '?':
241			if (tm_info.deformat != tm_info.format[TM_DEFAULT])
242				format = tm_info.deformat;
243			else if (!*format)
244				format = tm_info.format[TM_DEFAULT];
245			continue;
246		case 'a':	/* abbreviated day of week name */
247			n = TM_DAY_ABBREV + tm->tm_wday;
248			goto index;
249		case 'A':	/* day of week name */
250			n = TM_DAY + tm->tm_wday;
251			goto index;
252		case 'b':	/* abbreviated month name */
253		case 'h':
254			n = TM_MONTH_ABBREV + tm->tm_mon;
255			goto index;
256		case 'B':	/* month name */
257			n = TM_MONTH + tm->tm_mon;
258			goto index;
259		case 'c':	/* `ctime(3)' date sans newline */
260			p = tm_info.format[TM_CTIME];
261			goto push;
262		case 'C':	/* 2 digit century */
263			cp = number(cp, ep, (long)(1900 + tm->tm_year) / 100, 2, width, pad);
264			continue;
265		case 'd':	/* day of month */
266			cp = number(cp, ep, (long)tm->tm_mday, 2, width, pad);
267			continue;
268		case 'D':	/* date */
269			p = tm_info.format[TM_DATE];
270			goto push;
271		case 'E':       /* OBSOLETE no pad day of month */
272			cp = number(cp, ep, (long)tm->tm_mday, 0, width, pad);
273			continue;
274		case 'e':       /* blank padded day of month */
275			cp = number(cp, ep, (long)tm->tm_mday, -2, width, pad);
276			continue;
277		case 'f':	/* TM_DEFAULT override */
278			p = tm_info.deformat;
279			goto push;
280		case 'F':	/* ISO 8601:2000 standard date format */
281			p = "%Y-%m-%d";
282			goto push;
283		case 'g':	/* %V 2 digit year */
284		case 'G':	/* %V 4 digit year */
285			n = tm->tm_year + 1900;
286			if (tm->tm_yday < 7)
287			{
288				if (tmweek(tm, 2, -1, -1) >= 52)
289					n--;
290			}
291			else if (tm->tm_yday > 358)
292			{
293				if (tmweek(tm, 2, -1, -1) <= 1)
294					n++;
295			}
296			if (c == 'g')
297			{
298				n %= 100;
299				c = 2;
300			}
301			else
302				c = 4;
303			cp = number(cp, ep, (long)n, c, width, pad);
304			continue;
305		case 'H':	/* hour (0 - 23) */
306			cp = number(cp, ep, (long)tm->tm_hour, 2, width, pad);
307			continue;
308		case 'i':	/* international `date(1)' date */
309			p = tm_info.format[TM_INTERNATIONAL];
310			goto push;
311		case 'I':	/* hour (0 - 12) */
312			if ((n = tm->tm_hour) > 12) n -= 12;
313			else if (n == 0) n = 12;
314			cp = number(cp, ep, (long)n, 2, width, pad);
315			continue;
316		case 'J':	/* Julian date (0 offset) */
317			cp = number(cp, ep, (long)tm->tm_yday, 3, width, pad);
318			continue;
319		case 'j':	/* Julian date (1 offset) */
320			cp = number(cp, ep, (long)(tm->tm_yday + 1), 3, width, pad);
321			continue;
322		case 'k':	/* `date(1)' date */
323			p = tm_info.format[TM_DATE_1];
324			goto push;
325		case 'K':
326			switch (alt)
327			{
328			case 'E':
329				p = (pad == '_') ? "%Y-%m-%d %H:%M:%S.%N %z" : "%Y-%m-%d+%H:%M:%S.%N%z";
330				break;
331			case 'O':
332				p = (pad == '_') ? "%Y-%m-%d %H:%M:%S.%N" : "%Y-%m-%d+%H:%M:%S.%N";
333				break;
334			default:
335				p = (pad == '_') ? "%Y-%m-%d %H:%M:%S" : "%Y-%m-%d+%H:%M:%S";
336				break;
337			}
338			goto push;
339		case 'l':	/* `ls -l' date */
340			if (t)
341			{
342				now = tmxgettime();
343				if (warped(t, now))
344				{
345					p = tm_info.format[TM_DISTANT];
346					goto push;
347				}
348			}
349			p = tm_info.format[TM_RECENT];
350			goto push;
351		case 'L':	/* TM_DEFAULT */
352		case 'O':	/* OBSOLETE */
353			p = tm_info.format[TM_DEFAULT];
354			goto push;
355		case 'm':	/* month number */
356			cp = number(cp, ep, (long)(tm->tm_mon + 1), 2, width, pad);
357			continue;
358		case 'M':	/* minutes */
359			cp = number(cp, ep, (long)tm->tm_min, 2, width, pad);
360			continue;
361		case 'n':
362			if (cp < ep)
363				*cp++ = '\n';
364			continue;
365		case 'N':	/* nanosecond part */
366			cp = number(cp, ep, (long)tm->tm_nsec, 9, width, pad);
367			continue;
368		case 'o':	/* set options */
369			if (arg)
370				goto options;
371			/*OBSOLETE*/
372			p = tm_info.deformat;
373			goto push;
374		case 'p':	/* meridian */
375			n = TM_MERIDIAN + (tm->tm_hour >= 12);
376			goto index;
377		case 'q':	/* time zone type (nation code) */
378			if (!(flags & TM_UTC))
379			{
380				if ((zp = tm->tm_zone) != tm_info.local)
381					for (; zp >= tm_data.zone; zp--)
382						if (p = zp->type)
383							goto string;
384				else if (p = zp->type)
385					goto string;
386			}
387			continue;
388		case 'Q':	/* %Q<alpha> or %Q<delim>recent<delim>distant<delim> */
389			if (c = *format)
390			{
391				format++;
392				if (isalpha(c))
393				{
394					switch (c)
395					{
396					case 'd':	/* `ls -l' distant date */
397						p = tm_info.format[TM_DISTANT];
398						goto push;
399					case 'r':	/* `ls -l' recent date */
400						p = tm_info.format[TM_RECENT];
401						goto push;
402					default:
403						format--;
404						break;
405					}
406				}
407				else
408				{
409					if (t)
410					{
411						now = tmxgettime();
412						p = warped(t, now) ? (char*)0 : (char*)format;
413					}
414					else
415						p = (char*)format;
416					i = 0;
417					while (n = *format)
418					{
419						format++;
420						if (n == c)
421						{
422							if (!p)
423								p = (char*)format;
424							if (++i == 2)
425								goto push_delimiter;
426						}
427					}
428				}
429			}
430			continue;
431		case 'r':
432			p = tm_info.format[TM_MERIDIAN_TIME];
433			goto push;
434		case 'R':
435			p = "%H:%M";
436			goto push;
437		case 's':	/* seconds[.nanoseconds] since the epoch */
438		case '#':
439			now = t;
440			f = fmt;
441			*f++ = '%';
442			if (pad == '0')
443				*f++ = pad;
444			if (width)
445				f += sfsprintf(f, &fmt[sizeof(fmt)] - f, "%d", width);
446			f += sfsprintf(f, &fmt[sizeof(fmt)] - f, "I%du", sizeof(Tmxsec_t));
447			cp += sfsprintf(cp, ep - cp, fmt, tmxsec(now));
448			if (parts > 1)
449			{
450				n = sfsprintf(cp, ep - cp, ".%09I*u", sizeof(Tmxnsec_t), tmxnsec(now));
451				if (prec && n >= prec)
452					n = prec + 1;
453				cp += n;
454			}
455			continue;
456		case 'S':	/* seconds */
457			cp = number(cp, ep, (long)tm->tm_sec, 2, width, pad);
458			if ((flags & TM_SUBSECOND) && (format - 2) != oformat)
459			{
460				p = ".%N";
461				goto push;
462			}
463			continue;
464		case 't':
465			if (cp < ep)
466				*cp++ = '\t';
467			continue;
468		case 'T':
469			p = tm_info.format[TM_TIME];
470			goto push;
471		case 'u':	/* weekday number [1(Monday)-7] */
472			if (!(i = tm->tm_wday))
473				i = 7;
474			cp = number(cp, ep, (long)i, 0, width, pad);
475			continue;
476		case 'U':	/* week number, Sunday as first day */
477			cp = number(cp, ep, (long)tmweek(tm, 0, -1, -1), 2, width, pad);
478			continue;
479		case 'V':	/* ISO week number */
480			cp = number(cp, ep, (long)tmweek(tm, 2, -1, -1), 2, width, pad);
481			continue;
482		case 'W':	/* week number, Monday as first day */
483			cp = number(cp, ep, (long)tmweek(tm, 1, -1, -1), 2, width, pad);
484			continue;
485		case 'w':	/* weekday number [0(Sunday)-6] */
486			cp = number(cp, ep, (long)tm->tm_wday, 0, width, pad);
487			continue;
488		case 'x':
489			p = tm_info.format[TM_DATE];
490			goto push;
491		case 'X':
492			p = tm_info.format[TM_TIME];
493			goto push;
494		case 'y':	/* year in the form yy */
495			cp = number(cp, ep, (long)(tm->tm_year % 100), 2, width, pad);
496			continue;
497		case 'Y':	/* year in the form ccyy */
498			cp = number(cp, ep, (long)(1900 + tm->tm_year), 4, width, pad);
499			continue;
500		case 'z':	/* time zone west offset */
501			if (arg)
502			{
503				if ((zp = tmzone(arg, &f, 0, 0)) && !*f && tm->tm_zone != zp)
504					tm = tmxtm(tm, tmxtime(tm, tm->tm_zone->west + (tm->tm_isdst ? tm->tm_zone->dst : 0)), zp);
505				continue;
506			}
507			if ((ep - cp) >= 16)
508				cp = tmpoff(cp, ep - cp, "", (flags & TM_UTC) ? 0 : tm->tm_zone->west + (tm->tm_isdst ? tm->tm_zone->dst : 0), 24 * 60);
509			continue;
510		case 'Z':	/* time zone */
511			if (arg)
512			{
513				if ((zp = tmzone(arg, &f, 0, 0)) && !*f && tm->tm_zone != zp)
514					tm = tmxtm(tm, tmxtime(tm, tm->tm_zone->west + (tm->tm_isdst ? tm->tm_zone->dst : 0)), zp);
515				continue;
516			}
517			p = (flags & TM_UTC) ? tm_info.format[TM_UT] : tm->tm_isdst && tm->tm_zone->daylight ? tm->tm_zone->daylight : tm->tm_zone->standard;
518			goto string;
519		case '+':	/* old %+flag */
520		case '!':	/* old %!flag */
521			format--;
522			/*FALLTHROUGH*/
523		case '=':	/* old %=[=][+-]flag */
524			for (arg = argbuf; *format == '=' || *format == '-' || *format == '+' || *format == '!'; format++)
525				if (arg < &argbuf[sizeof(argbuf) - 2])
526					*arg++ = *format;
527			if (*arg++ = *format)
528				format++;
529			*arg = 0;
530			arg = argbuf;
531			goto options;
532		default:
533			if (cp < ep)
534				*cp++ = '%';
535			if (cp < ep)
536				*cp++ = c;
537			continue;
538		}
539	index:
540		p = tm_info.format[n];
541	string:
542		while (cp < ep && (*cp = *p++))
543			cp++;
544		continue;
545	options:
546		c = '+';
547		i = 0;
548		for (;;)
549		{
550			switch (*arg++)
551			{
552			case 0:
553				n = 0;
554				break;
555			case '=':
556				i = !i;
557				continue;
558			case '+':
559			case '-':
560			case '!':
561				c = *(arg - 1);
562				continue;
563			case 'l':
564				n = TM_LEAP;
565				break;
566			case 'n':
567			case 's':
568				n = TM_SUBSECOND;
569				break;
570			case 'u':
571				n = TM_UTC;
572				break;
573			default:
574				continue;
575			}
576			if (!n)
577				break;
578
579			/*
580			 * right, the global state stinks
581			 * but we respect its locale-like status
582			 */
583
584			if (c == '+')
585			{
586				if (!(flags & n))
587				{
588					flags |= n;
589					tm_info.flags |= n;
590					tm = tmxtm(tm, t, (flags & TM_UTC) ? &tm_data.zone[2] : tm->tm_zone);
591					if (!i)
592						tm_info.flags &= ~n;
593				}
594			}
595			else if (flags & n)
596			{
597				flags &= ~n;
598				tm_info.flags &= ~n;
599				tm = tmxtm(tm, t, (flags & TM_UTC) ? &tm_data.zone[2] : tm->tm_zone);
600				if (!i)
601					tm_info.flags |= n;
602			}
603		}
604		continue;
605	push:
606		c = 0;
607	push_delimiter:
608		if (sp < &stack[elementsof(stack)])
609		{
610			sp->format = (char*)format;
611			format = p;
612			sp->delimiter = delimiter;
613			delimiter = c;
614			sp++;
615		}
616		continue;
617	}
618	tm_info.flags = flags;
619	if (cp >= ep)
620		cp = ep - 1;
621	*cp = 0;
622	return cp;
623}
624