2023-02-08 11:04:11 +00:00
|
|
|
//
|
|
|
|
// DateTimeParser.cpp
|
|
|
|
//
|
|
|
|
// Library: Foundation
|
|
|
|
// Package: DateTime
|
|
|
|
// Module: DateTimeParser
|
|
|
|
//
|
|
|
|
// Copyright (c) 2004-2006, Applied Informatics Software Engineering GmbH.
|
|
|
|
// and Contributors.
|
|
|
|
//
|
|
|
|
// SPDX-License-Identifier: BSL-1.0
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
|
|
#include "Poco/DateTimeParser.h"
|
|
|
|
#include "Poco/DateTimeFormat.h"
|
|
|
|
#include "Poco/DateTime.h"
|
|
|
|
#include "Poco/Exception.h"
|
|
|
|
#include "Poco/Ascii.h"
|
|
|
|
|
|
|
|
|
|
|
|
namespace Poco {
|
|
|
|
|
|
|
|
|
|
|
|
#define SKIP_JUNK() \
|
|
|
|
while (it != end && !Ascii::isDigit(*it)) ++it
|
|
|
|
|
|
|
|
|
|
|
|
#define SKIP_DIGITS() \
|
|
|
|
while (it != end && Ascii::isDigit(*it)) ++it
|
|
|
|
|
|
|
|
|
|
|
|
#define PARSE_NUMBER(var) \
|
|
|
|
while (it != end && Ascii::isDigit(*it)) var = var*10 + ((*it++) - '0')
|
|
|
|
|
|
|
|
|
|
|
|
#define PARSE_NUMBER_N(var, n) \
|
|
|
|
{ int i = 0; while (i++ < n && it != end && Ascii::isDigit(*it)) var = var*10 + ((*it++) - '0'); }
|
|
|
|
|
|
|
|
|
|
|
|
#define PARSE_FRACTIONAL_N(var, n) \
|
|
|
|
{ int i = 0; while (i < n && it != end && Ascii::isDigit(*it)) { var = var*10 + ((*it++) - '0'); i++; } while (i++ < n) var *= 10; }
|
|
|
|
|
|
|
|
|
|
|
|
void DateTimeParser::parse(const std::string& fmt, const std::string& str, DateTime& dateTime, int& timeZoneDifferential)
|
|
|
|
{
|
|
|
|
if (fmt.empty() || str.empty())
|
|
|
|
throw SyntaxException("Empty string.");
|
|
|
|
|
|
|
|
int year = 0;
|
|
|
|
int month = 0;
|
|
|
|
int day = 0;
|
|
|
|
int hour = 0;
|
|
|
|
int minute = 0;
|
|
|
|
int second = 0;
|
|
|
|
int millis = 0;
|
|
|
|
int micros = 0;
|
|
|
|
int tzd = 0;
|
|
|
|
|
|
|
|
std::string::const_iterator it = str.begin();
|
|
|
|
std::string::const_iterator end = str.end();
|
|
|
|
std::string::const_iterator itf = fmt.begin();
|
|
|
|
std::string::const_iterator endf = fmt.end();
|
|
|
|
|
|
|
|
while (itf != endf && it != end)
|
|
|
|
{
|
|
|
|
if (*itf == '%')
|
|
|
|
{
|
|
|
|
if (++itf != endf)
|
|
|
|
{
|
|
|
|
switch (*itf)
|
|
|
|
{
|
|
|
|
case 'w':
|
|
|
|
case 'W':
|
|
|
|
while (it != end && Ascii::isSpace(*it)) ++it;
|
|
|
|
while (it != end && Ascii::isAlpha(*it)) ++it;
|
|
|
|
break;
|
|
|
|
case 'b':
|
|
|
|
case 'B':
|
|
|
|
month = parseMonth(it, end);
|
|
|
|
break;
|
|
|
|
case 'd':
|
|
|
|
case 'e':
|
|
|
|
case 'f':
|
|
|
|
SKIP_JUNK();
|
|
|
|
PARSE_NUMBER_N(day, 2);
|
|
|
|
break;
|
|
|
|
case 'm':
|
|
|
|
case 'n':
|
|
|
|
case 'o':
|
|
|
|
SKIP_JUNK();
|
|
|
|
PARSE_NUMBER_N(month, 2);
|
|
|
|
break;
|
|
|
|
case 'y':
|
|
|
|
SKIP_JUNK();
|
|
|
|
PARSE_NUMBER_N(year, 2);
|
|
|
|
if (year >= 69)
|
|
|
|
year += 1900;
|
|
|
|
else
|
|
|
|
year += 2000;
|
|
|
|
break;
|
|
|
|
case 'Y':
|
|
|
|
SKIP_JUNK();
|
|
|
|
PARSE_NUMBER_N(year, 4);
|
|
|
|
break;
|
|
|
|
case 'r':
|
|
|
|
SKIP_JUNK();
|
|
|
|
PARSE_NUMBER(year);
|
|
|
|
if (year < 1000)
|
|
|
|
{
|
|
|
|
if (year >= 69)
|
|
|
|
year += 1900;
|
|
|
|
else
|
|
|
|
year += 2000;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'H':
|
|
|
|
case 'h':
|
|
|
|
SKIP_JUNK();
|
|
|
|
PARSE_NUMBER_N(hour, 2);
|
|
|
|
break;
|
|
|
|
case 'a':
|
|
|
|
case 'A':
|
|
|
|
hour = parseAMPM(it, end, hour);
|
|
|
|
break;
|
|
|
|
case 'M':
|
|
|
|
SKIP_JUNK();
|
|
|
|
PARSE_NUMBER_N(minute, 2);
|
|
|
|
break;
|
|
|
|
case 'S':
|
|
|
|
SKIP_JUNK();
|
|
|
|
PARSE_NUMBER_N(second, 2);
|
|
|
|
break;
|
|
|
|
case 's':
|
|
|
|
SKIP_JUNK();
|
|
|
|
PARSE_NUMBER_N(second, 2);
|
|
|
|
if (it != end && (*it == '.' || *it == ','))
|
|
|
|
{
|
|
|
|
++it;
|
|
|
|
PARSE_FRACTIONAL_N(millis, 3);
|
|
|
|
PARSE_FRACTIONAL_N(micros, 3);
|
|
|
|
SKIP_DIGITS();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'i':
|
|
|
|
SKIP_JUNK();
|
|
|
|
PARSE_NUMBER_N(millis, 3);
|
|
|
|
break;
|
|
|
|
case 'c':
|
|
|
|
SKIP_JUNK();
|
|
|
|
PARSE_NUMBER_N(millis, 1);
|
|
|
|
millis *= 100;
|
|
|
|
break;
|
|
|
|
case 'F':
|
|
|
|
SKIP_JUNK();
|
|
|
|
PARSE_FRACTIONAL_N(millis, 3);
|
|
|
|
PARSE_FRACTIONAL_N(micros, 3);
|
|
|
|
SKIP_DIGITS();
|
|
|
|
break;
|
|
|
|
case 'z':
|
|
|
|
case 'Z':
|
|
|
|
tzd = parseTZD(it, end);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
++itf;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else ++itf;
|
|
|
|
}
|
|
|
|
if (month == 0) month = 1;
|
|
|
|
if (day == 0) day = 1;
|
|
|
|
if (DateTime::isValid(year, month, day, hour, minute, second, millis, micros))
|
|
|
|
dateTime.assign(year, month, day, hour, minute, second, millis, micros);
|
|
|
|
else
|
|
|
|
throw SyntaxException("date/time component out of range");
|
|
|
|
timeZoneDifferential = tzd;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
DateTime DateTimeParser::parse(const std::string& fmt, const std::string& str, int& timeZoneDifferential)
|
|
|
|
{
|
|
|
|
DateTime result;
|
|
|
|
parse(fmt, str, result, timeZoneDifferential);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool DateTimeParser::tryParse(const std::string& fmt, const std::string& str, DateTime& dateTime, int& timeZoneDifferential)
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
parse(fmt, str, dateTime, timeZoneDifferential);
|
|
|
|
}
|
|
|
|
catch (Exception&)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void DateTimeParser::parse(const std::string& str, DateTime& dateTime, int& timeZoneDifferential)
|
|
|
|
{
|
|
|
|
if (!tryParse(str, dateTime, timeZoneDifferential))
|
|
|
|
throw SyntaxException("Unsupported or invalid date/time format");
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
DateTime DateTimeParser::parse(const std::string& str, int& timeZoneDifferential)
|
|
|
|
{
|
|
|
|
DateTime result;
|
|
|
|
if (tryParse(str, result, timeZoneDifferential))
|
|
|
|
return result;
|
|
|
|
else
|
|
|
|
throw SyntaxException("Unsupported or invalid date/time format");
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool DateTimeParser::tryParse(const std::string& str, DateTime& dateTime, int& timeZoneDifferential)
|
|
|
|
{
|
|
|
|
if (str.length() < 4) return false;
|
|
|
|
|
|
|
|
if (str[3] == ',')
|
|
|
|
return tryParse("%w, %e %b %r %H:%M:%S %Z", str, dateTime, timeZoneDifferential);
|
|
|
|
else if (str[3] == ' ')
|
|
|
|
return tryParse(DateTimeFormat::ASCTIME_FORMAT, str, dateTime, timeZoneDifferential);
|
|
|
|
else if (str.find(',') < 10)
|
|
|
|
return tryParse("%W, %e %b %r %H:%M:%S %Z", str, dateTime, timeZoneDifferential);
|
|
|
|
else if (Ascii::isDigit(str[0]))
|
|
|
|
{
|
|
|
|
if (str.find(' ') != std::string::npos || str.length() == 10)
|
|
|
|
return tryParse(DateTimeFormat::SORTABLE_FORMAT, str, dateTime, timeZoneDifferential);
|
|
|
|
else if (str.find('.') != std::string::npos || str.find(',') != std::string::npos)
|
|
|
|
return tryParse(DateTimeFormat::ISO8601_FRAC_FORMAT, str, dateTime, timeZoneDifferential);
|
|
|
|
else
|
|
|
|
return tryParse(DateTimeFormat::ISO8601_FORMAT, str, dateTime, timeZoneDifferential);
|
|
|
|
}
|
|
|
|
else return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int DateTimeParser::parseTZD(std::string::const_iterator& it, const std::string::const_iterator& end)
|
|
|
|
{
|
|
|
|
struct Zone
|
|
|
|
{
|
|
|
|
const char* designator;
|
|
|
|
int timeZoneDifferential;
|
|
|
|
};
|
|
|
|
|
|
|
|
static Zone zones[] =
|
|
|
|
{
|
|
|
|
{"Z", 0},
|
|
|
|
{"UT", 0},
|
|
|
|
{"GMT", 0},
|
|
|
|
{"BST", 1*3600},
|
|
|
|
{"IST", 1*3600},
|
|
|
|
{"WET", 0},
|
|
|
|
{"WEST", 1*3600},
|
|
|
|
{"CET", 1*3600},
|
|
|
|
{"CEST", 2*3600},
|
|
|
|
{"EET", 2*3600},
|
|
|
|
{"EEST", 3*3600},
|
|
|
|
{"MSK", 3*3600},
|
|
|
|
{"MSD", 4*3600},
|
|
|
|
{"NST", -3*3600-1800},
|
|
|
|
{"NDT", -2*3600-1800},
|
|
|
|
{"AST", -4*3600},
|
|
|
|
{"ADT", -3*3600},
|
|
|
|
{"EST", -5*3600},
|
|
|
|
{"EDT", -4*3600},
|
|
|
|
{"CST", -6*3600},
|
|
|
|
{"CDT", -5*3600},
|
|
|
|
{"MST", -7*3600},
|
|
|
|
{"MDT", -6*3600},
|
|
|
|
{"PST", -8*3600},
|
|
|
|
{"PDT", -7*3600},
|
|
|
|
{"AKST", -9*3600},
|
|
|
|
{"AKDT", -8*3600},
|
2023-02-07 21:28:27 +00:00
|
|
|
{"HST", -10*3600},
|
2023-02-08 11:04:11 +00:00
|
|
|
{"AEST", 10*3600},
|
|
|
|
{"AEDT", 11*3600},
|
2023-02-07 21:28:27 +00:00
|
|
|
{"ACST", 9*3600+1800},
|
|
|
|
{"ACDT", 10*3600+1800},
|
2023-02-08 11:04:11 +00:00
|
|
|
{"AWST", 8*3600},
|
|
|
|
{"AWDT", 9*3600}
|
|
|
|
};
|
|
|
|
|
|
|
|
int tzd = 0;
|
|
|
|
while (it != end && Ascii::isSpace(*it)) ++it;
|
|
|
|
if (it != end)
|
|
|
|
{
|
|
|
|
if (Ascii::isAlpha(*it))
|
|
|
|
{
|
|
|
|
std::string designator;
|
|
|
|
designator += *it++;
|
|
|
|
if (it != end && Ascii::isAlpha(*it)) designator += *it++;
|
|
|
|
if (it != end && Ascii::isAlpha(*it)) designator += *it++;
|
|
|
|
if (it != end && Ascii::isAlpha(*it)) designator += *it++;
|
|
|
|
for (unsigned i = 0; i < sizeof(zones)/sizeof(Zone); ++i)
|
|
|
|
{
|
|
|
|
if (designator == zones[i].designator)
|
|
|
|
{
|
|
|
|
tzd = zones[i].timeZoneDifferential;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (it != end && (*it == '+' || *it == '-'))
|
|
|
|
{
|
|
|
|
int sign = *it == '+' ? 1 : -1;
|
|
|
|
++it;
|
|
|
|
int hours = 0;
|
|
|
|
PARSE_NUMBER_N(hours, 2);
|
|
|
|
if (it != end && *it == ':') ++it;
|
|
|
|
int minutes = 0;
|
|
|
|
PARSE_NUMBER_N(minutes, 2);
|
|
|
|
tzd += sign*(hours*3600 + minutes*60);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return tzd;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int DateTimeParser::parseMonth(std::string::const_iterator& it, const std::string::const_iterator& end)
|
|
|
|
{
|
|
|
|
std::string month;
|
|
|
|
while (it != end && (Ascii::isSpace(*it) || Ascii::isPunct(*it))) ++it;
|
|
|
|
bool isFirst = true;
|
|
|
|
while (it != end && Ascii::isAlpha(*it))
|
|
|
|
{
|
|
|
|
char ch = (*it++);
|
|
|
|
if (isFirst) { month += Ascii::toUpper(ch); isFirst = false; }
|
|
|
|
else month += Ascii::toLower(ch);
|
|
|
|
}
|
|
|
|
if (month.length() < 3) throw SyntaxException("Month name must be at least three characters long", month);
|
|
|
|
for (int i = 0; i < 12; ++i)
|
|
|
|
{
|
|
|
|
if (DateTimeFormat::MONTH_NAMES[i].find(month) == 0)
|
|
|
|
return i + 1;
|
|
|
|
}
|
|
|
|
throw SyntaxException("Not a valid month name", month);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int DateTimeParser::parseDayOfWeek(std::string::const_iterator& it, const std::string::const_iterator& end)
|
|
|
|
{
|
|
|
|
std::string dow;
|
|
|
|
while (it != end && (Ascii::isSpace(*it) || Ascii::isPunct(*it))) ++it;
|
|
|
|
bool isFirst = true;
|
|
|
|
while (it != end && Ascii::isAlpha(*it))
|
|
|
|
{
|
|
|
|
char ch = (*it++);
|
|
|
|
if (isFirst) { dow += Ascii::toUpper(ch); isFirst = false; }
|
|
|
|
else dow += Ascii::toLower(ch);
|
|
|
|
}
|
|
|
|
if (dow.length() < 3) throw SyntaxException("Weekday name must be at least three characters long", dow);
|
|
|
|
for (int i = 0; i < 7; ++i)
|
|
|
|
{
|
|
|
|
if (DateTimeFormat::WEEKDAY_NAMES[i].find(dow) == 0)
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
throw SyntaxException("Not a valid weekday name", dow);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int DateTimeParser::parseAMPM(std::string::const_iterator& it, const std::string::const_iterator& end, int hour)
|
|
|
|
{
|
|
|
|
std::string ampm;
|
|
|
|
while (it != end && (Ascii::isSpace(*it) || Ascii::isPunct(*it))) ++it;
|
|
|
|
while (it != end && Ascii::isAlpha(*it))
|
|
|
|
{
|
|
|
|
char ch = (*it++);
|
|
|
|
ampm += Ascii::toUpper(ch);
|
|
|
|
}
|
|
|
|
if (ampm == "AM")
|
|
|
|
{
|
|
|
|
if (hour == 12)
|
|
|
|
return 0;
|
|
|
|
else
|
|
|
|
return hour;
|
|
|
|
}
|
|
|
|
else if (ampm == "PM")
|
|
|
|
{
|
|
|
|
if (hour < 12)
|
|
|
|
return hour + 12;
|
|
|
|
else
|
|
|
|
return hour;
|
|
|
|
}
|
|
|
|
else throw SyntaxException("Not a valid AM/PM designator", ampm);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} // namespace Poco
|