From a749f83da3040c94e487a8ee6693788126979011 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 12 Jun 2011 13:59:25 -0400 Subject: [PATCH] Parsing - Nibbler learned how to parse formtted dates. Date now uses Nibbler. - Added Nibbler unit tests. --- src/Date.cpp | 279 ++--------------------------------------- src/Expression.cpp | 17 ++- src/Nibbler.cpp | 305 ++++++++++++++++++++++++++++++++++++++++++++- test/nibbler.t.cpp | 85 ++++++++++++- 4 files changed, 415 insertions(+), 271 deletions(-) diff --git a/src/Date.cpp b/src/Date.cpp index 9b0f0776a..478a77e14 100644 --- a/src/Date.cpp +++ b/src/Date.cpp @@ -24,12 +24,14 @@ // USA // //////////////////////////////////////////////////////////////////////////////// + #include #include #include #include #include #include +#include #include #include #include @@ -83,13 +85,6 @@ Date::Date (const int m, const int d, const int y, //////////////////////////////////////////////////////////////////////////////// Date::Date (const std::string& input, const std::string& format /* = "m/d/Y" */) { - int month = 0; - int day = 0; - int year = -1; // So we can check later. - int hour = 0; - int minute = 0; - int second = 0; - // Perhaps it is an epoch date, in string form? if (isEpoch (input)) return; @@ -98,268 +93,16 @@ Date::Date (const std::string& input, const std::string& format /* = "m/d/Y" */) if (isRelativeDate (input)) return; - unsigned int i = 0; // Index into input. + // Parse an ISO date. + Nibbler n (input); + if (n.getDateISO (mT) && n.depleted ()) + return; - // Format may include: mMdDyYVaAbBhHNSjJ - // - // Note that the format should never include T or Z, as that interferes with - // the potential parsing for ISO dates constructed from the above format. - for (unsigned int f = 0; f < format.length (); ++f) - { - switch (format[f]) - { - // Single or double digit. - case 'm': - if (i >= input.length () || - ! isdigit (input[i])) - { - throw std::string ("\"") + input + "\" is not a valid date (m). Expected format '" + format + "'"; - } + // Parse a formatted date. + if (n.getDate (format, mT) && n.depleted ()) + return; - if (i + 1 < input.length () && - (input[i + 0] == '0' || input[i + 0] == '1') && - isdigit (input[i + 1])) - { - month = atoi (input.substr (i, 2).c_str ()); - i += 2; - } - else - { - month = atoi (input.substr (i, 1).c_str ()); - ++i; - } - break; - - case 'd': - if (i >= input.length () || - ! isdigit (input[i])) - { - throw std::string ("\"") + input + "\" is not a valid date (d). Expected format '" + format + "'"; - } - - if (i + 1 < input.length () && - (input[i + 0] == '0' || input[i + 0] == '1' || input[i + 0] == '2' || input[i + 0] == '3') && - isdigit (input[i + 1])) - { - day = atoi (input.substr (i, 2).c_str ()); - i += 2; - } - else - { - day = atoi (input.substr (i, 1).c_str ()); - ++i; - } - break; - - // Double digit. - case 'y': - if (i + 1 >= input.length () || - ! isdigit (input[i + 0]) || - ! isdigit (input[i + 1])) - { - throw std::string ("\"") + input + "\" is not a valid date (y). Expected format '" + format + "'"; - } - - year = atoi (input.substr (i, 2).c_str ()) + 2000; - i += 2; - break; - - case 'M': - if (i + 1 >= input.length () || - ! isdigit (input[i + 0]) || - ! isdigit (input[i + 1])) - { - throw std::string ("\"") + input + "\" is not a valid date (M). Expected format '" + format + "'"; - } - - month = atoi (input.substr (i, 2).c_str ()); - i += 2; - break; - - case 'D': - if (i + 1 >= input.length () || - ! isdigit (input[i + 0]) || - ! isdigit (input[i + 1])) - { - throw std::string ("\"") + input + "\" is not a valid date (D). Expected format '" + format + "'"; - } - - day = atoi (input.substr (i, 2).c_str ()); - i += 2; - break; - - case 'V': - if (i + 1 >= input.length () || - ! isdigit (input[i + 0]) || - ! isdigit (input[i + 1])) - { - throw std::string ("\"") + input + "\" is not a valid date (V). Expected format '" + format + "'"; - } - - i += 2; - break; - - // Quadruple digit. - case 'Y': - if (i + 3 >= input.length () || - ! isdigit (input[i + 0]) || - ! isdigit (input[i + 1]) || - ! isdigit (input[i + 2]) || - ! isdigit (input[i + 3])) - { - throw std::string ("\"") + input + "\" is not a valid date (Y). Expected format '" + format + "'"; - } - - year = atoi (input.substr (i, 4).c_str ()); - i += 4; - break; - - // Short names with 3 characters - case 'a': - if (i + 2 >= input.length () || - isdigit (input[i + 0]) || - isdigit (input[i + 1]) || - isdigit (input[i + 2])) - { - throw std::string ("\"") + input + "\" is not a valid date (a). Expected format '" + format + "'"; - } - - i += 3; - break; - - case 'b': - if (i + 2 >= input.length () || - isdigit (input[i + 0]) || - isdigit (input[i + 1]) || - isdigit (input[i + 2])) - { - throw std::string ("\"") + input + "\" is not a valid date (b). Expected format '" + format + "'"; - } - - month = Date::monthOfYear (input.substr (i, 3).c_str()); - i += 3; - break; - - // Long names - case 'A': - if (i + 2 >= input.length () || - isdigit (input[i + 0]) || - isdigit (input[i + 1]) || - isdigit (input[i + 2])) - { - throw std::string ("\"") + input + "\" is not a valid date (A). Expected format '" + format + "'"; - } - - i += Date::dayName( Date::dayOfWeek (input.substr (i, 3).c_str()) ).size(); - break; - - case 'B': - if (i + 2 >= input.length () || - isdigit (input[i + 0]) || - isdigit (input[i + 1]) || - isdigit (input[i + 2])) - { - throw std::string ("\"") + input + "\" is not a valid date (B). Expected format '" + format + "'"; - } - - month = Date::monthOfYear (input.substr (i, 3).c_str()); - i += Date::monthName(month).size(); - break; - - // Single or double digit. - case 'h': - if (i >= input.length () || - ! isdigit (input[i])) - { - throw std::string ("\"") + input + "\" is not a valid date (h). Expected format '" + format + "'"; - } - - if (i + 1 < input.length () && - (input[i + 0] == '0' || input[i + 0] == '1' || input[i + 0] == '2') && - isdigit (input[i + 1])) - { - hour = atoi (input.substr (i, 2).c_str ()); - i += 2; - } - else - { - hour = atoi (input.substr (i, 1).c_str ()); - ++i; - } - break; - - case 'H': - if (i + 1 >= input.length () || - ! isdigit (input[i + 0]) || - ! isdigit (input[i + 1])) - { - throw std::string ("\"") + input + "\" is not a valid date (H). Expected format '" + format + "'"; - } - - hour = atoi (input.substr (i, 2).c_str ()); - i += 2; - break; - - case 'N': - if (i + 1 >= input.length () || - ! isdigit (input[i + 0]) || - ! isdigit (input[i + 1])) - { - throw std::string ("\"") + input + "\" is not a valid date (N). Expected format '" + format + "'"; - } - - minute = atoi (input.substr (i, 2).c_str ()); - i += 2; - break; - - case 'S': - if (i + 1 >= input.length () || - ! isdigit (input[i + 0]) || - ! isdigit (input[i + 1])) - { - throw std::string ("\"") + input + "\" is not a valid date (S). Expected format '" + format + "'"; - } - - second = atoi (input.substr (i, 2).c_str ()); - i += 2; - break; - - default: - if (i >= input.length () || - input[i] != format[f]) - { - throw std::string ("\"") + input + "\" is not a valid date. Expected format '" + format + "'"; - } - ++i; - break; - } - } - - // Default the year to the current year, for formats that lack Y/y. - if (year == -1) - { - time_t now = time (NULL); - struct tm* default_year = localtime (&now); - year = default_year->tm_year + 1900; - } - - if (i < input.length ()) - throw std::string ("\"") + input + "\" is not a valid date. Expected format '" + format + "'"; - - if (!valid (month, day, year)) - throw std::string ("\"") + input + "\" is not a valid date but is in a valid format."; - - // Convert to epoch. - struct tm t = {0}; - t.tm_isdst = -1; // Requests that mktime determine summer time effect. - t.tm_mday = day; - t.tm_mon = month - 1; - t.tm_year = year - 1900; - t.tm_hour = hour; - t.tm_min = minute; - t.tm_sec = second; - - mT = mktime (&t); + throw std::string ("'") + input + "' is not a valid date in the '" + format + "' format."; } //////////////////////////////////////////////////////////////////////////////// @@ -451,6 +194,8 @@ const std::string Date::toString (const std::string& format /*= "m/d/Y" */) cons case 'H': sprintf (buffer, "%02d", this->hour ()); break; case 'N': sprintf (buffer, "%02d", this->minute ()); break; case 'S': sprintf (buffer, "%02d", this->second ()); break; + case 'j': sprintf (buffer, "%d", this->dayOfYear ()); break; + case 'J': sprintf (buffer, "%03d", this->dayOfYear ()); break; default: sprintf (buffer, "%c", c); break; } diff --git a/src/Expression.cpp b/src/Expression.cpp index 10be61f77..785604225 100644 --- a/src/Expression.cpp +++ b/src/Expression.cpp @@ -186,7 +186,8 @@ void Expression::expand_sequence () } //////////////////////////////////////////////////////////////////////////////// -// Nibble the whole thing. +// Nibble the whole bloody thing. Nuke it from orbit - it's the only way to be +// sure. void Expression::expand_tokens () { Arguments temp; @@ -194,10 +195,16 @@ void Expression::expand_tokens () // Get a list of all operators. std::vector operators = Arguments::operator_list (); - // Look at all args. + // Date format, for both parsing and rendering. + std::string date_format = context.config.get ("dateformat"); + + // Fake polymorphism. std::string s; int i; double d; + time_t t; + + // Look at all args. std::vector >::iterator arg; for (arg = _args.begin (); arg != _args.end (); ++arg) { @@ -213,6 +220,12 @@ void Expression::expand_tokens () else if (n.getInt (i)) temp.push_back (std::make_pair (format (i), "int")); + else if (n.getDateISO (t)) + temp.push_back (std::make_pair (Date (t).toISO (), "date")); + + else if (n.getDate (date_format, t)) + temp.push_back (std::make_pair (Date (t).toString (date_format), "date")); + else temp.push_back (*arg); } diff --git a/src/Nibbler.cpp b/src/Nibbler.cpp index 91a31e9e9..20465d8c3 100644 --- a/src/Nibbler.cpp +++ b/src/Nibbler.cpp @@ -25,12 +25,14 @@ // //////////////////////////////////////////////////////////////////////////////// +#include #include #include #include #include #include #include +#include #include const char* c_digits = "0123456789"; @@ -584,7 +586,308 @@ bool Nibbler::getDateISO (time_t& t) //////////////////////////////////////////////////////////////////////////////// bool Nibbler::getDate (const std::string& format, time_t& t) { - return false; + std::string::size_type i = mCursor; + + int month = 0; + int day = 0; + int year = -1; // So we can check later. + int hour = 0; + int minute = 0; + int second = 0; + + for (unsigned int f = 0; f < format.length (); ++f) + { + switch (format[f]) + { + case 'm': + if (i + 2 <= mLength && + (mInput[i + 0] == '0' || mInput[i + 0] == '1') && + isdigit (mInput[i + 1])) + { + month = atoi (mInput.substr (i, 2).c_str ()); + i += 2; + } + else if (i + 1 <= mLength && + isdigit (mInput[i + 0])) + { + month = mInput[i] - '0'; + i += 1; + } + else + return false; + break; + + case 'd': + if (i + 2 <= mLength && + isdigit (mInput[i + 1]) && + isdigit (mInput[i + 1])) + { + day = atoi (mInput.substr (i, 2).c_str ()); + i += 2; + } + else if (i + 1 <= mLength && + isdigit (mInput[i + 0])) + { + day = mInput[i] - '0'; + i += 1; + } + else + return false; + break; + + case 'y': + if (i + 2 <= mLength && + isdigit (mInput[i + 0]) && + isdigit (mInput[i + 1])) + { + year = 2000 + atoi (mInput.substr (i, 2).c_str ()); + i += 2; + } + else + return false; + break; + + case 'M': + if (i + 2 <= mLength && + isdigit (mInput[i + 0]) && + isdigit (mInput[i + 1])) + { + month = atoi (mInput.substr (i, 2).c_str ()); + i += 2; + } + else + return false; + break; + + case 'D': + if (i + 2 <= mLength && + isdigit (mInput[i + 0]) && + isdigit (mInput[i + 1])) + { + day = atoi (mInput.substr (i, 2).c_str ()); + i += 2; + } + else + return false; + break; + + // Merely parse, not extract. + case 'V': + if (i + 2 <= mLength && + isdigit (mInput[i + 0]) && + isdigit (mInput[i + 1])) + { + day = atoi (mInput.substr (i, 2).c_str ()); + i += 2; + } + else + return false; + break; + + case 'Y': + if (i + 4 <= mLength && + isdigit (mInput[i + 0]) && + isdigit (mInput[i + 1]) && + isdigit (mInput[i + 2]) && + isdigit (mInput[i + 3])) + { + year = atoi (mInput.substr (i, 4).c_str ()); + i += 4; + } + else + return false; + break; + + // Merely parse, not extract. + case 'a': + if (i + 3 <= mLength && + ! isdigit (mInput[i + 0]) && + ! isdigit (mInput[i + 1]) && + ! isdigit (mInput[i + 2])) + i += 3; + else + return false; + break; + + // Merely parse, not extract. + case 'b': + if (i + 3 <= mLength && + ! isdigit (mInput[i + 0]) && + ! isdigit (mInput[i + 1]) && + ! isdigit (mInput[i + 2])) + { + month = Date::monthOfYear (mInput.substr (i, 3).c_str()); + i += 3; + } + else + return false; + break; + + // Merely parse, not extract. + case 'A': + if (i + 3 <= mLength && + ! isdigit (mInput[i + 0]) && + ! isdigit (mInput[i + 1]) && + ! isdigit (mInput[i + 2])) + i += Date::dayName (Date::dayOfWeek (mInput.substr (i, 3).c_str ())).size (); + else + return false; + break; + + case 'B': + if (i + 3 <= mLength && + ! isdigit (mInput[i + 0]) && + ! isdigit (mInput[i + 1]) && + ! isdigit (mInput[i + 2])) + { + month = Date::monthOfYear (mInput.substr (i, 3).c_str ()); + i += Date::monthName (month).size (); + } + else + return false; + break; + + case 'h': + if (i + 2 <= mLength && + (mInput[i + 0] == '0' || mInput[i + 0] == '1') && + isdigit (mInput[i + 1])) + { + hour = atoi (mInput.substr (i, 2).c_str ()); + i += 2; + } + else if (i + 1 <= mLength && + isdigit (mInput[i + 0])) + { + hour = atoi (mInput.substr (i, 1).c_str ()); + i += 1; + } + else + return false; + break; + + case 'H': + if (i + 2 <= mLength && + isdigit (mInput[i + 0]) && + isdigit (mInput[i + 1])) + { + hour = atoi (mInput.substr (i, 2).c_str ()); + i += 2; + } + else + return false; + break; + + case 'N': + if (i + 2 <= mLength && + isdigit (mInput[i + 0]) && + isdigit (mInput[i + 1])) + { + minute = atoi (mInput.substr (i, 2).c_str ()); + i += 2; + } + else + return false; + break; + + case 'S': + if (i + 2 <= mLength && + isdigit (mInput[i + 0]) && + isdigit (mInput[i + 1])) + { + second = atoi (mInput.substr (i, 2).c_str ()); + i += 2; + } + else + return false; + break; + + case 'j': + if (i + 3 <= mLength && + isdigit (mInput[i + 0]) && + isdigit (mInput[i + 1]) && + isdigit (mInput[i + 2])) + { + day = atoi (mInput.substr (i, 3).c_str ()); + i += 3; + } + else if (i + 2 <= mLength && + isdigit (mInput[i + 0]) && + isdigit (mInput[i + 1])) + { + day = atoi (mInput.substr (i, 2).c_str ()); + i += 2; + } + else if (i + 1 <= mLength && + isdigit (mInput[i + 0])) + { + day = atoi (mInput.substr (i, 1).c_str ()); + i += 1; + } + else + return false; + break; + + case 'J': + if (i + 3 <= mLength && + isdigit (mInput[i + 0]) && + isdigit (mInput[i + 1]) && + isdigit (mInput[i + 2])) + { + day = atoi (mInput.substr (i, 3).c_str ()); + i += 3; + } + else + return false; + break; + + default: + if (i + 1 <= mLength && + mInput[i] == format[f]) + ++i; + else + return false; + break; + } + } + + // Default the year to the current year, for formats that lack Y/y. + if (year == -1) + { + time_t now = time (NULL); + struct tm* default_year = localtime (&now); + year = default_year->tm_year + 1900; + } + + // Convert to epoch. + struct tm tms = {0}; + tms.tm_isdst = -1; // Requests that mktime determine summer time effect. + + if (month == 0 && day >= 0 && day <= 365) + { + tms.tm_yday = day; + tms.tm_mon = 0; + + if (! Date::valid (day, year)) + return false; + } + else + { + tms.tm_mday = day; + tms.tm_mon = month > 0 ? month - 1 : 0; + + if (! Date::valid (month, day, year)) + return false; + } + + tms.tm_year = year - 1900; + tms.tm_hour = hour; + tms.tm_min = minute; + tms.tm_sec = second; + + t = mktime (&tms); + std::cout << "# " << year << " " << month << " " << day << " " << hour << " " << minute << " " << second << "\n"; + mCursor = i; + return true; } //////////////////////////////////////////////////////////////////////////////// diff --git a/test/nibbler.t.cpp b/test/nibbler.t.cpp index e103e6883..80503d41d 100644 --- a/test/nibbler.t.cpp +++ b/test/nibbler.t.cpp @@ -25,6 +25,7 @@ // //////////////////////////////////////////////////////////////////////////////// #include +#include #include #include @@ -33,7 +34,7 @@ Context context; //////////////////////////////////////////////////////////////////////////////// int main (int argc, char** argv) { - UnitTest t (193); + UnitTest t (242); try { @@ -42,6 +43,7 @@ int main (int argc, char** argv) int i; double d; time_t ti; + Date dt; std::vector options; // Make sure the nibbler behaves itself with trivial input. @@ -313,6 +315,87 @@ int main (int argc, char** argv) t.is (ti, 1234567890, "'20090213T233130Z': getDateISO () -> 1234567890"); t.ok (n.depleted (), "depleted"); + // bool getDate (time_t&, const std::string&); + t.diag ("Nibbler::getDate"); + n = Nibbler ("1/1/2008"); + t.ok (n.getDate ("m/d/Y", ti), "m/d/Y ok"); + dt = Date (ti); + t.is (dt.month (), 1, "ctor (std::string) -> m"); + t.is (dt.day (), 1, "ctor (std::string) -> d"); + t.is (dt.year (), 2008, "ctor (std::string) -> y"); + + n = Nibbler ("20080101"); + t.ok (n.getDate ("YMD", ti), "YMD ok"); + dt = Date (ti); + t.is (dt.month (), 1, "ctor (std::string) -> m"); + t.is (dt.day (), 1, "ctor (std::string) -> d"); + t.is (dt.year (), 2008, "ctor (std::string) -> y"); + + n = Nibbler ("12/31/2007"); + t.ok (n.getDate ("m/d/Y", ti), "m/d/Y ok"); + dt = Date (ti); + t.is (dt.month (), 12, "ctor (std::string) -> m"); + t.is (dt.day (), 31, "ctor (std::string) -> d"); + t.is (dt.year (), 2007, "ctor (std::string) -> y"); + + n = Nibbler ("20071231"); + t.ok (n.getDate ("YMD", ti), "YMD ok"); + dt = Date (ti); + t.is (dt.month (), 12, "ctor (std::string) -> m"); + t.is (dt.day (), 31, "ctor (std::string) -> d"); + t.is (dt.year (), 2007, "ctor (std::string) -> y"); + + n = Nibbler ("Tue 01 Jan 2008 (01)"); + t.ok (n.getDate ("a D b Y (V)", ti), "a D b Y (V)"); + dt = Date (ti); + t.is (dt.month (), 1, "ctor (std::string) -> m"); + t.is (dt.day (), 1, "ctor (std::string) -> d"); + t.is (dt.year (), 2008, "ctor (std::string) -> y"); + + n = Nibbler ("Tuesday, January 1, 2008"); + t.ok (n.getDate ("A, B d, Y", ti), "A, B d, Y ok"); + dt = Date (ti); + t.is (dt.month (), 1, "ctor (std::string) -> m"); + t.is (dt.day (), 1, "ctor (std::string) -> d"); + t.is (dt.year (), 2008, "ctor (std::string) -> y"); + + n = Nibbler ("v01 Tue 2008-01-01"); + t.ok (n.getDate ("vV a Y-M-D", ti), "vV a Y-M-D ok"); + dt = Date (ti); + t.is (dt.month (), 1, "ctor (std::string) -> m"); + t.is (dt.day (), 1, "ctor (std::string) -> d"); + t.is (dt.year (), 2008, "ctor (std::string) -> y"); + + n = Nibbler ("6/7/2010 1:23:45"); + t.ok (n.getDate ("m/d/Y h:N:S", ti), "m/d/Y h:N:S ok"); + dt = Date (ti); + t.is (dt.month (), 6, "ctor (std::string) -> m"); + t.is (dt.day (), 7, "ctor (std::string) -> d"); + t.is (dt.year (), 2010, "ctor (std::string) -> Y"); + t.is (dt.hour (), 1, "ctor (std::string) -> h"); + t.is (dt.minute (), 23, "ctor (std::string) -> N"); + t.is (dt.second (), 45, "ctor (std::string) -> S"); + + n = Nibbler ("6/7/2010 01:23:45"); + t.ok (n.getDate ("m/d/Y H:N:S", ti), "m/d/Y H:N:S ok"); + dt = Date (ti); + t.is (dt.month (), 6, "ctor (std::string) -> m"); + t.is (dt.day (), 7, "ctor (std::string) -> d"); + t.is (dt.year (), 2010, "ctor (std::string) -> Y"); + t.is (dt.hour (), 1, "ctor (std::string) -> h"); + t.is (dt.minute (), 23, "ctor (std::string) -> N"); + t.is (dt.second (), 45, "ctor (std::string) -> S"); + + n = Nibbler ("6/7/2010 12:34:56"); + t.ok (n.getDate ("m/d/Y H:N:S", ti), "m/d/Y H:N:S ok"); + dt = Date (ti); + t.is (dt.month (), 6, "ctor (std::string) -> m"); + t.is (dt.day (), 7, "ctor (std::string) -> d"); + t.is (dt.year (), 2010, "ctor (std::string) -> Y"); + t.is (dt.hour (), 12, "ctor (std::string) -> h"); + t.is (dt.minute (), 34, "ctor (std::string) -> N"); + t.is (dt.second (), 56, "ctor (std::string) -> S"); + // bool getOneOf (const std::vector &, std::string&); t.diag ("Nibbler::getOneOf"); options.push_back ("one");