xref: /titanic_41/usr/src/lib/libast/common/tm/tmxfmt.c (revision d5ace9454616652a717c9831d949dffa319381f9)
1 /***********************************************************************
2 *                                                                      *
3 *               This software is part of the ast package               *
4 *          Copyright (c) 1985-2008 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 
44 static char*
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*
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		pad;
108 	int		delimiter;
109 	int		width;
110 	int		prec;
111 	int		parts;
112 	char*		f;
113 	const char*	oformat;
114 	Tm_t*		tp;
115 	Tm_zone_t*	zp;
116 	Time_t		now;
117 	Stack_t*	sp;
118 	Stack_t		stack[8];
119 	char		fmt[32];
120 
121 	tmlocale();
122 	tp = tmxmake(t);
123 	if (!format || !*format)
124 		format = tm_info.deformat;
125 	oformat = format;
126 	flags = tm_info.flags;
127 	sp = &stack[0];
128 	cp = buf;
129 	ep = buf + len;
130 	delimiter = 0;
131 	for (;;)
132 	{
133 		if ((c = *format++) == delimiter)
134 		{
135 			delimiter = 0;
136 			if (sp <= &stack[0])
137 				break;
138 			sp--;
139 			format = sp->format;
140 			delimiter = sp->delimiter;
141 			continue;
142 		}
143 		if (c != '%')
144 		{
145 			if (cp < ep)
146 				*cp++ = c;
147 			continue;
148 		}
149 		pad = 0;
150 		width = 0;
151 		prec = 0;
152 		parts = 0;
153 		for (;;)
154 		{
155 			switch (c = *format++)
156 			{
157 			case '_':
158 			case '-':
159 				pad = c;
160 				continue;
161 			case 'E':
162 			case 'O':
163 				if (!isalpha(*format))
164 					break;
165 				continue;
166 			case '0':
167 				if (!parts)
168 				{
169 					pad = c;
170 					continue;
171 				}
172 				/*FALLTHROUGH*/
173 			case '1':
174 			case '2':
175 			case '3':
176 			case '4':
177 			case '5':
178 			case '6':
179 			case '7':
180 			case '8':
181 			case '9':
182 				switch (parts)
183 				{
184 				case 0:
185 					parts++;
186 					/*FALLTHROUGH*/
187 				case 1:
188 					width = width * 10 + (c - '0');
189 					break;
190 				case 2:
191 					prec = prec * 10 + (c - '0');
192 					break;
193 				}
194 				continue;
195 			case '.':
196 				if (!parts++)
197 					parts++;
198 				continue;
199 			default:
200 				break;
201 			}
202 			break;
203 		}
204 		switch (c)
205 		{
206 		case 0:
207 			format--;
208 			continue;
209 		case '%':
210 			if (cp < ep)
211 				*cp++ = '%';
212 			continue;
213 		case '?':
214 			if (tm_info.deformat != tm_info.format[TM_DEFAULT])
215 				format = tm_info.deformat;
216 			else if (!*format)
217 				format = tm_info.format[TM_DEFAULT];
218 			continue;
219 		case 'a':	/* abbreviated day of week name */
220 			n = TM_DAY_ABBREV + tp->tm_wday;
221 			goto index;
222 		case 'A':	/* day of week name */
223 			n = TM_DAY + tp->tm_wday;
224 			goto index;
225 		case 'b':	/* abbreviated month name */
226 		case 'h':
227 			n = TM_MONTH_ABBREV + tp->tm_mon;
228 			goto index;
229 		case 'B':	/* month name */
230 			n = TM_MONTH + tp->tm_mon;
231 			goto index;
232 		case 'c':	/* `ctime(3)' date sans newline */
233 			p = tm_info.format[TM_CTIME];
234 			goto push;
235 		case 'C':	/* 2 digit century */
236 			cp = number(cp, ep, (long)(1900 + tp->tm_year) / 100, 2, width, pad);
237 			continue;
238 		case 'd':	/* day of month */
239 			cp = number(cp, ep, (long)tp->tm_mday, 2, width, pad);
240 			continue;
241 		case 'D':	/* date */
242 			p = tm_info.format[TM_DATE];
243 			goto push;
244 		case 'E':       /* OBSOLETE no pad day of month */
245 			cp = number(cp, ep, (long)tp->tm_mday, 0, width, pad);
246 			continue;
247 		case 'e':       /* blank padded day of month */
248 			cp = number(cp, ep, (long)tp->tm_mday, -2, width, pad);
249 			continue;
250 		case 'f':	/* TM_DEFAULT override */
251 		case 'o':	/* OBSOLETE */
252 			p = tm_info.deformat;
253 			goto push;
254 		case 'F':	/* ISO 8601:2000 standard date format */
255 			p = "%Y-%m-%d";
256 			goto push;
257 		case 'g':	/* %V 2 digit year */
258 		case 'G':	/* %V 4 digit year */
259 			n = tp->tm_year + 1900;
260 			if (tp->tm_yday < 7)
261 			{
262 				if (tmweek(tp, 2, -1, -1) >= 52)
263 					n--;
264 			}
265 			else if (tp->tm_yday > 358)
266 			{
267 				if (tmweek(tp, 2, -1, -1) <= 1)
268 					n++;
269 			}
270 			if (c == 'g')
271 			{
272 				n %= 100;
273 				c = 2;
274 			}
275 			else
276 				c = 4;
277 			cp = number(cp, ep, (long)n, c, width, pad);
278 			continue;
279 		case 'H':	/* hour (0 - 23) */
280 			cp = number(cp, ep, (long)tp->tm_hour, 2, width, pad);
281 			continue;
282 		case 'i':	/* international `date(1)' date */
283 			p = tm_info.format[TM_INTERNATIONAL];
284 			goto push;
285 		case 'I':	/* hour (0 - 12) */
286 			if ((n = tp->tm_hour) > 12) n -= 12;
287 			else if (n == 0) n = 12;
288 			cp = number(cp, ep, (long)n, 2, width, pad);
289 			continue;
290 		case 'J':	/* Julian date (0 offset) */
291 			cp = number(cp, ep, (long)tp->tm_yday, 3, width, pad);
292 			continue;
293 		case 'j':	/* Julian date (1 offset) */
294 			cp = number(cp, ep, (long)(tp->tm_yday + 1), 3, width, pad);
295 			continue;
296 		case 'k':	/* `date(1)' date */
297 			p = tm_info.format[TM_DATE_1];
298 			goto push;
299 		case 'K':
300 			p = "%Y-%m-%d+%H:%M:%S";
301 			goto push;
302 		case 'l':	/* `ls -l' date */
303 			if (t)
304 			{
305 				now = tmxgettime();
306 				if (warped(t, now))
307 				{
308 					p = tm_info.format[TM_DISTANT];
309 					goto push;
310 				}
311 			}
312 			p = tm_info.format[TM_RECENT];
313 			goto push;
314 		case 'L':	/* TM_DEFAULT */
315 		case 'O':	/* OBSOLETE */
316 			p = tm_info.format[TM_DEFAULT];
317 			goto push;
318 		case 'm':	/* month number */
319 			cp = number(cp, ep, (long)(tp->tm_mon + 1), 2, width, pad);
320 			continue;
321 		case 'M':	/* minutes */
322 			cp = number(cp, ep, (long)tp->tm_min, 2, width, pad);
323 			continue;
324 		case 'n':
325 			if (cp < ep)
326 				*cp++ = '\n';
327 			continue;
328 		case 'N':	/* nanosecond part */
329 			cp = number(cp, ep, (long)tp->tm_nsec, 9, width, pad);
330 			continue;
331 		case 'p':	/* meridian */
332 			n = TM_MERIDIAN + (tp->tm_hour >= 12);
333 			goto index;
334 		case 'q':	/* time zone type (nation code) */
335 			if (!(flags & TM_UTC))
336 			{
337 				if ((zp = tm_info.zone) != tm_info.local)
338 					for (; zp >= tm_data.zone; zp--)
339 						if (p = zp->type)
340 							goto string;
341 				else if (p = zp->type)
342 					goto string;
343 			}
344 			continue;
345 		case 'Q':	/* %Q<alpha> or %Q<delim>recent<delim>distant<delim> */
346 			if (c = *format)
347 			{
348 				format++;
349 				if (isalpha(c))
350 				{
351 					switch (c)
352 					{
353 					case 'd':	/* `ls -l' distant date */
354 						p = tm_info.format[TM_DISTANT];
355 						goto push;
356 					case 'r':	/* `ls -l' recent date */
357 						p = tm_info.format[TM_RECENT];
358 						goto push;
359 					default:
360 						format--;
361 						break;
362 					}
363 				}
364 				else
365 				{
366 					if (t)
367 					{
368 						now = tmxgettime();
369 						p = warped(t, now) ? (char*)0 : (char*)format;
370 					}
371 					else
372 						p = (char*)format;
373 					i = 0;
374 					while (n = *format)
375 					{
376 						format++;
377 						if (n == c)
378 						{
379 							if (!p)
380 								p = (char*)format;
381 							if (++i == 2)
382 								goto push_delimiter;
383 						}
384 					}
385 				}
386 			}
387 			continue;
388 		case 'r':
389 			p = tm_info.format[TM_MERIDIAN_TIME];
390 			goto push;
391 		case 'R':
392 			p = "%H:%M";
393 			goto push;
394 		case 's':	/* seconds[.nanoseconds] since the epoch */
395 		case '#':
396 			if (t)
397 				now = t;
398 			else
399 				now = tmxgettime();
400 			f = fmt;
401 			*f++ = '%';
402 			if (pad == '0')
403 				*f++ = pad;
404 			if (width)
405 				f += sfsprintf(f, &fmt[sizeof(fmt)] - f, "%d", width);
406 			f += sfsprintf(f, &fmt[sizeof(fmt)] - f, "I%du", sizeof(Tmxsec_t));
407 			cp += sfsprintf(cp, ep - cp, fmt, tmxsec(now));
408 			if (parts > 1)
409 			{
410 				n = sfsprintf(cp, ep - cp, ".%09I*u", sizeof(Tmxnsec_t), tmxnsec(now));
411 				if (prec && n >= prec)
412 					n = prec + 1;
413 				cp += n;
414 			}
415 			continue;
416 		case 'S':	/* seconds */
417 			cp = number(cp, ep, (long)tp->tm_sec, 2, width, pad);
418 			if ((flags & TM_SUBSECOND) && (format - 2) != oformat)
419 			{
420 				p = ".%N";
421 				goto push;
422 			}
423 			continue;
424 		case 't':
425 			if (cp < ep)
426 				*cp++ = '\t';
427 			continue;
428 		case 'T':
429 			p = tm_info.format[TM_TIME];
430 			goto push;
431 		case 'u':	/* weekday number [1(Monday)-7] */
432 			if (!(i = tp->tm_wday))
433 				i = 7;
434 			cp = number(cp, ep, (long)i, 0, width, pad);
435 			continue;
436 		case 'U':	/* week number, Sunday as first day */
437 			cp = number(cp, ep, (long)tmweek(tp, 0, -1, -1), 2, width, pad);
438 			continue;
439 		case 'V':	/* ISO week number */
440 			cp = number(cp, ep, (long)tmweek(tp, 2, -1, -1), 2, width, pad);
441 			continue;
442 		case 'W':	/* week number, Monday as first day */
443 			cp = number(cp, ep, (long)tmweek(tp, 1, -1, -1), 2, width, pad);
444 			continue;
445 		case 'w':	/* weekday number [0(Sunday)-6] */
446 			cp = number(cp, ep, (long)tp->tm_wday, 0, width, pad);
447 			continue;
448 		case 'x':
449 			p = tm_info.format[TM_DATE];
450 			goto push;
451 		case 'X':
452 			p = tm_info.format[TM_TIME];
453 			goto push;
454 		case 'y':	/* year in the form yy */
455 			cp = number(cp, ep, (long)(tp->tm_year % 100), 2, width, pad);
456 			continue;
457 		case 'Y':	/* year in the form ccyy */
458 			cp = number(cp, ep, (long)(1900 + tp->tm_year), 4, width, pad);
459 			continue;
460 		case 'z':	/* time zone west offset */
461 			if ((ep - cp) >= 16)
462 				cp = tmpoff(cp, ep - cp, "", (flags & TM_UTC) ? 0 : tm_info.zone->west - (tp->tm_isdst ? 60 : 0), 24 * 60);
463 			continue;
464 		case 'Z':	/* time zone */
465 			p = (flags & TM_UTC) ? tm_info.format[TM_UT] : tp->tm_isdst && tm_info.zone->daylight ? tm_info.zone->daylight : tm_info.zone->standard;
466 			goto string;
467 		case '+':	/* old %+flag */
468 		case '!':	/* old %!flag */
469 			format--;
470 			/*FALLTHROUGH*/
471 		case '=':	/* %=[=][+-]flag */
472 			if (i = *format == '=')
473 				format++;
474 			if (*format == '+' || *format == '-' || *format == '!')
475 				c = *format++;
476 			else
477 				c = '+';
478 			switch (*format++)
479 			{
480 			case 0:
481 				format--;
482 				continue;
483 			case 'l':
484 				n = TM_LEAP;
485 				break;
486 			case 'n':
487 			case 's':
488 				n = TM_SUBSECOND;
489 				break;
490 			case 'u':
491 				n = TM_UTC;
492 				break;
493 			}
494 			if (n)
495 			{
496 				/*
497 				 * right, the global state stinks
498 				 * but we respect its locale-like status
499 				 */
500 
501 				if (c == '+')
502 				{
503 					if (!(flags & n))
504 					{
505 						flags |= n;
506 						tm_info.flags |= n;
507 						tp = tmxmake(t);
508 						if (!i)
509 							tm_info.flags &= ~n;
510 					}
511 				}
512 				else if (flags & n)
513 				{
514 					flags &= ~n;
515 					tm_info.flags &= ~n;
516 					tp = tmxmake(t);
517 					if (!i)
518 						tm_info.flags |= n;
519 				}
520 			}
521 			continue;
522 		default:
523 			if (cp < ep)
524 				*cp++ = '%';
525 			if (cp < ep)
526 				*cp++ = c;
527 			continue;
528 		}
529 	index:
530 		p = tm_info.format[n];
531 	string:
532 		while (cp < ep && (*cp = *p++))
533 			cp++;
534 		continue;
535 	push:
536 		c = 0;
537 	push_delimiter:
538 		if (sp < &stack[elementsof(stack)])
539 		{
540 			sp->format = (char*)format;
541 			format = p;
542 			sp->delimiter = delimiter;
543 			delimiter = c;
544 			sp++;
545 		}
546 		continue;
547 	}
548 	tm_info.flags = flags;
549 	if (cp >= ep)
550 		cp = ep - 1;
551 	*cp = 0;
552 	return cp;
553 }
554