//////////////////////////////////////////////////////////////////////////////// // // Copyright 2006 - 2015, Paul Beckingham, Federico Hernandez. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. // // http://www.opensource.org/licenses/mit-license.php // //////////////////////////////////////////////////////////////////////////////// #include #include #include #include #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 () { clear (); } //////////////////////////////////////////////////////////////////////////////// ISO8601d::~ISO8601d () { } //////////////////////////////////////////////////////////////////////////////// ISO8601d::operator time_t () const { return _value; } //////////////////////////////////////////////////////////////////////////////// // Supported: // // result ::= date 'T' time 'Z' # UTC // | date 'T' time # Local // | date-ext 'T' time-ext 'Z' # UTC // | date-ext 'T' time-ext offset-ext # Specified TZ // | date-ext 'T' time-ext # Local // | date-ext # Local // | time-ext 'Z' // | time-ext offset-ext Not needed // | time-ext // ; // // date-ext ::= ±YYYYY-MM-DD Νot needed // | ±YYYYY-Www-D Νot needed // | ±YYYYY-Www Νot needed // | ±YYYYY-DDD Νot needed // | YYYY-MM-DD // | YYYY-DDD // | YYYY-Www-D // | YYYY-Www // ; // // time-ext ::= hh:mm:ss[,ss] // | hh:mm[,mm] // | hh[,hh] Ambiguous (number) // ; // // time-utc-ext ::= hh:mm[:ss] 'Z' ; // // offset-ext ::= ±hh[:mm] ; // // Not yet supported: // // recurrence ::= // | 'R' [n] '/' designated '/' datetime-ext # duration end // | 'R' [n] '/' designated '/' datetime # duration end // | 'R' [n] '/' designated # duration // | 'R' [n] '/' datetime-ext '/' designated # start duration // | 'R' [n] '/' datetime-ext '/' datetime-ext # start end // | 'R' [n] '/' datetime '/' designated # start duration // | 'R' [n] '/' datetime '/' datetime # start end // ; // bool ISO8601d::parse (const std::string& input, std::string::size_type& start) { auto i = start; Nibbler n (input.substr (i)); if (parse_date_time (n) || // Strictest first. parse_date_time_ext (n) || parse_date_ext (n) || parse_time_utc_ext (n) || parse_time_off_ext (n) || parse_time_ext (n)) // Time last, as it is the most permissive. { // Check the values and determine time_t. if (validate ()) { // Record cursor position. start = n.cursor (); resolve (); return true; } } return false; } //////////////////////////////////////////////////////////////////////////////// void ISO8601d::clear () { _year = 0; _month = 0; _week = 0; _weekday = 0; _julian = 0; _day = 0; _seconds = 0; _offset = 0; _utc = false; _value = 0; } //////////////////////////////////////////////////////////////////////////////// bool ISO8601d::parse_date_time (Nibbler& n) { n.save (); int year, month, day, hour, minute, second; if (n.getDigit4 (year) && n.getDigit2 (month) && month && n.getDigit2 (day) && day && n.skip ('T') && n.getDigit2 (hour) && n.getDigit2 (minute) && minute < 60 && n.getDigit2 (second) && second < 60) { if (n.skip ('Z')) _utc = true; _year = year; _month = month; _day = day; _seconds = (((hour * 60) + minute) * 60) + second; return true; } _year = 0; _month = 0; _day = 0; _seconds = 0; n.restore (); return false; } //////////////////////////////////////////////////////////////////////////////// // date-ext 'T' time-ext 'Z' // date-ext 'T' time-ext offset-ext // date-ext 'T' time-ext bool ISO8601d::parse_date_time_ext (Nibbler& n) { n.save (); if (parse_date_ext (n)) { if (n.skip ('T') && parse_time_ext (n)) { if (n.skip ('Z')) _utc = true; else if (parse_off_ext (n)) ; if (! Lexer::isDigit (n.next ())) return true; } // Restore date_ext _year = 0; _month = 0; _week = 0; _weekday = 0; _julian = 0; _day = 0; } n.restore (); return false; } //////////////////////////////////////////////////////////////////////////////// // YYYY-MM-DD // YYYY-DDD // YYYY-Www-D // YYYY-Www bool ISO8601d::parse_date_ext (Nibbler& n) { Nibbler backup (n); int year; if (n.getDigit4 (year) && n.skip ('-')) { int month; int day; if (n.skip ('W') && n.getDigit2 (_week) && _week) { if (n.skip ('-') && n.getDigit (_weekday)) { } _year = year; if (!Lexer::isDigit (n.next ())) return true; } else if (n.getDigit3 (_julian) && _julian) { _year = year; if (!Lexer::isDigit (n.next ())) return true; } else if (n.getDigit2 (month) && month && n.skip ('-') && n.getDigit2 (day) && day) { _year = year; _month = month; _day = day; if (!Lexer::isDigit (n.next ())) return true; } } n = backup; return false; } //////////////////////////////////////////////////////////////////////////////// // ±hh[:mm] bool ISO8601d::parse_off_ext (Nibbler& n) { Nibbler backup (n); std::string sign; if (n.getN (1, sign) && (sign == "+" || sign == "-")) { int offset; int hh; int mm; if (n.getDigit2 (hh) && hh <= 12 && !n.getDigit (mm)) { offset = hh * 3600; if (n.skip (':')) { if (n.getDigit2 (mm) && mm < 60) { offset += mm * 60; } else { n = backup; return false; } } _offset = (sign == "-") ? -offset : offset; if (!Lexer::isDigit (n.next ())) return true; } } n = backup; return false; } //////////////////////////////////////////////////////////////////////////////// // hh:mm[:ss] bool ISO8601d::parse_time_ext (Nibbler& n) { Nibbler backup (n); int seconds = 0; int hh; int mm; int ss; if (n.getDigit2 (hh) && hh <= 24 && n.skip (':') && n.getDigit2 (mm) && mm < 60) { seconds = (hh * 3600) + (mm * 60); if (n.skip (':')) { if (n.getDigit2 (ss) && ss < 60) { seconds += ss; _seconds = seconds; if (!Lexer::isDigit (n.next ())) return true; } n = backup; return false; } _seconds = seconds; if (!Lexer::isDigit (n.next ())) return true; } n = backup; return false; } //////////////////////////////////////////////////////////////////////////////// // time-ext 'Z' bool ISO8601d::parse_time_utc_ext (Nibbler& n) { n.save (); if (parse_time_ext (n) && n.skip ('Z')) { _utc = true; if (!Lexer::isDigit (n.next ())) return true; } n.restore (); return false; } //////////////////////////////////////////////////////////////////////////////// // time-ext offset-ext bool ISO8601d::parse_time_off_ext (Nibbler& n) { Nibbler backup (n); if (parse_time_ext (n) && parse_off_ext (n)) { if (!Lexer::isDigit (n.next ())) return true; } n = backup; return false; } //////////////////////////////////////////////////////////////////////////////// // Using Zeller's Congruence. int ISO8601d::dayOfWeek (int year, int month, int day) { int adj = (14 - month) / 12; int m = month + 12 * adj - 2; int y = year - adj; return (day + (13 * m - 1) / 5 + y + y / 4 - y / 100 + y / 400) % 7; } //////////////////////////////////////////////////////////////////////////////// // Validation via simple range checking. bool ISO8601d::validate () { // _year; if ((_year && (_year < 1900 || _year > 2200)) || (_month && (_month < 1 || _month > 12)) || (_week && (_week < 1 || _week > 53)) || (_weekday && (_weekday < 0 || _weekday > 6)) || (_julian && (_julian < 1 || _julian > Date::daysInYear (_year))) || (_day && (_day < 1 || _day > Date::daysInMonth (_month, _year))) || (_seconds && (_seconds < 1 || _seconds > 86400)) || (_offset && (_offset < -86400 || _offset > 86400))) return false; return true; } //////////////////////////////////////////////////////////////////////////////// // int tm_sec; seconds (0 - 60) // int tm_min; minutes (0 - 59) // int tm_hour; hours (0 - 23) // int tm_mday; day of month (1 - 31) // int tm_mon; month of year (0 - 11) // int tm_year; year - 1900 // int tm_wday; day of week (Sunday = 0) // int tm_yday; day of year (0 - 365) // int tm_isdst; is summer time in effect? // char *tm_zone; abbreviation of timezone name // long tm_gmtoff; offset from UTC in seconds void ISO8601d::resolve () { // Don't touch the original values. int year = _year; int month = _month; int week = _week; int weekday = _weekday; int julian = _julian; int day = _day; int seconds = _seconds; int offset = _offset; bool utc = _utc; // Get current time. time_t now = time (NULL); // A UTC offset needs to be accommodated. Once the offset is subtracted, // only local and UTC times remain. if (offset) { seconds -= offset; now -= offset; utc = true; } // Get 'now' in the relevant location. struct tm* t_now = utc ? gmtime (&now) : localtime (&now); int seconds_now = (t_now->tm_hour * 3600) + (t_now->tm_min * 60) + t_now->tm_sec; // Project forward one day if the specified seconds are earlier in the day // than the current seconds. if (year == 0 && month == 0 && day == 0 && week == 0 && weekday == 0 && seconds < seconds_now) { seconds += 86400; } // Convert week + weekday --> julian. if (week) { julian = (week * 7) + weekday - dayOfWeek (year, 1, 4) - 3; } // Provide default values for year, month, day. else { // Default values for year, month, day: // // y m d --> y m d // y m - --> y m 1 // y - - --> y 1 1 // - - - --> now now now // if (year == 0) { year = t_now->tm_year + 1900; month = t_now->tm_mon + 1; day = t_now->tm_mday; } else { if (month == 0) { month = 1; day = 1; } else if (day == 0) day = 1; } } if (julian) { month = 1; day = julian; } struct tm t = {0}; t.tm_isdst = -1; // Requests that mktime/gmtime determine summer time effect. t.tm_year = year - 1900; t.tm_mon = month - 1; t.tm_mday = day; if (seconds > 86400) { int days = seconds / 86400; t.tm_mday += days; seconds %= 86400; } t.tm_hour = seconds / 3600; t.tm_min = (seconds % 3600) / 60; t.tm_sec = seconds % 60; _value = utc ? timegm (&t) : mktime (&t); } //////////////////////////////////////////////////////////////////////////////// ISO8601p::ISO8601p () { clear (); } //////////////////////////////////////////////////////////////////////////////// ISO8601p::ISO8601p (time_t input) { clear (); _value = input; } //////////////////////////////////////////////////////////////////////////////// ISO8601p::ISO8601p (const std::string& input) { clear (); if (Lexer::isAllDigits (input)) { time_t value = (time_t) strtol (input.c_str (), NULL, 10); if (value == 0 || value > 60) { _value = value; return; } } std::string::size_type idx = 0; parse (input, idx); } //////////////////////////////////////////////////////////////////////////////// ISO8601p::~ISO8601p () { } //////////////////////////////////////////////////////////////////////////////// ISO8601p& ISO8601p::operator= (const ISO8601p& other) { if (this != &other) { _year = other._year; _month = other._month; _day = other._day; _hours = other._hours; _minutes = other._minutes; _seconds = other._seconds; _value = other._value; } return *this; } //////////////////////////////////////////////////////////////////////////////// bool ISO8601p::operator< (const ISO8601p& other) { return _value < other._value; } //////////////////////////////////////////////////////////////////////////////// bool ISO8601p::operator> (const ISO8601p& other) { return _value > other._value; } //////////////////////////////////////////////////////////////////////////////// ISO8601p::operator std::string () const { std::stringstream s; s << _value; return s.str (); } //////////////////////////////////////////////////////////////////////////////// ISO8601p::operator time_t () const { return _value; } //////////////////////////////////////////////////////////////////////////////// // Supported: // // duration ::= designated # duration // // designated ::= 'P' [nn 'Y'] [nn 'M'] [nn 'D'] ['T' [nn 'H'] [nn 'M'] [nn 'S']] // // Not supported: // // duration ::= designated '/' datetime-ext # duration end // | degignated '/' datetime # duration end // | designated # duration // | 'P' datetime-ext '/' datetime-ext # start end // | 'P' datetime '/' datetime # start end // | 'P' datetime-ext # start // | 'P' datetime # start // | datetime-ext '/' designated # start duration // | datetime-ext '/' 'P' datetime-ext # start end // | datetime-ext '/' datetime-ext # start end // | datetime '/' designated # start duration // | datetime '/' 'P' datetime # start end // | datetime '/' datetime # start end // ; // bool ISO8601p::parse (const std::string& input, std::string::size_type& start) { // Attempt and ISO parse first. auto original_start = start; Nibbler n (input.substr (original_start)); n.save (); if (parse_designated (n)) { // Check the values and determine time_t. if (validate ()) { // Record cursor position. start = n.cursor (); resolve (); return true; } } // 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 instead 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 instead 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; } //////////////////////////////////////////////////////////////////////////////// void ISO8601p::clear () { _year = 0; _month = 0; _day = 0; _hours = 0; _minutes = 0; _seconds = 0; _value = 0; } //////////////////////////////////////////////////////////////////////////////// const std::string ISO8601p::format () const { if (_value) { time_t t = _value; int seconds = t % 60; t /= 60; int minutes = t % 60; t /= 60; int hours = t % 24; t /= 24; int days = t; std::stringstream s; s << 'P'; if (days) s << days << 'D'; if (hours || minutes || seconds) { s << 'T'; if (hours) s << hours << 'H'; if (minutes) s << minutes << 'M'; if (seconds) s << seconds << 'S'; } return s.str (); } else { return "PT0S"; } } //////////////////////////////////////////////////////////////////////////////// const std::string ISO8601p::formatVague () const { char formatted[24]; float days = (float) _value / 86400.0; if (_value >= 86400 * 365) sprintf (formatted, "%.1fy", (days / 365.0)); else if (_value >= 86400 * 84) sprintf (formatted, "%1dmo", (int) (days / 30)); else if (_value >= 86400 * 13) sprintf (formatted, "%dw", (int) (float) (days / 7.0)); else if (_value >= 86400) sprintf (formatted, "%dd", (int) days); else if (_value >= 3600) sprintf (formatted, "%dh", (int) (_value / 3600)); else if (_value >= 60) sprintf (formatted, "%dmin", (int) (_value / 60)); else if (_value >= 1) sprintf (formatted, "%ds", (int) _value); else formatted[0] = '\0'; return std::string (formatted); } //////////////////////////////////////////////////////////////////////////////// // 'P' [nn 'Y'] [nn 'M'] [nn 'D'] ['T' [nn 'H'] [nn 'M'] [nn 'S']] bool ISO8601p::parse_designated (Nibbler& n) { Nibbler backup (n); if (n.skip ('P')) { int value; n.save (); if (n.getUnsignedInt (value) && n.skip ('Y')) _year = value; else n.restore (); n.save (); if (n.getUnsignedInt (value) && n.skip ('M')) _month = value; else n.restore (); n.save (); if (n.getUnsignedInt (value) && n.skip ('D')) _day = value; else n.restore (); if (n.skip ('T')) { n.save (); if (n.getUnsignedInt (value) && n.skip ('H')) _hours = value; else n.restore (); n.save (); if (n.getUnsignedInt (value) && n.skip ('M')) _minutes = value; else n.restore (); n.save (); if (n.getUnsignedInt (value) && n.skip ('S')) _seconds = value; else n.restore (); } return true; } n = backup; return false; } //////////////////////////////////////////////////////////////////////////////// bool ISO8601p::validate () { return _year || _month || _day || _hours || _minutes || _seconds; } //////////////////////////////////////////////////////////////////////////////// // Allow un-normalized values. void ISO8601p::resolve () { _value = (_year * 365 * 86400) + (_month * 30 * 86400) + (_day * 86400) + (_hours * 3600) + (_minutes * 60) + _seconds; } ////////////////////////////////////////////////////////////////////////////////