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