////////////////////////////////////////////////////////////////////////////////
//
// Copyright 2020, 2022 - 2023, Thomas Lauf, 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 <Datetime.h>
#include <DatetimeParser.h>
#include <ctime>
#include <iostream>
#include <test.h>

////////////////////////////////////////////////////////////////////////////////
void testParseOpenRange (
  UnitTest& t,
  const std::string& input)
{
  std::string label = std::string ("DatetimeParser::parse_range (\"") + input + "\") --> ";

  DatetimeParser iso;

  auto range = iso.parse_range (input);

  t.ok (range.is_open (), label + "[start,...)");
}

////////////////////////////////////////////////////////////////////////////////
void testParseClosedRange (
  UnitTest& t,
  const std::string& input)
{
  std::string label = std::string ("DatetimeParser::parse_range (\"") + input + "\") --> ";

  DatetimeParser iso;

  auto range = iso.parse_range (input);

  t.ok (! range.is_open (), label + "[start,end)");
}

////////////////////////////////////////////////////////////////////////////////
void testParse (
  UnitTest& t,
  const std::string& input)
{
  std::string label = std::string ("DatetimeParser::parse_range positive '") + input + "' --> success";

  DatetimeParser positive;

  try
  {
    positive.parse_range (input);
    t.pass (label);
  }
  catch (const std::string& e)
  {
    t.fail ("exception thrown");
    t.diag (e);
  }
}

////////////////////////////////////////////////////////////////////////////////
void testParseError (
  UnitTest& t,
  const std::string& input)
{
  std::string label = std::string ("DatetimeParser::parse_range negative '") + input + "' --> fail";

  DatetimeParser neg;
  try
  {
    neg.parse_range (input);
    t.fail ("No exception thrown");
  }
  catch (const std::string& e)
  {
    t.pass (label);
  }
}

