1//
2// DateTimeParser.cpp
3//
4// Library: Foundation
5// Package: DateTime
6// Module: DateTimeParser
7//
8// Copyright (c) 2004-2006, Applied Informatics Software Engineering GmbH.
9// and Contributors.
10//
11// SPDX-License-Identifier: BSL-1.0
12//
13
14
15#include "Poco/DateTimeParser.h"
16#include "Poco/DateTimeFormat.h"
17#include "Poco/DateTime.h"
18#include "Poco/Exception.h"
19#include "Poco/Ascii.h"
20
21
22namespace Poco {
23
24
25#define SKIP_JUNK() \
26 while (it != end && !Ascii::isDigit(*it)) ++it
27
28
29#define SKIP_DIGITS() \
30 while (it != end && Ascii::isDigit(*it)) ++it
31
32
33#define PARSE_NUMBER(var) \
34 while (it != end && Ascii::isDigit(*it)) var = var*10 + ((*it++) - '0')
35
36
37#define PARSE_NUMBER_N(var, n) \
38 { int i = 0; while (i++ < n && it != end && Ascii::isDigit(*it)) var = var*10 + ((*it++) - '0'); }
39
40
41#define PARSE_FRACTIONAL_N(var, n) \
42 { int i = 0; while (i < n && it != end && Ascii::isDigit(*it)) { var = var*10 + ((*it++) - '0'); i++; } while (i++ < n) var *= 10; }
43
44
45void DateTimeParser::parse(const std::string& fmt,
46 const std::string& str,
47 DateTime& dateTime,
48 int& timeZoneDifferential)
49{
50 if (fmt.empty() || str.empty() || (DateTimeFormat::hasFormat(fmt) && !DateTimeFormat::isValid(str)))
51 throw SyntaxException("Invalid DateTimeString:" + str);
52
53 int year = 0;
54 int month = 0;
55 int day = 0;
56 int hour = 0;
57 int minute = 0;
58 int second = 0;
59 int millis = 0;
60 int micros = 0;
61 int tzd = 0;
62
63 std::string::const_iterator it = str.begin();
64 std::string::const_iterator end = str.end();
65 std::string::const_iterator itf = fmt.begin();
66 std::string::const_iterator endf = fmt.end();
67
68 while (itf != endf && it != end)
69 {
70 if (*itf == '%')
71 {
72 if (++itf != endf)
73 {
74 switch (*itf)
75 {
76 case 'w':
77 case 'W':
78 while (it != end && Ascii::isSpace(*it)) ++it;
79 while (it != end && Ascii::isAlpha(*it)) ++it;
80 break;
81 case 'b':
82 case 'B':
83 month = parseMonth(it, end);
84 break;
85 case 'd':
86 case 'e':
87 case 'f':
88 SKIP_JUNK();
89 PARSE_NUMBER_N(day, 2);
90 break;
91 case 'm':
92 case 'n':
93 case 'o':
94 SKIP_JUNK();
95 PARSE_NUMBER_N(month, 2);
96 break;
97 case 'y':
98 SKIP_JUNK();
99 PARSE_NUMBER_N(year, 2);
100 if (year >= 69)
101 year += 1900;
102 else
103 year += 2000;
104 break;
105 case 'Y':
106 SKIP_JUNK();
107 PARSE_NUMBER_N(year, 4);
108 break;
109 case 'r':
110 SKIP_JUNK();
111 PARSE_NUMBER(year);
112 if (year < 1000)
113 {
114 if (year >= 69)
115 year += 1900;
116 else
117 year += 2000;
118 }
119 break;
120 case 'H':
121 case 'h':
122 SKIP_JUNK();
123 PARSE_NUMBER_N(hour, 2);
124 break;
125 case 'a':
126 case 'A':
127 hour = parseAMPM(it, end, hour);
128 break;
129 case 'M':
130 SKIP_JUNK();
131 PARSE_NUMBER_N(minute, 2);
132 break;
133 case 'S':
134 SKIP_JUNK();
135 PARSE_NUMBER_N(second, 2);
136 break;
137 case 's':
138 SKIP_JUNK();
139 PARSE_NUMBER_N(second, 2);
140 if (it != end && (*it == '.' || *it == ','))
141 {
142 ++it;
143 PARSE_FRACTIONAL_N(millis, 3);
144 PARSE_FRACTIONAL_N(micros, 3);
145 SKIP_DIGITS();
146 }
147 break;
148 case 'i':
149 SKIP_JUNK();
150 PARSE_NUMBER_N(millis, 3);
151 break;
152 case 'c':
153 SKIP_JUNK();
154 PARSE_NUMBER_N(millis, 1);
155 millis *= 100;
156 break;
157 case 'F':
158 SKIP_JUNK();
159 PARSE_FRACTIONAL_N(millis, 3);
160 PARSE_FRACTIONAL_N(micros, 3);
161 SKIP_DIGITS();
162 break;
163 case 'z':
164 case 'Z':
165 tzd = parseTZD(it, end);
166 break;
167 }
168 ++itf;
169 }
170 }
171 else ++itf;
172 }
173 if (month == 0) month = 1;
174 if (day == 0) day = 1;
175 if (DateTime::isValid(year, month, day, hour, minute, second, millis, micros))
176 dateTime.assign(year, month, day, hour, minute, second, millis, micros);
177 else
178 throw SyntaxException("date/time component out of range");
179 timeZoneDifferential = tzd;
180}
181
182
183DateTime DateTimeParser::parse(const std::string& fmt, const std::string& str, int& timeZoneDifferential)
184{
185 DateTime result;
186 parse(fmt, str, result, timeZoneDifferential);
187 return result;
188}
189
190
191bool DateTimeParser::tryParse(const std::string& fmt,
192 const std::string& str,
193 DateTime& dateTime,
194 int& timeZoneDifferential)
195{
196 try
197 {
198 parse(fmt, str, dateTime, timeZoneDifferential);
199 }
200 catch (Exception&)
201 {
202 return false;
203 }
204 return true;
205}
206
207
208void DateTimeParser::parse(const std::string& str, DateTime& dateTime, int& timeZoneDifferential)
209{
210 if (!tryParse(str, dateTime, timeZoneDifferential))
211 throw SyntaxException("Unsupported or invalid date/time format");
212}
213
214
215DateTime DateTimeParser::parse(const std::string& str, int& timeZoneDifferential)
216{
217 DateTime result;
218 if (tryParse(str, result, timeZoneDifferential))
219 return result;
220 else
221 throw SyntaxException("Unsupported or invalid date/time format");
222}
223
224
225bool DateTimeParser::tryParse(const std::string& str, DateTime& dateTime, int& timeZoneDifferential)
226{
227 if (str.length() < 4) return false;
228
229 if (str[3] == ',')
230 return tryParse("%w, %e %b %r %H:%M:%S %Z", str, dateTime, timeZoneDifferential);
231 else if (str[3] == ' ')
232 return tryParse(DateTimeFormat::ASCTIME_FORMAT, str, dateTime, timeZoneDifferential);
233 else if (str.find(',') < 10)
234 return tryParse("%W, %e %b %r %H:%M:%S %Z", str, dateTime, timeZoneDifferential);
235 else if (Ascii::isDigit(str[0]))
236 {
237 if (str.find(' ') != std::string::npos || str.length() == 10)
238 return tryParse(DateTimeFormat::SORTABLE_FORMAT, str, dateTime, timeZoneDifferential);
239 else if (str.find('.') != std::string::npos || str.find(',') != std::string::npos)
240 return tryParse(DateTimeFormat::ISO8601_FRAC_FORMAT, str, dateTime, timeZoneDifferential);
241 else
242 return tryParse(DateTimeFormat::ISO8601_FORMAT, str, dateTime, timeZoneDifferential);
243 }
244 else return false;
245}
246
247
248int DateTimeParser::parseTZD(std::string::const_iterator& it, const std::string::const_iterator& end)
249{
250 struct Zone
251 {
252 const char* designator;
253 int timeZoneDifferential;
254 };
255
256 static Zone zones[] =
257 {
258 {"Z", 0},
259 {"UT", 0},
260 {"GMT", 0},
261 {"BST", 1*3600},
262 {"IST", 1*3600},
263 {"WET", 0},
264 {"WEST", 1*3600},
265 {"CET", 1*3600},
266 {"CEST", 2*3600},
267 {"EET", 2*3600},
268 {"EEST", 3*3600},
269 {"MSK", 3*3600},
270 {"MSD", 4*3600},
271 {"NST", -3*3600-1800},
272 {"NDT", -2*3600-1800},
273 {"AST", -4*3600},
274 {"ADT", -3*3600},
275 {"EST", -5*3600},
276 {"EDT", -4*3600},
277 {"CST", -6*3600},
278 {"CDT", -5*3600},
279 {"MST", -7*3600},
280 {"MDT", -6*3600},
281 {"PST", -8*3600},
282 {"PDT", -7*3600},
283 {"AKST", -9*3600},
284 {"AKDT", -8*3600},
285 {"HST", -10*3600},
286 {"AEST", 10*3600},
287 {"AEDT", 11*3600},
288 {"ACST", 9*3600+1800},
289 {"ACDT", 10*3600+1800},
290 {"AWST", 8*3600},
291 {"AWDT", 9*3600}
292 };
293
294 int tzd = 0;
295 while (it != end && Ascii::isSpace(*it)) ++it;
296 if (it != end)
297 {
298 if (Ascii::isAlpha(*it))
299 {
300 std::string designator;
301 designator += *it++;
302 if (it != end && Ascii::isAlpha(*it)) designator += *it++;
303 if (it != end && Ascii::isAlpha(*it)) designator += *it++;
304 if (it != end && Ascii::isAlpha(*it)) designator += *it++;
305 for (unsigned i = 0; i < sizeof(zones)/sizeof(Zone); ++i)
306 {
307 if (designator == zones[i].designator)
308 {
309 tzd = zones[i].timeZoneDifferential;
310 break;
311 }
312 }
313 }
314 if (it != end && (*it == '+' || *it == '-'))
315 {
316 int sign = *it == '+' ? 1 : -1;
317 ++it;
318 int hours = 0;
319 PARSE_NUMBER_N(hours, 2);
320 if (it != end && *it == ':') ++it;
321 int minutes = 0;
322 PARSE_NUMBER_N(minutes, 2);
323 tzd += sign*(hours*3600 + minutes*60);
324 }
325 }
326 return tzd;
327}
328
329
330int DateTimeParser::parseMonth(std::string::const_iterator& it, const std::string::const_iterator& end)
331{
332 std::string month;
333 while (it != end && (Ascii::isSpace(*it) || Ascii::isPunct(*it))) ++it;
334 bool isFirst = true;
335 while (it != end && Ascii::isAlpha(*it))
336 {
337 char ch = (*it++);
338 if (isFirst)
339 {
340 month += static_cast<char>(Ascii::toUpper(ch));
341 isFirst = false;
342 }
343 else month += static_cast<char>(Ascii::toLower(ch));
344 }
345 if (month.length() < 3) throw SyntaxException("Month name must be at least three characters long", month);
346 for (int i = 0; i < 12; ++i)
347 {
348 if (DateTimeFormat::MONTH_NAMES[i].find(month) == 0)
349 return i + 1;
350 }
351 throw SyntaxException("Not a valid month name", month);
352}
353
354
355int DateTimeParser::parseDayOfWeek(std::string::const_iterator& it, const std::string::const_iterator& end)
356{
357 std::string dow;
358 while (it != end && (Ascii::isSpace(*it) || Ascii::isPunct(*it))) ++it;
359 bool isFirst = true;
360 while (it != end && Ascii::isAlpha(*it))
361 {
362 char ch = (*it++);
363 if (isFirst)
364 {
365 dow += static_cast<char>(Ascii::toUpper(ch));
366 isFirst = false;
367 }
368 else dow += static_cast<char>(Ascii::toLower(ch));
369 }
370 if (dow.length() < 3) throw SyntaxException("Weekday name must be at least three characters long", dow);
371 for (int i = 0; i < 7; ++i)
372 {
373 if (DateTimeFormat::WEEKDAY_NAMES[i].find(dow) == 0)
374 return i;
375 }
376 throw SyntaxException("Not a valid weekday name", dow);
377}
378
379
380int DateTimeParser::parseAMPM(std::string::const_iterator& it, const std::string::const_iterator& end, int hour)
381{
382 std::string ampm;
383 while (it != end && (Ascii::isSpace(*it) || Ascii::isPunct(*it))) ++it;
384 while (it != end && Ascii::isAlpha(*it))
385 {
386 char ch = (*it++);
387 ampm += static_cast<char>(Ascii::toUpper(ch));
388 }
389 if (ampm == "AM")
390 {
391 if (hour == 12)
392 return 0;
393 else
394 return hour;
395 }
396 else if (ampm == "PM")
397 {
398 if (hour < 12)
399 return hour + 12;
400 else
401 return hour;
402 }
403 else throw SyntaxException("Not a valid AM/PM designator", ampm);
404}
405
406
407} // namespace Poco
408