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: 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 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 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 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 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 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 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 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 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 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 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 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 = %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")); 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 dateMask.Set(TYPE_MINUTE); 1018 case 'S': 1019 if (element->value > 59) 1020 goto next_format; 1021 1022 break; 1023 case 'y': 1024 case 'Y': 1025 // accept all values 1026 break; 1027 case 'z': // time zone 1028 case 'Z': 1029 // a numerical timezone must be introduced by '+' 1030 // or '-' and it must not exceed 2399 1031 if ((element->modifier != MODIFY_MINUS 1032 && element->modifier != MODIFY_PLUS) 1033 || element->value > 2399) 1034 goto next_format; 1035 break; 1036 case 'T': 1037 dateMask.Set(TYPE_UNIT); 1038 break; 1039 case '-': 1040 if ((element->flags & FLAG_HAS_DASH) != 0) { 1041 element--; // consider this element again 1042 break; 1043 } 1044 // supposed to fall through 1045 default: 1046 goto next_format; 1047 } 1048 break; 1049 1050 case VALUE_STRING: 1051 switch (format[0]) { 1052 case 'a': // weekday 1053 case 'A': 1054 if (element->type != TYPE_WEEKDAY) 1055 goto next_format; 1056 break; 1057 case 'b': // month 1058 case 'B': 1059 if (element->type != TYPE_MONTH) 1060 goto next_format; 1061 1062 dateMask.Set(TYPE_MONTH); 1063 break; 1064 case 'p': // meridian 1065 if (element->type != TYPE_MERIDIAN) 1066 goto next_format; 1067 break; 1068 case 'z': // time zone 1069 case 'Z': 1070 if (element->type != TYPE_TIME_ZONE) 1071 goto next_format; 1072 break; 1073 case 'T': // time unit 1074 if (element->type != TYPE_UNIT) 1075 goto next_format; 1076 1077 dateMask.Set(TYPE_UNIT); 1078 break; 1079 default: 1080 goto next_format; 1081 } 1082 break; 1083 } 1084 1085 // format matched at this point, check next element 1086 if (optional[position]) 1087 format++; 1088 format++; 1089 position++; 1090 element++; 1091 continue; 1092 1093 next_format: 1094 // format didn't match element - let's see if the current 1095 // one is only optional (in which case we can continue) 1096 if (!optional[position]) 1097 goto skip_format; 1098 1099 optional[position] = false; 1100 format += 2; 1101 position++; 1102 // skip the closing ']' 1103 } 1104 1105 // check if the format is already empty (since we reached our last 1106 // element) 1107 while (format[0]) { 1108 if (format[0] == '[') 1109 format += 3; 1110 else if (isspace(format[0])) 1111 format++; 1112 else 1113 break; 1114 } 1115 if (format[0]) 1116 goto skip_format; 1117 1118 // made it here? then we seem to have found our guy 1119 1120 return computeDate(sFormatsTable[index], optional, elements, now, 1121 dateMask, _flags); 1122 1123 skip_format: 1124 // check if the next format has the same beginning as the skipped one, 1125 // and if so, skip that one, too. 1126 1127 int32 length = format + 1 - sFormatsTable[index]; 1128 1129 while (sFormatsTable[index + 1] 1130 && !strncmp(sFormatsTable[index], sFormatsTable[index + 1], length)) 1131 index++; 1132 } 1133 1134 // didn't find any matching formats 1135 return B_ERROR; 1136 } 1137 1138 1139 time_t 1140 parsedate(const char* dateString, time_t now) 1141 { 1142 int flags = 0; 1143 1144 return parsedate_etc(dateString, now, &flags); 1145 } 1146 1147 1148 void 1149 set_dateformats(const char** table) 1150 { 1151 sFormatsTable = table ? table : kFormatsTable; 1152 } 1153 1154 1155 const char** 1156 get_dateformats(void) 1157 { 1158 return const_cast<const char**>(sFormatsTable); 1159 } 1160 1161