File Coverage

blib/lib/DateTime/Format/GnuAt.pm
Criterion Covered Total %
statement 91 133 68.4
branch 47 76 61.8
condition 11 20 55.0
subroutine 13 13 100.0
pod 2 2 100.0
total 164 244 67.2


line stmt bran cond sub pod time code
1             package DateTime::Format::GnuAt;
2              
3             our $VERSION = '0.03';
4              
5 1     1   20598 use strict;
  1         3  
  1         38  
6 1     1   6 use warnings;
  1         2  
  1         32  
7 1     1   6 use Carp;
  1         7  
  1         98  
8 1     1   1440 use DateTime;
  1         190338  
  1         2722  
9              
10             my @periods = qw(minute min hour day week month year);
11             my $period_re = join '|', @periods;
12             $period_re = qr/(?:$period_re)/i;
13              
14             sub _period {
15 16     16   34 my $period = lc shift;
16 16 100       473 return ($period eq 'min' ? 'minutes' : $period.'s');
17             }
18              
19             my (%month, %wday);
20             my @months = qw(january february march april may june july august
21             september october november december);
22             @month{map substr($_, 0, 3), @months} = (1..12);
23              
24             my @wdays = qw(monday tuesday wednesday thursday friday saturday sunday);
25             @wday{map substr($_, 0, 3), @wdays} = (1..7);
26              
27             sub _make_alternation_re {
28 19 100       71 my $re = join '|',
29             map {
30 2     2   10 substr($_, 0, 3) . (length > 3 ? '(?:' . substr($_, 3) . ')?' : '')
31             } @_;
32 2         237 return qr/(?:$re)\b/i;
33             }
34              
35             my $month_re = _make_alternation_re(@months);
36             my $wday_re = _make_alternation_re(@wdays );
37              
38             sub new {
39 1     1 1 17 my $class = shift;
40 1         4 my $self = {};
41 1         6 bless $self, $class;
42             }
43              
44             sub _reset {
45 29     29   37 my ($self, $opts) = @_;
46 29         41 %{$self} = ();
  29         205  
47 29         61 my $now = delete $opts->{now};
48 29 50       122 $self->{now} = (defined $now ? $now->clone : DateTime->now(time_zone => 'local'));
49 29         378 $self->{now}->set(second => 0);
50             }
51              
52             sub parse_datetime {
53 29     29 1 39048 my ($self, $spec, %opts) = @_;
54              
55 29         80 $self->_reset(\%opts);
56              
57 29         8617 for ($spec) {
58 29         109 local ($@, $SIG{__DIE__});
59 29         38 eval {
60 29         86 /^\s*/gc;
61              
62 29 100       71 if ($self->_parse_spec_base()) {
63 24         56 /\G\s*/gc;
64 24         53 $self->_parse_inc_or_dec;
65             }
66 25         74 /\G\s*/gc;
67 25 100       60 /\G\S/gc and die "unparsed rubbish";
68              
69 24         77 $self->{date}->set_time_zone('UTC');
70             };
71 29         1910 $self->{error} = $@;
72 29 100       192 return $self->{date} unless $@;
73             }
74              
75 5         472 croak "unable to parse datetime specification '$spec'";
76             }
77              
78             sub _parse_spec_base {
79 29     29   38 my $self = shift;
80 29 100       56 if ($self->_parse_date) {
    100          
81 9         26 return 1;
82             }
83             elsif ($self->_parse_time) {
84 15         23 my $pos = pos;
85 15 100 100     64 unless (/\G\s+/gc and $self->_parse_date) {
86 6         16 pos = $pos;
87 6         14 my $base = $self->{now};
88 6         21 my $base_hour = $base->hour;
89 6         39 my $base_min = $base->min;
90 6 100 66     62 if ( ( $base_hour > $self->{hour} ) or
      33        
91             ( ( $base_hour == $self->{hour} ) and
92             ( $base_min >= $self->{min} ) ) ) {
93 3         11 $base = $base->add(days => 1);
94             }
95 6         1438 $self->{date} = $base;
96             }
97              
98 15         60 $self->{date}->set(hour => $self->{hour},
99             minute => $self->{min},
100             second => 0);
101              
102              
103 15         4525 return 1;
104             }
105             return
106 1         3 }
107              
108             sub _parse_date {
109 41     41   59 my $self = shift;
110              
111 41         55 my $now = $self->{now};
112              
113 41 100       884 if (/\G($month_re)\s+(\d\d?)(?:(?:\s+|\s*,\s*)(\d\d(?:\d\d)?))?/gco) {
    50          
    50          
    50          
    50          
    50          
    100          
    50          
    50          
    50          
    50          
114             # month_name day_number
115             # month_name day_number year_number
116             # month_name day_number ',' year_number
117 13         22 @{$self}{qw(month_name day year)} = ($1, $2, $3);
  13         58  
118             }
119             elsif (/\G(?:next\s+)?($wday_re)/gcio) {
120             # day_of_week
121 0         0 $self->{wday_name} = $1;
122 0         0 my $wday = $self->{wday} = $wday{lc substr $1, 0, 3};
123 0         0 my $delta = $wday - $now->day_of_week;
124 0 0       0 $delta += 7 if $delta <= 0;
125 0         0 $self->{date} = $now->add(days => $delta);
126 0         0 return 1;
127             }
128             elsif (/\Gtoday\b/gci) {
129             # TODAY
130 0         0 $self->{today} = 1;
131 0         0 $self->{date} = $now;
132 0         0 return 1;
133             }
134             elsif (/\Gtomorrow\b/gci) {
135             # TOMORROW
136 0         0 $self->{tomorrow} = 1;
137 0         0 $self->{date} = $now->add(days => 1);
138 0         0 return 1;
139             }
140             elsif (/\G(\d\d?)\.(\d\d?)\.(\d\d(?:\d\d)?)\b/gc) {
141             # DOTTEDDATE (dd.mm.[cc]yy)
142 0         0 @{$self}{qw(day month year)} = ($1, $2, $3);
  0         0  
143             }
144             elsif (/\G(\d\d(?:\d\d)?)-(\d\d?)-(\d\d?)\b/gc) {
145             # HYPHENDATE ([cc]yy-mm-dd)
146 0         0 @{$self}{qw(year month day)} = ($1, $2, $3);
  0         0  
147             }
148             elsif (/\Gnow\b/gci) {
149             # NOW
150 9         15 $self->{is_now} = 1;
151 9         13 $self->{date} = $now;
152 9         25 return 1;
153             }
154             elsif (/\G(\d\d?)\s+($month_re)(?:\s+(\d\d(?:\d\d)?))?/gco) {
155             # day_number month_name
156             # day_number month_name year_number
157 0         0 @{$self}{qw(day month_name year)} = ($1, $2, $3);
  0         0  
158             }
159             elsif (/\G(\d\d?)\/(\d\d?)\/(\d\d(?:\d\d)?)\b/gc) {
160             # month_number '/' day_number '/' year_number
161 0         0 @{$self}{qw(month day year)} = ($1, $2, $3);
  0         0  
162             }
163             elsif (/\G(\d\d?)(\d\d)(\d\d(?:\d\d)?)\b/gc) {
164             # concatenated_date (m[m]dd[cc]yy)
165 0         0 @{$self}{qw(month day year)} = ($1, $2, $3);
  0         0  
166             }
167             elsif (/\Gnext\s+($period_re)\b/gcio) {
168             # NEXT inc_dec_period
169 0         0 $self->{next_period} = $1;
170 0         0 $self->{date} = $now->add(_period($1) => 1);
171 0         0 return 1;
172             }
173             else {
174 19         80 return;
175             }
176              
177 13   33     86 $self->{month} //= $month{lc substr $self->{month_name}, 0, 3};
178              
179 13 50       29 if (defined (my $year = $self->{year})) {
180 0 0       0 if (length $year <= 2) {
181 0 0       0 $self->{year4} = $year + ($year < 70 ? 2000 : 1900);
182             }
183             else {
184 0         0 $self->{year4} = $year;
185             }
186             }
187             else {
188 13         39 my $now_day = $now->day;
189 13         81 my $now_month = $now->month;
190 13         68 $self->{year4} = $now->year;
191 13 100 33     101 $self->{year4}++ if ( ($now_month > $self->{month}) or
      66        
192             ( ($now_month == $self->{month}) and
193             ($now_day > $self->{day}) ) );
194             }
195              
196 13         50 $self->{date} = DateTime->new(year => $self->{year4},
197             month => $self->{month},
198             day => $self->{day},
199             hour => $now->hour,
200             minute => $now->minute,
201             time_zone => $now->time_zone);
202              
203 9         1700 return 1;
204              
205              
206             }
207              
208             sub _parse_time {
209 16     16   37 my $self = shift;
210              
211 16 50       92 if (/\G(\d\d)(\d\d)\b/gc) {
    100          
    50          
    50          
    50          
212             # hr24clock_hr_min (hhmm)
213 0         0 @{$self}{qw(hour min)} = ($1, $2);
  0         0  
214             }
215             elsif (/\G(([012]?[0-9])(?:[:'h,.](\d\d))?(?:\s*([ap]m))?\b)/gci) {
216             # time_hour am_pm
217             # time_hour_min
218             # time_hour_min am_pm
219 15   50     55 @{$self}{qw(hour min am_pm)} = ($2, ($3 // 0), $4);
  15         76  
220              
221 15 50       41 if (defined $4) {
222 0         0 my $hour = $2;
223 0 0       0 if ($hour > 11) {
224 0 0       0 $hour > 12 and return;
225 0         0 $hour = 0;
226             }
227 0 0       0 $hour += 12 if lc($4) eq 'pm';
228 0         0 $self->{hour} = $hour;
229             }
230             }
231             elsif (/\Gnoon\b/gc) {
232 0         0 @{$self}{qw(hour min noon)} = (12, 0, 1);
  0         0  
233             }
234             elsif (/\Gmidnight\b/gc) {
235 0         0 @{$self}{qw(hour min midnight)} = (0, 0, 1);
  0         0  
236             }
237             elsif (/\Gteatime\b/gc) {
238 0         0 @{$self}{qw(hour min teatime)} = (16, 0, 1);
  0         0  
239             }
240             else {
241             return
242 1         3 }
243              
244 15 100       53 if (/\G\s*(utc)\b/gci) {
245 1         7 $self->{tz} = uc $1;
246 1         8 $self->{now}->set_time_zone($self->{tz});
247             }
248              
249 15         252 return 1;
250             }
251              
252             sub _parse_inc_or_dec {
253 24     24   27 my $self = shift;
254              
255 24 100       245 if (/\G([+-])\s*(\d+)\s*($period_re)s?\b/gci) {
256 16         47 @{$self}{qw(increment increment_period)} = ("$1$2", $3);
  16         4452  
257 16 50       411 my $method = ($1 eq '+' ? 'add' : 'subtract');
258 16         46 $self->{date} = $self->{date}->$method(_period($3) => $2);
259              
260 16         8668 return 1;
261             }
262 8         16 return;
263             }
264              
265             1;
266             __END__