xref: /haiku/src/system/libroot/os/parsedate.cpp (revision 7a74a5df454197933bc6e80a542102362ee98703)
1 /*
2  * Copyright 2003-2010, 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 	{"UTC",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 0},
231 	// the following list has been generated from info found at
232 	//     http://www.timegenie.com/timezones
233 	{"ACDT",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 1050 * 36},
234 	{"ACIT",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 800 * 36},
235 	{"ACST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 950 * 36},
236 	{"ACT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -500 * 36},
237 	{"ACWST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 875 * 36},
238 	{"ADT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -300 * 36},
239 	{"AEDT",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 1100 * 36},
240 	{"AEST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 1000 * 36},
241 	{"AFT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 450 * 36},
242 	{"AKDT",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -800 * 36},
243 	{"AKST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -900 * 36},
244 	{"AMDT",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 500 * 36},
245 	{"AMST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 400 * 36},
246 	{"ANAST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 1300 * 36},
247 	{"ANAT",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 1200 * 36},
248 	{"APO",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 825 * 36},
249 	{"ARDT",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -200 * 36},
250 	{"ART",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -300 * 36},
251 	{"AST",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -400 * 36},
252 	{"AWST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 800 * 36},
253 	{"AZODT",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 0 * 36},
254 	{"AZOST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -100 * 36},
255 	{"AZST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 500 * 36},
256 	{"AZT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 400 * 36},
257 	{"BIT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -1200 * 36},
258 	{"BDT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 600 * 36},
259 	{"BEST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -200 * 36},
260 	{"BIOT",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 600 * 36},
261 	{"BNT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 800 * 36},
262 	{"BOT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -400 * 36},
263 	{"BRST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -200 * 36},
264 	{"BRT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -300 * 36},
265 	{"BST",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 100 * 36},
266 	{"BTT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 600 * 36},
267 	{"BWDT",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -300 * 36},
268 	{"BWST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -400 * 36},
269 	{"CAST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 500 * 36},
270 	{"CAT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 200 * 36},
271 	{"CCT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 650 * 36},
272 	{"CDT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -500 * 36},
273 	{"CEST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 200 * 36},
274 	{"CET",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 100 * 36},
275 	{"CGST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -200 * 36},
276 	{"CGT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -300 * 36},
277 	{"CHADT",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 1375 * 36},
278 	{"CHAST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 1275 * 36},
279 	{"CHST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 1000 * 36},
280 	{"CIST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -800 * 36},
281 	{"CKT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -1000 * 36},
282 	{"CLDT",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -300 * 36},
283 	{"CLST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -400 * 36},
284 	{"COT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -500 * 36},
285 	{"CST",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -600 * 36},
286 	{"CVT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -100 * 36},
287 	{"CXT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 700 * 36},
288 	{"DAVT",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 700 * 36},
289 	{"DTAT",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 1000 * 36},
290 	{"EADT",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -500 * 36},
291 	{"EAST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -600 * 36},
292 	{"EAT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 300 * 36},
293 	{"ECT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -500 * 36},
294 	{"EDT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -400 * 36},
295 	{"EEST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 300 * 36},
296 	{"EET",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 200 * 36},
297 	{"EGT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -100 * 36},
298 	{"EGST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 0 * 36},
299 	{"EKST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 600 * 36},
300 	{"EST",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -500 * 36},
301 	{"FJT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 1200 * 36},
302 	{"FKDT",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -300 * 36},
303 	{"FKST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -400 * 36},
304 	{"GALT",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -600 * 36},
305 	{"GET",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 400 * 36},
306 	{"GFT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -300 * 36},
307 	{"GILT",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 1200 * 36},
308 	{"GIT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -900 * 36},
309 	{"GST",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 400 * 36},
310 	{"GYT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -400 * 36},
311 	{"HADT",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -900 * 36},
312 	{"HAST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -1000 * 36},
313 	{"HKST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 800 * 36},
314 	{"HMT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 500 * 36},
315 	{"ICT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 700 * 36},
316 	{"IDT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 300 * 36},
317 	{"IRDT",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 450 * 36},
318 	{"IRKST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 900 * 36},
319 	{"IRKT",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 800 * 36},
320 	{"IRST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 350 * 36},
321 	{"IST",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 200 * 36},
322 	{"JFDT",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -300 * 36},
323 	{"JFST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -400 * 36},
324 	{"JST",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 900 * 36},
325 	{"KGST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 600 * 36},
326 	{"KGT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 500 * 36},
327 	{"KRAST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 800 * 36},
328 	{"KRAT",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 700 * 36},
329 	{"KOST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 1100 * 36},
330 	{"KOVT",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 700 * 36},
331 	{"KOVST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 800 * 36},
332 	{"KST",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 900 * 36},
333 	{"LHDT",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 1100 * 36},
334 	{"LHST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 1050 * 36},
335 	{"LINT",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 1400 * 36},
336 	{"LKT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 600 * 36},
337 	{"MAGST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 1200 * 36},
338 	{"MAGT",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 1100 * 36},
339 	{"MAWT",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 600 * 36},
340 	{"MBT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 800 * 36},
341 	{"MDT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -600 * 36},
342 	{"MIT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -950 * 36},
343 	{"MHT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 1200 * 36},
344 	{"MMT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 650 * 36},
345 	{"MNT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 800 * 36},
346 	{"MNST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 900 * 36},
347 	{"MSD",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 400 * 36},
348 	{"MSK",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 300 * 36},
349 	{"MST",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -700 * 36},
350 	{"MUST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 500 * 36},
351 	{"MUT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 400 * 36},
352 	{"MVT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 500 * 36},
353 	{"MYT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 800 * 36},
354 	{"NCT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 1100 * 36},
355 	{"NDT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -250 * 36},
356 	{"NFT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 1150 * 36},
357 	{"NPT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 575 * 36},
358 	{"NRT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 1200 * 36},
359 	{"NOVST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 700 * 36},
360 	{"NOVT",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 600 * 36},
361 	{"NST",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -350 * 36},
362 	{"NUT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -1100 * 36},
363 	{"NZDT",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 1300 * 36},
364 	{"NZST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 1200 * 36},
365 	{"OMSK",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 700 * 36},
366 	{"OMST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 600 * 36},
367 	{"PDT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -700 * 36},
368 	{"PETST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 1300 * 36},
369 	{"PET",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -500 * 36},
370 	{"PETT",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 1200 * 36},
371 	{"PGT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 1000 * 36},
372 	{"PHOT",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 1300 * 36},
373 	{"PHT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 800 * 36},
374 	{"PIT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 800 * 36},
375 	{"PKT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 500 * 36},
376 	{"PKST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 600 * 36},
377 	{"PMDT",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -200 * 36},
378 	{"PMST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -300 * 36},
379 	{"PONT",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 1100 * 36},
380 	{"PST",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -800 * 36},
381 	{"PWT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 900 * 36},
382 	{"PYST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -300 * 36},
383 	{"PYT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -400 * 36},
384 	{"RET",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 400 * 36},
385 	{"ROTT",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -300 * 36},
386 	{"SAMST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 500 * 36},
387 	{"SAMT",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 400 * 36},
388 	{"SAST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 200 * 36},
389 	{"SBT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 1100 * 36},
390 	{"SCDT",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 1300 * 36},
391 	{"SCST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 1200 * 36},
392 	{"SCT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 400 * 36},
393 	{"SGT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 800 * 36},
394 	{"SIT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 800 * 36},
395 	{"SLT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -400 * 36},
396 	{"SLST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -300 * 36},
397 	{"SRT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -300 * 36},
398 	{"SST",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -1100 * 36},
399 	{"SYST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 300 * 36},
400 	{"SYT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 200 * 36},
401 	{"TAHT",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -1000 * 36},
402 	{"TFT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 500 * 36},
403 	{"TJT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 500 * 36},
404 	{"TKT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -1000 * 36},
405 	{"TMT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 500 * 36},
406 	{"TOT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 1300 * 36},
407 	{"TPT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 900 * 36},
408 	{"TRUT",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 1000 * 36},
409 	{"TVT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 1200 * 36},
410 	{"TWT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 800 * 36},
411 	{"UYT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -300 * 36},
412 	{"UYST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -200 * 36},
413 	{"UZT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 500 * 36},
414 	{"VLAST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 1100 * 36},
415 	{"VLAT",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 1000 * 36},
416 	{"VOST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 600 * 36},
417 	{"VST",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, -450 * 36},
418 	{"VUT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 1100 * 36},
419 	{"WAST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 200 * 36},
420 	{"WAT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 100 * 36},
421 	{"WEST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 100 * 36},
422 	{"WET",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 0 * 36},
423 	{"WFT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 1200 * 36},
424 	{"WIB",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 700 * 36},
425 	{"WIT",		NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 900 * 36},
426 	{"WITA",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 800 * 36},
427 	{"WKST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 500 * 36},
428 	{"YAKST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 1000 * 36},
429 	{"YAKT",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 900 * 36},
430 	{"YAPT",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 1000 * 36},
431 	{"YEKST",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 600 * 36},
432 	{"YEKT",	NULL,	TYPE_TIME_ZONE,	FLAG_NONE, UNIT_SECOND, 500 * 36},
433 
434 	{NULL}
435 };
436 
437 #define MAX_ELEMENTS	32
438 
439 class DateMask {
440 	public:
441 		DateMask() : fMask(0UL) {}
442 
443 		void Set(uint8 type) { fMask |= Flag(type); }
444 		bool IsSet(uint8 type) { return (fMask & Flag(type)) != 0; }
445 
446 		bool HasTime();
447 		bool IsComplete();
448 
449 	private:
450 		inline uint32 Flag(uint8 type) const { return 1UL << type; }
451 
452 		uint32	fMask;
453 };
454 
455 
456 struct parsed_element {
457 	uint8		base_type;
458 	uint8		type;
459 	uint8		flags;
460 	uint8		unit;
461 	uint8		value_type;
462 	int8		modifier;
463 	bigtime_t	value;
464 
465 	void SetCharType(uint8 fieldType, int8 modify = MODIFY_NONE);
466 
467 	void Adopt(const known_identifier& identifier);
468 	void AdoptUnit(const known_identifier& identifier);
469 	bool IsNextLastThis();
470 };
471 
472 
473 void
474 parsed_element::SetCharType(uint8 fieldType, int8 modify)
475 {
476 	base_type = type = fieldType;
477 	value_type = VALUE_CHAR;
478 	modifier = modify;
479 }
480 
481 
482 void
483 parsed_element::Adopt(const known_identifier& identifier)
484 {
485 	base_type = type = identifier.type;
486 	flags = identifier.flags;
487 	unit = identifier.unit;
488 
489 	if (identifier.type == TYPE_MODIFIER)
490 		modifier = identifier.value;
491 
492 	value_type = VALUE_STRING;
493 	value = identifier.value;
494 }
495 
496 
497 void
498 parsed_element::AdoptUnit(const known_identifier& identifier)
499 {
500 	base_type = type = TYPE_UNIT;
501 	flags = identifier.flags;
502 	unit = identifier.unit;
503 	value *= identifier.value;
504 }
505 
506 
507 inline bool
508 parsed_element::IsNextLastThis()
509 {
510 	return base_type == TYPE_MODIFIER
511 		&& (modifier == MODIFY_NEXT || modifier == MODIFY_LAST
512 			|| modifier == MODIFY_THIS);
513 }
514 
515 
516 //	#pragma mark -
517 
518 
519 bool
520 DateMask::HasTime()
521 {
522 	// this will cause
523 	return IsSet(TYPE_HOUR);
524 }
525 
526 
527 /*!	This method checks if the date mask is complete in the
528 	sense that it doesn't need to have a prefilled "struct tm"
529 	when its time value is computed.
530 */
531 bool
532 DateMask::IsComplete()
533 {
534 	// mask must be absolute, at last
535 	if ((fMask & Flag(TYPE_UNIT)) != 0)
536 		return false;
537 
538 	// minimal set of flags to have a complete set
539 	return !(~fMask & (Flag(TYPE_DAY) | Flag(TYPE_MONTH)));
540 }
541 
542 
543 //	#pragma mark -
544 
545 
546 static status_t
547 preparseDate(const char* dateString, parsed_element* elements)
548 {
549 	int32 index = 0, modify = MODIFY_NONE;
550 	char c;
551 
552 	if (dateString == NULL)
553 		return B_ERROR;
554 
555 	memset(&elements[0], 0, sizeof(parsed_element));
556 
557 	for (; (c = dateString[0]) != '\0'; dateString++) {
558 		// we don't care about spaces
559 		if (isspace(c)) {
560 			modify = MODIFY_NONE;
561 			continue;
562 		}
563 
564 		// if we're reached our maximum number of elements, bail out
565 		if (index >= MAX_ELEMENTS)
566 			return B_ERROR;
567 
568 		if (c == ',') {
569 			elements[index].SetCharType(TYPE_COMMA);
570 		} else if (c == '.') {
571 			elements[index].SetCharType(TYPE_DOT);
572 		} else if (c == '/') {
573 			// "-" is handled differently (as a modifier)
574 			elements[index].SetCharType(TYPE_DASH);
575 		} else if (c == ':') {
576 			elements[index].SetCharType(TYPE_COLON);
577 		} else if (c == '+') {
578 			modify = MODIFY_PLUS;
579 
580 			// this counts for the next element
581 			continue;
582 		} else if (c == '-') {
583 			modify = MODIFY_MINUS;
584 			elements[index].flags = FLAG_HAS_DASH;
585 
586 			// this counts for the next element
587 			continue;
588 		} else if (isdigit(c)) {
589 			// fetch whole number
590 
591 			elements[index].type = TYPE_UNKNOWN;
592 			elements[index].value_type = VALUE_NUMERICAL;
593 			elements[index].value = atoll(dateString);
594 			elements[index].modifier = modify;
595 
596 			// skip number
597 			while (isdigit(dateString[1]))
598 				dateString++;
599 
600 			// check for "1st", "2nd, "3rd", "4th", ...
601 
602 			const char* suffixes[] = {"th", "st", "nd", "rd"};
603 			const char* validSuffix = elements[index].value > 3
604 				? "th" : suffixes[elements[index].value];
605 			if (!strncasecmp(dateString + 1, validSuffix, 2)
606 				&& !isalpha(dateString[3])) {
607 				// for now, just ignore the suffix - but we might be able
608 				// to deduce some meaning out of it, since it's not really
609 				// possible to put it in anywhere
610 				dateString += 2;
611 			}
612 		} else if (isalpha(c)) {
613 			// fetch whole string
614 
615 			const char* string = dateString;
616 			while (isalpha(dateString[1]))
617 				dateString++;
618 			int32 length = dateString + 1 - string;
619 
620 			// compare with known strings
621 			// ToDo: should understand other languages as well...
622 
623 			const known_identifier* identifier = kIdentifiers;
624 			for (; identifier->string; identifier++) {
625 				if (!strncasecmp(identifier->string, string, length)
626 					&& !identifier->string[length])
627 					break;
628 
629 				if (identifier->alternate_string != NULL
630 					&& !strncasecmp(identifier->alternate_string, string, length)
631 					&& !identifier->alternate_string[length])
632 					break;
633 			}
634 			if (identifier->string == NULL) {
635 				// unknown string, we don't have to parse any further
636 				return B_ERROR;
637 			}
638 
639 			if (index > 0 && identifier->type == TYPE_UNIT) {
640 				// this is just a unit, so it will give the last value a meaning
641 
642 				if (elements[--index].value_type != VALUE_NUMERICAL
643 					&& !elements[index].IsNextLastThis())
644 					return B_ERROR;
645 
646 				elements[index].AdoptUnit(*identifier);
647 			} else if (index > 0 && elements[index - 1].IsNextLastThis()) {
648 				if (identifier->type == TYPE_MONTH
649 					|| identifier->type == TYPE_WEEKDAY) {
650 					index--;
651 
652 					switch (elements[index].value) {
653 						case -1:
654 							elements[index].modifier = MODIFY_LAST;
655 							break;
656 						case 0:
657 							elements[index].modifier = MODIFY_THIS;
658 							break;
659 						case 1:
660 							elements[index].modifier = MODIFY_NEXT;
661 							break;
662 					}
663 					elements[index].Adopt(*identifier);
664 					elements[index].type = TYPE_UNIT;
665 				} else
666 					return B_ERROR;
667 			} else {
668 				elements[index].Adopt(*identifier);
669 			}
670 		}
671 
672 		// see if we can join any preceding modifiers
673 
674 		if (index > 0
675 			&& elements[index - 1].type == TYPE_MODIFIER
676 			&& (elements[index].flags & FLAG_NOT_MODIFIABLE) == 0) {
677 			// copy the current one to the last and go on
678 			elements[index].modifier = elements[index - 1].modifier;
679 			elements[index].value *= elements[index - 1].value;
680 			elements[index].flags |= elements[index - 1].flags;
681 			elements[index - 1] = elements[index];
682 		} else {
683 			// we filled out one parsed_element
684 			index++;
685 		}
686 
687 		if (index < MAX_ELEMENTS)
688 			memset(&elements[index], 0, sizeof(parsed_element));
689 	}
690 
691 	// were there any elements?
692 	if (index == 0)
693 		return B_ERROR;
694 
695 	elements[index].type = TYPE_END;
696 
697 	return B_OK;
698 }
699 
700 
701 static void
702 computeRelativeUnit(parsed_element& element, struct tm& tm, int* _flags)
703 {
704 	// set the relative start depending on unit
705 
706 	switch (element.unit) {
707 		case UNIT_YEAR:
708 			tm.tm_mon = 0;	// supposed to fall through
709 		case UNIT_MONTH:
710 			tm.tm_mday = 1;	// supposed to fall through
711 		case UNIT_DAY:
712 			tm.tm_hour = 0;
713 			tm.tm_min = 0;
714 			tm.tm_sec = 0;
715 			break;
716 	}
717 
718 	// adjust value
719 
720 	if ((element.flags & FLAG_RELATIVE) != 0) {
721 		bigtime_t value = element.value;
722 		if (element.modifier == MODIFY_MINUS)
723 			value = -element.value;
724 
725 		if (element.unit == UNIT_MONTH)
726 			tm.tm_mon += value;
727 		else if (element.unit == UNIT_DAY)
728 			tm.tm_mday += value;
729 		else if (element.unit == UNIT_SECOND) {
730 			tm.tm_sec += value;
731 			*_flags |= PARSEDATE_MINUTE_RELATIVE_TIME;
732 		} else if (element.unit == UNIT_YEAR)
733 			tm.tm_year += value;
734 	} else if (element.base_type == TYPE_WEEKDAY) {
735 		tm.tm_mday += element.value - tm.tm_wday;
736 
737 		if (element.modifier == MODIFY_NEXT)
738 			tm.tm_mday += 7;
739 		else if (element.modifier == MODIFY_LAST)
740 			tm.tm_mday -= 7;
741 	} else if (element.base_type == TYPE_MONTH) {
742 		tm.tm_mon = element.value - 1;
743 
744 		if (element.modifier == MODIFY_NEXT)
745 			tm.tm_year++;
746 		else if (element.modifier == MODIFY_LAST)
747 			tm.tm_year--;
748 	}
749 }
750 
751 
752 /*!	Uses the format assignment (through "format", and "optional") for the
753 	parsed elements and calculates the time value with respect to "now".
754 	Will also set the day/minute relative flags in "_flags".
755 */
756 static time_t
757 computeDate(const char* format, bool* optional, parsed_element* elements,
758 	time_t now, DateMask dateMask, int* _flags)
759 {
760 	TRACE(("matches: %s\n", format));
761 
762 	parsed_element* element = elements;
763 	uint32 position = 0;
764 	struct tm tm;
765 
766 	if (now == -1)
767 		now = time(NULL);
768 
769 	int nowYear = -1;
770 	if (dateMask.IsComplete())
771 		memset(&tm, 0, sizeof(tm));
772 	else {
773 		localtime_r(&now, &tm);
774 		nowYear = tm.tm_year;
775 		if (dateMask.HasTime()) {
776 			tm.tm_min = 0;
777 			tm.tm_sec = 0;
778 		}
779 
780 		*_flags = PARSEDATE_RELATIVE_TIME;
781 	}
782 
783 	while (element->type != TYPE_END) {
784 		// skip whitespace
785 		while (isspace(format[0]))
786 			format++;
787 
788 		if (format[0] == '[' && format[2] == ']') {
789 			// does this optional parameter not match our date string?
790 			if (!optional[position]) {
791 				format += 3;
792 				position++;
793 				continue;
794 			}
795 
796 			format++;
797 		}
798 
799 		switch (element->value_type) {
800 			case VALUE_CHAR:
801 				// skip the single character
802 				break;
803 
804 			case VALUE_NUMERICAL:
805 				switch (format[0]) {
806 					case 'd':
807 						tm.tm_mday = element->value;
808 						break;
809 					case 'm':
810 						tm.tm_mon = element->value - 1;
811 						break;
812 					case 'H':
813 					case 'I':
814 						tm.tm_hour = element->value;
815 						break;
816 					case 'M':
817 						tm.tm_min = element->value;
818 						break;
819 					case 'S':
820 						tm.tm_sec = element->value;
821 						break;
822 					case 'y':
823 					case 'Y':
824 					{
825 						if (nowYear < 0) {
826 							struct tm tmNow;
827 							localtime_r(&now, &tmNow);
828 							nowYear	= tmNow.tm_year;
829 						}
830 						int nowYearInCentury = nowYear % 100;
831 						int nowCentury = 1900 + nowYear - nowYearInCentury;
832 
833 						tm.tm_year = element->value;
834 						if (tm.tm_year < 1900) {
835 							// just a relative year like 11 (2011)
836 
837 							// interpret something like 50 as 1950 but
838 							// something like 11 as 2011 (assuming now is 2011)
839 							if (nowYearInCentury + 10 < tm.tm_year % 100)
840 								tm.tm_year -= 100;
841 
842 							tm.tm_year += nowCentury - 1900;
843 						}
844 						else {
845 							tm.tm_year -= 1900;
846 						}
847 						break;
848 					}
849 					case 'z':	// time zone
850 					case 'Z':
851 					{
852 						bigtime_t value
853 							= (element->value - element->value % 100) * 36
854 								+ (element->value % 100) * 60;
855 						if (element->modifier == MODIFY_MINUS)
856 							value *= -1;
857 						tm.tm_sec -= value + timezone;
858 						break;
859 					}
860 					case 'T':
861 						computeRelativeUnit(*element, tm, _flags);
862 						break;
863 					case '-':
864 						// there is no TYPE_DASH element for this (just a flag)
865 						format++;
866 						continue;
867 				}
868 				break;
869 
870 			case VALUE_STRING:
871 				switch (format[0]) {
872 					case 'a':	// weekday
873 					case 'A':
874 						// we'll apply this element later, if still necessary
875 						if (!dateMask.IsComplete())
876 							computeRelativeUnit(*element, tm, _flags);
877 						break;
878 					case 'b':	// month
879 					case 'B':
880 						tm.tm_mon = element->value - 1;
881 						break;
882 					case 'p':	// meridian
883 						tm.tm_sec += element->value;
884 						break;
885 					case 'z':	// time zone
886 					case 'Z':
887 						tm.tm_sec -= element->value + timezone;
888 						break;
889 					case 'T':	// time unit
890 						if ((element->flags & FLAG_NOW) != 0) {
891 							*_flags = PARSEDATE_MINUTE_RELATIVE_TIME
892 								| PARSEDATE_RELATIVE_TIME;
893 							break;
894 						}
895 
896 						computeRelativeUnit(*element, tm, _flags);
897 						break;
898 				}
899 				break;
900 		}
901 
902 		// format matched at this point, check next element
903 		format++;
904 		if (format[0] == ']')
905 			format++;
906 
907 		position++;
908 		element++;
909 	}
910 
911 	return mktime(&tm);
912 }
913 
914 
915 // #pragma mark - public API
916 
917 
918 time_t
919 parsedate_etc(const char* dateString, time_t now, int* _flags)
920 {
921 	// preparse date string so that it can be easily compared to our formats
922 
923 	parsed_element elements[MAX_ELEMENTS];
924 
925 	if (preparseDate(dateString, elements) < B_OK) {
926 		*_flags = PARSEDATE_INVALID_DATE;
927 		return B_ERROR;
928 	}
929 
930 #if TRACE_PARSEDATE
931 	printf("parsedate(\"%s\", now %ld)\n", dateString, now);
932 	for (int32 index = 0; elements[index].type != TYPE_END; index++) {
933 		parsed_element e = elements[index];
934 
935 		printf("  %ld: type = %u, base_type = %u, unit = %u, flags = %u, "
936 			"modifier = %u, value = %Ld (%s)\n", index, e.type, e.base_type,
937 			e.unit, e.flags, e.modifier, e.value,
938 			e.value_type == VALUE_NUMERICAL ? "numerical"
939 				: (e.value_type == VALUE_STRING ? "string" : "char"));
940 	}
941 #endif
942 
943 	bool optional[MAX_ELEMENTS];
944 
945 	for (int32 index = 0; sFormatsTable[index]; index++) {
946 		// test if this format matches our date string
947 
948 		const char* format = sFormatsTable[index];
949 		uint32 position = 0;
950 		DateMask dateMask;
951 
952 		parsed_element* element = elements;
953 		while (element->type != TYPE_END) {
954 			// skip whitespace
955 			while (isspace(format[0]))
956 				format++;
957 
958 			if (format[0] == '[' && format[2] == ']') {
959 				optional[position] = true;
960 				format++;
961 			} else
962 				optional[position] = false;
963 
964 			switch (element->value_type) {
965 				case VALUE_CHAR:
966 					// check the allowed single characters
967 
968 					switch (element->type) {
969 						case TYPE_DOT:
970 							if (format[0] != '.')
971 								goto next_format;
972 							break;
973 						case TYPE_DASH:
974 							if (format[0] != '-')
975 								goto next_format;
976 							break;
977 						case TYPE_COMMA:
978 							if (format[0] != ',')
979 								goto next_format;
980 							break;
981 						case TYPE_COLON:
982 							if (format[0] != ':')
983 								goto next_format;
984 							break;
985 						default:
986 							goto next_format;
987 					}
988 					break;
989 
990 				case VALUE_NUMERICAL:
991 					// make sure that unit types are respected
992 					if (element->type == TYPE_UNIT && format[0] != 'T')
993 						goto next_format;
994 
995 					switch (format[0]) {
996 						case 'd':
997 							if (element->value > 31)
998 								goto next_format;
999 
1000 							dateMask.Set(TYPE_DAY);
1001 							break;
1002 						case 'm':
1003 							if (element->value > 12)
1004 								goto next_format;
1005 
1006 							dateMask.Set(TYPE_MONTH);
1007 							break;
1008 						case 'H':
1009 						case 'I':
1010 							if (element->value > 24)
1011 								goto next_format;
1012 
1013 							dateMask.Set(TYPE_HOUR);
1014 							break;
1015 						case 'M':
1016 							dateMask.Set(TYPE_MINUTE);
1017 						case 'S':
1018 							if (element->value > 59)
1019 								goto next_format;
1020 
1021 							break;
1022 						case 'y':
1023 						case 'Y':
1024 							// accept all values
1025 							break;
1026 						case 'z':	// time zone
1027 						case 'Z':
1028 							// a numerical timezone must be introduced by '+'
1029 							// or '-' and it must not exceed 2399
1030 							if ((element->modifier != MODIFY_MINUS
1031 									&& element->modifier != MODIFY_PLUS)
1032 								|| element->value > 2399)
1033 								goto next_format;
1034 							break;
1035 						case 'T':
1036 							dateMask.Set(TYPE_UNIT);
1037 							break;
1038 						case '-':
1039 							if ((element->flags & FLAG_HAS_DASH) != 0) {
1040 								element--;	// consider this element again
1041 								break;
1042 							}
1043 							// supposed to fall through
1044 						default:
1045 							goto next_format;
1046 					}
1047 					break;
1048 
1049 				case VALUE_STRING:
1050 					switch (format[0]) {
1051 						case 'a':	// weekday
1052 						case 'A':
1053 							if (element->type != TYPE_WEEKDAY)
1054 								goto next_format;
1055 							break;
1056 						case 'b':	// month
1057 						case 'B':
1058 							if (element->type != TYPE_MONTH)
1059 								goto next_format;
1060 
1061 							dateMask.Set(TYPE_MONTH);
1062 							break;
1063 						case 'p':	// meridian
1064 							if (element->type != TYPE_MERIDIAN)
1065 								goto next_format;
1066 							break;
1067 						case 'z':	// time zone
1068 						case 'Z':
1069 							if (element->type != TYPE_TIME_ZONE)
1070 								goto next_format;
1071 							break;
1072 						case 'T':	// time unit
1073 							if (element->type != TYPE_UNIT)
1074 								goto next_format;
1075 
1076 							dateMask.Set(TYPE_UNIT);
1077 							break;
1078 						default:
1079 							goto next_format;
1080 					}
1081 					break;
1082 			}
1083 
1084 			// format matched at this point, check next element
1085 			if (optional[position])
1086 				format++;
1087 			format++;
1088 			position++;
1089 			element++;
1090 			continue;
1091 
1092 		next_format:
1093 			// format didn't match element - let's see if the current
1094 			// one is only optional (in which case we can continue)
1095 			if (!optional[position])
1096 				goto skip_format;
1097 
1098 			optional[position] = false;
1099 			format += 2;
1100 			position++;
1101 				// skip the closing ']'
1102 		}
1103 
1104 		// check if the format is already empty (since we reached our last
1105 		// element)
1106 		while (format[0]) {
1107 			if (format[0] == '[')
1108 				format += 3;
1109 			else if (isspace(format[0]))
1110 				format++;
1111 			else
1112 				break;
1113 		}
1114 		if (format[0])
1115 			goto skip_format;
1116 
1117 		// made it here? then we seem to have found our guy
1118 
1119 		return computeDate(sFormatsTable[index], optional, elements, now,
1120 			dateMask, _flags);
1121 
1122 	skip_format:
1123 		// check if the next format has the same beginning as the skipped one,
1124 		// and if so, skip that one, too.
1125 
1126 		int32 length = format + 1 - sFormatsTable[index];
1127 
1128 		while (sFormatsTable[index + 1]
1129 			&& !strncmp(sFormatsTable[index], sFormatsTable[index + 1], length))
1130 			index++;
1131 	}
1132 
1133 	// didn't find any matching formats
1134 	return B_ERROR;
1135 }
1136 
1137 
1138 time_t
1139 parsedate(const char* dateString, time_t now)
1140 {
1141 	int flags = 0;
1142 
1143 	return parsedate_etc(dateString, now, &flags);
1144 }
1145 
1146 
1147 void
1148 set_dateformats(const char** table)
1149 {
1150 	sFormatsTable = table ? table : kFormatsTable;
1151 }
1152 
1153 
1154 const char**
1155 get_dateformats(void)
1156 {
1157 	return const_cast<const char**>(sFormatsTable);
1158 }
1159 
1160