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  * scan date expression in s using format
30  * if non-null, e points to the first invalid sequence in s
31  * if non-null, f points to the first unused format char
32  * t provides default values
33  */
34 
35 #include <tmx.h>
36 #include <ctype.h>
37 
38 typedef struct
39 {
40 	int32_t		nsec;
41 	int		year;
42 	int		mon;
43 	int		week;
44 	int		weektype;
45 	int		yday;
46 	int		mday;
47 	int		wday;
48 	int		hour;
49 	int		min;
50 	int		sec;
51 	int		meridian;
52 	int		zone;
53 } Set_t;
54 
55 #define CLEAR(s)	(s.year=s.mon=s.week=s.weektype=s.yday=s.mday=s.wday=s.hour=s.min=s.sec=s.meridian=(-1),s.nsec=1000000000L,s.zone=TM_LOCALZONE)
56 
57 #define INDEX(m,x)	(((n)>=((x)-(m)))?((n)-=((x)-(m))):(n))
58 
59 #define NUMBER(d,m,x)	do \
60 			{ \
61 				n = 0; \
62 				u = (char*)s; \
63 				while (s < (const char*)(u + d) && *s >= '0' && *s <= '9') \
64 					n = n * 10 + *s++ - '0'; \
65 				if (u == (char*)s || n < m || n > x) \
66 					goto next; \
67 			} while (0)
68 
69 /*
70  * generate a Time_t from tm + set
71  */
72 
73 static Time_t
74 gen(register Tm_t* tm, register Set_t* set)
75 {
76 	register int	n;
77 	Time_t		t;
78 
79 	if (set->year >= 0)
80 		tm->tm_year = set->year;
81 	if (set->mon >= 0)
82 	{
83 		if (set->year < 0 && set->mon < tm->tm_mon)
84 			tm->tm_year++;
85 		tm->tm_mon = set->mon;
86 		if (set->yday < 0 && set->mday < 0)
87 			tm->tm_mday = set->mday = 1;
88 	}
89 	if (set->week >= 0)
90 	{
91 		if (set->mon < 0)
92 		{
93 			tmweek(tm, set->weektype, set->week, set->wday);
94 			set->wday = -1;
95 		}
96 	}
97 	else if (set->yday >= 0)
98 	{
99 		if (set->mon < 0)
100 		{
101 			tm->tm_mon = 0;
102 			tm->tm_mday = set->yday + 1;
103 		}
104 	}
105 	else if (set->mday >= 0)
106 		tm->tm_mday = set->mday;
107 	if (set->hour >= 0)
108 	{
109 		if (set->hour < tm->tm_hour && set->yday < 0 && set->mday < 0 && set->wday < 0)
110 			tm->tm_mday++;
111 		tm->tm_hour = set->hour;
112 		tm->tm_min = (set->min >= 0) ? set->min : 0;
113 		tm->tm_sec = (set->sec >= 0) ? set->sec : 0;
114 	}
115 	else if (set->min >= 0)
116 	{
117 		tm->tm_min = set->min;
118 		tm->tm_sec = (set->sec >= 0) ? set->sec : 0;
119 	}
120 	else if (set->sec >= 0)
121 		tm->tm_sec = set->sec;
122 	if (set->nsec < 1000000000L)
123 		tm->tm_nsec = set->nsec;
124 	if (set->meridian > 0)
125 	{
126 		if (tm->tm_hour < 12)
127 			tm->tm_hour += 12;
128 	}
129 	else if (set->meridian == 0)
130 	{
131 		if (tm->tm_hour >= 12)
132 			tm->tm_hour -= 12;
133 	}
134 	t = tmxtime(tm, set->zone);
135 	tm = 0;
136 	if (set->yday >= 0)
137 	{
138 		tm = tmxmake(t);
139 		tm->tm_mday += set->yday - tm->tm_yday;
140 	}
141 	else if (set->wday >= 0)
142 	{
143 		tm = tmxmake(t);
144 		if ((n = set->wday - tm->tm_wday) < 0)
145 			n += 7;
146 		tm->tm_mday += n;
147 	}
148 	if (set->nsec < 1000000000L)
149 	{
150 		if (!tm)
151 			tm = tmxmake(t);
152 		tm->tm_nsec = set->nsec;
153 	}
154 	return tm ? tmxtime(tm, set->zone) : t;
155 }
156 
157 /*
158  * the format scan workhorse
159  */
160 
161 static Time_t
162 scan(register const char* s, char** e, const char* format, char** f, Time_t t, long flags)
163 {
164 	register int	d;
165 	register int	n;
166 	register char*	p;
167 	register Tm_t*	tm;
168 	const char*	b;
169 	char*		u;
170 	char*		stack[4];
171 	int		m;
172 	int		hi;
173 	int		lo;
174 	int		pedantic;
175 	Time_t		x;
176 	Set_t		set;
177 	Tm_zone_t*	zp;
178 
179 	char**		sp = &stack[0];
180 
181 	while (isspace(*s))
182 		s++;
183 	b = s;
184  again:
185 	CLEAR(set);
186 	tm = tmxmake(t);
187 	tm_info.date = tm_info.zone;
188 	pedantic = (flags & TM_PEDANTIC) != 0;
189 	for (;;)
190 	{
191 		if (!(d = *format++))
192 		{
193 			if (sp <= &stack[0])
194 			{
195 				format--;
196 				break;
197 			}
198 			format = (const char*)*--sp;
199 		}
200 		else if (!*s)
201 		{
202 			format--;
203 			break;
204 		}
205 		else if (d == '%' && (d = *format) && format++ && d != '%')
206 		{
207 		more:
208 			switch (d)
209 			{
210 			case 'a':
211 				lo = TM_DAY_ABBREV;
212 				hi = pedantic ? TM_DAY : TM_TIME;
213 				goto get_wday;
214 			case 'A':
215 				lo = pedantic ? TM_DAY : TM_DAY_ABBREV;
216 				hi = TM_TIME;
217 			get_wday:
218 				if ((n = tmlex(s, &u, tm_info.format + lo, hi - lo, NiL, 0)) < 0)
219 					goto next;
220 				s = u;
221 				INDEX(TM_DAY_ABBREV, TM_DAY);
222 				set.wday = n;
223 				continue;
224 			case 'b':
225 			case 'h':
226 				lo = TM_MONTH_ABBREV;
227 				hi = pedantic ? TM_MONTH : TM_DAY_ABBREV;
228 				goto get_mon;
229 			case 'B':
230 				lo = pedantic ? TM_MONTH : TM_MONTH_ABBREV;
231 				hi = TM_DAY_ABBREV;
232 			get_mon:
233 				if ((n = tmlex(s, &u, tm_info.format + lo, hi - lo, NiL, 0)) < 0)
234 					goto next;
235 				s = u;
236 				INDEX(TM_MONTH_ABBREV, TM_MONTH);
237 				set.mon = n;
238 				continue;
239 			case 'c':
240 				p = "%a %b %e %T %Y";
241 				break;
242 			case 'C':
243 				NUMBER(2, 19, 99);
244 				set.year = (n - 19) * 100 + tm->tm_year % 100;
245 				continue;
246 			case 'd':
247 				if (pedantic && !isdigit(*s))
248 					goto next;
249 				/*FALLTHROUGH*/
250 			case 'e':
251 				NUMBER(2, 1, 31);
252 				set.mday = n;
253 				continue;
254 			case 'D':
255 				p = "%m/%d/%y";
256 				break;
257 			case 'E':
258 			case 'O':
259 				if (*format)
260 				{
261 					d = *format++;
262 					goto more;
263 				}
264 				continue;
265 			case 'H':
266 			case 'k':
267 				NUMBER(2, 0, 23);
268 				set.hour = n;
269 				continue;
270 			case 'I':
271 			case 'l':
272 				NUMBER(2, 1, 12);
273 				set.hour = n;
274 				continue;
275 			case 'j':
276 				NUMBER(3, 1, 366);
277 				set.yday = n - 1;
278 				continue;
279 			case 'm':
280 				NUMBER(2, 1, 12);
281 				set.mon = n - 1;
282 				continue;
283 			case 'M':
284 				NUMBER(2, 0, 59);
285 				set.min = n;
286 				continue;
287 			case 'n':
288 				if (pedantic)
289 					while (*s == '\n')
290 						s++;
291 				else
292 					while (isspace(*s))
293 						s++;
294 				continue;
295 			case 'N':
296 				NUMBER(9, 0, 999999999L);
297 				set.nsec = n;
298 				continue;
299 			case 'p':
300 				if ((n = tmlex(s, &u, tm_info.format + TM_MERIDIAN, TM_UT - TM_MERIDIAN, NiL, 0)) < 0)
301 					goto next;
302 				set.meridian = n;
303 				s = u;
304 				continue;
305 			case 'r':
306 				p = "%I:%M:%S %p";
307 				break;
308 			case 'R':
309 				p = "%H:%M:%S";
310 				break;
311 			case 's':
312 				x = strtoul(s, &u, 0);
313 				if (s == u)
314 					goto next;
315 				tm = tmxmake(tmxsns(x, 0));
316 				s = u;
317 				CLEAR(set);
318 				continue;
319 			case 'S':
320 				NUMBER(2, 0, 61);
321 				set.sec = n;
322 				continue;
323 			case 'u':
324 				NUMBER(2, 1, 7);
325 				set.wday = n % 7;
326 				continue;
327 			case 'U':
328 				NUMBER(2, 0, 52);
329 				set.week = n;
330 				set.weektype = 0;
331 				continue;
332 			case 'V':
333 				NUMBER(2, 1, 53);
334 				set.week = n;
335 				set.weektype = 2;
336 				continue;
337 			case 'w':
338 				NUMBER(2, 0, 6);
339 				set.wday = n;
340 				continue;
341 			case 'W':
342 				NUMBER(2, 0, 52);
343 				set.week = n;
344 				set.weektype = 1;
345 				continue;
346 			case 'x':
347 				p = tm_info.format[TM_DATE];
348 				break;
349 			case 'X':
350 				p = tm_info.format[TM_TIME];
351 				break;
352 			case 'y':
353 				NUMBER(2, 0, 99);
354 				if (n < TM_WINDOW)
355 					n += 100;
356 				set.year = n;
357 				continue;
358 			case 'Y':
359 				NUMBER(4, 1969, 2100);
360 				set.year = n - 1900;
361 				continue;
362 			case 'Z':
363 			case 'q':
364 				if (zp = tmtype(s, &u))
365 				{
366 					s = u;
367 					u = zp->type;
368 				}
369 				else
370 					u = 0;
371 				if (d == 'q')
372 					continue;
373 			case 'z':
374 				if ((zp = tmzone(s, &u, u, &m)))
375 				{
376 					s = u;
377 					set.zone = zp->west + m;
378 					tm_info.date = zp;
379 				}
380 				continue;
381 			case '|':
382 				s = b;
383 				goto again;
384 			case '&':
385 				x = gen(tm, &set);
386 				x = tmxdate(s, e, t);
387 				if (s == (const char*)*e)
388 					goto next;
389 				t = x;
390 				s = (const char*)*e;
391 				if (!*format || *format == '%' && *(format + 1) == '|')
392 					goto done;
393 				goto again;
394 			default:
395 				goto next;
396 			}
397 			if (sp >= &stack[elementsof(stack)])
398 				goto next;
399 			*sp++ = (char*)format;
400 			format = (const char*)p;
401 		}
402 		else if (isspace(d))
403 			while (isspace(*s))
404 				s++;
405 		else if (*s != d)
406 			break;
407 		else
408 			s++;
409 	}
410  next:
411 	if (sp > &stack[0])
412 		format = (const char*)stack[0];
413 	if (*format)
414 	{
415 		p = (char*)format;
416 		if (!*s && *p == '%' && *(p + 1) == '|')
417 			format += strlen(format);
418 		else
419 			while (*p)
420 				if (*p++ == '%' && *p && *p++ == '|' && *p)
421 				{
422 					format = (const char*)p;
423 					s = b;
424 					goto again;
425 				}
426 	}
427 	t = gen(tm, &set);
428  done:
429 	if (e)
430 	{
431 		while (isspace(*s))
432 			s++;
433 		*e = (char*)s;
434 	}
435 	if (f)
436 	{
437 		while (isspace(*format))
438 			format++;
439 		*f = (char*)format;
440 	}
441 	return t;
442 }
443 
444 /*
445  *  format==0	DATEMSK
446  * *format==0	DATEMSK and tmxdate()
447  * *format!=0	format
448  */
449 
450 Time_t
451 tmxscan(const char* s, char** e, const char* format, char** f, Time_t t, long flags)
452 {
453 	register char*	v;
454 	register char**	p;
455 	char*		q;
456 	char*		r;
457 	Time_t		x;
458 
459 	static int	initialized;
460 	static char**	datemask;
461 
462 	tmlocale();
463 	if (!format || !*format)
464 	{
465 		if (!initialized)
466 		{
467 			register Sfio_t*	sp;
468 			register int		n;
469 			off_t			m;
470 
471 			initialized = 1;
472 			if ((v = getenv("DATEMSK")) && *v && (sp = sfopen(NiL, v, "r")))
473 			{
474 				for (n = 1; sfgetr(sp, '\n', 0); n++);
475 				m = sfseek(sp, 0L, SEEK_CUR);
476 				if (p = newof(0, char*, n, m))
477 				{
478 					sfseek(sp, 0L, SEEK_SET);
479 					v = (char*)(p + n);
480 					if (sfread(sp, v, m) != m)
481 					{
482 						free(p);
483 						p = 0;
484 					}
485 					else
486 					{
487 						datemask = p;
488 						v[m] = 0;
489 						while (*v)
490 						{
491 							*p++ = v;
492 							if (!(v = strchr(v, '\n')))
493 								break;
494 							*v++ = 0;
495 						}
496 						*p = 0;
497 					}
498 				}
499 			}
500 		}
501 		if (p = datemask)
502 			while (v = *p++)
503 			{
504 				x = scan(s, &q, v, &r, t, flags);
505 				if (!*q && !*r)
506 				{
507 					if (e)
508 						*e = q;
509 					if (f)
510 						*f = r;
511 					return x;
512 				}
513 			}
514 		if (f)
515 			*f = (char*)format;
516 		if (format)
517 			return tmxdate(s, e, t);
518 		if (e)
519 			*e = (char*)s;
520 		return 0;
521 	}
522 	return scan(s, e, format, f, t, flags);
523 }
524