Detect whether a date is meant as range

- Add DatetimeParser::parse_range: If a date contains no time, it is assumed to be a fixed range, else an open range starting at given datetime
- Add tests for summary with named dates 'yesterday' and 'today'
- Remove closing of filter in CmdSummary.cpp
- Closes #333

Signed-off-by: Thomas Lauf <thomas.lauf@tngtech.com>
This commit is contained in:
Thomas Lauf 2020-06-05 13:35:19 +02:00
parent 2906f36830
commit 63f230013a
8 changed files with 3757 additions and 7 deletions

View file

@ -36,6 +36,7 @@
#include <set>
#include <Duration.h>
#include <timew.h>
#include "DatetimeParser.h"
////////////////////////////////////////////////////////////////////////////////
A2::A2 (const std::string& raw, Lexer::Type lextype)
@ -724,7 +725,9 @@ Interval CLI::getFilter (const Range& default_range) const
else if (args.size () == 1 &&
args[0] == "<date>")
{
filter.setRange ({Datetime (start), 0});
DatetimeParser dtp;
Range range = dtp.parse_range(start);
filter.setRange (range);
}
// from <date>
@ -734,7 +737,6 @@ Interval CLI::getFilter (const Range& default_range) const
{
filter.setRange ({Datetime (start), 0});
}
// <date> to/- <date>
else if (args.size () == 3 &&
args[0] == "<date>" &&

View file

@ -11,6 +11,7 @@ set (timew_SRCS AtomicFile.cpp AtomicFile.h
ChartConfig.h
Database.cpp Database.h
Datafile.cpp Datafile.h
DatetimeParser.cpp DatetimeParser.h
Exclusion.cpp Exclusion.h
Extensions.cpp Extensions.h
Interval.cpp Interval.h

2924
src/DatetimeParser.cpp Normal file

File diff suppressed because it is too large Load diff

148
src/DatetimeParser.h Normal file
View file

@ -0,0 +1,148 @@
////////////////////////////////////////////////////////////////////////////////
//
// Copyright 2020, 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
//
////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDED_DATETIMEPARSER
#define INCLUDED_DATETIMEPARSER
#include <string>
#include <ctime>
#include <Pig.h>
#include <Range.h>
class DatetimeParser
{
public:
DatetimeParser () = default;
Range parse_range(const std::string&);
private:
void clear ();
bool parse_named (Pig&);
bool parse_named_day (Pig&);
bool parse_named_month (Pig&);
bool parse_epoch (Pig&);
bool parse_date_time_ext (Pig&);
bool parse_date_ext (Pig&);
bool parse_off_ext (Pig&);
bool parse_time_ext (Pig&, bool terminated = true);
bool parse_time_utc_ext (Pig&);
bool parse_time_off_ext (Pig&);
bool parse_date_time (Pig&);
bool parse_date (Pig&);
bool parse_time_utc (Pig&);
bool parse_time_off (Pig&);
bool parse_time (Pig&, bool terminated = true);
bool parse_informal_time (Pig&);
bool parse_off (Pig&);
bool parse_year (Pig&, int&);
bool parse_month (Pig&, int&);
bool parse_week (Pig&, int&);
bool parse_julian (Pig&, int&);
bool parse_day (Pig&, int&);
bool parse_weekday (Pig&, int&);
bool parse_hour (Pig&, int&);
bool parse_minute (Pig&, int&);
bool parse_second (Pig&, int&);
bool parse_off_hour (Pig&, int&);
bool parse_off_minute (Pig&, int&);
bool initializeNow (Pig&);
bool initializeYesterday (Pig&);
bool initializeToday (Pig&);
bool initializeTomorrow (Pig&);
bool initializeOrdinal (Pig&);
bool initializeDayName (Pig&);
bool initializeMonthName (Pig&);
bool initializeLater (Pig&);
bool initializeSopd (Pig&);
bool initializeSod (Pig&);
bool initializeSond (Pig&);
bool initializeEopd (Pig&);
bool initializeEod (Pig&);
bool initializeEond (Pig&);
bool initializeSopw (Pig&);
bool initializeSow (Pig&);
bool initializeSonw (Pig&);
bool initializeEopw (Pig&);
bool initializeEow (Pig&);
bool initializeEonw (Pig&);
bool initializeSopww (Pig&);
bool initializeSonww (Pig&);
bool initializeSoww (Pig&);
bool initializeEopww (Pig&);
bool initializeEonww (Pig&);
bool initializeEoww (Pig&);
bool initializeSopm (Pig&);
bool initializeSom (Pig&);
bool initializeSonm (Pig&);
bool initializeEopm (Pig&);
bool initializeEom (Pig&);
bool initializeEonm (Pig&);
bool initializeSopq (Pig&);
bool initializeSoq (Pig&);
bool initializeSonq (Pig&);
bool initializeEopq (Pig&);
bool initializeEoq (Pig&);
bool initializeEonq (Pig&);
bool initializeSopy (Pig&);
bool initializeSoy (Pig&);
bool initializeSony (Pig&);
bool initializeEopy (Pig&);
bool initializeEoy (Pig&);
bool initializeEony (Pig&);
bool initializeEaster (Pig&);
bool initializeMidsommar (Pig&);
bool initializeMidsommarafton (Pig&);
bool initializeInformalTime (Pig&);
void easter (struct tm*) const;
void midsommar (struct tm*) const;
void midsommarafton (struct tm*) const;
bool initializeNthDayInMonth (const std::vector <std::string>&);
bool isOrdinal (const std::string&, int&);
bool validate ();
void resolve ();
public:
int _year {0};
int _month {0};
int _week {0};
int _weekday {0};
int _julian {0};
int _day {0};
int _seconds {0};
int _offset {0};
bool _utc {false};
time_t _date {0};
};
#endif

View file

@ -47,9 +47,6 @@ int CmdSummary (
// Create a filter, and if empty, choose 'today'.
auto filter = cli.getFilter (Range { Datetime ("today"), Datetime ("tomorrow") });
if (! filter.is_ended())
filter.end = filter.start + Duration("1d").toTime_t();
// Load the data.
auto tracked = getTracked (database, rules, filter);
@ -115,7 +112,11 @@ int CmdSummary (
// Each day is rendered separately.
time_t grand_total = 0;
Datetime previous;
for (Datetime day = filter.start; day < filter.end; day++)
auto days_start = filter.is_started() ? filter.start : tracked.front ().start;
auto days_end = filter.is_ended() ? filter.end : tracked.back ().end;
for (Datetime day = days_start; day < days_end; day++)
{
auto day_range = getFullDay (day);
time_t daily_total = 0;

View file

@ -34,7 +34,7 @@ include_directories (${CMAKE_SOURCE_DIR}
include_directories (${CMAKE_INSTALL_PREFIX}/include)
link_directories(${CMAKE_INSTALL_PREFIX}/lib)
set (test_SRCS AtomicFileTest data.t exclusion.t helper.t interval.t range.t rules.t util.t TagInfoDatabase.t)
set (test_SRCS AtomicFileTest data.t DatetimeParser.t exclusion.t helper.t interval.t range.t rules.t util.t TagInfoDatabase.t)
add_custom_target (test ./run_all --verbose
DEPENDS ${test_SRCS}

634
test/DatetimeParser.t.cpp Normal file
View file

@ -0,0 +1,634 @@
////////////////////////////////////////////////////////////////////////////////
//
// Copyright 2020, 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 <cmake.h>
#include <Datetime.h>
#include <test.h>
#include <iostream>
#include <time.h>
#include <src/DatetimeParser.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 hm = (12 * 3600) + (34 * 60); // The time 12:34:00 in seconds.
int z = 3600; // TZ offset.
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");
// Informal time.
int t8a = (8 * 3600);
int t830a = (8 * 3600) + (30 * 60);
int t8p = (20 * 3600);
int t830p = (20 * 3600) + (30 * 60);
int t12p = (12 * 3600);
int t1p = (13 * 3600);
Datetime time_now;
int adjust = (time_now.hour () > 10 || (time_now.hour () == 10 && time_now.minute () > 30)) ? 86400 : 0;
testParseOpenRange(t, "10:30am");
adjust = (time_now.hour () > 8 || (time_now.hour () == 8 && time_now.minute () > 30)) ? 86400 : 0;
testParseOpenRange(t, "8:30am");
testParseOpenRange(t, "8:30a");
testParseOpenRange(t, "8:30");
adjust = (time_now.hour () >= 8) ? 86400 : 0;
testParseOpenRange(t, "8am");
testParseOpenRange(t, "8a");
adjust = (time_now.hour () > 20 || (time_now.hour () == 20 && time_now.minute () > 30)) ? 86400 : 0;
testParseOpenRange(t, "8:30pm");
testParseOpenRange(t, "8:30p");
adjust = (time_now.hour () >= 20) ? 86400 : 0;
testParseOpenRange(t, "8pm");
testParseOpenRange(t, "8p");
adjust = (time_now.hour () >= 12) ? 86400 : 0;
testParseOpenRange(t, "12pm");
adjust = (time_now.hour () >= 13) ? 86400 : 0;
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;
}
////////////////////////////////////////////////////////////////////////////////

View file

@ -177,6 +177,46 @@ W{5} {2:%Y-%m-%d} {2:%a} @1 BAZ 10:00:00 11:00:00 1:00:00 1:00:00
""".format(yesterday, now, tomorrow,
yesterday.isocalendar()[1], now.isocalendar()[1], tomorrow.isocalendar()[1]), out)
def test_with_named_date_yesterday(self):
"""Summary should work with 'yesterday'"""
now = datetime.now()
yesterday = now - timedelta(days=1)
tomorrow = now + timedelta(days=1)
self.t("track {0:%Y-%m-%d}T10:00:00 - {0:%Y-%m-%d}T11:00:00 FOO".format(yesterday))
self.t("track {0:%Y-%m-%d}T10:00:00 - {0:%Y-%m-%d}T11:00:00 BAR".format(now))
self.t("track {0:%Y-%m-%d}T10:00:00 - {0:%Y-%m-%d}T11:00:00 BAZ".format(tomorrow))
code, out, err = self.t("summary :ids yesterday")
self.assertIn("""
Wk Date Day ID Tags Start End Time Total
--- ---------- --- -- ---- -------- -------- ------- -------
W{1} {0:%Y-%m-%d} {0:%a} @3 FOO 10:00:00 11:00:00 1:00:00 1:00:00
1:00:00
""".format(yesterday, yesterday.isocalendar()[1]), out)
def test_with_named_date_today(self):
"""Summary should work with 'today'"""
now = datetime.now()
yesterday = now - timedelta(days=1)
tomorrow = now + timedelta(days=1)
self.t("track {0:%Y-%m-%d}T10:00:00 - {0:%Y-%m-%d}T11:00:00 FOO".format(yesterday))
self.t("track {0:%Y-%m-%d}T10:00:00 - {0:%Y-%m-%d}T11:00:00 BAR".format(now))
self.t("track {0:%Y-%m-%d}T10:00:00 - {0:%Y-%m-%d}T11:00:00 BAZ".format(tomorrow))
code, out, err = self.t("summary :ids today")
self.assertIn("""
Wk Date Day ID Tags Start End Time Total
--- ---------- --- -- ---- -------- -------- ------- -------
W{1} {0:%Y-%m-%d} {0:%a} @2 BAR 10:00:00 11:00:00 1:00:00 1:00:00
1:00:00
""".format(now, now.isocalendar()[1]), out)
def test_with_day_gap(self):
"""Summary should skip days with no data"""
self.t("track 2017-03-09T10:00:00 - 2017-03-09T11:00:00")