App-JobLog
view release on metacpan or search on metacpan
lib/App/JobLog/TimeGrammar.pm view on Meta::CPAN
}
}
for ( $h->{modifier} || '' ) {
when (/beg/) { $h->{modifier} = 'beginning' }
when (/end/) { $h->{modifier} = 'end' }
when (/las/) { $h->{modifier} = 'last' }
when (/thi/) { $h->{modifier} = 'this' }
when (/nex/) { $h->{modifier} = 'next' }
}
}
}
# whether the particular date expression refers to a fixed
# rather than relative date
sub is_fixed {
my $h = shift;
return 1
if exists $h->{year};
if ( $h->{type} eq 'verbal' ) {
if ( exists $h->{modifier} ) {
return 1 if $h->{modifier} =~ /this|last|next/;
}
if ( exists $h->{day} ) {
return 1 if $h->{day} =~ /yes|tod|tom/;
}
}
return 0;
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
App::JobLog::TimeGrammar - parse natural (English) language time expressions
=head1 VERSION
version 1.042
=head1 SYNOPSIS
#!/usr/bin/perl
use Modern::Perl;
use DateTime;
use App::JobLog::Time qw(tz);
use App::JobLog::TimeGrammar qw(parse);
# for demonstration purposes we modify "today"
$App::JobLog::Time::today =
DateTime->new( year => 2011, month => 2, day => 17, time_zone => tz );
for my $phrase ( 'Monday until the end of the week', 'Tuesday at 9:00 p.m.' ) {
my ( $start, $end, $endpoints ) = parse($phrase);
say $phrase;
say "$start - $end; both endpoints specified? "
. ( $endpoints ? 'yes' : 'no' );
}
produces
Monday until the end of the week
2011-02-14T00:00:00 - 2011-02-20T23:59:59; both endpoints specified? yes
Tuesday at 9:00 p.m.
2011-02-08T21:00:00 - 2011-02-15T23:59:59; both endpoints specified? no
=head1 DESCRIPTION
C<App::JobLog::TimeGrammar> converts natural language time expressions into pairs of
C<DateTime> objects representing intervals. This requires disambiguating ambiguous
terms such as 'yesterday', whose interpretation varies from day to day, and 'Friday', whose
interpretation must be fixed by some frame of reference. The heuristic used by this code
is to look first for a fixed date, either a fully specified date such as 2011/2/17 or
one fixed relative to the current moment such as 'now'. If such a date is present in the time
expression it determines the context for the other date, if it is present. Otherwise
it is assumed that the closest appropriate pair of dates immediately before the current
moment are intended.
Given a pair consisting of fixed and an ambiguous date, we assume the ambiguous date has the
sense such that it is ordered correctly relative to the fixed date and the interval between
them is minimized.
If the time expression provides no time of day, such as 8:00, it is assumed that the first moment
intended is the first second of the first day and the last moment is the last second of the second
day. If no second date is provided the endpoint of the interval will be the last moment of the single
date specified. If a larger time period such as week, month, or year is specified, e.g., 'last week', the
first moment is the first second in the period and the last moment is the last second.
If you wish to parse a single date, not an interval, you can ignore the second date, though you should
check the third value returned by C<parse>, whether an interval was parsed.
C<parse> will croak if it cannot parse the expression given.
=head2 Time Grammar
The following is a semi-formal BNF grammar of time understood by C<App::JobLog::TimeGrammar>. In this
formalization C<s> represents whitespace, C<d> represents a digit, and C<\\n> represents a back reference
to the nth item in parenthesis in the given rule. After the first three rules the rules are alphabetized
to facilitate finding them.
<expression> = s* ( <ever> | <span> ) s*
<ever> = "all" | "always" | "ever" | [ [ "the" s ] ( "entire" | "whole" ) s ] "log"
<span> = <date> [ <span_divider> <date> ]
<at> = "at" | "@"
<at_time> = [ ( s | s* <at> s* ) <time> ]
<at_time_on> = [ <at> s ] <time> s "on" s
<beginning> = "beg" [ "in" [ "ning" ] ]
<date> = <numeric> | <verbal>
<day_first> = d{1,2} s <month>
<divider> = "-" | "/" | "."
<dm_full> = d{1,2} s <month> [ "," ] s d{4}
<dom> = d{1,2}
<full> = <at_time_on> <full_no_time> | <full_no_time> <at_time>
<full_month> = "january" | "february" | "march" | "april" | "may" | "june" | "july" | "august" | "september" | "october" | "november" | "december"
<full_no_time> = <dm_full> | <md_full>
( run in 0.475 second using v1.01-cache-2.11-cpan-d7a12ab2c7f )