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 *                 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 <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 
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*		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