Subject: | Add support for parsing "before" and "after" |
English date parsing isn't complete without support for parsing things like "after today". A patch to add such support is attached.
Subject: | range_parser_before_and_after.patch |
diff --git a/Date-RangeParser-EN-0.06/Changes b/Date-RangeParser-EN-0.06/Changes
index f5a614a..9ae2856 100644
--- a/Date-RangeParser-EN-0.06/Changes
+++ b/Date-RangeParser-EN-0.06/Changes
@@ -1,5 +1,15 @@
Revision history for Perl extension Date::RangeParser::EN
+0.07 Fri Apr 18 12:26:13 2014
+ - Add support for infinite datetimes
+ - Add support for parsing
+ * "before [element]"
+ * "< [element]"
+ * "<= [element]"
+ * "after [element]"
+ * "> [element]"
+ * ">= [element]"
+
0.06 Mon Nov 12 19:25:17 2012
- Fix bug where "31st of last month" in 11/2012 would not parse properly.
diff --git a/Date-RangeParser-EN-0.06/lib/Date/RangeParser/EN.pm b/Date-RangeParser-EN-0.06/lib/Date/RangeParser/EN.pm
index 4122538..43411d2 100644
--- a/Date-RangeParser-EN-0.06/lib/Date/RangeParser/EN.pm
+++ b/Date-RangeParser-EN-0.06/lib/Date/RangeParser/EN.pm
@@ -104,6 +104,13 @@ At the very least, this given class must implement a C<new> method that accepts
This gives you the freedom to set your time zones and such however you need to.
+=item * B<infinite_past_class>
+=item * B<infinite_future_class>
+
+By default, Date::RangeParser::EN uses DateTime::Infinite::Past and DateTime::Infinite::Future to create open-ended ranges (for example "after today"). If you have extended these classes, you may pass the corresponding names in.
+
+The given classes must implement a C<new> method that accepts no arguments.
+
=item * B<now_callback>
By default, Date::RangeParser::EN uses DateTime->now to determine the current date/time for calculations. If you need to work with a different time (for instance, if you need to adjust for time zones), you may pass a callback (code reference) which returns a DateTime object.
@@ -267,6 +274,20 @@ More formally, this will parse the following kinds of date strings:
RANGE-RANGE : the very start of the first range to the very end of the second
10/10-10/20 (ranges must not contain hyphens, "-")
+ before ELEMENT : all dates before the very start of the date specified in the ELEMENT
+ < ELEMENT
+ before today
+
+ <= ELEMENT : all dates up to the very end of the date specified in the ELEMENT
+ <= today
+
+ after ELEMENT : all dates after the very end of the date specified in the ELEMENT
+ > ELEMENT
+ after next Tuesday
+
+ >= ELEMENT : the date specified in the ELEMENT to the end of forever
+ >= this Friday
+
since ELEMENT : the date specified in the ELEMENT to the end of the current day
since last Sunday
@@ -647,6 +668,36 @@ sub parse_range
(undef, $end) = $self->parse_range($second);
}
+ elsif ($string =~ /^(?:before|<) /i) {
+ $string =~ s/^(?:before|<) //i;
+ ($end) = $self->parse_range($string);
+
+ if ( defined $end ) {
+ $beg = $self->_infinite_past_class->new();
+ $end = $end->subtract(seconds => 1);
+ }
+ }
+ elsif ($string =~ /^<= /) {
+ $string =~ s/^<= //;
+ $beg = $self->_infinite_past_class->new();
+ ($end) = $self->parse_range($string);
+ }
+
+ elsif ($string =~ /^(?:after|>) /i) {
+ $string =~ s/^(?:after|>) //i;
+ (undef, $beg) = $self->parse_range($string);
+
+ if ( defined $beg ) {
+ $beg = $beg->add(seconds => 1);
+ $end = $self->_infinite_future_class->new();
+ }
+ }
+ elsif ($string =~ /^>= /) {
+ $string =~ s/^>= //;
+ ($beg) = $self->parse_range($string);
+ $end = $self->_infinite_future_class->new();
+ }
+
elsif ($string =~ /^since /i) {
$string =~ s/^since //i;
($beg) = $self->parse_range($string);
@@ -655,8 +706,6 @@ sub parse_range
$end = $self->_now()->clone->set(hour => 23, minute => 59, second => 59);
}
- # TODO: Support "after [date]". This requires handling of infinite dates.
-
# See if this is a range between two other dates separated by -
elsif ($string !~ /^\d+-\d+$/ and $string =~ /^[^-]+-[^-]+$/)
{
@@ -707,6 +756,16 @@ sub _datetime_class {
return $self->{datetime_class} || 'DateTime';
}
+sub _infinite_future_class {
+ my $self = shift;
+ return $self->{infinite_future_class} || 'DateTime::Infinite::Future';
+}
+
+sub _infinite_past_class {
+ my $self = shift;
+ return $self->{infinite_past_class} || 'DateTime::Infinite::Past';
+}
+
sub _parse_date_manip
{
my ($self, $val) = @_;
@@ -810,7 +869,7 @@ Thanks to Sterling Hanenkamp for adding support for explicit date ranges, improv
=head1 COPYRIGHT AND LICENSE
-Copyright (C) 2012 Grant Street Group.
+Copyright (C) 2012-2014 Grant Street Group.
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
diff --git a/Date-RangeParser-EN-0.06/t/01-parse_range.t b/Date-RangeParser-EN-0.06/t/01-parse_range.t
index 0adb817..fe0318d 100644
--- a/Date-RangeParser-EN-0.06/t/01-parse_range.t
+++ b/Date-RangeParser-EN-0.06/t/01-parse_range.t
@@ -390,6 +390,67 @@ my @tests = (
as_of => '2012-11-12',
beg => '12/31/2012 12:00AM',
end => '12/31/2012 11:59PM',
+ }, {
+ date_range_string => 'before 4/18/2014',
+ as_of => '2014-04-18',
+ beg => '-inf',
+ end => '04/17/2014 11:59PM',
+ }, {
+ date_range_string => '< 4/18/2014',
+ as_of => '2010-01-28',
+ beg => '-inf',
+ end => '04/17/2014 11:59PM',
+ }, {
+ date_range_string => '<= 12/25/2013',
+ as_of => '2014-04-18',
+ beg => '-inf',
+ end => '12/25/2013 12:00AM',
+ }, {
+ date_range_string => 'after 2/5/1990',
+ as_of => '2014-04-18',
+ beg => '02/06/1990 12:00AM',
+ end => 'inf',
+ }, {
+ date_range_string => '> 2/5/1990',
+ as_of => '2010-01-28',
+ beg => '02/06/1990 12:00AM',
+ end => 'inf',
+ }, {
+ date_range_string => '>= 07/04/2776',
+ as_of => '1776-07-04',
+ beg => '07/04/2776 12:00AM',
+ end => 'inf',
+ }, {
+ # "after X" means "after the end of X"
+ # "after today" extends into infinity (it doesn't end)
+ # thus "after after today" means "after the end of infinity"
+ # and begins in infinity :)
+ date_range_string => 'after after today',
+ as_of => '2000-01-01',
+ beg => 'inf',
+ end => 'inf',
+ }, {
+ date_range_string => 'before before today',
+ as_of => '2000-01-01',
+ beg => '-inf',
+ end => '-inf',
+ }, {
+ date_range_string => 'before after today',
+ as_of => '2000-01-01',
+ beg => '-inf',
+ end => '01/01/2000 11:59PM',
+ }, {
+ # "since" truncates ->{end} to the end of the as_of date
+ date_range_string => 'since before 01/01/3000',
+ as_of => '2000-01-01',
+ beg => '-inf',
+ end => '01/01/2000 11:59PM',
+ }, {
+ # this isn't really a range, but that's expected
+ date_range_string => 'after after today - before before today',
+ as_of => '2000-01-01',
+ beg => 'inf',
+ end => '-inf',
}
);
@@ -414,8 +475,19 @@ for my $test (@tests)
ok(defined $beg, "Beginning date for $test->{date_range_string} is defined");
ok(defined $end, "End date for $test->{date_range_string} is defined");
- cmp_ok($beg->strftime("%m/%d/%Y %I:%M%p"), 'eq', $test->{beg}, "Beginning date ok for $test->{date_range_string}");
- cmp_ok($end->strftime("%m/%d/%Y %I:%M%p"), 'eq', $test->{end}, "Ending date ok for $test->{date_range_string}");
+ # stftime makes no sense on infinite times
+ if ( $beg->is_infinite ) {
+ cmp_ok( "$beg", 'eq', $test->{beg}, "Beginning date ok for $test->{date_range_string}");
+ }
+ else {
+ cmp_ok($beg->strftime("%m/%d/%Y %I:%M%p"), 'eq', $test->{beg}, "Beginning date ok for $test->{date_range_string}");
+ }
+ if ( $end->is_infinite ) {
+ cmp_ok( "$end", 'eq', $test->{end}, "Beginning date ok for $test->{date_range_string}");
+ }
+ else {
+ cmp_ok($end->strftime("%m/%d/%Y %I:%M%p"), 'eq', $test->{end}, "Ending date ok for $test->{date_range_string}");
+ }
};
}