headers/bsd: Add sys/queue.h.
[haiku.git] / src / system / libroot / os / parsedate.cpp
blob7c9f1c7ff32ce2214bcc24db30054bd0ceac5d52
1 /*
2 * Copyright 2003-2010, Axel Dörfler, axeld@pinc-software.de.
3 * Distributed under the terms of the MIT License.
4 */
7 #include <parsedate.h>
9 #include <ctype.h>
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include <strings.h>
15 #include <OS.h>
18 #define TRACE_PARSEDATE 0
19 #if TRACE_PARSEDATE
20 # define TRACE(x) printf x ;
21 #else
22 # define TRACE(x) ;
23 #endif
26 /* The date format is as follows:
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
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.
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
111 static const char* const* sFormatsTable = kFormatsTable;
114 enum field_type {
115 TYPE_UNKNOWN = 0,
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,
127 TYPE_DASH,
128 TYPE_DOT,
129 TYPE_COMMA,
130 TYPE_COLON,
132 TYPE_UNIT,
133 TYPE_MODIFIER,
134 TYPE_END,
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
145 enum units {
146 UNIT_NONE,
147 UNIT_YEAR,
148 UNIT_MONTH,
149 UNIT_DAY,
150 UNIT_SECOND,
153 enum value_type {
154 VALUE_NUMERICAL,
155 VALUE_STRING,
156 VALUE_CHAR,
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,
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;
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},
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},
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},
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},
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},
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},
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},
435 {NULL}
438 #define MAX_ELEMENTS 32
440 class DateMask {
441 public:
442 DateMask() : fMask(0UL) {}
444 void Set(uint8 type) { fMask |= Flag(type); }
445 bool IsSet(uint8 type) { return (fMask & Flag(type)) != 0; }
447 bool HasTime();
448 bool IsComplete();
450 private:
451 inline uint32 Flag(uint8 type) const { return 1UL << type; }
453 uint32 fMask;
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;
466 void SetCharType(uint8 fieldType, int8 modify = MODIFY_NONE);
468 void Adopt(const known_identifier& identifier);
469 void AdoptUnit(const known_identifier& identifier);
470 bool IsNextLastThis();
474 void
475 parsed_element::SetCharType(uint8 fieldType, int8 modify)
477 base_type = type = fieldType;
478 value_type = VALUE_CHAR;
479 modifier = modify;
483 void
484 parsed_element::Adopt(const known_identifier& identifier)
486 base_type = type = identifier.type;
487 flags = identifier.flags;
488 unit = identifier.unit;
490 if (identifier.type == TYPE_MODIFIER)
491 modifier = identifier.value;
493 value_type = VALUE_STRING;
494 value = identifier.value;
498 void
499 parsed_element::AdoptUnit(const known_identifier& identifier)
501 base_type = type = TYPE_UNIT;
502 flags = identifier.flags;
503 unit = identifier.unit;
504 value *= identifier.value;
508 inline bool
509 parsed_element::IsNextLastThis()
511 return base_type == TYPE_MODIFIER
512 && (modifier == MODIFY_NEXT || modifier == MODIFY_LAST
513 || modifier == MODIFY_THIS);
517 // #pragma mark -
520 bool
521 DateMask::HasTime()
523 // this will cause
524 return IsSet(TYPE_HOUR);
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.
532 bool
533 DateMask::IsComplete()
535 // mask must be absolute, at last
536 if ((fMask & Flag(TYPE_UNIT)) != 0)
537 return false;
539 // minimal set of flags to have a complete set
540 return !(~fMask & (Flag(TYPE_DAY) | Flag(TYPE_MONTH)));
544 // #pragma mark -
547 static status_t
548 preparseDate(const char* dateString, parsed_element* elements)
550 int32 index = 0, modify = MODIFY_NONE;
551 char c;
553 if (dateString == NULL)
554 return B_ERROR;
556 memset(&elements[0], 0, sizeof(parsed_element));
558 for (; (c = dateString[0]) != '\0'; dateString++) {
559 // we don't care about spaces
560 if (isspace(c)) {
561 modify = MODIFY_NONE;
562 continue;
565 // if we're reached our maximum number of elements, bail out
566 if (index >= MAX_ELEMENTS)
567 return B_ERROR;
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;
581 // this counts for the next element
582 continue;
583 } else if (c == '-') {
584 modify = MODIFY_MINUS;
585 elements[index].flags = FLAG_HAS_DASH;
587 // this counts for the next element
588 continue;
589 } else if (isdigit(c)) {
590 // fetch whole number
592 elements[index].type = TYPE_UNKNOWN;
593 elements[index].value_type = VALUE_NUMERICAL;
594 elements[index].value = atoll(dateString);
595 elements[index].modifier = modify;
597 // skip number
598 while (isdigit(dateString[1]))
599 dateString++;
601 // check for "1st", "2nd, "3rd", "4th", ...
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;
613 } else if (isalpha(c)) {
614 // fetch whole string
616 const char* string = dateString;
617 while (isalpha(dateString[1]))
618 dateString++;
619 int32 length = dateString + 1 - string;
621 // compare with known strings
622 // ToDo: should understand other languages as well...
624 const known_identifier* identifier = kIdentifiers;
625 for (; identifier->string; identifier++) {
626 if (!strncasecmp(identifier->string, string, length)
627 && !identifier->string[length])
628 break;
630 if (identifier->alternate_string != NULL
631 && !strncasecmp(identifier->alternate_string, string, length)
632 && !identifier->alternate_string[length])
633 break;
635 if (identifier->string == NULL) {
636 // unknown string, we don't have to parse any further
637 return B_ERROR;
640 if (index > 0 && identifier->type == TYPE_UNIT) {
641 // this is just a unit, so it will give the last value a meaning
643 if (elements[--index].value_type != VALUE_NUMERICAL
644 && !elements[index].IsNextLastThis())
645 return B_ERROR;
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--;
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;
664 elements[index].Adopt(*identifier);
665 elements[index].type = TYPE_UNIT;
666 } else
667 return B_ERROR;
668 } else {
669 elements[index].Adopt(*identifier);
673 // see if we can join any preceding modifiers
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++;
688 if (index < MAX_ELEMENTS)
689 memset(&elements[index], 0, sizeof(parsed_element));
692 // were there any elements?
693 if (index == 0)
694 return B_ERROR;
696 elements[index].type = TYPE_END;
698 return B_OK;
702 static void
703 computeRelativeUnit(parsed_element& element, struct tm& tm, int* _flags)
705 // set the relative start depending on unit
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;
719 // adjust value
721 if ((element.flags & FLAG_RELATIVE) != 0) {
722 bigtime_t value = element.value;
723 if (element.modifier == MODIFY_MINUS)
724 value = -element.value;
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;
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;
745 if (element.modifier == MODIFY_NEXT)
746 tm.tm_year++;
747 else if (element.modifier == MODIFY_LAST)
748 tm.tm_year--;
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".
757 static time_t
758 computeDate(const char* format, bool* optional, parsed_element* elements,
759 time_t now, DateMask dateMask, int* _flags)
761 TRACE(("matches: %s\n", format));
763 parsed_element* element = elements;
764 uint32 position = 0;
765 struct tm tm;
767 if (now == -1)
768 now = time(NULL);
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;
781 *_flags = PARSEDATE_RELATIVE_TIME;
784 while (element->type != TYPE_END) {
785 // skip whitespace
786 while (isspace(format[0]))
787 format++;
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;
797 format++;
800 switch (element->value_type) {
801 case VALUE_CHAR:
802 // skip the single character
803 break;
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':
826 if (nowYear < 0) {
827 struct tm tmNow;
828 localtime_r(&now, &tmNow);
829 nowYear = tmNow.tm_year;
831 int nowYearInCentury = nowYear % 100;
832 int nowCentury = 1900 + nowYear - nowYearInCentury;
834 tm.tm_year = element->value;
835 if (tm.tm_year < 1900) {
836 // just a relative year like 11 (2011)
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;
843 tm.tm_year += nowCentury - 1900;
845 else {
846 tm.tm_year -= 1900;
848 break;
850 case 'z': // time zone
851 case 'Z':
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;
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;
869 break;
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;
897 computeRelativeUnit(*element, tm, _flags);
898 break;
900 break;
903 // format matched at this point, check next element
904 format++;
905 if (format[0] == ']')
906 format++;
908 position++;
909 element++;
912 return mktime(&tm);
916 // #pragma mark - public API
919 time_t
920 parsedate_etc(const char* dateString, time_t now, int* _flags)
922 // preparse date string so that it can be easily compared to our formats
924 parsed_element elements[MAX_ELEMENTS];
926 if (preparseDate(dateString, elements) < B_OK) {
927 *_flags = PARSEDATE_INVALID_DATE;
928 return B_ERROR;
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];
936 printf(" %ld: type = %u, base_type = %u, unit = %u, flags = %u, "
937 "modifier = %u, value = %Ld (%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"));
942 #endif
944 bool optional[MAX_ELEMENTS];
946 for (int32 index = 0; sFormatsTable[index]; index++) {
947 // test if this format matches our date string
949 const char* format = sFormatsTable[index];
950 uint32 position = 0;
951 DateMask dateMask;
953 parsed_element* element = elements;
954 while (element->type != TYPE_END) {
955 // skip whitespace
956 while (isspace(format[0]))
957 format++;
959 if (format[0] == '[' && format[2] == ']') {
960 optional[position] = true;
961 format++;
962 } else
963 optional[position] = false;
965 switch (element->value_type) {
966 case VALUE_CHAR:
967 // check the allowed single characters
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;
989 break;
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;
996 switch (format[0]) {
997 case 'd':
998 if (element->value > 31)
999 goto next_format;
1001 dateMask.Set(TYPE_DAY);
1002 break;
1003 case 'm':
1004 if (element->value > 12)
1005 goto next_format;
1007 dateMask.Set(TYPE_MONTH);
1008 break;
1009 case 'H':
1010 case 'I':
1011 if (element->value > 24)
1012 goto next_format;
1014 dateMask.Set(TYPE_HOUR);
1015 break;
1016 case 'M':
1017 if (element->value > 59)
1018 goto next_format;
1020 dateMask.Set(TYPE_MINUTE);
1021 break;
1022 case 'S':
1023 if (element->value > 59)
1024 goto next_format;
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;
1049 // supposed to fall through
1050 default:
1051 goto next_format;
1053 break;
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;
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;
1082 dateMask.Set(TYPE_UNIT);
1083 break;
1084 default:
1085 goto next_format;
1087 break;
1090 // format matched at this point, check next element
1091 if (optional[position])
1092 format++;
1093 format++;
1094 position++;
1095 element++;
1096 continue;
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;
1104 optional[position] = false;
1105 format += 2;
1106 position++;
1107 // skip the closing ']'
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;
1120 if (format[0])
1121 goto skip_format;
1123 // made it here? then we seem to have found our guy
1125 return computeDate(sFormatsTable[index], optional, elements, now,
1126 dateMask, _flags);
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.
1132 int32 length = format + 1 - sFormatsTable[index];
1134 while (sFormatsTable[index + 1]
1135 && !strncmp(sFormatsTable[index], sFormatsTable[index + 1], length))
1136 index++;
1139 // didn't find any matching formats
1140 return B_ERROR;
1144 time_t
1145 parsedate(const char* dateString, time_t now)
1147 int flags = 0;
1149 return parsedate_etc(dateString, now, &flags);
1153 void
1154 set_dateformats(const char** table)
1156 sFormatsTable = table ? table : kFormatsTable;
1160 const char**
1161 get_dateformats(void)
1163 return const_cast<const char**>(sFormatsTable);