xref: /haiku/src/system/libroot/os/parsedate.cpp (revision 425ac1b60a56f4df7a0e88bd784545c0ec4fa01f)
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 
47 static 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 };
111 static const char* const* sFormatsTable = kFormatsTable;
112 
113 
114 enum 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 
145 enum units {
146 	UNIT_NONE,
147 	UNIT_YEAR,
148 	UNIT_MONTH,
149 	UNIT_DAY,
150 	UNIT_SECOND,
151 };
152 
153 enum value_type {
154 	VALUE_NUMERICAL,
155 	VALUE_STRING,
156 	VALUE_CHAR,
157 };
158 
159 enum 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 
168 struct 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 
177 static 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 
440 class DateMask {
441 	public:
DateMask()442 		DateMask() : fMask(0UL) {}
443 
Set(uint8 type)444 		void Set(uint8 type) { fMask |= Flag(type); }
IsSet(uint8 type)445 		bool IsSet(uint8 type) { return (fMask & Flag(type)) != 0; }
446 
447 		bool HasTime();
448 		bool IsComplete();
449 
450 	private:
Flag(uint8 type) const451 		inline uint32 Flag(uint8 type) const { return 1UL << type; }
452 
453 		uint32	fMask;
454 };
455 
456 
457 struct 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 
474 void
SetCharType(uint8 fieldType,int8 modify)475 parsed_element::SetCharType(uint8 fieldType, int8 modify)
476 {
477 	base_type = type = fieldType;
478 	value_type = VALUE_CHAR;
479 	modifier = modify;
480 }
481 
482 
483 void
Adopt(const known_identifier & identifier)484 parsed_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 
498 void
AdoptUnit(const known_identifier & identifier)499 parsed_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 
508 inline bool
IsNextLastThis()509 parsed_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 
520 bool
HasTime()521 DateMask::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 */
532 bool
IsComplete()533 DateMask::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 
547 static status_t
preparseDate(const char * dateString,parsed_element * elements)548 preparseDate(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 
702 static void
computeRelativeUnit(parsed_element & element,struct tm & tm,int * _flags)703 computeRelativeUnit(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 */
757 static time_t
computeDate(const char * format,bool * optional,parsed_element * elements,time_t now,DateMask dateMask,int * _flags)758 computeDate(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 
919 time_t
parsedate_etc(const char * dateString,time_t now,int * _flags)920 parsedate_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 
1144 time_t
parsedate(const char * dateString,time_t now)1145 parsedate(const char* dateString, time_t now)
1146 {
1147 	int flags = 0;
1148 
1149 	return parsedate_etc(dateString, now, &flags);
1150 }
1151 
1152 
1153 void
set_dateformats(const char ** table)1154 set_dateformats(const char** table)
1155 {
1156 	sFormatsTable = table ? table : kFormatsTable;
1157 }
1158 
1159 
1160 const char**
get_dateformats(void)1161 get_dateformats(void)
1162 {
1163 	return const_cast<const char**>(sFormatsTable);
1164 }
1165 
1166