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