1/***********************************************************************
2*                                                                      *
3*               This software is part of the ast package               *
4*          Copyright (c) 1985-2011 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':       /* blank padded day of month */
272			cp = number(cp, ep, (long)tm->tm_mday, -2, width, pad);
273			continue;
274		case 'f':	/* (AST) OBSOLETE use %Qf */
275			p = "%Qf";
276			goto push;
277		case 'F':	/* ISO 8601:2000 standard date format */
278			p = "%Y-%m-%d";
279			goto push;
280		case 'g':	/* %V 2 digit year */
281		case 'G':	/* %V 4 digit year */
282			n = tm->tm_year + 1900;
283			if (tm->tm_yday < 7)
284			{
285				if (tmweek(tm, 2, -1, -1) >= 52)
286					n--;
287			}
288			else if (tm->tm_yday > 358)
289			{
290				if (tmweek(tm, 2, -1, -1) <= 1)
291					n++;
292			}
293			if (c == 'g')
294			{
295				n %= 100;
296				c = 2;
297			}
298			else
299				c = 4;
300			cp = number(cp, ep, (long)n, c, width, pad);
301			continue;
302		case 'H':	/* hour (0 - 23) */
303			cp = number(cp, ep, (long)tm->tm_hour, 2, width, pad);
304			continue;
305		case 'i':	/* (AST) OBSOLETE use %QI */
306			p = "%QI";
307			goto push;
308		case 'I':	/* hour (0 - 12) */
309			if ((n = tm->tm_hour) > 12) n -= 12;
310			else if (n == 0) n = 12;
311			cp = number(cp, ep, (long)n, 2, width, pad);
312			continue;
313		case 'j':	/* Julian date (1 offset) */
314			cp = number(cp, ep, (long)(tm->tm_yday + 1), 3, 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 'k':	/* (AST) OBSOLETE use %QD */
320			p = "%QD";
321			goto push;
322		case 'K':	/* (AST) largest to smallest */
323			switch (alt)
324			{
325			case 'E':
326				p = (pad == '_') ? "%Y-%m-%d %H:%M:%S.%N %z" : "%Y-%m-%d+%H:%M:%S.%N%z";
327				break;
328			case 'O':
329				p = (pad == '_') ? "%Y-%m-%d %H:%M:%S.%N" : "%Y-%m-%d+%H:%M:%S.%N";
330				break;
331			default:
332				p = (pad == '_') ? "%Y-%m-%d %H:%M:%S" : "%Y-%m-%d+%H:%M:%S";
333				break;
334			}
335			goto push;
336		case 'l':	/* (AST) OBSOLETE use %QL */
337			p = "%QL";
338			goto push;
339		case 'L':	/* (AST) OBSOLETE use %Ql */
340			p = "%Ql";
341			goto push;
342		case 'm':	/* month number */
343			cp = number(cp, ep, (long)(tm->tm_mon + 1), 2, width, pad);
344			continue;
345		case 'M':	/* minutes */
346			cp = number(cp, ep, (long)tm->tm_min, 2, width, pad);
347			continue;
348		case 'n':
349			if (cp < ep)
350				*cp++ = '\n';
351			continue;
352		case 'N':	/* (AST|GNU) nanosecond part */
353			cp = number(cp, ep, (long)tm->tm_nsec, 9, width, pad);
354			continue;
355#if 0
356		case 'o':	/* (UNUSED) */
357			continue;
358#endif
359		case 'p':	/* meridian */
360			n = TM_MERIDIAN + (tm->tm_hour >= 12);
361			goto index;
362		case 'P':	/* (AST|GNU) lower case meridian */
363			p = tm_info.format[TM_MERIDIAN + (tm->tm_hour >= 12)];
364			while (cp < ep && (n = *p++))
365				*cp++ = isupper(n) ? tolower(n) : n;
366			continue;
367		case 'q':	/* (AST) OBSOLETE use %Qz */
368			p = "%Qz";
369			goto push;
370		case 'Q':	/* (AST) %Q<alpha> or %Q<delim>recent<delim>distant<delim> */
371			if (c = *format)
372			{
373				format++;
374				if (isalpha(c))
375				{
376					switch (c)
377					{
378					case 'd':	/* `ls -l' distant date */
379						p = tm_info.format[TM_DISTANT];
380						goto push;
381					case 'D':	/* `date(1)' date */
382						p = tm_info.format[TM_DATE_1];
383						goto push;
384					case 'f':	/* TM_DEFAULT override */
385						p = tm_info.deformat;
386						goto push;
387					case 'I':	/* international `date(1)' date */
388						p = tm_info.format[TM_INTERNATIONAL];
389						goto push;
390					case 'l':	/* TM_DEFAULT */
391						p = tm_info.format[TM_DEFAULT];
392						goto push;
393					case 'L':	/* `ls -l' date */
394						if (t)
395						{
396							now = tmxgettime();
397							if (warped(t, now))
398							{
399								p = tm_info.format[TM_DISTANT];
400								goto push;
401							}
402						}
403						p = tm_info.format[TM_RECENT];
404						goto push;
405					case 'o':	/* set options ( %([+-]flag...)o ) */
406						if (arg)
407						{
408							c = '+';
409							i = 0;
410							for (;;)
411							{
412								switch (*arg++)
413								{
414								case 0:
415									n = 0;
416									break;
417								case '=':
418									i = !i;
419									continue;
420								case '+':
421								case '-':
422								case '!':
423									c = *(arg - 1);
424									continue;
425								case 'l':
426									n = TM_LEAP;
427									break;
428								case 'n':
429								case 's':
430									n = TM_SUBSECOND;
431									break;
432								case 'u':
433									n = TM_UTC;
434									break;
435								default:
436									continue;
437								}
438								if (!n)
439									break;
440
441								/*
442								 * right, the global state stinks
443								 * but we respect its locale-like status
444								 */
445
446								if (c == '+')
447								{
448									if (!(flags & n))
449									{
450										flags |= n;
451										tm_info.flags |= n;
452										tm = tmxtm(tm, t, (flags & TM_UTC) ? &tm_data.zone[2] : tm->tm_zone);
453										if (!i)
454											tm_info.flags &= ~n;
455									}
456								}
457								else if (flags & n)
458								{
459									flags &= ~n;
460									tm_info.flags &= ~n;
461									tm = tmxtm(tm, t, (flags & TM_UTC) ? &tm_data.zone[2] : tm->tm_zone);
462									if (!i)
463										tm_info.flags |= n;
464								}
465							}
466						}
467						break;
468					case 'r':	/* `ls -l' recent date */
469						p = tm_info.format[TM_RECENT];
470						goto push;
471					case 'z':	/* time zone nation code */
472						if (!(flags & TM_UTC))
473						{
474							if ((zp = tm->tm_zone) != tm_info.local)
475								for (; zp >= tm_data.zone; zp--)
476									if (p = zp->type)
477										goto string;
478							else if (p = zp->type)
479								goto string;
480						}
481						break;
482					default:
483						format--;
484						break;
485					}
486				}
487				else
488				{
489					if (t)
490					{
491						now = tmxgettime();
492						p = warped(t, now) ? (char*)0 : (char*)format;
493					}
494					else
495						p = (char*)format;
496					i = 0;
497					while (n = *format)
498					{
499						format++;
500						if (n == c)
501						{
502							if (!p)
503								p = (char*)format;
504							if (++i == 2)
505								goto push_delimiter;
506						}
507					}
508				}
509			}
510			continue;
511		case 'r':
512			p = tm_info.format[TM_MERIDIAN_TIME];
513			goto push;
514		case 'R':
515			p = "%H:%M";
516			goto push;
517		case 's':	/* (DEFACTO) seconds[.nanoseconds] since the epoch */
518		case '#':
519			now = t;
520			f = fmt;
521			*f++ = '%';
522			if (pad == '0')
523				*f++ = pad;
524			if (width)
525				f += sfsprintf(f, &fmt[sizeof(fmt)] - f, "%d", width);
526			f += sfsprintf(f, &fmt[sizeof(fmt)] - f, "I%du", sizeof(Tmxsec_t));
527			cp += sfsprintf(cp, ep - cp, fmt, tmxsec(now));
528			if (parts > 1)
529			{
530				n = sfsprintf(cp, ep - cp, ".%09I*u", sizeof(Tmxnsec_t), tmxnsec(now));
531				if (prec && n >= prec)
532					n = prec + 1;
533				cp += n;
534			}
535			continue;
536		case 'S':	/* seconds */
537			cp = number(cp, ep, (long)tm->tm_sec, 2, width, pad);
538			if ((flags & TM_SUBSECOND) && (format - 2) != oformat)
539			{
540				p = ".%N";
541				goto push;
542			}
543			continue;
544		case 't':
545			if (cp < ep)
546				*cp++ = '\t';
547			continue;
548		case 'T':
549			p = tm_info.format[TM_TIME];
550			goto push;
551		case 'u':	/* weekday number [1(Monday)-7] */
552			if (!(i = tm->tm_wday))
553				i = 7;
554			cp = number(cp, ep, (long)i, 0, width, pad);
555			continue;
556		case 'U':	/* week number, Sunday as first day */
557			cp = number(cp, ep, (long)tmweek(tm, 0, -1, -1), 2, width, pad);
558			continue;
559#if 0
560		case 'v':	/* (UNUSED) */
561			continue;
562#endif
563		case 'V':	/* ISO week number */
564			cp = number(cp, ep, (long)tmweek(tm, 2, -1, -1), 2, width, pad);
565			continue;
566		case 'W':	/* week number, Monday as first day */
567			cp = number(cp, ep, (long)tmweek(tm, 1, -1, -1), 2, width, pad);
568			continue;
569		case 'w':	/* weekday number [0(Sunday)-6] */
570			cp = number(cp, ep, (long)tm->tm_wday, 0, width, pad);
571			continue;
572		case 'x':
573			p = tm_info.format[TM_DATE];
574			goto push;
575		case 'X':
576			p = tm_info.format[TM_TIME];
577			goto push;
578		case 'y':	/* year in the form yy */
579			cp = number(cp, ep, (long)(tm->tm_year % 100), 2, width, pad);
580			continue;
581		case 'Y':	/* year in the form ccyy */
582			cp = number(cp, ep, (long)(1900 + tm->tm_year), 4, width, pad);
583			continue;
584		case 'z':	/* time zone west offset */
585			if (arg)
586			{
587				if ((zp = tmzone(arg, &f, 0, 0)) && !*f && tm->tm_zone != zp)
588					tm = tmxtm(tm, tmxtime(tm, tm->tm_zone->west + (tm->tm_isdst ? tm->tm_zone->dst : 0)), zp);
589				continue;
590			}
591			if ((ep - cp) >= 16)
592				cp = tmpoff(cp, ep - cp, "", (flags & TM_UTC) ? 0 : tm->tm_zone->west + (tm->tm_isdst ? tm->tm_zone->dst : 0), pad == '_' ? -24 * 60 : 24 * 60);
593			continue;
594		case 'Z':	/* time zone */
595			if (arg)
596			{
597				if ((zp = tmzone(arg, &f, 0, 0)) && !*f && tm->tm_zone != zp)
598					tm = tmxtm(tm, tmxtime(tm, tm->tm_zone->west + (tm->tm_isdst ? tm->tm_zone->dst : 0)), zp);
599				continue;
600			}
601			p = (flags & TM_UTC) ? tm_info.format[TM_UT] : tm->tm_isdst && tm->tm_zone->daylight ? tm->tm_zone->daylight : tm->tm_zone->standard;
602			goto string;
603		case '=':	/* (AST) OBSOLETE use %([+-]flag...)Qo (old %=[=][+-]flag) */
604			for (arg = argbuf; *format == '=' || *format == '-' || *format == '+' || *format == '!'; format++)
605				if (arg < &argbuf[sizeof(argbuf) - 2])
606					*arg++ = *format;
607			if (*arg++ = *format)
608				format++;
609			*arg = 0;
610			arg = argbuf;
611			goto options;
612		default:
613			if (cp < ep)
614				*cp++ = '%';
615			if (cp < ep)
616				*cp++ = c;
617			continue;
618		}
619	index:
620		p = tm_info.format[n];
621	string:
622		while (cp < ep && (*cp = *p++))
623			cp++;
624		continue;
625	options:
626		c = '+';
627		i = 0;
628		for (;;)
629		{
630			switch (*arg++)
631			{
632			case 0:
633				n = 0;
634				break;
635			case '=':
636				i = !i;
637				continue;
638			case '+':
639			case '-':
640			case '!':
641				c = *(arg - 1);
642				continue;
643			case 'l':
644				n = TM_LEAP;
645				break;
646			case 'n':
647			case 's':
648				n = TM_SUBSECOND;
649				break;
650			case 'u':
651				n = TM_UTC;
652				break;
653			default:
654				continue;
655			}
656			if (!n)
657				break;
658
659			/*
660			 * right, the global state stinks
661			 * but we respect its locale-like status
662			 */
663
664			if (c == '+')
665			{
666				if (!(flags & n))
667				{
668					flags |= n;
669					tm_info.flags |= n;
670					tm = tmxtm(tm, t, (flags & TM_UTC) ? &tm_data.zone[2] : tm->tm_zone);
671					if (!i)
672						tm_info.flags &= ~n;
673				}
674			}
675			else if (flags & n)
676			{
677				flags &= ~n;
678				tm_info.flags &= ~n;
679				tm = tmxtm(tm, t, (flags & TM_UTC) ? &tm_data.zone[2] : tm->tm_zone);
680				if (!i)
681					tm_info.flags |= n;
682			}
683		}
684		continue;
685	push:
686		c = 0;
687	push_delimiter:
688		if (sp < &stack[elementsof(stack)])
689		{
690			sp->format = (char*)format;
691			format = p;
692			sp->delimiter = delimiter;
693			delimiter = c;
694			sp++;
695		}
696		continue;
697	}
698	tm_info.flags = flags;
699	if (cp >= ep)
700		cp = ep - 1;
701	*cp = 0;
702	return cp;
703}
704