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