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