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