xref: /haiku/src/system/libroot/os/parsedate.cpp (revision e6a9c269bea4e88b936bf9594dc3e922281419bf)
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 1
18 #if TRACE_PARSEDATE
19 #	define TRACE(x) debug_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 		memset(&elements[index], 0, sizeof(parsed_element));
486 	}
487 
488 	// were there any elements?
489 	if (index == 0)
490 		return B_ERROR;
491 
492 	elements[index].type = TYPE_END;
493 
494 	return B_OK;
495 }
496 
497 
498 static void
499 computeRelativeUnit(parsed_element& element, struct tm& tm, int* _flags)
500 {
501 	// set the relative start depending on unit
502 
503 	switch (element.unit) {
504 		case UNIT_YEAR:
505 			tm.tm_mon = 0;	// supposed to fall through
506 		case UNIT_MONTH:
507 			tm.tm_mday = 1;	// supposed to fall through
508 		case UNIT_DAY:
509 			tm.tm_hour = 0;
510 			tm.tm_min = 0;
511 			tm.tm_sec = 0;
512 			break;
513 	}
514 
515 	// adjust value
516 
517 	if ((element.flags & FLAG_RELATIVE) != 0) {
518 		if (element.unit == UNIT_MONTH)
519 			tm.tm_mon += element.value;
520 		else if (element.unit == UNIT_DAY)
521 			tm.tm_mday += element.value;
522 		else if (element.unit == UNIT_SECOND) {
523 			if (element.modifier == MODIFY_MINUS)
524 				tm.tm_sec -= element.value;
525 			else
526 				tm.tm_sec += element.value;
527 
528 			*_flags |= PARSEDATE_MINUTE_RELATIVE_TIME;
529 		} else if (element.unit == UNIT_YEAR)
530 			tm.tm_year += element.value;
531 	} else if (element.base_type == TYPE_WEEKDAY) {
532 		tm.tm_mday += element.value - tm.tm_wday;
533 
534 		if (element.modifier == MODIFY_NEXT)
535 			tm.tm_mday += 7;
536 		else if (element.modifier == MODIFY_LAST)
537 			tm.tm_mday -= 7;
538 	} else if (element.base_type == TYPE_MONTH) {
539 		tm.tm_mon = element.value - 1;
540 
541 		if (element.modifier == MODIFY_NEXT)
542 			tm.tm_year++;
543 		else if (element.modifier == MODIFY_LAST)
544 			tm.tm_year--;
545 	}
546 }
547 
548 
549 /*!	Uses the format assignment (through "format", and "optional") for the
550 	parsed elements and calculates the time value with respect to "now".
551 	Will also set the day/minute relative flags in "_flags".
552 */
553 static time_t
554 computeDate(const char* format, bool* optional, parsed_element* elements,
555 	time_t now, DateMask dateMask, int* _flags)
556 {
557 	TRACE(("matches: %s\n", format));
558 
559 	parsed_element* element = elements;
560 	uint32 position = 0;
561 	struct tm tm;
562 
563 	if (now == -1)
564 		now = time(NULL);
565 
566 	if (dateMask.IsComplete())
567 		memset(&tm, 0, sizeof(tm));
568 	else {
569 		localtime_r(&now, &tm);
570 
571 		if (dateMask.HasTime()) {
572 			tm.tm_min = 0;
573 			tm.tm_sec = 0;
574 		}
575 
576 		*_flags = PARSEDATE_RELATIVE_TIME;
577 	}
578 
579 	while (element->type != TYPE_END) {
580 		// skip whitespace
581 		while (isspace(format[0]))
582 			format++;
583 
584 		if (format[0] == '[' && format[2] == ']') {
585 			// does this optional parameter not match our date string?
586 			if (!optional[position]) {
587 				format += 3;
588 				position++;
589 				continue;
590 			}
591 
592 			format++;
593 		}
594 
595 		switch (element->value_type) {
596 			case VALUE_CHAR:
597 				// skip the single character
598 				break;
599 
600 			case VALUE_NUMERICAL:
601 				switch (format[0]) {
602 					case 'd':
603 						tm.tm_mday = element->value;
604 						break;
605 					case 'm':
606 						tm.tm_mon = element->value - 1;
607 						break;
608 					case 'H':
609 					case 'I':
610 						tm.tm_hour = element->value;
611 						break;
612 					case 'M':
613 						tm.tm_min = element->value;
614 						break;
615 					case 'S':
616 						tm.tm_sec = element->value;
617 						break;
618 					case 'y':
619 					case 'Y':
620 						tm.tm_year = element->value;
621 						if (tm.tm_year > 1900)
622 							tm.tm_year -= 1900;
623 						break;
624 					case 'T':
625 						computeRelativeUnit(*element, tm, _flags);
626 						break;
627 					case '-':
628 						// there is no TYPE_DASH element for this (just a flag)
629 						format++;
630 						continue;
631 				}
632 				break;
633 
634 			case VALUE_STRING:
635 				switch (format[0]) {
636 					case 'a':	// weekday
637 					case 'A':
638 						// we'll apply this element later, if still necessary
639 						if (!dateMask.IsComplete())
640 							computeRelativeUnit(*element, tm, _flags);
641 						break;
642 					case 'b':	// month
643 					case 'B':
644 						tm.tm_mon = element->value - 1;
645 						break;
646 					case 'p':	// meridian
647 						tm.tm_sec += element->value;
648 						break;
649 					case 'z':	// time zone
650 					case 'Z':
651 						tm.tm_sec += element->value - timezone;
652 						break;
653 					case 'T':	// time unit
654 						if ((element->flags & FLAG_NOW) != 0) {
655 							*_flags = PARSEDATE_MINUTE_RELATIVE_TIME
656 								| PARSEDATE_RELATIVE_TIME;
657 							break;
658 						}
659 
660 						computeRelativeUnit(*element, tm, _flags);
661 						break;
662 				}
663 				break;
664 		}
665 
666 		// format matched at this point, check next element
667 		format++;
668 		if (format[0] == ']')
669 			format++;
670 
671 		position++;
672 		element++;
673 	}
674 
675 	return mktime(&tm);
676 }
677 
678 
679 time_t
680 parsedate_etc(const char* dateString, time_t now, int* _flags)
681 {
682 	// preparse date string so that it can be easily compared to our formats
683 
684 	parsed_element elements[MAX_ELEMENTS];
685 
686 	if (preparseDate(dateString, elements) < B_OK) {
687 		*_flags = PARSEDATE_INVALID_DATE;
688 		return B_ERROR;
689 	}
690 
691 #if TRACE_PARSEDATE
692 	for (int32 index = 0; elements[index].type != TYPE_END; index++) {
693 		parsed_element e = elements[index];
694 
695 		printf("  %ld: type = %u, base_type = %u, unit = %u, flags = %u, "
696 			"value = %Ld (%s)\n", index, e.type, e.base_type, e.unit, e.flags,
697 			e.value, e.value_type == VALUE_NUMERICAL
698 			? "numerical" : (e.value_type == VALUE_STRING ? "string" : "char"));
699 	}
700 #endif
701 
702 	bool optional[MAX_ELEMENTS];
703 
704 	for (int32 index = 0; sFormatsTable[index]; index++) {
705 		// test if this format matches our date string
706 
707 		const char* format = sFormatsTable[index];
708 		uint32 position = 0;
709 		DateMask dateMask;
710 
711 		parsed_element* element = elements;
712 		while (element->type != TYPE_END) {
713 			// skip whitespace
714 			while (isspace(format[0]))
715 				format++;
716 
717 			if (format[0] == '[' && format[2] == ']') {
718 				optional[position] = true;
719 				format++;
720 			} else
721 				optional[position] = false;
722 
723 			switch (element->value_type) {
724 				case VALUE_CHAR:
725 					// check the allowed single characters
726 
727 					switch (element->type) {
728 						case TYPE_DOT:
729 							if (format[0] != '.')
730 								goto next_format;
731 							break;
732 						case TYPE_DASH:
733 							if (format[0] != '-')
734 								goto next_format;
735 							break;
736 						case TYPE_COMMA:
737 							if (format[0] != ',')
738 								goto next_format;
739 							break;
740 						case TYPE_COLON:
741 							if (format[0] != ':')
742 								goto next_format;
743 							break;
744 						default:
745 							goto next_format;
746 					}
747 					break;
748 
749 				case VALUE_NUMERICAL:
750 					// make sure that unit types are respected
751 					if (element->type == TYPE_UNIT && format[0] != 'T')
752 						goto next_format;
753 
754 					switch (format[0]) {
755 						case 'd':
756 							if (element->value > 31)
757 								goto next_format;
758 
759 							dateMask.Set(TYPE_DAY);
760 							break;
761 						case 'm':
762 							if (element->value > 12)
763 								goto next_format;
764 
765 							dateMask.Set(TYPE_MONTH);
766 							break;
767 						case 'H':
768 						case 'I':
769 							if (element->value > 24)
770 								goto next_format;
771 
772 							dateMask.Set(TYPE_HOUR);
773 							break;
774 						case 'M':
775 							dateMask.Set(TYPE_MINUTE);
776 						case 'S':
777 							if (element->value > 59)
778 								goto next_format;
779 
780 							break;
781 						case 'y':
782 						case 'Y':
783 							// accept all values
784 							break;
785 						case 'T':
786 							dateMask.Set(TYPE_UNIT);
787 							break;
788 						case '-':
789 							if ((element->flags & FLAG_HAS_DASH) != 0) {
790 								element--;	// consider this element again
791 								break;
792 							}
793 							// supposed to fall through
794 						default:
795 							goto next_format;
796 					}
797 					break;
798 
799 				case VALUE_STRING:
800 					switch (format[0]) {
801 						case 'a':	// weekday
802 						case 'A':
803 							if (element->type != TYPE_WEEKDAY)
804 								goto next_format;
805 							break;
806 						case 'b':	// month
807 						case 'B':
808 							if (element->type != TYPE_MONTH)
809 								goto next_format;
810 
811 							dateMask.Set(TYPE_MONTH);
812 							break;
813 						case 'p':	// meridian
814 							if (element->type != TYPE_MERIDIAN)
815 								goto next_format;
816 							break;
817 						case 'z':	// time zone
818 						case 'Z':
819 							if (element->type != TYPE_TIME_ZONE)
820 								goto next_format;
821 							break;
822 						case 'T':	// time unit
823 							if (element->type != TYPE_UNIT)
824 								goto next_format;
825 
826 							dateMask.Set(TYPE_UNIT);
827 							break;
828 						default:
829 							goto next_format;
830 					}
831 					break;
832 			}
833 
834 			// format matched at this point, check next element
835 			if (optional[position])
836 				format++;
837 			format++;
838 			position++;
839 			element++;
840 			continue;
841 
842 		next_format:
843 			// format didn't match element - let's see if the current
844 			// one is only optional (in which case we can continue)
845 			if (!optional[position])
846 				goto skip_format;
847 
848 			optional[position] = false;
849 			format += 2;
850 			position++;
851 				// skip the closing ']'
852 		}
853 
854 		// check if the format is already empty (since we reached our last
855 		// element)
856 		while (format[0]) {
857 			if (format[0] == '[')
858 				format += 3;
859 			else if (isspace(format[0]))
860 				format++;
861 			else
862 				break;
863 		}
864 		if (format[0])
865 			goto skip_format;
866 
867 		// made it here? then we seem to have found our guy
868 
869 		return computeDate(sFormatsTable[index], optional, elements, now,
870 			dateMask, _flags);
871 
872 	skip_format:
873 		// check if the next format has the same beginning as the skipped one,
874 		// and if so, skip that one, too.
875 
876 		int32 length = format + 1 - sFormatsTable[index];
877 
878 		while (sFormatsTable[index + 1]
879 			&& !strncmp(sFormatsTable[index], sFormatsTable[index + 1], length))
880 			index++;
881 	}
882 
883 	// didn't find any matching formats
884 	return B_ERROR;
885 }
886 
887 
888 time_t
889 parsedate(const char* dateString, time_t now)
890 {
891 	int flags = 0;
892 
893 	return parsedate_etc(dateString, now, &flags);
894 }
895 
896 
897 void
898 set_dateformats(const char** table)
899 {
900 	sFormatsTable = table ? table : kFormatsTable;
901 }
902 
903 
904 const char**
905 get_dateformats(void)
906 {
907 	return const_cast<const char**>(sFormatsTable);
908 }
909 
910