1 /***********************************************************************
2 *                                                                      *
3 *               This software is part of the ast package               *
4 *          Copyright (c) 1985-2012 AT&T Intellectual Property          *
5 *                      and is licensed under the                       *
6 *                 Eclipse Public License, Version 1.0                  *
7 *                    by AT&T Intellectual Property                     *
8 *                                                                      *
9 *                A copy of the License is available at                 *
10 *          http://www.eclipse.org/org/documents/epl-v10.html           *
11 *         (with md5 checksum b35adb5213ca9657e911e9befb180842)         *
12 *                                                                      *
13 *              Information and Software Systems Research               *
14 *                            AT&T Research                             *
15 *                           Florham Park NJ                            *
16 *                                                                      *
17 *               Glenn Fowler <glenn.s.fowler@gmail.com>                *
18 *                    David Korn <dgkorn@gmail.com>                     *
19 *                     Phong Vo <phongvo@gmail.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 
44 static char*
number(register char * s,register char * e,register long n,register int p,int w,int pad)45 number(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 
86 typedef 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 
97 char*
tmxfmt(char * buf,size_t len,const char * format,Time_t t)98 tmxfmt(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*		e;
115 	char*		f;
116 	const char*	oformat;
117 	Tm_t*		tm;
118 	Tm_zone_t*	zp;
119 	Time_t		now;
120 	Stack_t*	sp;
121 	Stack_t		stack[8];
122 	Tm_t		ts;
123 	char		argbuf[256];
124 	char		fmt[32];
125 
126 	tmlocale();
127 	tm = tmxtm(&ts, t, NiL);
128 	if (!format || !*format)
129 		format = tm_info.deformat;
130 	oformat = format;
131 	flags = tm_info.flags;
132 	sp = &stack[0];
133 	cp = buf;
134 	ep = buf + len;
135 	delimiter = 0;
136 	for (;;)
137 	{
138 		if ((c = *format++) == delimiter)
139 		{
140 			delimiter = 0;
141 			if (sp <= &stack[0])
142 				break;
143 			sp--;
144 			format = sp->format;
145 			delimiter = sp->delimiter;
146 			continue;
147 		}
148 		if (c != '%')
149 		{
150 			if (cp < ep)
151 				*cp++ = c;
152 			continue;
153 		}
154 		alt = 0;
155 		arg = 0;
156 		pad = 0;
157 		width = 0;
158 		prec = 0;
159 		parts = 0;
160 		for (;;)
161 		{
162 			switch (c = *format++)
163 			{
164 			case '_':
165 			case '-':
166 				pad = c;
167 				continue;
168 			case 'E':
169 			case 'O':
170 				if (!isalpha(*format))
171 					break;
172 				alt = c;
173 				continue;
174 			case '0':
175 				if (!parts)
176 				{
177 					pad = c;
178 					continue;
179 				}
180 				/*FALLTHROUGH*/
181 			case '1':
182 			case '2':
183 			case '3':
184 			case '4':
185 			case '5':
186 			case '6':
187 			case '7':
188 			case '8':
189 			case '9':
190 				switch (parts)
191 				{
192 				case 0:
193 					parts++;
194 					/*FALLTHROUGH*/
195 				case 1:
196 					width = width * 10 + (c - '0');
197 					break;
198 				case 2:
199 					prec = prec * 10 + (c - '0');
200 					break;
201 				}
202 				continue;
203 			case '.':
204 				if (!parts++)
205 					parts++;
206 				continue;
207 			case '(':
208 				i = 1;
209 				arg = argbuf;
210 				for (;;)
211 				{
212 					if (!(c = *format++))
213 					{
214 						format--;
215 						break;
216 					}
217 					else if (c == '(')
218 						i++;
219 					else if (c == ')' && !--i)
220 						break;
221 					else if (arg < &argbuf[sizeof(argbuf) - 1])
222 						*arg++ = c;
223 				}
224 				*arg = 0;
225 				arg = argbuf;
226 				continue;
227 			default:
228 				break;
229 			}
230 			break;
231 		}
232 		switch (c)
233 		{
234 		case 0:
235 			format--;
236 			continue;
237 		case '%':
238 			if (cp < ep)
239 				*cp++ = '%';
240 			continue;
241 		case '?':
242 			if (tm_info.deformat != tm_info.format[TM_DEFAULT])
243 				format = tm_info.deformat;
244 			else if (!*format)
245 				format = tm_info.format[TM_DEFAULT];
246 			continue;
247 		case 'a':	/* abbreviated day of week name */
248 			n = TM_DAY_ABBREV + tm->tm_wday;
249 			goto index;
250 		case 'A':	/* day of week name */
251 			n = TM_DAY + tm->tm_wday;
252 			goto index;
253 		case 'b':	/* abbreviated month name */
254 		case 'h':
255 			n = TM_MONTH_ABBREV + tm->tm_mon;
256 			goto index;
257 		case 'B':	/* month name */
258 			n = TM_MONTH + tm->tm_mon;
259 			goto index;
260 		case 'c':	/* `ctime(3)' date sans newline */
261 			p = tm_info.format[TM_CTIME];
262 			goto push;
263 		case 'C':	/* 2 digit century */
264 			cp = number(cp, ep, (long)(1900 + tm->tm_year) / 100, 2, width, pad);
265 			continue;
266 		case 'd':	/* day of month */
267 			cp = number(cp, ep, (long)tm->tm_mday, 2, width, pad);
268 			continue;
269 		case 'D':	/* date */
270 			p = tm_info.format[TM_DATE];
271 			goto push;
272 		case 'e':       /* blank padded day of month */
273 			cp = number(cp, ep, (long)tm->tm_mday, -2, width, pad);
274 			continue;
275 		case 'f':	/* (AST) OBSOLETE use %Qf */
276 			p = "%Qf";
277 			goto push;
278 		case 'F':	/* ISO 8601:2000 standard date format */
279 			p = "%Y-%m-%d";
280 			goto push;
281 		case 'g':	/* %V 2 digit year */
282 		case 'G':	/* %V 4 digit year */
283 			n = tm->tm_year + 1900;
284 			if (tm->tm_yday < 7)
285 			{
286 				if (tmweek(tm, 2, -1, -1) >= 52)
287 					n--;
288 			}
289 			else if (tm->tm_yday > 358)
290 			{
291 				if (tmweek(tm, 2, -1, -1) <= 1)
292 					n++;
293 			}
294 			if (c == 'g')
295 			{
296 				n %= 100;
297 				c = 2;
298 			}
299 			else
300 				c = 4;
301 			cp = number(cp, ep, (long)n, c, width, pad);
302 			continue;
303 		case 'H':	/* hour (0 - 23) */
304 			cp = number(cp, ep, (long)tm->tm_hour, 2, width, pad);
305 			continue;
306 		case 'i':	/* (AST) OBSOLETE use %QI */
307 			p = "%QI";
308 			goto push;
309 		case 'I':	/* hour (0 - 12) */
310 			if ((n = tm->tm_hour) > 12) n -= 12;
311 			else if (n == 0) n = 12;
312 			cp = number(cp, ep, (long)n, 2, width, pad);
313 			continue;
314 		case 'j':	/* Julian date (1 offset) */
315 			cp = number(cp, ep, (long)(tm->tm_yday + 1), 3, width, pad);
316 			continue;
317 		case 'J':	/* Julian date (0 offset) */
318 			cp = number(cp, ep, (long)tm->tm_yday, 3, width, pad);
319 			continue;
320 		case 'k':	/* (AST) OBSOLETE use %QD */
321 			p = "%QD";
322 			goto push;
323 		case 'K':	/* (AST) largest to smallest */
324 			switch (alt)
325 			{
326 			case 'E':
327 				p = (pad == '_') ? "%Y-%m-%d %H:%M:%S.%N %z" : "%Y-%m-%d+%H:%M:%S.%N%z";
328 				break;
329 			case 'O':
330 				p = (pad == '_') ? "%Y-%m-%d %H:%M:%S.%N" : "%Y-%m-%d+%H:%M:%S.%N";
331 				break;
332 			default:
333 				p = (pad == '_') ? "%Y-%m-%d %H:%M:%S" : "%Y-%m-%d+%H:%M:%S";
334 				break;
335 			}
336 			goto push;
337 		case 'l':	/* (AST) OBSOLETE use %QL */
338 			p = "%QL";
339 			goto push;
340 		case 'L':	/* (AST) OBSOLETE use %Ql */
341 			p = "%Ql";
342 			goto push;
343 		case 'm':	/* month number */
344 			cp = number(cp, ep, (long)(tm->tm_mon + 1), 2, width, pad);
345 			continue;
346 		case 'M':	/* minutes */
347 			cp = number(cp, ep, (long)tm->tm_min, 2, width, pad);
348 			continue;
349 		case 'n':
350 			if (cp < ep)
351 				*cp++ = '\n';
352 			continue;
353 		case 'N':	/* (AST|GNU) nanosecond part */
354 			cp = number(cp, ep, (long)tm->tm_nsec, 9, width, pad);
355 			continue;
356 #if 0
357 		case 'o':	/* (UNUSED) */
358 			continue;
359 #endif
360 		case 'p':	/* meridian */
361 			n = TM_MERIDIAN + (tm->tm_hour >= 12);
362 			goto index;
363 		case 'P':	/* (AST|GNU) lower case meridian */
364 			p = tm_info.format[TM_MERIDIAN + (tm->tm_hour >= 12)];
365 			while (cp < ep && (n = *p++))
366 				*cp++ = isupper(n) ? tolower(n) : n;
367 			continue;
368 		case 'q':	/* (AST) OBSOLETE use %Qz */
369 			p = "%Qz";
370 			goto push;
371 		case 'Q':	/* (AST) %Q<alpha> or %Q<delim>recent<delim>distant<delim> */
372 			if (c = *format)
373 			{
374 				format++;
375 				if (isalpha(c))
376 				{
377 					switch (c)
378 					{
379 					case 'd':	/* `ls -l' distant date */
380 						p = tm_info.format[TM_DISTANT];
381 						goto push;
382 					case 'D':	/* `date(1)' date */
383 						p = tm_info.format[TM_DATE_1];
384 						goto push;
385 					case 'f':	/* TM_DEFAULT override */
386 						p = tm_info.deformat;
387 						goto push;
388 					case 'I':	/* international `date(1)' date */
389 						p = tm_info.format[TM_INTERNATIONAL];
390 						goto push;
391 					case 'l':	/* TM_DEFAULT */
392 						p = tm_info.format[TM_DEFAULT];
393 						goto push;
394 					case 'L':	/* `ls -l' date */
395 						if (t)
396 						{
397 							now = tmxgettime();
398 							if (warped(t, now))
399 							{
400 								p = tm_info.format[TM_DISTANT];
401 								goto push;
402 							}
403 						}
404 						p = tm_info.format[TM_RECENT];
405 						goto push;
406 					case 'o':	/* set options ( %([+-]flag...)o ) */
407 						if (arg)
408 						{
409 							c = '+';
410 							i = 0;
411 							for (;;)
412 							{
413 								switch (*arg++)
414 								{
415 								case 0:
416 									n = 0;
417 									break;
418 								case '=':
419 									i = !i;
420 									continue;
421 								case '+':
422 								case '-':
423 								case '!':
424 									c = *(arg - 1);
425 									continue;
426 								case 'l':
427 									n = TM_LEAP;
428 									break;
429 								case 'n':
430 								case 's':
431 									n = TM_SUBSECOND;
432 									break;
433 								case 'u':
434 									n = TM_UTC;
435 									break;
436 								default:
437 									continue;
438 								}
439 								if (!n)
440 									break;
441 
442 								/*
443 								 * right, the global state stinks
444 								 * but we respect its locale-like status
445 								 */
446 
447 								if (c == '+')
448 								{
449 									if (!(flags & n))
450 									{
451 										flags |= n;
452 										tm_info.flags |= n;
453 										tm = tmxtm(tm, t, (flags & TM_UTC) ? &tm_data.zone[2] : tm->tm_zone);
454 										if (!i)
455 											tm_info.flags &= ~n;
456 									}
457 								}
458 								else if (flags & n)
459 								{
460 									flags &= ~n;
461 									tm_info.flags &= ~n;
462 									tm = tmxtm(tm, t, (flags & TM_UTC) ? &tm_data.zone[2] : tm->tm_zone);
463 									if (!i)
464 										tm_info.flags |= n;
465 								}
466 							}
467 						}
468 						break;
469 					case 'r':	/* `ls -l' recent date */
470 						p = tm_info.format[TM_RECENT];
471 						goto push;
472 					case 'z':	/* time zone nation code */
473 						if (arg)
474 						{
475 							if ((zp = tmzone(arg, &e, NiL, NiL)) && !*e)
476 							{
477 								tm->tm_zone = zp;
478 								flags &= ~TM_UTC;
479 							}
480 						}
481 						else if (!(flags & TM_UTC))
482 						{
483 							if ((zp = tm->tm_zone) != tm_info.local)
484 							{
485 								for (; zp >= tm_data.zone; zp--)
486 								{
487 									if (p = zp->type)
488 										goto string;
489 									if (zp->standard == zp->daylight)
490 										break;
491 								}
492 							}
493 							else if (p = zp->type)
494 								goto string;
495 						}
496 						break;
497 					default:
498 						format--;
499 						break;
500 					}
501 				}
502 				else
503 				{
504 					if (t)
505 					{
506 						now = tmxgettime();
507 						p = warped(t, now) ? (char*)0 : (char*)format;
508 					}
509 					else
510 						p = (char*)format;
511 					i = 0;
512 					while (n = *format)
513 					{
514 						format++;
515 						if (n == c)
516 						{
517 							if (!p)
518 								p = (char*)format;
519 							if (++i == 2)
520 								goto push_delimiter;
521 						}
522 					}
523 				}
524 			}
525 			continue;
526 		case 'r':
527 			p = tm_info.format[TM_MERIDIAN_TIME];
528 			goto push;
529 		case 'R':
530 			p = "%H:%M";
531 			goto push;
532 		case 's':	/* (DEFACTO) seconds[.nanoseconds] since the epoch */
533 		case '#':
534 			now = t;
535 			f = fmt;
536 			*f++ = '%';
537 			if (pad == '0')
538 				*f++ = pad;
539 			if (width)
540 				f += sfsprintf(f, &fmt[sizeof(fmt)] - f, "%d", width);
541 			f += sfsprintf(f, &fmt[sizeof(fmt)] - f, "I%du", sizeof(Tmxsec_t));
542 			cp += sfsprintf(cp, ep - cp, fmt, tmxsec(now));
543 			if (parts > 1)
544 			{
545 				n = sfsprintf(cp, ep - cp, ".%09I*u", sizeof(Tmxnsec_t), tmxnsec(now));
546 				if (prec && n >= prec)
547 					n = prec + 1;
548 				cp += n;
549 			}
550 			continue;
551 		case 'S':	/* seconds */
552 			cp = number(cp, ep, (long)tm->tm_sec, 2, width, pad);
553 			if ((flags & TM_SUBSECOND) && (format - 2) != oformat)
554 			{
555 				p = ".%N";
556 				goto push;
557 			}
558 			continue;
559 		case 't':
560 			if (cp < ep)
561 				*cp++ = '\t';
562 			continue;
563 		case 'T':
564 			p = tm_info.format[TM_TIME];
565 			goto push;
566 		case 'u':	/* weekday number [1(Monday)-7] */
567 			if (!(i = tm->tm_wday))
568 				i = 7;
569 			cp = number(cp, ep, (long)i, 0, width, pad);
570 			continue;
571 		case 'U':	/* week number, Sunday as first day */
572 			cp = number(cp, ep, (long)tmweek(tm, 0, -1, -1), 2, width, pad);
573 			continue;
574 #if 0
575 		case 'v':	/* (UNUSED) */
576 			continue;
577 #endif
578 		case 'V':	/* ISO week number */
579 			cp = number(cp, ep, (long)tmweek(tm, 2, -1, -1), 2, width, pad);
580 			continue;
581 		case 'W':	/* week number, Monday as first day */
582 			cp = number(cp, ep, (long)tmweek(tm, 1, -1, -1), 2, width, pad);
583 			continue;
584 		case 'w':	/* weekday number [0(Sunday)-6] */
585 			cp = number(cp, ep, (long)tm->tm_wday, 0, width, pad);
586 			continue;
587 		case 'x':
588 			p = tm_info.format[TM_DATE];
589 			goto push;
590 		case 'X':
591 			p = tm_info.format[TM_TIME];
592 			goto push;
593 		case 'y':	/* year in the form yy */
594 			cp = number(cp, ep, (long)(tm->tm_year % 100), 2, width, pad);
595 			continue;
596 		case 'Y':	/* year in the form ccyy */
597 			cp = number(cp, ep, (long)(1900 + tm->tm_year), 4, width, pad);
598 			continue;
599 		case 'z':	/* time zone west offset */
600 			if (arg)
601 			{
602 				if ((zp = tmzone(arg, &f, 0, 0)) && !*f && tm->tm_zone != zp)
603 					tm = tmxtm(tm, tmxtime(tm, tm->tm_zone->west + (tm->tm_isdst ? tm->tm_zone->dst : 0)), zp);
604 				continue;
605 			}
606 			if ((ep - cp) >= 16)
607 				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);
608 			continue;
609 		case 'Z':	/* time zone */
610 			if (arg)
611 			{
612 				if ((zp = tmzone(arg, &f, 0, 0)) && !*f && tm->tm_zone != zp)
613 				{
614 					tm = tmxtm(tm, tmxtime(tm, tm->tm_zone->west + (tm->tm_isdst ? tm->tm_zone->dst : 0)), zp);
615 					if (zp->west || zp->dst)
616 						flags &= ~TM_UTC;
617 				}
618 				continue;
619 			}
620 			p = (flags & TM_UTC) ? tm_info.local->standard : tm->tm_isdst && tm->tm_zone->daylight ? tm->tm_zone->daylight : tm->tm_zone->standard;
621 			goto string;
622 		case '=':	/* (AST) OBSOLETE use %([+-]flag...)Qo (old %=[=][+-]flag) */
623 			for (arg = argbuf; *format == '=' || *format == '-' || *format == '+' || *format == '!'; format++)
624 				if (arg < &argbuf[sizeof(argbuf) - 2])
625 					*arg++ = *format;
626 			if (*arg++ = *format)
627 				format++;
628 			*arg = 0;
629 			arg = argbuf;
630 			goto options;
631 		default:
632 			if (cp < ep)
633 				*cp++ = '%';
634 			if (cp < ep)
635 				*cp++ = c;
636 			continue;
637 		}
638 	index:
639 		p = tm_info.format[n];
640 	string:
641 		while (cp < ep && (*cp = *p++))
642 			cp++;
643 		continue;
644 	options:
645 		c = '+';
646 		i = 0;
647 		for (;;)
648 		{
649 			switch (*arg++)
650 			{
651 			case 0:
652 				n = 0;
653 				break;
654 			case '=':
655 				i = !i;
656 				continue;
657 			case '+':
658 			case '-':
659 			case '!':
660 				c = *(arg - 1);
661 				continue;
662 			case 'l':
663 				n = TM_LEAP;
664 				break;
665 			case 'n':
666 			case 's':
667 				n = TM_SUBSECOND;
668 				break;
669 			case 'u':
670 				n = TM_UTC;
671 				break;
672 			default:
673 				continue;
674 			}
675 			if (!n)
676 				break;
677 
678 			/*
679 			 * right, the global state stinks
680 			 * but we respect its locale-like status
681 			 */
682 
683 			if (c == '+')
684 			{
685 				if (!(flags & n))
686 				{
687 					flags |= n;
688 					tm_info.flags |= n;
689 					tm = tmxtm(tm, t, (flags & TM_UTC) ? &tm_data.zone[2] : tm->tm_zone);
690 					if (!i)
691 						tm_info.flags &= ~n;
692 				}
693 			}
694 			else if (flags & n)
695 			{
696 				flags &= ~n;
697 				tm_info.flags &= ~n;
698 				tm = tmxtm(tm, t, (flags & TM_UTC) ? &tm_data.zone[2] : tm->tm_zone);
699 				if (!i)
700 					tm_info.flags |= n;
701 			}
702 		}
703 		continue;
704 	push:
705 		c = 0;
706 	push_delimiter:
707 		if (sp < &stack[elementsof(stack)])
708 		{
709 			sp->format = (char*)format;
710 			format = p;
711 			sp->delimiter = delimiter;
712 			delimiter = c;
713 			sp++;
714 		}
715 		continue;
716 	}
717 	tm_info.flags = flags;
718 	if (cp >= ep)
719 		cp = ep - 1;
720 	*cp = 0;
721 	return cp;
722 }
723