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