Merge pull request #614 from duckduckgo/mintsoft/workdays_between

Extending Dates Role's Vague String handling & Fixing #346
master
Zaahir Moolla 2014-09-04 16:40:42 -04:00
commit 859f7e776d
11 changed files with 182 additions and 93 deletions

View File

@ -28,38 +28,29 @@ my @weekDays = ("S", "M", "T", "W", "T", "F", "S");
# read in css-file only once
my $css = share("style.css")->slurp;
my $month_regex = month_regex();
my $date_regex = date_regex();
my $datestring_regex = datestring_regex();
my $formatted_datestring_regex = formatted_datestring_regex();
handle remainder => sub {
my $query = $_;
my $date_object = DateTime->now;
my ($currentDay, $currentMonth, $currentYear) = ($date_object->day(), $date_object->month(), $date_object->year());
my $highlightDay = 0; # Initialized, but won't match, by default.
my $highlightDay = 0; # Initialized, but won't match, by default.
if ($query) {
my ($date_string, $other_format) = $query =~ qr#($date_regex)|((?:(?:next|last) )?$month_regex(?: [0-9]{4})?)#i;
if ($date_string) {
$date_object = parse_string_to_date($date_string);
my ($date_string) = $query =~ qr#($datestring_regex)#i; # Extract any datestring from the query.
return unless $date_object;
$highlightDay = $date_object->day();
} elsif ($other_format) {
$date_object = parse_vague_string_to_date($other_format);
$date_object = parse_datestring_to_date($date_string);
return unless $date_object;
# highlight today if current month is given
if (($date_object->year() eq $currentYear) && ($date_object->month() eq $currentMonth)) {
$highlightDay = $currentDay;
}
}
} else {
$highlightDay = $currentDay;
return unless $date_object;
$highlightDay = $date_object->day() if ($query =~ $formatted_datestring_regex); # They specified a date, so highlight.
}
# Highlight today if it's this month and no other day was chosen.
$highlightDay ||= $currentDay if (($date_object->year() eq $currentYear) && ($date_object->month() eq $currentMonth));
my $the_year = $date_object->year();
my $the_month = $date_object->month();
# return calendar
my $start = parse_string_to_date($the_year . "-" . $the_month . "-1");
my $start = parse_datestring_to_date($the_year . "-" . $the_month . "-1");
return format_result({
first_day => $start,
first_day_num => $start->day_of_week() % 7, # 0=Sunday

View File

@ -20,14 +20,14 @@ category 'time_sensitive';
topics 'everyday';
attribution github => ['http://github.com/cj01101', 'cj01101'];
my $date_regex = date_regex();
my $datestring_regex = datestring_regex();
handle query_lc => sub {
my $query = $_;
return unless $query =~ qr!^($date_regex)\s+(plus|\+|\-|minus)\s+(\d+|[a-z\s-]+)\s+((?:day|week|month|year)s?)$!;
return unless $query =~ qr!^($datestring_regex)\s+(plus|\+|\-|minus)\s+(\d+|[a-z\s-]+)\s+((?:day|week|month|year)s?)$!;
my ($input_date, $input_action, $input_number, $unit) = ($1, $2, $3, $4);
$input_date = parse_string_to_date($input_date);
$input_date = parse_datestring_to_date($input_date);
$input_number = str2nbr($input_number);
# check/tweak other (non-date) input

View File

@ -18,12 +18,12 @@ category 'calculations';
topics 'everyday';
attribution github => ['http://github.com/JetFault', 'JetFault'];
my $date_regex = date_regex();
my $datestring_regex = datestring_regex();
handle remainder => sub {
return unless $_ =~ qr/^($date_regex) (?:(?:and|to) )?($date_regex)(?:[,]? inclusive)?$/i;
return unless $_ =~ qr/^($datestring_regex) (?:(?:and|to) )?($datestring_regex)(?:[,]? inclusive)?$/i;
my ($date1, $date2) = parse_all_strings_to_date($1, $2);
my ($date1, $date2) = parse_all_datestrings_to_date($1, $2);
return unless ($date1 && $date2);
($date1, $date2) = ($date2, $date1) if ( DateTime->compare($date1, $date2) == 1 );

View File

@ -31,11 +31,11 @@ sub html_output {
}
my $date_regex = date_regex();
my $datestring_regex = datestring_regex();
handle remainder => sub {
return unless $_ =~ qr/^($date_regex) (?:(?:and|to) )?($date_regex)/i;
my ($start, $end) = (parse_string_to_date($1), parse_string_to_date($2));
return unless $_ =~ qr/^($datestring_regex) (?:(?:and|to) )?($datestring_regex)/i;
my ($start, $end) = (parse_datestring_to_date($1), parse_datestring_to_date($2));
return unless ($start && $end);
# Flip if the dates are the wrong way around

View File

@ -22,16 +22,17 @@ category 'calculations';
topics 'everyday';
attribution github => ['http://github.com/mgarriott', 'mgarriott'];
my $date_regex = date_regex();
my $datestring_regex = datestring_regex();
handle remainder => sub {
my $query = $_;
return unless $query =~ qr/($date_regex) (?:(?:and|to) )?($date_regex)/i;
my ($start_date, $end_date) = parse_all_strings_to_date($1, $2);
return unless ($start_date && $end_date);
($start_date, $end_date) = ($end_date, $start_date) if ( DateTime->compare($start_date, $end_date) == 1 );
return unless ($query =~ qr/($datestring_regex) (?:(?:and|to) )?($datestring_regex)/i);
my ($start_date, $end_date) = parse_all_datestrings_to_date($1, $2);
return unless ($start_date && $end_date);
($start_date, $end_date) = ($end_date, $start_date) if (DateTime->compare($start_date, $end_date) == 1);
my $calendar = Date::Calendar->new($Profiles->{US});
my $workdays = $calendar->delta_workdays($start_date->year(), $start_date->month(), $start_date->day(), $end_date->year(), $end_date->month(), $end_date->day(), 1, 1);

View File

@ -3,7 +3,6 @@ package DDG::GoodieRole::Dates;
use strict;
use warnings;
use feature 'state';
use Moo::Role;
@ -21,6 +20,7 @@ my %full_month_to_short = map { lc $_ => substr($_, 0, 3) } qw(January February
my %short_month_fix = map { lc $_ => $_ } (values %full_month_to_short);
my $short_month = qr#Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec#i;
my $full_month = qr#January|February|March|April|May|June|July|August|September|October|November|December#i;
my $month_regex = qr#$full_month|$short_month#;
my $time_24h = qr#(?:(?:[0-1][0-9])|(?:2[0-3]))[:]?[0-5][0-9][:]?[0-5][0-9]#i;
my $time_12h = qr#(?:(?:0[1-9])|(?:1[012])):[0-5][0-9]:[0-5][0-9]\s?(?:am|pm)#i;
my $date_number = qr#[0-3]?[0-9]#;
@ -36,6 +36,28 @@ my $number_suffixes = qr#(?:st|nd|rd|th)#i;
# Timezones: https://en.wikipedia.org/wiki/List_of_time_zone_abbreviations
my $tz_suffixes = qr#(?:[+-][0-9]{4})|ACDT|ACST|ACT|ADT|AEDT|AEST|AFT|AKDT|AKST|AMST|AMT|ART|AST|AWDT|AWST|AZOST|AZT|BDT|BIOT|BIT|BOT|BRT|BST|BTT|CAT|CCT|CDT|CEDT|CEST|CET|CHADT|CHAST|CHOT|CHUT|CIST|CIT|CKT|CLST|CLT|COST|COT|CST|CT|CVT|CWST|CXT|ChST|DAVT|DDUT|DFT|EASST|EAST|EAT|ECT|EDT|EEDT|EEST|EET|EGST|EGT|EIT|EST|FET|FJT|FKST|FKT|FNT|GALT|GAMT|GET|GFT|GILT|GIT|GMT|GST|GYT|HADT|HAEC|HAST|HKT|HMT|HOVT|HST|ICT|IDT|IOT|IRDT|IRKT|IRST|IST|JST|KGT|KOST|KRAT|KST|LHST|LINT|MAGT|MART|MAWT|MDT|MEST|MET|MHT|MIST|MIT|MMT|MSK|MST|MUT|MVT|MYT|NCT|NDT|NFT|NPT|NST|NT|NUT|NZDT|NZST|OMST|ORAT|PDT|PET|PETT|PGT|PHOT|PHT|PKT|PMDT|PMST|PONT|PST|PYST|PYT|RET|ROTT|SAKT|SAMT|SAST|SBT|SCT|SGT|SLST|SRT|SST|SYOT|TAHT|TFT|THA|TJT|TKT|TLT|TMT|TOT|TVT|UCT|ULAT|UTC|UYST|UYT|UZT|VET|VLAT|VOLT|VOST|VUT|WAKT|WAST|WAT|WEDT|WEST|WET|WIT|WST|YAKT|YEKT|Z#i;
# formats parsed by vague datestring, without colouring
# the context of the code using it
my $descriptive_datestring = qr{
(?:(?:next|last)\s(?:$month_regex)) | # next June, last jan
(?:(?:$month_regex)\s(?:[0-9]{4})) | # Jan 2014, August 2000
(?:(?:$date_number)\s?$number_suffixes?\s(?:$month_regex)) | # 18th Jan, 01 October
(?:(?:$month_regex)\s(?:$date_number)\s?$number_suffixes?) | # Dec 25, July 4th
(?:$month_regex) | # February, Aug
}ix;
# Used for parse_descriptive_datestring_to_date
my $descriptive_datestring_matches = qr#
(?:(?<q>next|last)\s(?<m>$month_regex)) |
(?:(?<m>$month_regex)\s(?<y>[0-9]{4})) |
(?:(?<d>$date_number)\s?$number_suffixes?\s(?<m>$month_regex)) |
(?:(?<m>$month_regex)\s(?<d>$date_number)\s?$number_suffixes?) |
(?<m>$month_regex)
#ix;
my $formatted_datestring = build_datestring_regex();
# Accessors for useful regexes
sub full_month_regex {
return $full_month;
@ -44,7 +66,7 @@ sub short_month_regex {
return $short_month;
}
sub month_regex {
return qr/$full_month|$short_month/;
return $month_regex;
}
sub full_day_of_week_regex {
return $full_day_of_week;
@ -53,9 +75,23 @@ sub short_day_of_week_regex {
return $short_day_of_week;
}
# Accessors for matching regexes
# These matches are for "in the right format"/"looks about right"
# not "are valid dates"; expects normalised whitespace
sub date_regex {
sub datestring_regex {
return qr#$formatted_datestring|$descriptive_datestring#i;
}
sub descriptive_datestring_regex {
return $descriptive_datestring;
}
sub formatted_datestring_regex {
return $formatted_datestring;
}
# Called once to build $formatted_datestring
sub build_datestring_regex {
my @regexes = ();
## unambigous and awesome date formats:
@ -81,16 +117,23 @@ sub date_regex {
## Ambiguous, but potentially valid date formats
push @regexes, $ambiguous_dates;
state $returned_regex = join '|', @regexes;
my $returned_regex = join '|', @regexes;
return qr/$returned_regex/i;
}
# Accepts a string which looks like date per the supplied date_regex (e.g. '31/10/1980')
# Returns a DateTime object representing that date or `undef` if the string cannot be parsed.
sub parse_string_to_date {
# Parses any string that *can* be parsed to a date object
sub parse_datestring_to_date {
my ($d) = @_;
return unless ($d =~ date_regex()); # Only handle white-listed strings, even if they might otherwise work.
return parse_formatted_datestring_to_date($d) || parse_descriptive_datestring_to_date($d);
}
# Accepts a string which looks like date per the supplied datestring_regex (e.g. '31/10/1980')
# Returns a DateTime object representing that date or `undef` if the string cannot be parsed.
sub parse_formatted_datestring_to_date {
my ($d) = @_;
return unless ($d =~ qr/^$formatted_datestring$/); # Only handle white-listed strings, even if they might otherwise work.
if ($d =~ $ambiguous_dates_matches) {
# guesswork for ambigous DMY/MDY and switch to ISO
my ($month, $day, $year) = ($+{'m'}, $+{'d'}, $+{'y'}); # Assume MDY, even though it's crazy, for backward compatibility
@ -117,7 +160,7 @@ sub parse_string_to_date {
# parses multiple dates and guesses the consistent format over the set;
# i.e. defaults to m/d/y unless one of them is obviously d/m/y then it'll
# treat them all as d/m/y
sub parse_all_strings_to_date {
sub parse_all_datestrings_to_date {
my @dates = @_;
# If there is an ambiguous date with a "month" over 12 in the set, we need to flip.
@ -131,7 +174,7 @@ sub parse_all_strings_to_date {
return if $month > 12; #there's a mish-mash of formats; give up
$date = "$year-$month-$day";
}
my $date_object = parse_string_to_date($date);
my $date_object = parse_datestring_to_date($date);
return unless $date_object;
push @dates_to_return, $date_object;
}
@ -139,6 +182,40 @@ sub parse_all_strings_to_date {
return @dates_to_return;
}
# Parses a really vague description and basically guesses
sub parse_descriptive_datestring_to_date {
my ($string) = @_;
return unless ($string =~ qr/^$descriptive_datestring_matches$/);
my $now = DateTime->now();
my $month = $+{'m'}; # Set in each alternative match.
if (my $day = $+{'d'}) {
return parse_datestring_to_date("$day $month ".$now->year());
}
elsif (my $relative_dir = $+{'q'}) {
my $tmp_date = parse_datestring_to_date("01 $month ".$now->year());
# next <month>
$tmp_date->add( years => 1) if ($relative_dir eq "next" && DateTime->compare($tmp_date, $now) != 1);
# last <month>
$tmp_date->add( years => -1) if ($relative_dir eq "last" && DateTime->compare($tmp_date, $now) != -1);
return $tmp_date;
}
elsif (my $year = $+{'y'}) {
# Month and year is the first of that month.
return parse_datestring_to_date("01 $month $year");
}
else {
# single named months
# "january" in january means the current month
# otherwise it always means the coming month of that name, be it this year or next year
return parse_datestring_to_date("01 ".$now->month()." ".$now->year()) if lc($now->month_name()) eq lc($month);
my $this_years_month = parse_datestring_to_date("01 $month ".$now->year());
$this_years_month->add( years => 1 ) if (DateTime->compare($this_years_month, $now) == -1);
return $this_years_month;
}
}
# Takes a DateTime object (or a string which can be parsed into one)
# and returns a standard formatted output string or an empty string if it cannot be parsed.
@ -149,42 +226,11 @@ sub date_output_string {
my $string = ''; # By default we've got nothing.
# They didn't give us a DateTime object, let's try to make one from whatever we got.
$dt = parse_string_to_date($dt) if (ref($dt) !~ /DateTime/);
$dt = parse_datestring_to_date($dt) if (ref($dt) !~ /DateTime/);
$string = $dt->strftime($ddg_format) if ($dt);
return $string;
}
# Parses a really vague description and basically guesses
sub parse_vague_string_to_date {
my ($string) = @_;
if($string =~ qr#(?:(?<q>next|last)\s(?<m>$full_month|$short_month))|(?:(?<m>$full_month|$short_month)\s(?<y>[0-9]{4}))|(?<m>$full_month|$short_month)#i) {
my $now = DateTime->now();
my $month = $+{'m'}; # Set in each alternative match.
if (my $relative_dir = $+{'q'}) {
my $tmp_date = parse_string_to_date("01 $month ".$now->year());
# next <month>
$tmp_date->add( years => 1) if ($relative_dir eq "next" && DateTime->compare($tmp_date, $now) != 1);
# last <month>
$tmp_date->add( years => -1) if ($relative_dir eq "last" && DateTime->compare($tmp_date, $now) != -1);
return $tmp_date;
}
elsif (my $year = $+{'y'}) {
# Month and year is the first of that month.
return parse_string_to_date("01 $month $year");
}
else {
# single named months
# "january" in january means the current month
# otherwise it always means the coming month of that name, be it this year or next year
return parse_string_to_date("01 ".$now->month()." ".$now->year()) if lc($now->month_name()) eq lc($month);
my $this_years_month = parse_string_to_date("01 $month ".$now->year());
$this_years_month->add( years => 1 ) if (DateTime->compare($this_years_month, $now) == -1);
return $this_years_month;
}
}
return;
}
1;

View File

@ -58,12 +58,18 @@ subtest 'Dates' => sub {
{ package RoleTester; use Moo; with 'DDG::GoodieRole::Dates'; 1; }
my $test_regex;
my $test_datestring_regex;
my $test_formatted_datestring_regex;
my $test_descriptive_datestring_regex;
subtest 'Initialization' => sub {
new_ok('RoleTester', [], 'Applied to a class');
$test_regex = RoleTester::date_regex();
isa_ok($test_regex, 'Regexp', 'date_regex()');
$test_datestring_regex = RoleTester::datestring_regex();
isa_ok($test_datestring_regex, 'Regexp', 'datestring_regex()');
$test_formatted_datestring_regex = RoleTester::formatted_datestring_regex();
isa_ok($test_formatted_datestring_regex, 'Regexp', 'formatted_datestring_regex()');
$test_descriptive_datestring_regex = RoleTester::descriptive_datestring_regex();
isa_ok($test_descriptive_datestring_regex, 'Regexp', 'descriptive_datestring_regex()');
};
subtest 'Working single dates' => sub {
@ -112,13 +118,17 @@ subtest 'Dates' => sub {
);
foreach my $test_date (sort keys %dates_to_match) {
like($test_date, qr/^$test_regex$/, "$test_date matches the date_regex");
like($test_date, qr/^$test_datestring_regex$/, "$test_date matches the datestring_regex");
like($test_date, qr/^$test_formatted_datestring_regex$/, "$test_date matches the formatted_datestring_regex");
# test_regex should not contain any submatches
$test_date =~ qr/^$test_regex$/;
$test_date =~ qr/^$test_datestring_regex$/;
ok(scalar @- == 1 && scalar @+ == 1, ' with no sub-captures.');
my $date_object = RoleTester::parse_string_to_date($test_date);
$test_formatted_datestring_regex =~ qr/^$test_datestring_regex$/;
ok(scalar @- == 1 && scalar @+ == 1, ' with no sub-captures.');
my $date_object = RoleTester::parse_formatted_datestring_to_date($test_date);
isa_ok($date_object, 'DateTime', $test_date);
is($date_object->epoch, $dates_to_match{$test_date}, '... which represents the correct time.');
}
@ -169,7 +179,7 @@ subtest 'Dates' => sub {
foreach my $set (@date_sets) {
my @source = @{$set->{src}};
eq_or_diff([map { $_->epoch } (RoleTester::parse_all_strings_to_date(@source))],
eq_or_diff([map { $_->epoch } (RoleTester::parse_all_datestrings_to_date(@source))],
$set->{output}, '"' . join(', ', @source) . '": dates parsed correctly');
}
};
@ -188,13 +198,13 @@ subtest 'Dates' => sub {
foreach my $test_string (sort keys %bad_strings_match) {
if ($bad_strings_match{$test_string}) {
like($test_string, qr/^$test_regex$/, "$test_string matches date_regex");
like($test_string, qr/^$test_formatted_datestring_regex$/, "$test_string matches formatted_datestring_regex");
} else {
unlike($test_string, qr/^$test_regex$/, "$test_string does not match date_regex");
unlike($test_string, qr/^$test_formatted_datestring_regex$/, "$test_string does not match formatted_datestring_regex");
}
my $result;
lives_ok { $result = RoleTester::parse_string_to_date($test_string) } '... and does not kill the parser.';
lives_ok { $result = RoleTester::parse_formatted_datestring_to_date($test_string) } '... and does not kill the parser.';
is($result, undef, '... and returns undef to signal failure.');
}
};
@ -211,7 +221,7 @@ subtest 'Dates' => sub {
foreach my $set (@invalid_date_sets) {
my @source = @$set;
my @date_results = RoleTester::parse_all_strings_to_date(@source);
my @date_results = RoleTester::parse_all_datestrings_to_date(@source);
is(@date_results, 0, '"' . join(', ', @source) . '": cannot be parsed in combination.');
}
};
@ -264,18 +274,55 @@ subtest 'Dates' => sub {
'last jan' => '01 Jan 2015',
'feb 2038' => '01 Feb 2038',
},
'2000-01-01T00:00:00Z' => {
'feb 21st' => '21 Feb 2000',
'11th feb' => '11 Feb 2000',
'march 13' => '13 Mar 2000',
'12 march' => '12 Mar 2000',
}
);
foreach my $query_time (sort keys %time_strings) {
set_fixed_time($query_time);
my %strings = %{$time_strings{$query_time}};
foreach my $test_date (sort keys %strings) {
my $result = RoleTester::parse_vague_string_to_date($test_date);
my $result = RoleTester::parse_descriptive_datestring_to_date($test_date);
isa_ok($result, 'DateTime', $test_date);
is(RoleTester::date_output_string($result), $strings{$test_date}, $test_date . ' relative to ' . $query_time);
}
}
restore_time();
};
subtest 'Valid mixture of formatted and descriptive dates' => sub {
set_fixed_time('2000-01-01T00:00:00Z');
my %mixed_dates_to_test = (
'2014-11-27' => 1417046400,
'1994-02-03T14:15:29' => 760284929,
'Sat, 09 Aug 2014 18:20:00' => 1407608400,
'08-Feb-94 14:15:29 GMT' => 760716929,
'13/12/2011' => 1323734400,
'01/01/2001' => 978307200,
'29 June 2014' => 1404000000,
'05 Mar 1990' => 636595200,
'June 01 2012' => 1338508800,
'May 05 2011' => 1304553600,
'February 21st' => 951091200,
'11th feb' => 950227200,
'11 march' => 952732800,
'11 mar' => 952732800,
'jun 21' => 961545600,
'next january' => 978307200,
'december' => 975628800,
);
foreach my $test_mixed_date (sort keys %mixed_dates_to_test) {
my $parsed_date_object = RoleTester::parse_datestring_to_date($test_mixed_date);
isa_ok($parsed_date_object, 'DateTime', $test_mixed_date);
is($parsed_date_object->epoch, $mixed_dates_to_test{$test_mixed_date}, ' ... represents the correct time.');
}
restore_time();
}
};
done_testing;

View File

@ -14,6 +14,7 @@ ddg_goodie_test(
)],
'calendar' => test_zci(qr/\nS M T W T F S[ ]+[A-Za-z]+ [0-9]{4}\n.+/, html => qr#<table class="calendar".+calendar__today.+</table>#),
'calendar november' => test_zci(qr/\nS M T W T F S November [0-9]{4}\n.+/, html => qr#<table class="calendar".+</table>#),
'calendar november 12th' => test_zci(qr/\nS M T W T F S November [0-9]{4}\n.+/, html => qr#<table class="calendar".+</table>#),
'calendar last november' => test_zci(qr/\nS M T W T F S November [0-9]{4}\n.+/, html => qr#<table class="calendar".+</table>#),
'calendar next november' => test_zci(qr/\nS M T W T F S November [0-9]{4}\n.+/, html => qr#<table class="calendar".+</table>#),
'calendar november 2009' => test_zci("

View File

@ -8,7 +8,6 @@ use DDG::Test::Goodie;
zci answer_type => 'date_math';
zci is_cached => 1;
my $year = 1900 + ( localtime() )[5];
ddg_goodie_test(
[qw(
DDG::Goodie::DateMath
@ -17,9 +16,8 @@ ddg_goodie_test(
'January 1 2012 plus 32 days' => test_zci( '01 Jan 2012 plus 32 days is 02 Feb 2012' ),
'January 1, 2012 plus 32 days' => test_zci( '01 Jan 2012 plus 32 days is 02 Feb 2012' ),
'January 1st 2012 plus 32 days' => test_zci( '01 Jan 2012 plus 32 days is 02 Feb 2012' ),
# 'January 1st plus 32 days' => test_zci( "January 1st $year plus 32 days is 2/2/$year" ),
'January 1st plus 32 days' => test_zci( qr/01 Jan [0-9]{4} plus 32 days is 02 Feb [0-9]{4}/ ),
'1/1/2012 plus 32 days' => test_zci( '01 Jan 2012 plus 32 days is 02 Feb 2012' ),
# '1/1 plus 32 days' => test_zci( "1/1/$year plus 32 days is 2/2/$year" ),
'1/1/2012 plus 5 weeks' => test_zci( '01 Jan 2012 plus 5 weeks is 01 Jan 2012' ),
'1/1/2012 plus 5 months' => test_zci( '01 Jan 2012 plus 5 months is 01 Jun 2012' ),
'1/1/2012 PLUS 5 years' => test_zci( '01 Jan 2012 plus 5 years is 01 Jan 2017' ),
@ -27,8 +25,6 @@ ddg_goodie_test(
'1/1/2012 plus 1 days' => test_zci( '01 Jan 2012 plus 1 day is 02 Jan 2012' ),
'01/01/2012 + 1 day' => test_zci( '01 Jan 2012 + 1 day is 02 Jan 2012' ),
'1/1/2012 minus ten days' => test_zci( '01 Jan 2012 minus 10 days is 22 Dec 2011' ),
# 'January First plus ten days' => test_zci( "January 1 $year plus 10 days is 1/11/$year" ),
# 'January first minus ten days' => test_zci('January 1 2014 minus 10 days is 12/22/2013'),
);
done_testing;

View File

@ -21,6 +21,8 @@ ddg_goodie_test(
'days between January 31st, 2000 and 31-Jan-2001 inclusive' => test_zci('There are 367 days between 31 Jan 2000 and 31 Jan 2001, inclusive.'),
'days between jan 1 2012 and jan 1 123456' => undef,
'days between jan 1 2012 and jan 1 1234' => test_zci("There are 284158 days between 01 Jan 1234 and 01 Jan 2012."),
'days between jan 1 and jan 15 inclusive' => test_zci(qr/^There are 15 days between.+inclusive\.$/),
'days between jan 1 and 15th feb' => test_zci(qr/^There are 45 days between.+and 15 Feb [0-9]{4}\.$/),
);
done_testing;

View File

@ -160,6 +160,11 @@ ddg_goodie_test(
# 'workdays between jan 3, 14 to jan 6, 2014 inclusive' =>
# test_zci("There are 2 workdays between 03 Jan 2014 and 06 Jan 2014."),
'business days between jan 10 and jan 20' =>
test_zci(qr"There are [1-9] workdays between 10 Jan [0-9]{4} and 20 Jan [0-9]{4}\."),
'business days between january and february' =>
test_zci(qr"There are [1-9][0-9] workdays between 01 Jan [0-9]{4} and 01 Feb [0-9]{4}\."),
# Invalid input
'workdays between 01/2014 01/2015' => undef,