- Nibbler learned how to parse formtted dates.  Date now uses Nibbler.
- Added Nibbler unit tests.
This commit is contained in:
Paul Beckingham 2011-06-12 13:59:25 -04:00
parent 236738c708
commit a749f83da3
4 changed files with 415 additions and 271 deletions

View file

@ -24,12 +24,14 @@
// USA
//
////////////////////////////////////////////////////////////////////////////////
#include <iomanip>
#include <sstream>
#include <time.h>
#include <assert.h>
#include <stdlib.h>
#include <ctype.h>
#include <Nibbler.h>
#include <Date.h>
#include <text.h>
#include <util.h>
@ -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;
}