xref: /haiku/src/system/libroot/os/parsedate.cpp (revision de4daffba01d87619480711c77dd13cbae5ede78)
1 /*
2  * Copyright 2003-2008, Axel Dörfler, axeld@pinc-software.de.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include <parsedate.h>
8 
9 #include <ctype.h>
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13 
14 #include <OS.h>
15 
16 
17 #define TRACE_PARSEDATE 0
18 #if TRACE_PARSEDATE
19 #	define TRACE(x) printf x ;
20 #else
21 #	define TRACE(x) ;
22 #endif
23 
24 
25 /* The date format is as follows:
26  *
27  *	a/A		weekday
28  *	d		day of month
29  *	b/B		month name
30  *	m		month
31  *	y/Y		year
32  *	H/I		hours
33  *	M		minute
34  *	S		seconds
35  *	p		meridian (i.e. am/pm)
36  *	T		time unit: last hour, next tuesday, today, ...
37  *	z/Z		time zone
38  *	-		dash or slash
39  *
40  *	Any of ",.:" is allowed and will be expected in the input string as is.
41  *	You can enclose a single field with "[]" to mark it as being optional.
42  *	A space stands for white space.
43  *	No other character is allowed.
44  */
45 
46 static const char * const kFormatsTable[] = {
47 	"[A][,] B d[,] H:M:S [p] Y[,] [Z]",
48 	"[A][,] B d[,] [Y][,] H:M:S [p] [Z]",
49 	"[A][,] B d[,] [Y][,] H:M [p][,] [Z]",
50 	"[A][,] B d[,] [Y][,] H [p][,] [Z]",
51 	"[A][,] B d[,] H:M [p][,] [Y] [Z]",
52 	"[A][,] d B[,] [Y][,] H:M [p][,] [Z]",
53 	"[A][,] d B[,] [Y][,] H:M:S [p][,] [Z]",
54 	"[A][,] d B[,] H:M:S [Y][,] [p][,] [Z]",
55 	"[A][,] d B[,] H:M [Y][,] [p][,] [Z]",
56 	"d.m.y H:M:S [p] [Z]",
57 	"d.m.y H:M [p] [Z]",
58 	"d.m.y",
59 	"[A][,] m-d-y[,] [H][:][M] [p]",
60 	"[A][,] m-d[,] H:M [p]",
61 	"[A][,] m-d[,] H[p]",
62 	"[A][,] m-d",
63 	"[A][,] B d[,] Y",
64 	"[A][,] H:M [p]",
65 	"[A][,] H [p]",
66 	"H:M [p][,] [A]",
67 	"H [p][,] [A]",
68 	"[A][,] B d[,] H:M:S [p] [Z] [Y]",
69 	"[A][,] B d[,] H:M [p] [Z] [Y]",
70 	"[A][,] d B [,] H:M:S [p] [Z] [Y]",
71 	"[A][,] d B [,] H:M [p] [Z] [Y]",
72 	"[A][,] d-B-Y H:M:S [p] [Z]",
73 	"[A][,] d-B-Y H:M [p] [Z]",
74 	"d B Y H:M:S [Z]",
75 	"d B Y H:M [Z]",
76 	"y-m-d",
77 	"y-m-d H:M:S [p] [Z]",
78 	"m-d-y H[p]",
79 	"m-d-y H:M[p]",
80 	"m-d-y H:M:S[p]",
81 	"H[p] m-d-y",
82 	"H:M[p] m-d-y",
83 	"H:M:S[p] m-d-y",
84 	"A[,] H:M:S [p] [Z]",
85 	"A[,] H:M [p] [Z]",
86 	"H:M:S [p] [Z]",
87 	"H:M [p] [Z]",
88 	"A[,] [B] [d] [Y]",
89 	"A[,] [d] [B] [Y]",
90 	"B d[,][Y] H[p][,] [Z]",
91 	"B d[,] H[p]",
92 	"B d [,] H:M [p]",
93 	"d B [,][Y] H [p] [Z]",
94 	"d B [,] H:M [p]",
95 	"B d [,][Y]",
96 	"B d [,] H:M [p][,] [Y]",
97 	"B d [,] H [p][,] [Y]",
98 	"d B [,][Y]",
99 	"H[p] [,] B d",
100 	"H:M[p] [,] B d",
101 	"T [T][T][T][T][T]",
102 	"T H:M:S [p]",
103 	"T H:M [p]",
104 	"T H [p]",
105 	"H:M [p] T",
106 	"H [p] T",
107 	"H [p]",
108 	NULL
109 };
110 static const char* const* sFormatsTable = kFormatsTable;
111 
112 
113 enum field_type {
114 	TYPE_UNKNOWN = 0,
115 
116 	TYPE_DAY,
117 	TYPE_MONTH,
118 	TYPE_YEAR,
119 	TYPE_WEEKDAY,
120 	TYPE_HOUR,
121 	TYPE_MINUTE,
122 	TYPE_SECOND,
123 	TYPE_TIME_ZONE,
124 	TYPE_MERIDIAN,
125 
126 	TYPE_DASH,
127 	TYPE_DOT,
128 	TYPE_COMMA,
129 	TYPE_COLON,
130 
131 	TYPE_UNIT,
132 	TYPE_MODIFIER,
133 	TYPE_END,
134 };
135 
136 #define FLAG_NONE				0
137 #define FLAG_RELATIVE			1
138 #define FLAG_NOT_MODIFIABLE		2
139 #define FLAG_NOW				4
140 #define FLAG_NEXT_LAST_THIS		8
141 #define FLAG_PLUS_MINUS			16
142 #define FLAG_HAS_DASH			32
143 
144 enum units {
145 	UNIT_NONE,
146 	UNIT_YEAR,
147 	UNIT_MONTH,
148 	UNIT_DAY,
149 	UNIT_SECOND,
150 };
151 
152 enum value_type {
153 	VALUE_NUMERICAL,
154 	VALUE_STRING,
155 	VALUE_CHAR,
156 };
157 
158 enum value_modifier {
159 	MODIFY_MINUS	= -2,
160 	MODIFY_LAST		= -1,
161 	MODIFY_NONE		= 0,
162 	MODIFY_THIS		= MODIFY_NONE,
163 	MODIFY_NEXT		= 1,
164 	MODIFY_PLUS		= 2,
165 };
166 
167 struct known_identifier {
168 	const char	*string;
169 	const char	*alternate_string;
170 	uint8		type;
171 	uint8		flags;
172 	uint8		unit;
173 	int32		value;
174 };
175 
176 static const known_identifier kIdentifiers[] = {
177 	{"today",		NULL,	TYPE_UNIT, FLAG_RELATIVE | FLAG_NOT_MODIFIABLE,
178 		UNIT_DAY, 0},
179 	{"tomorrow",	NULL,	TYPE_UNIT, FLAG_RELATIVE | FLAG_NOT_MODIFIABLE,
180 		UNIT_DAY, 1},
181 	{"yesterday",	NULL,	TYPE_UNIT, FLAG_RELATIVE | FLAG_NOT_MODIFIABLE,
182 		UNIT_DAY, -1},
183 	{"now",			NULL,	TYPE_UNIT,
184 		FLAG_RELATIVE | FLAG_NOT_MODIFIABLE | FLAG_NOW, 0},
185 
186 	{"this",		NULL,	TYPE_MODIFIER, FLAG_NEXT_LAST_THIS, UNIT_NONE,
187 		MODIFY_THIS},
188 	{"next",		NULL,	TYPE_MODIFIER, FLAG_NEXT_LAST_THIS, UNIT_NONE,
189 		MODIFY_NEXT},
190 	{"last",		NULL,	TYPE_MODIFIER, FLAG_NEXT_LAST_THIS, UNIT_NONE,
191 		MODIFY_LAST},
192 
193 	{"years",		"year",	TYPE_UNIT, FLAG_RELATIVE, UNIT_YEAR, 1},
194 	{"months",		"month",TYPE_UNIT, FLAG_RELATIVE, UNIT_MONTH, 1},
195 	{"weeks",		"week",	TYPE_UNIT, FLAG_RELATIVE, UNIT_DAY, 7},
196 	{"days",		"day",	TYPE_UNIT, FLAG_RELATIVE, UNIT_DAY, 1},
197 	{"hour",		NULL,	TYPE_UNIT, FLAG_RELATIVE, UNIT_SECOND, 1 * 60 * 60},
198 	{"hours",		"hrs",	TYPE_UNIT, FLAG_RELATIVE, UNIT_SECOND, 1 * 60 * 60},
199 	{"second",		"sec",	TYPE_UNIT, FLAG_RELATIVE, UNIT_SECOND, 1},
200 	{"seconds",		"secs",	TYPE_UNIT, FLAG_RELATIVE, UNIT_SECOND, 1},
201 	{"minute",		"min",	TYPE_UNIT, FLAG_RELATIVE, UNIT_SECOND, 60},
202 	{"minutes",		"mins",	TYPE_UNIT, FLAG_RELATIVE, UNIT_SECOND, 60},
203 
204 	{"am",			NULL,	TYPE_MERIDIAN, FLAG_NOT_MODIFIABLE, UNIT_SECOND, 0},
205 	{"pm",			NULL,	TYPE_MERIDIAN, FLAG_NOT_MODIFIABLE, UNIT_SECOND,
206 		12 * 60 * 60},
207 
208 	{"sunday",		"sun",	TYPE_WEEKDAY, FLAG_NONE, UNIT_DAY, 0},
209 	{"monday",		"mon",	TYPE_WEEKDAY, FLAG_NONE, UNIT_DAY, 1},
210 	{"tuesday",		"tue",	TYPE_WEEKDAY, FLAG_NONE, UNIT_DAY, 2},
211 	{"wednesday",	"wed",	TYPE_WEEKDAY, FLAG_NONE, UNIT_DAY, 3},
212 	{"thursday",	"thu",	TYPE_WEEKDAY, FLAG_NONE, UNIT_DAY, 4},
213 	{"friday",		"fri",	TYPE_WEEKDAY, FLAG_NONE, UNIT_DAY, 5},
214 	{"saturday",	"sat",	TYPE_WEEKDAY, FLAG_NONE, UNIT_DAY, 6},
215 
216 	{"january",		"jan",	TYPE_MONTH, FLAG_NONE, UNIT_MONTH, 1},
217 	{"february",	"feb", 	TYPE_MONTH, FLAG_NONE, UNIT_MONTH, 2},
218 	{"march",		"mar",	TYPE_MONTH, FLAG_NONE, UNIT_MONTH, 3},
219 	{"april",		"apr",	TYPE_MONTH, FLAG_NONE, UNIT_MONTH, 4},
220 	{"may",			"may",	TYPE_MONTH, FLAG_NONE, UNIT_MONTH, 5},
221 	{"june",		"jun",	TYPE_MONTH, FLAG_NONE, UNIT_MONTH, 6},
222 	{"july",		"jul",	TYPE_MONTH, FLAG_NONE, UNIT_MONTH, 7},
223 	{"august",		"aug",	TYPE_MONTH, FLAG_NONE, UNIT_MONTH, 8},
224 	{"september",	"sep",	TYPE_MONTH, FLAG_NONE, UNIT_MONTH, 9},
225 	{"october",		"oct",	TYPE_MONTH, FLAG_NONE, UNIT_MONTH, 10},
226 	{"november",	"nov",	TYPE_MONTH, FLAG_NONE, UNIT_MONTH, 11},
227 	{"december",	"dec",	TYPE_MONTH, FLAG_NONE, UNIT_MONTH, 12},
228 
229 	{"GMT",			NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 0},
230 		// TODO: add more time zones
231 
232 	{NULL}
233 };
234 
235 #define MAX_ELEMENTS	32
236 
237 class DateMask {
238 	public:
239 		DateMask() : fMask(0UL) {}
240 
241 		void Set(uint8 type) { fMask |= Flag(type); }
242 		bool IsSet(uint8 type) { return (fMask & Flag(type)) != 0; }
243 
244 		bool HasTime();
245 		bool IsComplete();
246 
247 	private:
248 		inline uint32 Flag(uint8 type) { return 1UL << type; }
249 
250 		uint32	fMask;
251 };
252 
253 
254 struct parsed_element {
255 	uint8		base_type;
256 	uint8		type;
257 	uint8		flags;
258 	uint8		unit;
259 	uint8		value_type;
260 	int8		modifier;
261 	bigtime_t	value;
262 
263 	void SetCharType(uint8 fieldType, int8 modify = MODIFY_NONE);
264 
265 	void Adopt(const known_identifier& identifier);
266 	void AdoptUnit(const known_identifier& identifier);
267 	bool IsNextLastThis();
268 };
269 
270 
271 void
272 parsed_element::SetCharType(uint8 fieldType, int8 modify)
273 {
274 	base_type = type = fieldType;
275 	value_type = VALUE_CHAR;
276 	modifier = modify;
277 }
278 
279 
280 void
281 parsed_element::Adopt(const known_identifier& identifier)
282 {
283 	base_type = type = identifier.type;
284 	flags = identifier.flags;
285 	unit = identifier.unit;
286 
287 	if (identifier.type == TYPE_MODIFIER)
288 		modifier = identifier.value;
289 
290 	value_type = VALUE_STRING;
291 	value = identifier.value;
292 }
293 
294 
295 void
296 parsed_element::AdoptUnit(const known_identifier& identifier)
297 {
298 	base_type = type = TYPE_UNIT;
299 	flags = identifier.flags;
300 	unit = identifier.unit;
301 	value *= identifier.value;
302 }
303 
304 
305 inline bool
306 parsed_element::IsNextLastThis()
307 {
308 	return base_type == TYPE_MODIFIER
309 		&& (modifier == MODIFY_NEXT || modifier == MODIFY_LAST
310 			|| modifier == MODIFY_THIS);
311 }
312 
313 
314 //	#pragma mark -
315 
316 
317 bool
318 DateMask::HasTime()
319 {
320 	// this will cause
321 	return IsSet(TYPE_HOUR);
322 }
323 
324 
325 /*!	This method checks if the date mask is complete in the
326 	sense that it doesn't need to have a prefilled "struct tm"
327 	when its time value is computed.
328 */
329 bool
330 DateMask::IsComplete()
331 {
332 	// mask must be absolute, at last
333 	if ((fMask & Flag(TYPE_UNIT)) != 0)
334 		return false;
335 
336 	// minimal set of flags to have a complete set
337 	return !(~fMask & (Flag(TYPE_DAY) | Flag(TYPE_MONTH)));
338 }
339 
340 
341 //	#pragma mark -
342 
343 
344 static status_t
345 preparseDate(const char* dateString, parsed_element* elements)
346 {
347 	int32 index = 0, modify = MODIFY_NONE;
348 	char c;
349 
350 	if (dateString == NULL)
351 		return B_ERROR;
352 
353 	memset(&elements[0], 0, sizeof(parsed_element));
354 
355 	for (; (c = dateString[0]) != '\0'; dateString++) {
356 		// we don't care about spaces
357 		if (isspace(c)) {
358 			modify = MODIFY_NONE;
359 			continue;
360 		}
361 
362 		// if we're reached our maximum number of elements, bail out
363 		if (index >= MAX_ELEMENTS)
364 			return B_ERROR;
365 
366 		if (c == ',') {
367 			elements[index].SetCharType(TYPE_COMMA);
368 		} else if (c == '.') {
369 			elements[index].SetCharType(TYPE_DOT);
370 		} else if (c == '/') {
371 			// "-" is handled differently (as a modifier)
372 			elements[index].SetCharType(TYPE_DASH);
373 		} else if (c == ':') {
374 			elements[index].SetCharType(TYPE_COLON);
375 		} else if (c == '+') {
376 			modify = MODIFY_PLUS;
377 
378 			// this counts for the next element
379 			continue;
380 		} else if (c == '-') {
381 			modify = MODIFY_MINUS;
382 			elements[index].flags = FLAG_HAS_DASH;
383 
384 			// this counts for the next element
385 			continue;
386 		} else if (isdigit(c)) {
387 			// fetch whole number
388 
389 			elements[index].type = TYPE_UNKNOWN;
390 			elements[index].value_type = VALUE_NUMERICAL;
391 			elements[index].value = atoll(dateString);
392 			elements[index].modifier = modify;
393 
394 			// skip number
395 			while (isdigit(dateString[1]))
396 				dateString++;
397 
398 			// check for "1st", "2nd, "3rd", "4th", ...
399 
400 			const char* suffixes[] = {"th", "st", "nd", "rd"};
401 			const char* validSuffix = elements[index].value > 3
402 				? "th" : suffixes[elements[index].value];
403 			if (!strncasecmp(dateString + 1, validSuffix, 2)
404 				&& !isalpha(dateString[3])) {
405 				// for now, just ignore the suffix - but we might be able
406 				// to deduce some meaning out of it, since it's not really
407 				// possible to put it in anywhere
408 				dateString += 2;
409 			}
410 		} else if (isalpha(c)) {
411 			// fetch whole string
412 
413 			const char* string = dateString;
414 			while (isalpha(dateString[1]))
415 				dateString++;
416 			int32 length = dateString + 1 - string;
417 
418 			// compare with known strings
419 			// ToDo: should understand other languages as well...
420 
421 			const known_identifier* identifier = kIdentifiers;
422 			for (; identifier->string; identifier++) {
423 				if (!strncasecmp(identifier->string, string, length)
424 					&& !identifier->string[length])
425 					break;
426 
427 				if (identifier->alternate_string != NULL
428 					&& !strncasecmp(identifier->alternate_string, string, length)
429 					&& !identifier->alternate_string[length])
430 					break;
431 			}
432 			if (identifier->string == NULL) {
433 				// unknown string, we don't have to parse any further
434 				return B_ERROR;
435 			}
436 
437 			if (index > 0 && identifier->type == TYPE_UNIT) {
438 				// this is just a unit, so it will give the last value a meaning
439 
440 				if (elements[--index].value_type != VALUE_NUMERICAL
441 					&& !elements[index].IsNextLastThis())
442 					return B_ERROR;
443 
444 				elements[index].AdoptUnit(*identifier);
445 			} else if (index > 0 && elements[index - 1].IsNextLastThis()) {
446 				if (identifier->type == TYPE_MONTH
447 					|| identifier->type == TYPE_WEEKDAY) {
448 					index--;
449 
450 					switch (elements[index].value) {
451 						case -1:
452 							elements[index].modifier = MODIFY_LAST;
453 							break;
454 						case 0:
455 							elements[index].modifier = MODIFY_THIS;
456 							break;
457 						case 1:
458 							elements[index].modifier = MODIFY_NEXT;
459 							break;
460 					}
461 					elements[index].Adopt(*identifier);
462 					elements[index].type = TYPE_UNIT;
463 				} else
464 					return B_ERROR;
465 			} else {
466 				elements[index].Adopt(*identifier);
467 			}
468 		}
469 
470 		// see if we can join any preceding modifiers
471 
472 		if (index > 0
473 			&& elements[index - 1].type == TYPE_MODIFIER
474 			&& (elements[index].flags & FLAG_NOT_MODIFIABLE) == 0) {
475 			// copy the current one to the last and go on
476 			elements[index].modifier = elements[index - 1].modifier;
477 			elements[index].value *= elements[index - 1].value;
478 			elements[index].flags |= elements[index - 1].flags;
479 			elements[index - 1] = elements[index];
480 		} else {
481 			// we filled out one parsed_element
482 			index++;
483 		}
484 
485 		if (index < MAX_ELEMENTS)
486 			memset(&elements[index], 0, sizeof(parsed_element));
487 	}
488 
489 	// were there any elements?
490 	if (index == 0)
491 		return B_ERROR;
492 
493 	elements[index].type = TYPE_END;
494 
495 	return B_OK;
496 }
497 
498 
499 static void
500 computeRelativeUnit(parsed_element& element, struct tm& tm, int* _flags)
501 {
502 	// set the relative start depending on unit
503 
504 	switch (element.unit) {
505 		case UNIT_YEAR:
506 			tm.tm_mon = 0;	// supposed to fall through
507 		case UNIT_MONTH:
508 			tm.tm_mday = 1;	// supposed to fall through
509 		case UNIT_DAY:
510 			tm.tm_hour = 0;
511 			tm.tm_min = 0;
512 			tm.tm_sec = 0;
513 			break;
514 	}
515 
516 	// adjust value
517 
518 	if ((element.flags & FLAG_RELATIVE) != 0) {
519 		if (element.unit == UNIT_MONTH)
520 			tm.tm_mon += element.value;
521 		else if (element.unit == UNIT_DAY)
522 			tm.tm_mday += element.value;
523 		else if (element.unit == UNIT_SECOND) {
524 			if (element.modifier == MODIFY_MINUS)
525 				tm.tm_sec -= element.value;
526 			else
527 				tm.tm_sec += element.value;
528 
529 			*_flags |= PARSEDATE_MINUTE_RELATIVE_TIME;
530 		} else if (element.unit == UNIT_YEAR)
531 			tm.tm_year += element.value;
532 	} else if (element.base_type == TYPE_WEEKDAY) {
533 		tm.tm_mday += element.value - tm.tm_wday;
534 
535 		if (element.modifier == MODIFY_NEXT)
536 			tm.tm_mday += 7;
537 		else if (element.modifier == MODIFY_LAST)
538 			tm.tm_mday -= 7;
539 	} else if (element.base_type == TYPE_MONTH) {
540 		tm.tm_mon = element.value - 1;
541 
542 		if (element.modifier == MODIFY_NEXT)
543 			tm.tm_year++;
544 		else if (element.modifier == MODIFY_LAST)
545 			tm.tm_year--;
546 	}
547 }
548 
549 
550 /*!	Uses the format assignment (through "format", and "optional") for the
551 	parsed elements and calculates the time value with respect to "now".
552 	Will also set the day/minute relative flags in "_flags".
553 */
554 static time_t
555 computeDate(const char* format, bool* optional, parsed_element* elements,
556 	time_t now, DateMask dateMask, int* _flags)
557 {
558 	TRACE(("matches: %s\n", format));
559 
560 	parsed_element* element = elements;
561 	uint32 position = 0;
562 	struct tm tm;
563 
564 	if (now == -1)
565 		now = time(NULL);
566 
567 	if (dateMask.IsComplete())
568 		memset(&tm, 0, sizeof(tm));
569 	else {
570 		localtime_r(&now, &tm);
571 
572 		if (dateMask.HasTime()) {
573 			tm.tm_min = 0;
574 			tm.tm_sec = 0;
575 		}
576 
577 		*_flags = PARSEDATE_RELATIVE_TIME;
578 	}
579 
580 	while (element->type != TYPE_END) {
581 		// skip whitespace
582 		while (isspace(format[0]))
583 			format++;
584 
585 		if (format[0] == '[' && format[2] == ']') {
586 			// does this optional parameter not match our date string?
587 			if (!optional[position]) {
588 				format += 3;
589 				position++;
590 				continue;
591 			}
592 
593 			format++;
594 		}
595 
596 		switch (element->value_type) {
597 			case VALUE_CHAR:
598 				// skip the single character
599 				break;
600 
601 			case VALUE_NUMERICAL:
602 				switch (format[0]) {
603 					case 'd':
604 						tm.tm_mday = element->value;
605 						break;
606 					case 'm':
607 						tm.tm_mon = element->value - 1;
608 						break;
609 					case 'H':
610 					case 'I':
611 						tm.tm_hour = element->value;
612 						break;
613 					case 'M':
614 						tm.tm_min = element->value;
615 						break;
616 					case 'S':
617 						tm.tm_sec = element->value;
618 						break;
619 					case 'y':
620 					case 'Y':
621 						tm.tm_year = element->value;
622 						if (tm.tm_year > 1900)
623 							tm.tm_year -= 1900;
624 						break;
625 					case 'T':
626 						computeRelativeUnit(*element, tm, _flags);
627 						break;
628 					case '-':
629 						// there is no TYPE_DASH element for this (just a flag)
630 						format++;
631 						continue;
632 				}
633 				break;
634 
635 			case VALUE_STRING:
636 				switch (format[0]) {
637 					case 'a':	// weekday
638 					case 'A':
639 						// we'll apply this element later, if still necessary
640 						if (!dateMask.IsComplete())
641 							computeRelativeUnit(*element, tm, _flags);
642 						break;
643 					case 'b':	// month
644 					case 'B':
645 						tm.tm_mon = element->value - 1;
646 						break;
647 					case 'p':	// meridian
648 						tm.tm_sec += element->value;
649 						break;
650 					case 'z':	// time zone
651 					case 'Z':
652 						tm.tm_sec += element->value - timezone;
653 						break;
654 					case 'T':	// time unit
655 						if ((element->flags & FLAG_NOW) != 0) {
656 							*_flags = PARSEDATE_MINUTE_RELATIVE_TIME
657 								| PARSEDATE_RELATIVE_TIME;
658 							break;
659 						}
660 
661 						computeRelativeUnit(*element, tm, _flags);
662 						break;
663 				}
664 				break;
665 		}
666 
667 		// format matched at this point, check next element
668 		format++;
669 		if (format[0] == ']')
670 			format++;
671 
672 		position++;
673 		element++;
674 	}
675 
676 	return mktime(&tm);
677 }
678 
679 
680 time_t
681 parsedate_etc(const char* dateString, time_t now, int* _flags)
682 {
683 	// preparse date string so that it can be easily compared to our formats
684 
685 	parsed_element elements[MAX_ELEMENTS];
686 
687 	if (preparseDate(dateString, elements) < B_OK) {
688 		*_flags = PARSEDATE_INVALID_DATE;
689 		return B_ERROR;
690 	}
691 
692 #if TRACE_PARSEDATE
693 	for (int32 index = 0; elements[index].type != TYPE_END; index++) {
694 		parsed_element e = elements[index];
695 
696 		printf("  %ld: type = %u, base_type = %u, unit = %u, flags = %u, "
697 			"value = %Ld (%s)\n", index, e.type, e.base_type, e.unit, e.flags,
698 			e.value, e.value_type == VALUE_NUMERICAL
699 			? "numerical" : (e.value_type == VALUE_STRING ? "string" : "char"));
700 	}
701 #endif
702 
703 	bool optional[MAX_ELEMENTS];
704 
705 	for (int32 index = 0; sFormatsTable[index]; index++) {
706 		// test if this format matches our date string
707 
708 		const char* format = sFormatsTable[index];
709 		uint32 position = 0;
710 		DateMask dateMask;
711 
712 		parsed_element* element = elements;
713 		while (element->type != TYPE_END) {
714 			// skip whitespace
715 			while (isspace(format[0]))
716 				format++;
717 
718 			if (format[0] == '[' && format[2] == ']') {
719 				optional[position] = true;
720 				format++;
721 			} else
722 				optional[position] = false;
723 
724 			switch (element->value_type) {
725 				case VALUE_CHAR:
726 					// check the allowed single characters
727 
728 					switch (element->type) {
729 						case TYPE_DOT:
730 							if (format[0] != '.')
731 								goto next_format;
732 							break;
733 						case TYPE_DASH:
734 							if (format[0] != '-')
735 								goto next_format;
736 							break;
737 						case TYPE_COMMA:
738 							if (format[0] != ',')
739 								goto next_format;
740 							break;
741 						case TYPE_COLON:
742 							if (format[0] != ':')
743 								goto next_format;
744 							break;
745 						default:
746 							goto next_format;
747 					}
748 					break;
749 
750 				case VALUE_NUMERICAL:
751 					// make sure that unit types are respected
752 					if (element->type == TYPE_UNIT && format[0] != 'T')
753 						goto next_format;
754 
755 					switch (format[0]) {
756 						case 'd':
757 							if (element->value > 31)
758 								goto next_format;
759 
760 							dateMask.Set(TYPE_DAY);
761 							break;
762 						case 'm':
763 							if (element->value > 12)
764 								goto next_format;
765 
766 							dateMask.Set(TYPE_MONTH);
767 							break;
768 						case 'H':
769 						case 'I':
770 							if (element->value > 24)
771 								goto next_format;
772 
773 							dateMask.Set(TYPE_HOUR);
774 							break;
775 						case 'M':
776 							dateMask.Set(TYPE_MINUTE);
777 						case 'S':
778 							if (element->value > 59)
779 								goto next_format;
780 
781 							break;
782 						case 'y':
783 						case 'Y':
784 							// accept all values
785 							break;
786 						case 'T':
787 							dateMask.Set(TYPE_UNIT);
788 							break;
789 						case '-':
790 							if ((element->flags & FLAG_HAS_DASH) != 0) {
791 								element--;	// consider this element again
792 								break;
793 							}
794 							// supposed to fall through
795 						default:
796 							goto next_format;
797 					}
798 					break;
799 
800 				case VALUE_STRING:
801 					switch (format[0]) {
802 						case 'a':	// weekday
803 						case 'A':
804 							if (element->type != TYPE_WEEKDAY)
805 								goto next_format;
806 							break;
807 						case 'b':	// month
808 						case 'B':
809 							if (element->type != TYPE_MONTH)
810 								goto next_format;
811 
812 							dateMask.Set(TYPE_MONTH);
813 							break;
814 						case 'p':	// meridian
815 							if (element->type != TYPE_MERIDIAN)
816 								goto next_format;
817 							break;
818 						case 'z':	// time zone
819 						case 'Z':
820 							if (element->type != TYPE_TIME_ZONE)
821 								goto next_format;
822 							break;
823 						case 'T':	// time unit
824 							if (element->type != TYPE_UNIT)
825 								goto next_format;
826 
827 							dateMask.Set(TYPE_UNIT);
828 							break;
829 						default:
830 							goto next_format;
831 					}
832 					break;
833 			}
834 
835 			// format matched at this point, check next element
836 			if (optional[position])
837 				format++;
838 			format++;
839 			position++;
840 			element++;
841 			continue;
842 
843 		next_format:
844 			// format didn't match element - let's see if the current
845 			// one is only optional (in which case we can continue)
846 			if (!optional[position])
847 				goto skip_format;
848 
849 			optional[position] = false;
850 			format += 2;
851 			position++;
852 				// skip the closing ']'
853 		}
854 
855 		// check if the format is already empty (since we reached our last
856 		// element)
857 		while (format[0]) {
858 			if (format[0] == '[')
859 				format += 3;
860 			else if (isspace(format[0]))
861 				format++;
862 			else
863 				break;
864 		}
865 		if (format[0])
866 			goto skip_format;
867 
868 		// made it here? then we seem to have found our guy
869 
870 		return computeDate(sFormatsTable[index], optional, elements, now,
871 			dateMask, _flags);
872 
873 	skip_format:
874 		// check if the next format has the same beginning as the skipped one,
875 		// and if so, skip that one, too.
876 
877 		int32 length = format + 1 - sFormatsTable[index];
878 
879 		while (sFormatsTable[index + 1]
880 			&& !strncmp(sFormatsTable[index], sFormatsTable[index + 1], length))
881 			index++;
882 	}
883 
884 	// didn't find any matching formats
885 	return B_ERROR;
886 }
887 
888 
889 time_t
890 parsedate(const char* dateString, time_t now)
891 {
892 	int flags = 0;
893 
894 	return parsedate_etc(dateString, now, &flags);
895 }
896 
897 
898 void
899 set_dateformats(const char** table)
900 {
901 	sFormatsTable = table ? table : kFormatsTable;
902 }
903 
904 
905 const char**
906 get_dateformats(void)
907 {
908 	return const_cast<const char**>(sFormatsTable);
909 }
910 
911