//////////////////////////////////////////////////////////////////////////////// // // Copyright 2006 - 2016, 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 #include #include #ifdef PRODUCT_TASKWARRIOR #include #endif #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])) std::string ISO8601d::weekstart = STRING_DATE_SUNDAY; int ISO8601d::minimumMatchLength = 3; bool ISO8601d::isoEnabled = true; bool ISO8601p::isoEnabled = true; //////////////////////////////////////////////////////////////////////////////// ISO8601d::ISO8601d () { clear (); _date = time (NULL); } //////////////////////////////////////////////////////////////////////////////// ISO8601d::ISO8601d (const std::string& input, const std::string& format /*= ""*/) { clear (); std::string::size_type start = 0; if (! parse (input, start, format)) throw ::format (STRING_DATE_INVALID_FORMAT, input, format); } //////////////////////////////////////////////////////////////////////////////// ISO8601d::ISO8601d (const time_t t) { clear (); _date = t; } //////////////////////////////////////////////////////////////////////////////// ISO8601d::ISO8601d (const int m, const int d, const int y) { clear (); // Error if not valid. struct tm t {}; t.tm_isdst = -1; // Requests that mktime determine summer time effect. t.tm_mday = d; t.tm_mon = m - 1; t.tm_year = y - 1900; _date = mktime (&t); } //////////////////////////////////////////////////////////////////////////////// ISO8601d::ISO8601d (const int m, const int d, const int y, const int hr, const int mi, const int se) { clear (); // Error if not valid. struct tm t {}; t.tm_isdst = -1; // Requests that mktime determine summer time effect. t.tm_mday = d; t.tm_mon = m - 1; t.tm_year = y - 1900; t.tm_hour = hr; t.tm_min = mi; t.tm_sec = se; _date = mktime (&t); } //////////////////////////////////////////////////////////////////////////////// ISO8601d::~ISO8601d () { } //////////////////////////////////////////////////////////////////////////////// // 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, const std::string& format /* = "" */) { auto i = start; Nibbler n (input.substr (i)); // Parse epoch first, as it's the most common scenario. if (parse_epoch (n)) { // ::validate and ::resolve are not needed in this case. start = n.cursor (); return true; } else if (parse_formatted (n, format)) { // Check the values and determine time_t. if (validate ()) { start = n.cursor (); resolve (); return true; } } // Allow parse_date_time and parse_date_time_ext regardless of // ISO8601d::isoEnabled setting, because these formats are relied upon by // the 'import' command, JSON parser and hook system. else if (parse_date_time (n) || // Strictest first. parse_date_time_ext (n) || (ISO8601d::isoEnabled && (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 ()) { start = n.cursor (); resolve (); return true; } } else if (parse_named (n)) { // ::validate and ::resolve are not needed in this case. start = n.cursor (); 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; _date = 0; } //////////////////////////////////////////////////////////////////////////////// bool ISO8601d::parse_formatted (Nibbler& n, const std::string& format) { // Short-circuit on missing format. if (format == "") return false; n.save (); int month = -1; // So we can check later. int day = -1; int year = -1; int hour = -1; int minute = -1; int second = -1; // For parsing, unused. int wday = -1; int week = -1; for (unsigned int f = 0; f < format.length (); ++f) { switch (format[f]) { case 'm': if (n.getDigit (month)) { if (month == 0) n.getDigit (month); if (month == 1) if (n.getDigit (month)) month += 10; } else { n.restore (); return false; } break; case 'M': if (! n.getDigit2 (month)) { n.restore (); return false; } break; case 'd': if (n.getDigit (day)) { if (day == 0) n.getDigit (day); if (day == 1 || day == 2 || day == 3) { int tens = day; if (n.getDigit (day)) day += 10 * tens; } } else { n.restore (); return false; } break; case 'D': if (! n.getDigit2 (day)) { n.restore (); return false; } break; case 'y': if (! n.getDigit2 (year)) { n.restore (); return false; } year += 2000; break; case 'Y': if (! n.getDigit4 (year)) { n.restore (); return false; } break; case 'h': if (n.getDigit (hour)) { if (hour == 0) n.getDigit (hour); if (hour == 1 || hour == 2) { int tens = hour; if (n.getDigit (hour)) hour += 10 * tens; } } else { n.restore (); return false; } break; case 'H': if (! n.getDigit2 (hour)) { n.restore (); return false; } break; case 'n': if (n.getDigit (minute)) { if (minute == 0) n.getDigit (minute); if (minute < 6) { int tens = minute; if (n.getDigit (minute)) minute += 10 * tens; } } else { n.restore (); return false; } break; case 'N': if (! n.getDigit2 (minute)) { n.restore (); return false; } break; case 's': if (n.getDigit (second)) { if (second == 0) n.getDigit (second); if (second < 6) { int tens = second; if (n.getDigit (second)) second += 10 * tens; } } else { n.restore (); return false; } break; case 'S': if (! n.getDigit2 (second)) { n.restore (); return false; } break; case 'v': if (n.getDigit (week)) { if (week == 0) n.getDigit (week); if (week < 6) { int tens = week; if (n.getDigit (week)) week += 10 * tens; } } else { n.restore (); return false; } break; case 'V': if (! n.getDigit2 (week)) { n.restore (); return false; } break; case 'a': { auto cursor = n.cursor (); wday = ISO8601d::dayOfWeek (n.str ().substr (cursor, 3)); if (wday == -1) { n.restore (); return false; } n.skipN (3); } break; case 'A': { std::string dayName; if (n.getUntil (format[f + 1], dayName)) { wday = ISO8601d::dayOfWeek (Lexer::lowerCase (dayName)); if (wday == -1) { n.restore (); return false; } } } break; case 'b': { auto cursor = n.cursor (); month = ISO8601d::monthOfYear (n.str ().substr (cursor, 3)); if (month == -1) { n.restore (); return false; } n.skipN (3); } break; case 'B': { std::string monthName; if (n.getUntil (format[f + 1], monthName)) { month = ISO8601d::monthOfYear (Lexer::lowerCase (monthName)); if (month == -1) { n.restore (); return false; } } } break; default: if (! n.skip (format[f])) { n.restore (); return false; } break; } } // It is possible that the format='Y-M-D', and the input is Y-M-DTH:N:SZ, and // this should not be considered a match. if (! n.depleted () && ! Lexer::isWhitespace (n.next ())) { n.restore (); return false; } // Missing values are filled in from the current date. if (year == -1) { ISO8601d now; year = now.year (); if (month == -1) { month = now.month (); if (day == -1) { day = now.day (); if (hour == -1) { hour = now.hour (); if (minute == -1) { minute = now.minute (); if (second == -1) second = now.second (); } } } } } // Any remaining undefined values are assigned defaults. if (month == -1) month = 1; if (day == -1) day = 1; if (hour == -1) hour = 0; if (minute == -1) minute = 0; if (second == -1) second = 0; _year = year; _month = month; _day = day; _seconds = (hour * 3600) + (minute * 60) + second; return true; } //////////////////////////////////////////////////////////////////////////////// bool ISO8601d::parse_named (Nibbler& n) { #ifdef PRODUCT_TASKWARRIOR n.save (); std::string token; if (n.getUntilWS (token)) { Variant v; if (namedDates (token, v)) { _date = v.get_date (); return true; } } n.restore (); #endif return false; } //////////////////////////////////////////////////////////////////////////////// // Valid epoch values are unsigned integers after 1980-01-01T00:00:00Z. This // restriction means that '12' will not be identified as an epoch date. bool ISO8601d::parse_epoch (Nibbler& n) { n.save (); int epoch; if (n.getUnsignedInt (epoch) && n.depleted () && epoch >= 315532800) { _date = static_cast (epoch); return true; } n.restore (); return false; } //////////////////////////////////////////////////////////////////////////////// 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; } //////////////////////////////////////////////////////////////////////////////// // 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 > ISO8601d::daysInYear (_year))) || (_day && (_day < 1 || _day > ISO8601d::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 {}; 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; _date = utc ? timegm (&t) : mktime (&t); } //////////////////////////////////////////////////////////////////////////////// time_t ISO8601d::toEpoch () const { return _date; } //////////////////////////////////////////////////////////////////////////////// std::string ISO8601d::toEpochString () const { std::stringstream epoch; epoch << _date; return epoch.str (); } //////////////////////////////////////////////////////////////////////////////// // 19980119T070000Z = YYYYMMDDThhmmssZ std::string ISO8601d::toISO () const { struct tm* t = gmtime (&_date); std::stringstream iso; iso << std::setw (4) << std::setfill ('0') << t->tm_year + 1900 << std::setw (2) << std::setfill ('0') << t->tm_mon + 1 << std::setw (2) << std::setfill ('0') << t->tm_mday << "T" << std::setw (2) << std::setfill ('0') << t->tm_hour << std::setw (2) << std::setfill ('0') << t->tm_min << std::setw (2) << std::setfill ('0') << t->tm_sec << "Z"; return iso.str (); } //////////////////////////////////////////////////////////////////////////////// // 1998-01-19T07:00:00 = YYYY-MM-DDThh:mm:ss std::string ISO8601d::toISOLocalExtended () const { struct tm* t = localtime (&_date); std::stringstream iso; iso << std::setw (4) << std::setfill ('0') << t->tm_year + 1900 << "-" << std::setw (2) << std::setfill ('0') << t->tm_mon + 1 << "-" << std::setw (2) << std::setfill ('0') << t->tm_mday << "T" << std::setw (2) << std::setfill ('0') << t->tm_hour << ":" << std::setw (2) << std::setfill ('0') << t->tm_min << ":" << std::setw (2) << std::setfill ('0') << t->tm_sec; return iso.str (); } //////////////////////////////////////////////////////////////////////////////// double ISO8601d::toJulian () const { return (_date / 86400.0) + 2440587.5; } //////////////////////////////////////////////////////////////////////////////// void ISO8601d::toMDY (int& m, int& d, int& y) const { struct tm* t = localtime (&_date); m = t->tm_mon + 1; d = t->tm_mday; y = t->tm_year + 1900; } //////////////////////////////////////////////////////////////////////////////// const std::string ISO8601d::toString ( const std::string& format /*= "m/d/Y" */) const { std::stringstream formatted; for (unsigned int i = 0; i < format.length (); ++i) { int c = format[i]; switch (c) { case 'm': formatted << this->month (); break; case 'M': formatted << std::setw (2) << std::setfill ('0') << this->month (); break; case 'd': formatted << this->day (); break; case 'D': formatted << std::setw (2) << std::setfill ('0') << this->day (); break; case 'y': formatted << std::setw (2) << std::setfill ('0') << (this->year () % 100); break; case 'Y': formatted << this->year (); break; case 'a': formatted << ISO8601d::dayNameShort (dayOfWeek ()); break; case 'A': formatted << ISO8601d::dayName (dayOfWeek ()); break; case 'b': formatted << ISO8601d::monthNameShort (month ()); break; case 'B': formatted << ISO8601d::monthName (month ()); break; case 'v': formatted << ISO8601d::weekOfYear (ISO8601d::dayOfWeek (ISO8601d::weekstart)); break; case 'V': formatted << std::setw (2) << std::setfill ('0') << ISO8601d::weekOfYear (ISO8601d::dayOfWeek (ISO8601d::weekstart)); break; case 'h': formatted << this->hour (); break; case 'H': formatted << std::setw (2) << std::setfill ('0') << this->hour (); break; case 'n': formatted << this->minute (); break; case 'N': formatted << std::setw (2) << std::setfill ('0') << this->minute (); break; case 's': formatted << this->second (); break; case 'S': formatted << std::setw (2) << std::setfill ('0') << this->second (); break; case 'j': formatted << this->dayOfYear (); break; case 'J': formatted << std::setw (3) << std::setfill ('0') << this->dayOfYear (); break; default: formatted << static_cast (c); break; } } return formatted.str (); } //////////////////////////////////////////////////////////////////////////////// ISO8601d ISO8601d::startOfDay () const { return ISO8601d (month (), day (), year ()); } //////////////////////////////////////////////////////////////////////////////// ISO8601d ISO8601d::startOfWeek () const { ISO8601d sow (_date); sow -= (dayOfWeek () * 86400); return ISO8601d (sow.month (), sow.day (), sow.year ()); } //////////////////////////////////////////////////////////////////////////////// ISO8601d ISO8601d::startOfMonth () const { return ISO8601d (month (), 1, year ()); } //////////////////////////////////////////////////////////////////////////////// ISO8601d ISO8601d::startOfYear () const { return ISO8601d (1, 1, year ()); } //////////////////////////////////////////////////////////////////////////////// bool ISO8601d::valid (const std::string& input, const std::string& format /*= ""*/) { try { ISO8601d test (input, format); } catch (...) { return false; } return true; } //////////////////////////////////////////////////////////////////////////////// bool ISO8601d::valid (const int m, const int d, const int y, const int hr, const int mi, const int se) { if (hr < 0 || hr > 23) return false; if (mi < 0 || mi > 59) return false; if (se < 0 || se > 59) return false; return ISO8601d::valid (m, d, y); } //////////////////////////////////////////////////////////////////////////////// bool ISO8601d::valid (const int m, const int d, const int y) { // Check that the year is valid. if (y < 0) return false; // Check that the month is valid. if (m < 1 || m > 12) return false; // Finally check that the days fall within the acceptable range for this // month, and whether or not this is a leap year. if (d < 1 || d > ISO8601d::daysInMonth (m, y)) return false; return true; } //////////////////////////////////////////////////////////////////////////////// // Julian bool ISO8601d::valid (const int d, const int y) { // Check that the year is valid. if (y < 0) return false; if (d < 1 || d > ISO8601d::daysInYear (y)) return false; return true; } //////////////////////////////////////////////////////////////////////////////// // Static bool ISO8601d::leapYear (int year) { return ((! (year % 4)) && (year % 100)) || ! (year % 400); } //////////////////////////////////////////////////////////////////////////////// // Static int ISO8601d::daysInMonth (int month, int year) { static int days[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; if (month == 2 && ISO8601d::leapYear (year)) return 29; return days[month - 1]; } //////////////////////////////////////////////////////////////////////////////// // Static int ISO8601d::daysInYear (int year) { return ISO8601d::leapYear (year) ? 366 : 365; } //////////////////////////////////////////////////////////////////////////////// // Static std::string ISO8601d::monthName (int month) { static const char* months[12] = { STRING_DATE_JANUARY, STRING_DATE_FEBRUARY, STRING_DATE_MARCH, STRING_DATE_APRIL, STRING_DATE_MAY, STRING_DATE_JUNE, STRING_DATE_JULY, STRING_DATE_AUGUST, STRING_DATE_SEPTEMBER, STRING_DATE_OCTOBER, STRING_DATE_NOVEMBER, STRING_DATE_DECEMBER, }; assert (month > 0); assert (month <= 12); return Lexer::ucFirst (months[month - 1]); } //////////////////////////////////////////////////////////////////////////////// // Static std::string ISO8601d::monthNameShort (int month) { static const char* months[12] = { STRING_DATE_JANUARY, STRING_DATE_FEBRUARY, STRING_DATE_MARCH, STRING_DATE_APRIL, STRING_DATE_MAY, STRING_DATE_JUNE, STRING_DATE_JULY, STRING_DATE_AUGUST, STRING_DATE_SEPTEMBER, STRING_DATE_OCTOBER, STRING_DATE_NOVEMBER, STRING_DATE_DECEMBER, }; assert (month > 0); assert (month <= 12); return Lexer::ucFirst (months[month - 1]).substr (0, 3); } //////////////////////////////////////////////////////////////////////////////// // Static std::string ISO8601d::dayName (int dow) { static const char* days[7] = { STRING_DATE_SUNDAY, STRING_DATE_MONDAY, STRING_DATE_TUESDAY, STRING_DATE_WEDNESDAY, STRING_DATE_THURSDAY, STRING_DATE_FRIDAY, STRING_DATE_SATURDAY, }; return Lexer::ucFirst (days[dow]); } //////////////////////////////////////////////////////////////////////////////// // Static std::string ISO8601d::dayNameShort (int dow) { static const char* days[7] = { STRING_DATE_SUNDAY, STRING_DATE_MONDAY, STRING_DATE_TUESDAY, STRING_DATE_WEDNESDAY, STRING_DATE_THURSDAY, STRING_DATE_FRIDAY, STRING_DATE_SATURDAY, }; return Lexer::ucFirst (days[dow]).substr (0, 3); } //////////////////////////////////////////////////////////////////////////////// // Static int ISO8601d::dayOfWeek (const std::string& input) { if (ISO8601d::minimumMatchLength== 0) ISO8601d::minimumMatchLength = 3; if (closeEnough (STRING_DATE_SUNDAY, input, ISO8601d::minimumMatchLength)) return 0; else if (closeEnough (STRING_DATE_MONDAY, input, ISO8601d::minimumMatchLength)) return 1; else if (closeEnough (STRING_DATE_TUESDAY, input, ISO8601d::minimumMatchLength)) return 2; else if (closeEnough (STRING_DATE_WEDNESDAY, input, ISO8601d::minimumMatchLength)) return 3; else if (closeEnough (STRING_DATE_THURSDAY, input, ISO8601d::minimumMatchLength)) return 4; else if (closeEnough (STRING_DATE_FRIDAY, input, ISO8601d::minimumMatchLength)) return 5; else if (closeEnough (STRING_DATE_SATURDAY, input, ISO8601d::minimumMatchLength)) return 6; return -1; } //////////////////////////////////////////////////////////////////////////////// // Using Zeller's Congruence. // Static 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; } //////////////////////////////////////////////////////////////////////////////// // Static int ISO8601d::monthOfYear (const std::string& input) { if (ISO8601d::minimumMatchLength== 0) ISO8601d::minimumMatchLength= 3; if (closeEnough (STRING_DATE_JANUARY, input, ISO8601d::minimumMatchLength)) return 1; else if (closeEnough (STRING_DATE_FEBRUARY, input, ISO8601d::minimumMatchLength)) return 2; else if (closeEnough (STRING_DATE_MARCH, input, ISO8601d::minimumMatchLength)) return 3; else if (closeEnough (STRING_DATE_APRIL, input, ISO8601d::minimumMatchLength)) return 4; else if (closeEnough (STRING_DATE_MAY, input, ISO8601d::minimumMatchLength)) return 5; else if (closeEnough (STRING_DATE_JUNE, input, ISO8601d::minimumMatchLength)) return 6; else if (closeEnough (STRING_DATE_JULY, input, ISO8601d::minimumMatchLength)) return 7; else if (closeEnough (STRING_DATE_AUGUST, input, ISO8601d::minimumMatchLength)) return 8; else if (closeEnough (STRING_DATE_SEPTEMBER, input, ISO8601d::minimumMatchLength)) return 9; else if (closeEnough (STRING_DATE_OCTOBER, input, ISO8601d::minimumMatchLength)) return 10; else if (closeEnough (STRING_DATE_NOVEMBER, input, ISO8601d::minimumMatchLength)) return 11; else if (closeEnough (STRING_DATE_DECEMBER, input, ISO8601d::minimumMatchLength)) return 12; return -1; } //////////////////////////////////////////////////////////////////////////////// // Static int ISO8601d::length (const std::string& format) { int len = 0; for (auto& i : format) { switch (i) { case 'm': case 'M': case 'd': case 'D': case 'y': case 'v': case 'V': case 'h': case 'H': case 'n': case 'N': case 's': case 'S': len += 2; break; case 'b': case 'j': case 'J': case 'a': len += 3; break; case 'Y': len += 4; break; case 'A': case 'B': len += 10; break; // Calculate the width, don't assume a single character width. default: len += mk_wcwidth (i); break; } } return len; } //////////////////////////////////////////////////////////////////////////////// int ISO8601d::month () const { struct tm* t = localtime (&_date); return t->tm_mon + 1; } //////////////////////////////////////////////////////////////////////////////// int ISO8601d::week () const { return ISO8601d::weekOfYear (ISO8601d::dayOfWeek (ISO8601d::weekstart)); } //////////////////////////////////////////////////////////////////////////////// int ISO8601d::day () const { struct tm* t = localtime (&_date); return t->tm_mday; } //////////////////////////////////////////////////////////////////////////////// int ISO8601d::year () const { struct tm* t = localtime (&_date); return t->tm_year + 1900; } //////////////////////////////////////////////////////////////////////////////// int ISO8601d::weekOfYear (int weekStart) const { struct tm* t = localtime (&_date); char weekStr[3]; if (weekStart == 0) strftime(weekStr, sizeof(weekStr), "%U", t); else if (weekStart == 1) strftime(weekStr, sizeof(weekStr), "%V", t); else throw std::string (STRING_DATE_BAD_WEEKSTART); int weekNumber = atoi (weekStr); if (weekStart == 0) weekNumber += 1; return weekNumber; } //////////////////////////////////////////////////////////////////////////////// int ISO8601d::dayOfWeek () const { struct tm* t = localtime (&_date); return t->tm_wday; } //////////////////////////////////////////////////////////////////////////////// int ISO8601d::dayOfYear () const { struct tm* t = localtime (&_date); return t->tm_yday + 1; } //////////////////////////////////////////////////////////////////////////////// int ISO8601d::hour () const { struct tm* t = localtime (&_date); return t->tm_hour; } //////////////////////////////////////////////////////////////////////////////// int ISO8601d::minute () const { struct tm* t = localtime (&_date); return t->tm_min; } //////////////////////////////////////////////////////////////////////////////// int ISO8601d::second () const { struct tm* t = localtime (&_date); return t->tm_sec; } //////////////////////////////////////////////////////////////////////////////// bool ISO8601d::operator== (const ISO8601d& rhs) const { return rhs._date == _date; } //////////////////////////////////////////////////////////////////////////////// bool ISO8601d::operator!= (const ISO8601d& rhs) const { return rhs._date != _date; } //////////////////////////////////////////////////////////////////////////////// bool ISO8601d::operator< (const ISO8601d& rhs) const { return _date < rhs._date; } //////////////////////////////////////////////////////////////////////////////// bool ISO8601d::operator> (const ISO8601d& rhs) const { return _date > rhs._date; } //////////////////////////////////////////////////////////////////////////////// bool ISO8601d::operator<= (const ISO8601d& rhs) const { return _date <= rhs._date; } //////////////////////////////////////////////////////////////////////////////// bool ISO8601d::operator>= (const ISO8601d& rhs) const { return _date >= rhs._date; } //////////////////////////////////////////////////////////////////////////////// bool ISO8601d::sameHour (const ISO8601d& rhs) const { return this->year () == rhs.year () && this->month () == rhs.month () && this->day () == rhs.day () && this->hour () == rhs.hour (); } //////////////////////////////////////////////////////////////////////////////// bool ISO8601d::sameDay (const ISO8601d& rhs) const { return this->year () == rhs.year () && this->month () == rhs.month () && this->day () == rhs.day (); } //////////////////////////////////////////////////////////////////////////////// bool ISO8601d::sameWeek (const ISO8601d& rhs) const { return this->year () == rhs.year () && this->week () == rhs.week (); } //////////////////////////////////////////////////////////////////////////////// bool ISO8601d::sameMonth (const ISO8601d& rhs) const { return this->year () == rhs.year () && this->month () == rhs.month (); } //////////////////////////////////////////////////////////////////////////////// bool ISO8601d::sameYear (const ISO8601d& rhs) const { return this->year () == rhs.year (); } //////////////////////////////////////////////////////////////////////////////// ISO8601d ISO8601d::operator+ (const int delta) { return ISO8601d (_date + delta); } //////////////////////////////////////////////////////////////////////////////// ISO8601d ISO8601d::operator- (const int delta) { return ISO8601d (_date - delta); } //////////////////////////////////////////////////////////////////////////////// ISO8601d& ISO8601d::operator+= (const int delta) { _date += (time_t) delta; return *this; } //////////////////////////////////////////////////////////////////////////////// ISO8601d& ISO8601d::operator-= (const int delta) { _date -= (time_t) delta; return *this; } //////////////////////////////////////////////////////////////////////////////// time_t ISO8601d::operator- (const ISO8601d& rhs) { return _date - rhs._date; } //////////////////////////////////////////////////////////////////////////////// // Prefix decrement by one day. void ISO8601d::operator-- () { ISO8601d yesterday = startOfDay () - 1; yesterday = ISO8601d (yesterday.month (), yesterday.day (), yesterday.year (), hour (), minute (), second ()); _date = yesterday._date; } //////////////////////////////////////////////////////////////////////////////// // Postfix decrement by one day. void ISO8601d::operator-- (int) { ISO8601d yesterday = startOfDay () - 1; yesterday = ISO8601d (yesterday.month (), yesterday.day (), yesterday.year (), hour (), minute (), second ()); _date = yesterday._date; } //////////////////////////////////////////////////////////////////////////////// // Prefix increment by one day. void ISO8601d::operator++ () { ISO8601d tomorrow = (startOfDay () + 90001).startOfDay (); tomorrow = ISO8601d (tomorrow.month (), tomorrow.day (), tomorrow.year (), hour (), minute (), second ()); _date = tomorrow._date; } //////////////////////////////////////////////////////////////////////////////// // Postfix increment by one day. void ISO8601d::operator++ (int) { ISO8601d tomorrow = (startOfDay () + 90001).startOfDay (); tomorrow = ISO8601d (tomorrow.month (), tomorrow.day (), tomorrow.year (), hour (), minute (), second ()); _date = tomorrow._date; } //////////////////////////////////////////////////////////////////////////////// /* std::string ISO8601d::dump () const { std::stringstream s; s << "ISO8601d" << " y" << _year << " m" << _month << " w" << _week << " wd" << _weekday << " j" << _julian << " d" << _day << " s" << _seconds << " off" << _offset << " utc" << _utc << " =" << _date << " " << (_date ? toISO () : ""); return s.str (); } */ //////////////////////////////////////////////////////////////////////////////// ISO8601p::ISO8601p () { clear (); } //////////////////////////////////////////////////////////////////////////////// ISO8601p::ISO8601p (time_t input) { clear (); _period = 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) { _period = 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; _period = other._period; } return *this; } //////////////////////////////////////////////////////////////////////////////// bool ISO8601p::operator< (const ISO8601p& other) { return _period < other._period; } //////////////////////////////////////////////////////////////////////////////// bool ISO8601p::operator> (const ISO8601p& other) { return _period > other._period; } //////////////////////////////////////////////////////////////////////////////// ISO8601p::operator std::string () const { std::stringstream s; s << _period; return s.str (); } //////////////////////////////////////////////////////////////////////////////// ISO8601p::operator time_t () const { return _period; } //////////////////////////////////////////////////////////////////////////////// // 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) { _period = 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; _period = 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; _period = 0; } //////////////////////////////////////////////////////////////////////////////// const std::string ISO8601p::format () const { if (_period) { time_t t = _period; 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"; } } //////////////////////////////////////////////////////////////////////////////// // Range Representation // --------- --------------------- // >= 365d {n.n}y // >= 90d {n}mo // >= 14d {n}w // >= 1d {n}d // >= 1h {n}h // >= 1min {n}min // {n}s // const std::string ISO8601p::formatVague () const { float days = (float) _period / 86400.0; std::stringstream formatted; if (_period >= 86400 * 365) formatted << std::fixed << std::setprecision (1) << (days / 365) << "y"; else if (_period >= 86400 * 90) formatted << static_cast (days / 30) << "mo"; else if (_period >= 86400 * 14) formatted << static_cast (days / 7) << "w"; else if (_period >= 86400) formatted << static_cast (days) << "d"; else if (_period >= 3600) formatted << static_cast (_period / 3600) << "h"; else if (_period >= 60) formatted << static_cast (_period / 60) << "min"; else if (_period >= 1) formatted << static_cast (_period) << "s"; return formatted.str (); } //////////////////////////////////////////////////////////////////////////////// // '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 () { _period = (_year * 365 * 86400) + (_month * 30 * 86400) + (_day * 86400) + (_hours * 3600) + (_minutes * 60) + _seconds; } //////////////////////////////////////////////////////////////////////////////// /* std::string ISO8601p::dump () const { std::stringstream s; s << "ISO8601p" << " y" << _year << " mo" << _month << " d" << _day << " h" << _hours << " mi" << _minutes << " s" << _seconds << " =" << _period << " " << (_period ? format () : ""); return s.str (); } */ ////////////////////////////////////////////////////////////////////////////////