////////////////////////////////////////////////////////////////////////////////
//
// 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 <cmake.h>
#include <sstream>
#include <iomanip>
#include <stdlib.h>
#include <assert.h>
#include <Lexer.h>
#include <util.h>
#include <ISO8601.h>
#include <Dates.h>
#include <text.h>
#include <utf8.h>
#include <i18n.h>

#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 ()
{
}

////////////////////////////////////////////////////////////////////////////////
ISO8601d::operator time_t () const
{
  return _date;
}

////////////////////////////////////////////////////////////////////////////////
// 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));
  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_time     (n) ||
             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 ())
    {
      start = n.cursor ();
      resolve ();
      return true;
    }
  }

  else if (parse_epoch (n) ||
           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 (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 (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)
{
  n.save ();
  std::string token;
  if (n.getUntilWS (token))
  {
    Variant v;
    if (namedDates (token, v))
    {
      _date = v.get_date ();
      return true;
    }
  }

  n.restore ();
  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 <time_t> (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 ();
}

////////////////////////////////////////////////////////////////////////////////
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
{
  // Making this local copy seems to fix a bug.  Remove the local copy and
  // you'll see segmentation faults and all kinds of gibberish.
  std::string localFormat = format;

  char buffer[12];
  std::string formatted;
  for (unsigned int i = 0; i < localFormat.length (); ++i)
  {
    int c = localFormat[i];
    switch (c)
    {
    case 'm': sprintf (buffer, "%d",    this->month ());                        break;
    case 'M': sprintf (buffer, "%02d",  this->month ());                        break;
    case 'd': sprintf (buffer, "%d",    this->day ());                          break;
    case 'D': sprintf (buffer, "%02d",  this->day ());                          break;
    case 'y': sprintf (buffer, "%02d",  this->year () % 100);                   break;
    case 'Y': sprintf (buffer, "%d",    this->year ());                         break;
    case 'a': sprintf (buffer, "%.3s",  ISO8601d::dayName (dayOfWeek ()).c_str ()); break;
    case 'A': sprintf (buffer, "%.10s", ISO8601d::dayName (dayOfWeek ()).c_str ()); break;
    case 'b': sprintf (buffer, "%.3s",  ISO8601d::monthName (month ()).c_str ());   break;
    case 'B': sprintf (buffer, "%.10s", ISO8601d::monthName (month ()).c_str ());   break;
    case 'v': sprintf (buffer, "%d",    ISO8601d::weekOfYear (ISO8601d::dayOfWeek (ISO8601d::weekstart))); break;
    case 'V': sprintf (buffer, "%02d",  ISO8601d::weekOfYear (ISO8601d::dayOfWeek (ISO8601d::weekstart))); break;
    case 'h': sprintf (buffer, "%d",    this->hour ());                         break;
    case 'H': sprintf (buffer, "%02d",  this->hour ());                         break;
    case 'n': sprintf (buffer, "%d",    this->minute ());                       break;
    case 'N': sprintf (buffer, "%02d",  this->minute ());                       break;
    case 's': sprintf (buffer, "%d",    this->second ());                       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;
    }

    formatted += buffer;
  }

  return formatted;
}

////////////////////////////////////////////////////////////////////////////////
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 ucFirst (months[month - 1]);
}

////////////////////////////////////////////////////////////////////////////////
// Static
void ISO8601d::dayName (int dow, std::string& name)
{
  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,
  };

  name = ucFirst (days[dow]);
}

////////////////////////////////////////////////////////////////////////////////
// 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 ucFirst (days[dow]);
}

////////////////////////////////////////////////////////////////////////////////
// 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+ (time_t delta)
{
  return ISO8601d (_date + delta);
}

////////////////////////////////////////////////////////////////////////////////
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 <std::string> 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 <int> (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 <int> (quantity * static_cast <double> (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";
  }
}

////////////////////////////////////////////////////////////////////////////////
const std::string ISO8601p::formatVague () const
{
  char formatted[24];
  float days = (float) _period / 86400.0;

       if (_period >= 86400 * 365) sprintf (formatted, "%.1fy", (days / 365.0));
  else if (_period >= 86400 * 84)  sprintf (formatted, "%1dmo", (int) (days / 30));
  else if (_period >= 86400 * 13)  sprintf (formatted, "%dw",   (int) (float) (days / 7.0));
  else if (_period >= 86400)       sprintf (formatted, "%dd",   (int) days);
  else if (_period >= 3600)        sprintf (formatted, "%dh",   (int) (_period / 3600));
  else if (_period >= 60)          sprintf (formatted, "%dmin", (int) (_period / 60));
  else if (_period >= 1)           sprintf (formatted, "%ds",   (int) _period);
  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 ()
{
  _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 ();
}

////////////////////////////////////////////////////////////////////////////////