////////////////////////////////////////////////////////////////////////////////
int main (int, char**)
{
  UnitTest t (361);

  // Determine local and UTC time.
  time_t now = time (nullptr);
  struct tm* local_now = localtime (&now);
  int local_s = (local_now->tm_hour * 3600) +
                (local_now->tm_min  * 60)   +
                local_now->tm_sec;
  local_now->tm_hour  = 0;
  local_now->tm_min   = 0;
  local_now->tm_sec   = 0;
  local_now->tm_isdst = -1;
  time_t local = mktime (local_now);
  std::cout << "# local midnight today " << local << '\n';

  int year = 2013;
  int mo = 12;

  local_now->tm_year  = year - 1900;
  local_now->tm_mon   = mo - 1;
  local_now->tm_mday  = 6;
  local_now->tm_isdst = 0;
  time_t local6 = mktime (local_now);
  std::cout << "# local midnight 2013-12-06 " << local6 << '\n';

  local_now->tm_year  = year - 1900;
  local_now->tm_mon   = mo - 1;
  local_now->tm_mday  = 1;
  local_now->tm_isdst = 0;
  time_t local1 = mktime (local_now);
  std::cout << "# local midnight 2013-12-01 " << local1 << '\n';

  struct tm* utc_now = gmtime (&now);
  int utc_s = (utc_now->tm_hour * 3600) +
              (utc_now->tm_min  * 60)   +
              utc_now->tm_sec;
  utc_now->tm_hour  = 0;
  utc_now->tm_min   = 0;
  utc_now->tm_sec   = 0;
  utc_now->tm_isdst = -1;
  time_t utc = timegm (utc_now);
  std::cout << "# utc midnight today " << utc << '\n';

  utc_now->tm_year  = year - 1900;
  utc_now->tm_mon   = mo - 1;
  utc_now->tm_mday  = 6;
  utc_now->tm_isdst = 0;
  time_t utc6 = timegm (utc_now);
  std::cout << "# utc midnight 2013-12-06 " << utc6 << '\n';

  utc_now->tm_year  = year - 1900;
  utc_now->tm_mon   = mo - 1;
  utc_now->tm_mday  = 1;
  utc_now->tm_isdst = 0;
  time_t utc1 = timegm (utc_now);
  std::cout << "# utc midnight 2013-12-01 " << utc1 << '\n';

  int hms = (12 * 3600) + (34 * 60) + 56; // The time 12:34:56 in seconds.

  int ld = local_s > hms ? 86400 : 0;     // Local extra day if now > hms.
  int ud = utc_s   > hms ? 86400 : 0;     // UTC extra day if now > hms.
  std::cout << "# ld " << ld << '\n';
  std::cout << "# ud " << ud << '\n';

  // Aggregated
  testParseOpenRange(t, "12:34:56  ");

  // time-ext
  testParseOpenRange(t, "12:34:56Z");
  testParseOpenRange(t, "12:34Z");
  testParseOpenRange(t, "12:34:56+01:00");
  testParseOpenRange(t, "12:34:56+01");
  testParseOpenRange(t, "12:34+01:00");
  testParseOpenRange(t, "12:34+01");
  testParseOpenRange(t, "12:34:56");
  testParseOpenRange(t, "12:34");

  // datetime-ext
  testParseClosedRange(t, "2013-12-06");
  testParseClosedRange(t, "2013-340");
  testParseClosedRange(t, "2013-W49-5");
  testParseClosedRange(t, "2013-W49");
  testParseClosedRange(t, "2013-12");

  testParseOpenRange(t, "2013-12-06T12:34:56");
  testParseOpenRange(t, "2013-12-06T12:34");
  testParseOpenRange(t, "2013-340T12:34:56");
  testParseOpenRange(t, "2013-340T12:34");
  testParseOpenRange(t, "2013-W49-5T12:34:56");
  testParseOpenRange(t, "2013-W49-5T12:34");
  testParseOpenRange(t, "2013-W49T12:34:56");
  testParseOpenRange(t, "2013-W49T12:34");

  testParseOpenRange(t, "2013-12-06T12:34:56Z");
  testParseOpenRange(t, "2013-12-06T12:34Z");
  testParseOpenRange(t, "2013-340T12:34:56Z");
  testParseOpenRange(t, "2013-340T12:34Z");
  testParseOpenRange(t, "2013-W49-5T12:34:56Z");
  testParseOpenRange(t, "2013-W49-5T12:34Z");
  testParseOpenRange(t, "2013-W49T12:34:56Z");
  testParseOpenRange(t, "2013-W49T12:34Z");

  testParseOpenRange(t, "2013-12-06T12:34:56+01:00");
  testParseOpenRange(t, "2013-12-06T12:34:56+01");
  testParseOpenRange(t, "2013-12-06T12:34:56-01:00");
  testParseOpenRange(t, "2013-12-06T12:34:56-01");
  testParseOpenRange(t, "2013-12-06T12:34+01:00");
  testParseOpenRange(t, "2013-12-06T12:34+01");
  testParseOpenRange(t, "2013-12-06T12:34-01:00");
  testParseOpenRange(t, "2013-12-06T12:34-01");
  testParseOpenRange(t, "2013-340T12:34:56+01:00");
  testParseOpenRange(t, "2013-340T12:34:56+01");
  testParseOpenRange(t, "2013-340T12:34:56-01:00");
  testParseOpenRange(t, "2013-340T12:34:56-01");
  testParseOpenRange(t, "2013-340T12:34+01:00");
  testParseOpenRange(t, "2013-340T12:34+01");
  testParseOpenRange(t, "2013-340T12:34-01:00");
  testParseOpenRange(t, "2013-340T12:34-01");
  testParseOpenRange(t, "2013-W49-5T12:34:56+01:00");
  testParseOpenRange(t, "2013-W49-5T12:34:56+01");
  testParseOpenRange(t, "2013-W49-5T12:34:56-01:00");
  testParseOpenRange(t, "2013-W49-5T12:34:56-01");
  testParseOpenRange(t, "2013-W49-5T12:34+01:00");
  testParseOpenRange(t, "2013-W49-5T12:34+01");
  testParseOpenRange(t, "2013-W49-5T12:34-01:00");
  testParseOpenRange(t, "2013-W49-5T12:34-01");
  testParseOpenRange(t, "2013-W49T12:34:56+01:00");
  testParseOpenRange(t, "2013-W49T12:34:56+01");
  testParseOpenRange(t, "2013-W49T12:34:56-01:00");
  testParseOpenRange(t, "2013-W49T12:34:56-01");
  testParseOpenRange(t, "2013-W49T12:34+01:00");
  testParseOpenRange(t, "2013-W49T12:34+01");
  testParseOpenRange(t, "2013-W49T12:34-01:00");
  testParseOpenRange(t, "2013-W49T12:34-01");

  // The only non-extended forms.
  testParseOpenRange(t, "20131206T123456Z");
  testParseOpenRange(t, "20131206T123456");

  // Non-extended forms.

  // time
  testParseOpenRange(t, "123456Z");
  testParseOpenRange(t, "1234Z");
  testParseOpenRange(t, "123456+0100");
  testParseOpenRange(t, "123456+01");
  testParseOpenRange(t, "1234+0100");
  testParseOpenRange(t, "1234+01");
  testParseOpenRange(t, "123456");
  testParseOpenRange(t, "1234");

  // datetime
  testParseClosedRange(t, "20131206");
  testParseClosedRange(t, "2013340");
  testParseClosedRange(t, "2013W495");
  testParseClosedRange(t, "2013W49");
  testParseClosedRange(t, "201312");

  testParseOpenRange(t, "20131206T123456");
  testParseOpenRange(t, "20131206T1234");
  testParseOpenRange(t, "2013340T123456");
  testParseOpenRange(t, "2013340T1234");
  testParseOpenRange(t, "2013W495T123456");
  testParseOpenRange(t, "2013W495T1234");
  testParseOpenRange(t, "2013W49T123456");
  testParseOpenRange(t, "2013W49T1234");

  testParseOpenRange(t, "20131206T123456Z");
  testParseOpenRange(t, "20131206T1234Z");
  testParseOpenRange(t, "2013340T123456Z");
  testParseOpenRange(t, "2013340T1234Z");
  testParseOpenRange(t, "2013W495T123456Z");
  testParseOpenRange(t, "2013W495T1234Z");
  testParseOpenRange(t, "2013W49T123456Z");
  testParseOpenRange(t, "2013W49T1234Z");

  testParseOpenRange(t, "20131206T123456+0100");
  testParseOpenRange(t, "20131206T123456+01");
  testParseOpenRange(t, "20131206T123456-0100");
  testParseOpenRange(t, "20131206T123456-01");
  testParseOpenRange(t, "20131206T1234+0100");
  testParseOpenRange(t, "20131206T1234+01");
  testParseOpenRange(t, "20131206T1234-0100");
  testParseOpenRange(t, "20131206T1234-01");
  testParseOpenRange(t, "2013340T123456+0100");
  testParseOpenRange(t, "2013340T123456+01");
  testParseOpenRange(t, "2013340T123456-0100");
  testParseOpenRange(t, "2013340T123456-01");
  testParseOpenRange(t, "2013340T1234+0100");
  testParseOpenRange(t, "2013340T1234+01");
  testParseOpenRange(t, "2013340T1234-0100");
  testParseOpenRange(t, "2013340T1234-01");
  testParseOpenRange(t, "2013W495T123456+0100");
  testParseOpenRange(t, "2013W495T123456+01");
  testParseOpenRange(t, "2013W495T123456-0100");
  testParseOpenRange(t, "2013W495T123456-01");
  testParseOpenRange(t, "2013W495T1234+0100");
  testParseOpenRange(t, "2013W495T1234+01");
  testParseOpenRange(t, "2013W495T1234-0100");
  testParseOpenRange(t, "2013W495T1234-01");
  testParseOpenRange(t, "2013W49T123456+0100");
  testParseOpenRange(t, "2013W49T123456+01");
  testParseOpenRange(t, "2013W49T123456-0100");
  testParseOpenRange(t, "2013W49T123456-01");
  testParseOpenRange(t, "2013W49T1234+0100");
  testParseOpenRange(t, "2013W49T1234+01");
  testParseOpenRange(t, "2013W49T1234-0100");
  testParseOpenRange(t, "2013W49T1234-01");

  testParseOpenRange(t, "10:30am");
  testParseOpenRange(t, "8:30am");
  testParseOpenRange(t, "8:30a");
  testParseOpenRange(t, "8:30");
  testParseOpenRange(t, "8am");
  testParseOpenRange(t, "8a");
  testParseOpenRange(t, "8:30pm");
  testParseOpenRange(t, "8:30p");
  testParseOpenRange(t, "8pm");
  testParseOpenRange(t, "8p");
  testParseOpenRange(t, "12pm");
  testParseOpenRange(t, "1pm");

  // Names.
  testParseClosedRange (t, "yesterday");
  testParseClosedRange (t, "tomorrow");

  testParseClosedRange (t, "january");
  testParseClosedRange (t, "february");
  testParseClosedRange (t, "march");
  testParseClosedRange (t, "april");
  testParseClosedRange (t, "may");
  testParseClosedRange (t, "june");
  testParseClosedRange (t, "july");
  testParseClosedRange (t, "august");
  testParseClosedRange (t, "september");
  testParseClosedRange (t, "october");
  testParseClosedRange (t, "november");
  testParseClosedRange (t, "december");

  testParseClosedRange (t, "jan");
  testParseClosedRange (t, "feb");
  testParseClosedRange (t, "mar");
  testParseClosedRange (t, "apr");
  testParseClosedRange (t, "may");
  testParseClosedRange (t, "jun");
  testParseClosedRange (t, "jul");
  testParseClosedRange (t, "aug");
  testParseClosedRange (t, "sep");
  testParseClosedRange (t, "oct");
  testParseClosedRange (t, "nov");
  testParseClosedRange (t, "dec");

  testParseClosedRange (t, "sunday");
  testParseClosedRange (t, "monday");
  testParseClosedRange (t, "tuesday");
  testParseClosedRange (t, "wednesday");
  testParseClosedRange (t, "thursday");
  testParseClosedRange (t, "friday");
  testParseClosedRange (t, "saturday");

  testParseClosedRange (t, "sun");
  testParseClosedRange (t, "mon");
  testParseClosedRange (t, "tue");
  testParseClosedRange (t, "wed");
  testParseClosedRange (t, "thu");
  testParseClosedRange (t, "fri");
  testParseClosedRange (t, "sat");

  Datetime r11 ("eow");
  Datetime r16 ("sonw");
  Datetime r23 ("sow");
  Datetime r17 ("sonm");
  Datetime r18 ("som");
  Datetime r19 ("sony");
  Datetime r19a ("soy");
  Datetime r19b ("eoy");
  Datetime r19c ("soq");
  Datetime r19d ("eoq");

  Datetime first ("1st");
  Datetime second ("2nd");
  Datetime third ("3rd");
  Datetime fourth ("4th");

  Datetime later ("later");

  // Test all format options.
  Datetime r32 ("2015-10-28T12:55:00");

  // Test all parse options.
  Datetime r33 ("2015 10 28 19 28 01", "Y M D H N S");
  t.is(r33.year (),   2015,         "Y works");
  t.is(r33.month (),    10,         "M works");
  t.is(r33.day (),      28,         "D works");
  t.is(r33.hour (),     19,         "H works");
  t.is(r33.minute (),   28,         "N works");
  t.is(r33.second (),    1,         "S works");

  Datetime r34 ("15 5 4 3 2 1", "y m d h n s");
  t.is(r34.year (),   2015,         "y works");
  t.is(r34.month (),     5,         "m works");
  t.is(r34.day (),       4,         "d works");
  t.is(r34.hour (),      3,         "h works");
  t.is(r34.minute (),    2,         "n works");
  t.is(r34.second (),    1,         "s works");

  Datetime r35 ("Wednesday October 28 2015", "A B D Y");
  t.is(r35.year (),    2015,        "Y works");
  t.is(r35.month (),     10,        "B works");
  t.is(r35.day (),       28,        "D works");
  t.is(r35.dayOfWeek (),  3,        "A works");

  Datetime r36 ("Wed Oct 28 15", "a b d y");
  t.is(r36.year (),    2015,        "y works");
  t.is(r36.month (),     10,        "b works");
  t.is(r36.day (),       28,        "d works");
  t.is(r36.dayOfWeek (),  3,        "a works");

  Datetime r37 ("19th");
  t.is (r37.day (),      19,        "'19th' --> 19");

  // Embedded parsing.
  testParseError (t, "nowadays");
  testParse (t, "now+1d");
  testParse (t, "now-1d");
  testParseOpenRange (t, "now)");
  testParseError (t, "now7");
  testParseError (t, "tomorrov");

  testParseError (t, "yesteryear");
  testParseClosedRange (t, "yest+1d");
  testParseClosedRange (t, "yest-1d");
  testParseClosedRange (t, "yest)");
  testParseError (t, "yest7");
  testParseClosedRange (t, "yesterday");

  testParse      (t, "1234567890+0");
  testParse      (t, "1234567890-0");
  testParse      (t, "1234567890)");

  // Negative tests, all expected to fail.
  testParseError (t, "");
  testParseError (t, "foo");
  testParseError (t, "-2014-07-07");
  testParseError (t, "2014-07-");
  testParseError (t, "2014-0-12");
  testParseError (t, "abcd-ab-ab");
  testParseError (t, "2014-000");
  testParseClosedRange (t, "2014-001");
  testParseClosedRange (t, "2014-365");
  testParseError (t, "2014-366");
  testParseError (t, "2014-367");
  testParseError (t, "2014-999");
  testParseError (t, "2014-999999999");
  testParseError (t, "2014-W00");
  testParseError (t, "2014-W54");
  testParseError (t, "2014-W240");
  testParseError (t, "2014-W248");
  testParseError (t, "2014-W24200");
  //testParseError (t, "2014-00");        // Looks like Datetime::parse_time_off 'hhmm-hh'
  testParseError (t, "2014-13");
  testParseError (t, "2014-99");
  testParseError (t, "25:00");
  testParseError (t, "99:00");
  testParseError (t, "12:60");
  testParseError (t, "12:99");
  testParseError (t, "12:ab");
  testParseError (t, "ab:12");
  testParseError (t, "ab:cd");
  testParseError (t, "-12:12");
  testParseError (t, "12:-12");
  testParseError (t, "25:00Z");
  testParseError (t, "99:00Z");
  testParseError (t, "12:60Z");
  testParseError (t, "12:99Z");
  testParseError (t, "12:abZ");
  testParseError (t, "ab:12Z");
  testParseError (t, "ab:cdZ");
  testParseError (t, "-12:12Z");
  testParseError (t, "12:-12Z");
  testParseError (t, "25:00+01:00");
  testParseError (t, "99:00+01:00");
  testParseError (t, "12:60+01:00");
  testParseError (t, "12:99+01:00");
  testParseError (t, "12:ab+01:00");
  testParseError (t, "ab:12+01:00");
  testParseError (t, "ab:cd+01:00");
  testParseError (t, "-12:12+01:00");
  testParseError (t, "12:-12+01:00");
  testParseError (t, "25:00-01:00");
  testParseError (t, "99:00-01:00");
  testParseError (t, "12:60-01:00");
  testParseError (t, "12:99-01:00");
  testParseError (t, "12:ab-01:00");
  testParseError (t, "ab:12-01:00");
  testParseError (t, "ab:cd-01:00");
  testParseError (t, "-12:12-01:00");
  testParseError (t, "12:-12-01:00");
  testParseError (t, "25:00:00");
  testParseError (t, "99:00:00");
  testParseError (t, "12:60:00");
  testParseError (t, "12:99:00");
  testParseError (t, "12:12:60");
  testParseError (t, "12:12:99");
  testParseError (t, "12:ab:00");
  testParseError (t, "ab:12:00");
  testParseError (t, "12:12:ab");
  testParseError (t, "ab:cd:ef");
  testParseError (t, "-12:12:12");
  testParseError (t, "12:-12:12");
  testParseError (t, "12:12:-12");
  testParseError (t, "25:00:00Z");
  testParseError (t, "99:00:00Z");
  testParseError (t, "12:60:00Z");
  testParseError (t, "12:99:00Z");
  testParseError (t, "12:12:60Z");
  testParseError (t, "12:12:99Z");
  testParseError (t, "12:ab:00Z");
  testParseError (t, "ab:12:00Z");
  testParseError (t, "12:12:abZ");
  testParseError (t, "ab:cd:efZ");
  testParseError (t, "-12:12:12Z");
  testParseError (t, "12:-12:12Z");
  testParseError (t, "12:12:-12Z");
  testParseError (t, "25:00:00+01:00");
  testParseError (t, "95:00:00+01:00");
  testParseError (t, "12:60:00+01:00");
  testParseError (t, "12:99:00+01:00");
  testParseError (t, "12:12:60+01:00");
  testParseError (t, "12:12:99+01:00");
  testParseError (t, "12:ab:00+01:00");
  testParseError (t, "ab:12:00+01:00");
  testParseError (t, "12:12:ab+01:00");
  testParseError (t, "ab:cd:ef+01:00");
  testParseError (t, "-12:12:12+01:00");
  testParseError (t, "12:-12:12+01:00");
  testParseError (t, "12:12:-12+01:00");
  testParseError (t, "25:00:00-01:00");
  testParseError (t, "95:00:00-01:00");
  testParseError (t, "12:60:00-01:00");
  testParseError (t, "12:99:00-01:00");
  testParseError (t, "12:12:60-01:00");
  testParseError (t, "12:12:99-01:00");
  testParseError (t, "12:ab:00-01:00");
  testParseError (t, "ab:12:00-01:00");
  testParseError (t, "12:12:ab-01:00");
  testParseError (t, "ab:cd:ef-01:00");
  testParseError (t, "-12:12:12-01:00");
  testParseError (t, "12:-12:12-01:00");
  testParseError (t, "12:12:-12-01:00");
  testParseError (t, "12:12:12-13:00");
  testParseError (t, "12:12:12-24:00");
  testParseError (t, "12:12:12-99:00");
  testParseError (t, "12:12:12-03:60");
  testParseError (t, "12:12:12-03:99");
  testParseError (t, "12:12:12-3:20");
  testParseError (t, "12:12:12-03:2");
  testParseError (t, "12:12:12-3:2");
  testParseError (t, "12:12:12+13:00");
  testParseError (t, "12:12:12+24:00");
  testParseError (t, "12:12:12+99:00");
  testParseError (t, "12:12:12+03:60");
  testParseError (t, "12:12:12+03:99");
  testParseError (t, "12:12:12+3:20");
  testParseError (t, "12:12:12+03:2");
  testParseError (t, "12:12:12+3:2");
  testParseError (t, "12:12-13:00");
  testParseError (t, "12:12-24:00");
  testParseError (t, "12:12-99:00");
  testParseError (t, "12:12-03:60");
  testParseError (t, "12:12-03:99");
  testParseError (t, "12:12-3:20");
  testParseError (t, "12:12-03:2");
  testParseError (t, "12:12-3:2");
  testParseError (t, "12:12+13:00");
  testParseError (t, "12:12+24:00");
  testParseError (t, "12:12+99:00");
  testParseError (t, "12:12+03:60");
  testParseError (t, "12:12+03:99");
  testParseError (t, "12:12+3:20");
  testParseError (t, "12:12+03:2");
  testParseError (t, "12:12+3:2");

  // Test with standalone date enable/disabled.
  Datetime::standaloneDateEnabled = true;
  testParseClosedRange(t, "20170319");
  Datetime::standaloneDateEnabled = false;
  testParseError (t, "20170319");
  Datetime::standaloneDateEnabled = true;

  Datetime::standaloneTimeEnabled = true;
  testParseOpenRange(t, "235959");
  Datetime::standaloneTimeEnabled = false;
  testParseError (t, "235959");
  Datetime::standaloneTimeEnabled = true;

  // Weekdays and month names can no longer be followed by ':' or '='.
  testParseClosedRange(t, "jan");
  testParseError (t, "jan:");
  testParseClosedRange(t, "mon");
  testParseError (t, "mon:");

  return 0;
}

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