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