From 645af76ddb34a0de09eed3a96cf2c782400b7884 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 9 Aug 2015 17:15:25 -0400 Subject: [PATCH] ISO8601: Merged Duration::parse functionality --- src/ISO8601.cpp | 163 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 161 insertions(+), 2 deletions(-) diff --git a/src/ISO8601.cpp b/src/ISO8601.cpp index b1295dcd6..1e4c313d2 100644 --- a/src/ISO8601.cpp +++ b/src/ISO8601.cpp @@ -31,6 +31,80 @@ #include #include +#define DAY 86400 +#define HOUR 3600 +#define MINUTE 60 +#define SECOND 1 + +static struct +{ + std::string unit; + int seconds; + bool standalone; +} durations[] = +{ + // These are sorted by first character, then length, so that Nibbler::getOneOf + // returns a maximal match. + {"annual", 365 * DAY, true}, + {"biannual", 730 * DAY, true}, + {"bimonthly", 61 * DAY, true}, + {"biweekly", 14 * DAY, true}, + {"biyearly", 730 * DAY, true}, + {"daily", 1 * DAY, true}, + {"days", 1 * DAY, false}, + {"day", 1 * DAY, true}, + {"d", 1 * DAY, false}, + {"fortnight", 14 * DAY, true}, + {"hours", 1 * HOUR, false}, + {"hour", 1 * HOUR, true}, + {"hrs", 1 * HOUR, false}, + {"hr", 1 * HOUR, true}, + {"h", 1 * HOUR, false}, + {"minutes", 1 * MINUTE, false}, + {"minute", 1 * MINUTE, true}, + {"mins", 1 * MINUTE, false}, + {"min", 1 * MINUTE, true}, + {"monthly", 30 * DAY, true}, + {"months", 30 * DAY, false}, + {"month", 30 * DAY, true}, + {"mnths", 30 * DAY, false}, + {"mths", 30 * DAY, false}, + {"mth", 30 * DAY, true}, + {"mos", 30 * DAY, false}, + {"mo", 30 * DAY, true}, + {"m", 30 * DAY, false}, + {"quarterly", 91 * DAY, true}, + {"quarters", 91 * DAY, false}, + {"quarter", 91 * DAY, true}, + {"qrtrs", 91 * DAY, false}, + {"qrtr", 91 * DAY, true}, + {"qtrs", 91 * DAY, false}, + {"qtr", 91 * DAY, true}, + {"q", 91 * DAY, false}, + {"semiannual", 183 * DAY, true}, + {"sennight", 14 * DAY, false}, + {"seconds", 1 * SECOND, false}, + {"second", 1 * SECOND, true}, + {"secs", 1 * SECOND, false}, + {"sec", 1 * SECOND, true}, + {"s", 1 * SECOND, false}, + {"weekdays", 1 * DAY, true}, + {"weekly", 7 * DAY, true}, + {"weeks", 7 * DAY, false}, + {"week", 7 * DAY, true}, + {"wks", 7 * DAY, false}, + {"wk", 7 * DAY, true}, + {"w", 7 * DAY, false}, + {"yearly", 365 * DAY, true}, + {"years", 365 * DAY, false}, + {"year", 365 * DAY, true}, + {"yrs", 365 * DAY, false}, + {"yr", 365 * DAY, true}, + {"y", 365 * DAY, false}, +}; + +#define NUM_DURATIONS (sizeof (durations) / sizeof (durations[0])) + //////////////////////////////////////////////////////////////////////////////// ISO8601d::ISO8601d () { @@ -601,8 +675,10 @@ ISO8601p::operator time_t () const // bool ISO8601p::parse (const std::string& input, std::string::size_type& start) { - auto i = start; - Nibbler n (input.substr (i)); + // Attempt and ISO parse first. + auto original_start = start; + Nibbler n (input.substr (original_start)); + n.save (); if (parse_designated (n)) { @@ -617,6 +693,89 @@ bool ISO8601p::parse (const std::string& input, std::string::size_type& start) } } + // Attempt a legacy format parse next. + n.restore (); + + // Static and so preserved between calls. + static std::vector units; + if (units.size () == 0) + for (unsigned int i = 0; i < NUM_DURATIONS; i++) + units.push_back (durations[i].unit); + + std::string number; + std::string unit; + + if (n.getOneOf (units, unit)) + { + if (n.depleted () || + Lexer::isWhitespace (n.next ()) || + Lexer::isSingleCharOperator (n.next ())) + { + start = original_start + n.cursor (); + + // Linear lookup - should be logarithmic. + for (unsigned int i = 0; i < NUM_DURATIONS; i++) + { + if (durations[i].unit == unit && + durations[i].standalone == true) + { + _value = static_cast (durations[i].seconds); + return true; + } + } + } + } + + else if (n.getNumber (number) && + number.find ('e') == std::string::npos && + number.find ('E') == std::string::npos && + (number.find ('+') == std::string::npos || number.find ('+') == 0) && + (number.find ('-') == std::string::npos || number.find ('-') == 0)) + { + n.skipWS (); + if (n.getOneOf (units, unit)) + { + // The "d" unit is a special case, because it is the only one that can + // legitimately occur at the beginning of a UUID, and be followed by an + // operator: + // + // 1111111d-0000-0000-0000-000000000000 + // + // Because Lexer::isDuration is higher precedence than Lexer::isUUID, + // the above UUID looks like: + // + // <1111111d> <-> ... + // duration op ... + // + // So as a special case, durations, with units of "d" are rejected if the + // quantity exceeds 10000. + // + if (unit == "d" && + strtol (number.c_str (), NULL, 10) > 10000) + return false; + + if (n.depleted () || + Lexer::isWhitespace (n.next ()) || + Lexer::isSingleCharOperator (n.next ())) + { + start = original_start + n.cursor (); + double quantity = strtod (number.c_str (), NULL); + + // Linear lookup - should be logarithmic. + double seconds = 1; + for (unsigned int i = 0; i < NUM_DURATIONS; i++) + { + if (durations[i].unit == unit) + { + seconds = durations[i].seconds; + _value = static_cast (quantity * static_cast (seconds)); + return true; + } + } + } + } + } + return false; }