line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package App::JobLog::TimeGrammar; |
2
|
|
|
|
|
|
|
$App::JobLog::TimeGrammar::VERSION = '1.040'; |
3
|
|
|
|
|
|
|
# ABSTRACT: parse natural (English) language time expressions |
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
|
6
|
3
|
|
|
3
|
|
786
|
use Exporter 'import'; |
|
3
|
|
|
|
|
7
|
|
|
3
|
|
|
|
|
160
|
|
7
|
|
|
|
|
|
|
our @EXPORT = qw( |
8
|
|
|
|
|
|
|
parse |
9
|
|
|
|
|
|
|
daytime |
10
|
|
|
|
|
|
|
); |
11
|
|
|
|
|
|
|
|
12
|
3
|
|
|
3
|
|
16
|
use Modern::Perl; |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
25
|
|
13
|
3
|
|
|
3
|
|
373
|
use DateTime; |
|
3
|
|
|
|
|
7
|
|
|
3
|
|
|
|
|
20
|
|
14
|
3
|
|
|
|
|
19
|
use Class::Autouse qw( |
15
|
|
|
|
|
|
|
App::JobLog::Log |
16
|
3
|
|
|
3
|
|
75
|
); |
|
3
|
|
|
|
|
8
|
|
17
|
3
|
|
|
3
|
|
259
|
use Carp 'croak'; |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
150
|
|
18
|
3
|
|
|
|
|
28
|
use autouse 'App::JobLog::Config' => qw( |
19
|
|
|
|
|
|
|
log |
20
|
|
|
|
|
|
|
sunday_begins_week |
21
|
|
|
|
|
|
|
pay_period_length |
22
|
|
|
|
|
|
|
start_pay_period |
23
|
|
|
|
|
|
|
DIRECTORY |
24
|
3
|
|
|
3
|
|
16
|
); |
|
3
|
|
|
|
|
34
|
|
25
|
3
|
|
|
|
|
13
|
use autouse 'App::JobLog::Time' => qw( |
26
|
|
|
|
|
|
|
now |
27
|
|
|
|
|
|
|
today |
28
|
|
|
|
|
|
|
tz |
29
|
3
|
|
|
3
|
|
575
|
); |
|
3
|
|
|
|
|
13
|
|
30
|
3
|
|
|
3
|
|
1237
|
no if $] >= 5.018, warnings => "experimental::smartmatch"; |
|
3
|
|
|
|
|
14
|
|
|
3
|
|
|
|
|
20
|
|
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
# some variables we need visible inside the date parsing regex |
33
|
|
|
|
|
|
|
# %matches holds a complete parsing |
34
|
|
|
|
|
|
|
# %buffer, as its name suggests, is a temporary buffer |
35
|
|
|
|
|
|
|
# $d1 and $d2 are the starting and ending dates |
36
|
|
|
|
|
|
|
our ( %matches, %buffer, $d1, $d2 ); |
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
# buffers for numeric month, day, or year |
39
|
|
|
|
|
|
|
our ( $b1, $b2 ); |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
# holds time of day information |
42
|
|
|
|
|
|
|
our $time_buffer; |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
# static maps for translating month and day names to numbers |
45
|
|
|
|
|
|
|
my ( %month_abbr, %day_abbr ); |
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
# the master date parsing regex |
48
|
|
|
|
|
|
|
my $re = qr{ |
49
|
|
|
|
|
|
|
\A \s*+ (?: (?&ever) | (?&span) ) \s*+ \Z |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
(?(DEFINE) |
52
|
|
|
|
|
|
|
|
53
|
0
|
|
|
|
|
0
|
(? (?: all | always | ever | (?:(?:the \s++)? (?: entire | whole ) \s++ )? log ) (?{ $matches{ever} = 1 }) ) |
54
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
(? |
56
|
911
|
|
|
|
|
1682
|
((?&date)) (?{ $d1 = $^N; stow($d1) }) |
|
911
|
|
|
|
|
1531
|
|
57
|
213
|
|
|
|
|
357
|
(?: (?&span_divider) ((?&date)) (?{ $d2 = $^N; stow($d2) }) )? |
|
213
|
|
|
|
|
327
|
|
58
|
|
|
|
|
|
|
) |
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
(? \s*+ (?: -++ | \b(?: through | thru | to | till?+ | until )\b ) \s*+) |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
(? at | @ ) |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
(? |
65
|
515
|
|
|
|
|
2635
|
(?{ $time_buffer = undef }) |
66
|
|
|
|
|
|
|
(?: (?: \s++ | \s*+ (?&at) \s*+ ) (?&time))? |
67
|
|
|
|
|
|
|
) |
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
(? (?:(?&at) \s++)? (?&time) \s++ on \s++ ) |
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
(? |
72
|
487
|
|
|
|
|
1949
|
(?{ (%buffer, $b1, $b2, $time_buffer) = ()}) |
73
|
|
|
|
|
|
|
(?: (?&numeric) | (?&verbal) ) |
74
|
1124
|
50
|
|
|
|
4037
|
(?{ $buffer{time} = $time_buffer if $time_buffer }) |
75
|
|
|
|
|
|
|
) |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
(? |
78
|
1846
|
|
|
|
|
12821
|
(?{ $time_buffer = undef }) |
79
|
|
|
|
|
|
|
( |
80
|
|
|
|
|
|
|
\d{1,2} |
81
|
|
|
|
|
|
|
(?: |
82
|
|
|
|
|
|
|
: \d{2} |
83
|
|
|
|
|
|
|
(?: |
84
|
|
|
|
|
|
|
: \d{2} |
85
|
|
|
|
|
|
|
)? |
86
|
|
|
|
|
|
|
)? |
87
|
|
|
|
|
|
|
(?: \s*+ (?&time_suffix))? |
88
|
|
|
|
|
|
|
) |
89
|
1878
|
|
|
|
|
15961
|
(?{ $time_buffer = $^N }) |
90
|
|
|
|
|
|
|
) |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
(? [ap] (?:m|\.m\.)) |
93
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
(? |
95
|
|
|
|
|
|
|
(?: |
96
|
|
|
|
|
|
|
(?&year) |
97
|
|
|
|
|
|
|
| |
98
|
|
|
|
|
|
|
(?&ym) |
99
|
|
|
|
|
|
|
| |
100
|
|
|
|
|
|
|
(?&at_time_on) (?&numeric_no_time) |
101
|
|
|
|
|
|
|
| |
102
|
|
|
|
|
|
|
(?&numeric_no_time) (?&at_time)) |
103
|
676
|
|
|
|
|
2132
|
(?{ $buffer{type} = 'numeric' }) |
104
|
|
|
|
|
|
|
) |
105
|
|
|
|
|
|
|
|
106
|
1004
|
|
|
|
|
4701
|
(? (?{ %buffer = () }) (\d{4}) (?{ $buffer{year} = $^N }) ) |
|
153
|
|
|
|
|
950
|
|
107
|
|
|
|
|
|
|
|
108
|
91
|
|
|
|
|
453
|
(? (?&year) (?÷r) (\d{1,2}) (?{ @buffer{qw(month unit)} = ($^N, 'months') }) ) |
109
|
|
|
|
|
|
|
|
110
|
557
|
|
|
|
|
4721
|
(? (?{ %buffer = () }) (?&us) | (?&iso) | (?&md) | (?&dom) ) |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
(? |
113
|
480
|
|
|
|
|
2604
|
(\d{1,2}) (?{ $b1 = $^N }) |
114
|
|
|
|
|
|
|
((?÷r)) |
115
|
142
|
|
|
|
|
704
|
(\d{1,2}) (?{ $b2 = $^N }) |
116
|
|
|
|
|
|
|
\g{-2} |
117
|
|
|
|
|
|
|
(\d{4}) |
118
|
|
|
|
|
|
|
(?{ |
119
|
80
|
|
|
|
|
217
|
$buffer{year} = $^N; |
120
|
80
|
|
|
|
|
137
|
$buffer{month} = $b1; |
121
|
80
|
|
|
|
|
376
|
$buffer{day} = $b2; |
122
|
|
|
|
|
|
|
}) |
123
|
|
|
|
|
|
|
) |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
(? |
126
|
102
|
|
|
|
|
614
|
(\d{4}) (?{ $b1 = $^N }) |
127
|
|
|
|
|
|
|
((?÷r)) |
128
|
102
|
|
|
|
|
394
|
(\d{1,2}) (?{ $b2 = $^N }) |
129
|
|
|
|
|
|
|
\g{-2} |
130
|
|
|
|
|
|
|
(\d{1,2}) |
131
|
|
|
|
|
|
|
(?{ |
132
|
102
|
|
|
|
|
228
|
$buffer{year} = $b1; |
133
|
102
|
|
|
|
|
179
|
$buffer{month} = $b2; |
134
|
102
|
|
|
|
|
487
|
$buffer{day} = $^N; |
135
|
|
|
|
|
|
|
}) |
136
|
|
|
|
|
|
|
) |
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
(? |
139
|
196
|
|
|
|
|
1022
|
(\d{1,2}) (?{ $b1 = $^N }) |
140
|
|
|
|
|
|
|
(?÷r) |
141
|
|
|
|
|
|
|
(\d{1,2}) |
142
|
|
|
|
|
|
|
(?{ |
143
|
62
|
|
|
|
|
144
|
$buffer{month} = $b1; |
144
|
62
|
|
|
|
|
335
|
$buffer{day} = $^N; |
145
|
|
|
|
|
|
|
}) |
146
|
|
|
|
|
|
|
) |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
(? |
149
|
|
|
|
|
|
|
(\d{1,2}) |
150
|
154
|
|
|
|
|
852
|
(?{ $buffer{day} = $^N }) |
151
|
|
|
|
|
|
|
) |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
(? |
154
|
|
|
|
|
|
|
(?: (?&my) | (?&named_period) | (?&relative_period) | (?&month_day) | (?&full) ) |
155
|
448
|
|
|
|
|
1655
|
(?{ $buffer{type} = 'verbal' }) |
156
|
|
|
|
|
|
|
) |
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
(? (?&modifiable_day) | (?&modifiable_month) | (?&modifiable_period) ) |
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
(? (?&at_time_on) (?&modifiable_day_no_time) | (?&modifiable_day_no_time) (?&at_time)) |
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
(? |
163
|
310
|
|
|
|
|
5236
|
((?:(?&modifier) \s++ )?) (?{ $b1 = $^N }) |
164
|
|
|
|
|
|
|
((?&weekday)) |
165
|
|
|
|
|
|
|
(?{ |
166
|
83
|
100
|
|
|
|
297
|
$buffer{modifier} = $b1 if $b1; |
167
|
83
|
|
|
|
|
527
|
$buffer{day} = $^N; |
168
|
|
|
|
|
|
|
}) |
169
|
|
|
|
|
|
|
) |
170
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
(? |
172
|
182
|
|
|
|
|
1886
|
((?:(?&month_modifier) \s++ )?) (?{ $b1 = $^N }) |
173
|
|
|
|
|
|
|
((?&month)) |
174
|
|
|
|
|
|
|
(?{ |
175
|
32
|
100
|
|
|
|
81
|
$buffer{modifier} = $b1 if $b1; |
176
|
32
|
|
|
|
|
198
|
$buffer{month} = $^N; |
177
|
|
|
|
|
|
|
}) |
178
|
|
|
|
|
|
|
) |
179
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
(? |
181
|
172
|
|
|
|
|
3791
|
(?{ $b1 = undef }) |
182
|
6
|
|
|
|
|
38
|
(?:((?&period_modifier)) \s*+ (?{ $b1 = $^N }))? |
183
|
|
|
|
|
|
|
((?&period)) |
184
|
|
|
|
|
|
|
(?{ |
185
|
11
|
100
|
|
|
|
38
|
$buffer{modifier} = $b1 if $b1; |
186
|
11
|
|
|
|
|
67
|
$buffer{period} = $^N; |
187
|
|
|
|
|
|
|
}) |
188
|
|
|
|
|
|
|
) |
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
(? pay | pp | pay \s*+ period ) |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
(? |
193
|
|
|
|
|
|
|
(?:(?&at) \s*+)? (?&time) \s++ (?&relative_period_no_time) |
194
|
|
|
|
|
|
|
| |
195
|
|
|
|
|
|
|
(?&relative_period_no_time) (?&at_time) |
196
|
|
|
|
|
|
|
| |
197
|
|
|
|
|
|
|
(?&now) |
198
|
|
|
|
|
|
|
) |
199
|
|
|
|
|
|
|
|
200
|
6
|
|
|
|
|
62
|
(? now (?{ $buffer{day} = 'today' })) |
201
|
|
|
|
|
|
|
|
202
|
7
|
|
|
|
|
47
|
(? ( yesterday | today | tomorrow ) (?{ $buffer{day} = $^N })) |
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
(? (?&at_time_on) (?&month_day_no_time) | (?&month_day_no_time) (?&at_time)) |
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
(? (?&month_first) | (?&day_first) ) |
207
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
(? |
209
|
43
|
|
|
|
|
174
|
((?&month)) (?{ $b1 = $^N }) |
210
|
|
|
|
|
|
|
\s++ |
211
|
|
|
|
|
|
|
(\d{1,2}) |
212
|
|
|
|
|
|
|
(?{ |
213
|
43
|
|
|
|
|
87
|
$buffer{month} = $b1; |
214
|
43
|
|
|
|
|
325
|
$buffer{day} = $^N; |
215
|
|
|
|
|
|
|
}) |
216
|
|
|
|
|
|
|
) |
217
|
|
|
|
|
|
|
|
218
|
1
|
|
|
|
|
8
|
(? ((?&month)) ,? \s*+ (?&year) (?{ @buffer{qw(month unit)} = ($^N, 'months') }) ) |
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
(? |
221
|
142
|
|
|
|
|
1578
|
(\d{1,2}) (?{ $b1 = $^N }) |
222
|
|
|
|
|
|
|
\s++ |
223
|
|
|
|
|
|
|
((?&month)) |
224
|
|
|
|
|
|
|
(?{ |
225
|
86
|
|
|
|
|
212
|
$buffer{month} = $^N; |
226
|
86
|
|
|
|
|
687
|
$buffer{day} = $b1; |
227
|
|
|
|
|
|
|
}) |
228
|
|
|
|
|
|
|
) |
229
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
(? (?&at_time_on) (?&full_no_time) | (?&full_no_time) (?&at_time)) |
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
(? (?&dm_full) | (?&md_full) ) |
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
(? |
235
|
127
|
|
|
|
|
1543
|
(\d{1,2}) (?{ $b1 = $^N }) |
236
|
|
|
|
|
|
|
\s++ |
237
|
83
|
|
|
|
|
369
|
((?&month)) (?{ $b2 = $^N }) |
238
|
|
|
|
|
|
|
,? \s++ |
239
|
|
|
|
|
|
|
(\d{4}) |
240
|
|
|
|
|
|
|
(?{ |
241
|
83
|
|
|
|
|
203
|
$buffer{year} = $^N; |
242
|
83
|
|
|
|
|
135
|
$buffer{month} = $b2; |
243
|
83
|
|
|
|
|
733
|
$buffer{day} = $b1; |
244
|
|
|
|
|
|
|
}) |
245
|
|
|
|
|
|
|
) |
246
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
(? |
248
|
40
|
|
|
|
|
183
|
((?&month)) (?{ $b2 = $^N }) |
249
|
|
|
|
|
|
|
\s++ |
250
|
40
|
|
|
|
|
164
|
(\d{1,2}) (?{ $b1 = $^N }) |
251
|
|
|
|
|
|
|
, \s++ |
252
|
|
|
|
|
|
|
(\d{4}) |
253
|
|
|
|
|
|
|
(?{ |
254
|
40
|
|
|
|
|
86
|
$buffer{year} = $^N; |
255
|
40
|
|
|
|
|
66
|
$buffer{month} = $b2; |
256
|
40
|
|
|
|
|
320
|
$buffer{day} = $b1; |
257
|
|
|
|
|
|
|
}) |
258
|
|
|
|
|
|
|
) |
259
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
(? (?&full_weekday) | (?&short_weekday) ) |
261
|
|
|
|
|
|
|
|
262
|
|
|
|
|
|
|
(? sunday | monday | tuesday | wednesday | thursday | friday | saturday ) |
263
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
(? sun | mon | tue | wed | thu | fri | sat ) |
265
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
(? (?&full_month) | (?&short_month) ) |
267
|
|
|
|
|
|
|
|
268
|
|
|
|
|
|
|
(? january | february | march | april | may | june | july | august | september | october | november | december ) |
269
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
(? jan | feb | mar | apr | may | jun | jul | aug | sep | oct | nov | dec ) |
271
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
(? last | this | next ) |
273
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
(? (?&modifier) | (?&termini) (?: \s++ of (?: \s++ the )? )? ) |
275
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
(? week | month | year | (?&pay) ) |
277
|
|
|
|
|
|
|
|
278
|
|
|
|
|
|
|
(? (?&modifier) | (?&termini) (?: \s++ of )? ) |
279
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
(? (?: the \s++ )? (?: (?&beginning) | end ) ) |
281
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
(? beg(?:in(?:ning)?)?) |
283
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
(? [-/.]) |
285
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
) |
287
|
|
|
|
|
|
|
}xi; |
288
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
# stows everything matched so far in %matches |
290
|
|
|
|
|
|
|
sub stow { |
291
|
1124
|
|
|
1124
|
0
|
4556
|
my %h = %buffer; |
292
|
1124
|
|
|
|
|
3218
|
$matches{ $_[0] } = \%h; |
293
|
1124
|
|
|
|
|
15313
|
%buffer = (); |
294
|
|
|
|
|
|
|
} |
295
|
|
|
|
|
|
|
|
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
sub daytime { |
298
|
416
|
|
|
416
|
1
|
649
|
my $time = shift; |
299
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
#parse |
301
|
416
|
|
|
|
|
2002
|
$time =~ /(?\d++) |
302
|
|
|
|
|
|
|
(?: |
303
|
|
|
|
|
|
|
: (?\d++) |
304
|
|
|
|
|
|
|
(?: |
305
|
|
|
|
|
|
|
: (?\d++) |
306
|
|
|
|
|
|
|
)? |
307
|
|
|
|
|
|
|
)? |
308
|
|
|
|
|
|
|
(?: \s*+ (?[ap]) (\.?)m\g{-1})? |
309
|
|
|
|
|
|
|
/ix; |
310
|
|
|
|
|
|
|
my ( $hour, $minute, $second, $suffix ) = |
311
|
416
|
|
50
|
1
|
|
8154
|
( $+{hour}, $+{minute} || 0, $+{second} || 0, lc( $+{suffix} || 'x' ) ); |
|
1
|
|
100
|
|
|
3414
|
|
|
1
|
|
100
|
|
|
456
|
|
|
1
|
|
|
|
|
5418
|
|
312
|
416
|
50
|
33
|
|
|
1971
|
$hour += 12 if $suffix eq 'p' && $hour < 12; |
313
|
416
|
50
|
|
|
|
1083
|
$suffix = 'p' if $hour > 11; |
314
|
416
|
50
|
33
|
|
|
1014
|
$hour = 0 if $hour == 12 && $suffix eq 'a'; |
315
|
416
|
50
|
33
|
|
|
3973
|
croak |
|
|
|
33
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
|
33
|
|
|
|
|
316
|
|
|
|
|
|
|
"impossible time: $time" #<--- syntax error at (eval 4158) line 23, near "croak "impossible time: $time"" |
317
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
if $hour > 23 |
319
|
|
|
|
|
|
|
|| $minute > 59 |
320
|
|
|
|
|
|
|
|| $second > 59 |
321
|
|
|
|
|
|
|
|| $suffix eq 'a' && $hour > 12; |
322
|
416
|
50
|
66
|
|
|
1552
|
$hour = 0 if $suffix eq 'a' && $hour == 12; |
323
|
|
|
|
|
|
|
return ( |
324
|
416
|
|
|
|
|
2616
|
hour => $hour, |
325
|
|
|
|
|
|
|
minute => $minute, |
326
|
|
|
|
|
|
|
second => $second, |
327
|
|
|
|
|
|
|
suffix => $suffix |
328
|
|
|
|
|
|
|
); |
329
|
|
|
|
|
|
|
} |
330
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
sub parse { |
333
|
445
|
|
|
445
|
1
|
435663
|
my $phrase = shift; |
334
|
445
|
|
|
|
|
1289
|
local ( %matches, %buffer, $d1, $d2, $b1, $b2, $time_buffer ); |
335
|
445
|
50
|
|
|
|
6249
|
if ( $phrase =~ $re ) { |
336
|
445
|
50
|
|
|
|
1034
|
if ( $matches{ever} ) { |
337
|
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
# we want the entire timespan of the log |
339
|
0
|
|
|
|
|
0
|
my ($se) = App::JobLog::Log->new->first_event; |
340
|
0
|
0
|
|
|
|
0
|
if ($se) { |
341
|
0
|
|
|
|
|
0
|
return $se->start, now, 0; |
342
|
|
|
|
|
|
|
} |
343
|
|
|
|
|
|
|
else { |
344
|
0
|
|
|
|
|
0
|
return now->subtract( seconds => 1 ), now, 0; |
345
|
|
|
|
|
|
|
} |
346
|
|
|
|
|
|
|
} |
347
|
|
|
|
|
|
|
|
348
|
445
|
|
|
|
|
812
|
my $h1 = $matches{$d1}; |
349
|
445
|
|
|
|
|
704
|
my $unit = delete $h1->{unit}; |
350
|
445
|
|
|
|
|
978
|
normalize($h1); |
351
|
445
|
100
|
|
|
|
921
|
if ($unit) { |
352
|
|
|
|
|
|
|
|
353
|
|
|
|
|
|
|
# $h1 is necessarily fixed and there is no time associated |
354
|
4
|
|
|
|
|
17
|
$h1 = fix_date( $h1, 1 ); |
355
|
4
|
|
|
|
|
1470
|
my $h2 = $h1->clone->add( $unit => 1 )->subtract( seconds => 1 ); |
356
|
4
|
|
|
|
|
7398
|
return $h1, $h2, 1; |
357
|
|
|
|
|
|
|
} |
358
|
|
|
|
|
|
|
else { |
359
|
441
|
|
|
|
|
912
|
my %t1 = extract_time( $h1, 1 ); |
360
|
441
|
|
|
|
|
878
|
my ( $h2, $count, %t2 ); |
361
|
441
|
100
|
66
|
|
|
1288
|
if ( $d2 && $matches{$d2} ) { |
362
|
20
|
|
|
|
|
47
|
$h2 = $matches{$d2}; |
363
|
20
|
|
|
|
|
48
|
normalize($h2); |
364
|
20
|
|
|
|
|
47
|
%t2 = extract_time($h2); |
365
|
20
|
|
|
|
|
48
|
$count = 2; |
366
|
|
|
|
|
|
|
} |
367
|
|
|
|
|
|
|
else { |
368
|
421
|
|
|
|
|
1909
|
$h2 = {%$h1}; |
369
|
421
|
|
|
|
|
1219
|
%t2 = ( hour => 23, minute => 59, second => 59 ); |
370
|
421
|
|
|
|
|
653
|
$count = 1; |
371
|
|
|
|
|
|
|
} |
372
|
441
|
|
|
|
|
877
|
infer_modifier( $h1, $h2 ); |
373
|
441
|
|
|
|
|
917
|
my ( $s1, $s2 ) = ( $t1{suffix}, $t2{suffix} ); |
374
|
441
|
|
|
|
|
904
|
delete $t1{suffix}, delete $t2{suffix}; |
375
|
441
|
100
|
|
|
|
862
|
if ( is_fixed($h1) ) { |
|
|
100
|
|
|
|
|
|
376
|
356
|
|
|
|
|
877
|
( $h1, $h2 ) = fixed_start( $h1, $h2, $count == 2 ); |
377
|
|
|
|
|
|
|
} |
378
|
|
|
|
|
|
|
elsif ( is_fixed($h2) ) { |
379
|
1
|
|
|
|
|
5
|
( $h1, $h2 ) = fixed_end( $h1, $h2 ); |
380
|
|
|
|
|
|
|
} |
381
|
|
|
|
|
|
|
else { |
382
|
84
|
|
|
|
|
161
|
( $h1, $h2 ) = before_now( $h1, $h2, $count == 2 ); |
383
|
|
|
|
|
|
|
} |
384
|
441
|
50
|
|
|
|
4786
|
croak "dates in \"$phrase\" are out of order" |
385
|
|
|
|
|
|
|
unless DateTime->compare( $h1, $h2 ) <= 0; |
386
|
441
|
|
|
|
|
43162
|
$h1->set(%t1); |
387
|
441
|
|
|
|
|
142542
|
$h2->set(%t2); |
388
|
441
|
50
|
|
|
|
137563
|
if ( $h1 > $h2 ) { |
389
|
0
|
0
|
0
|
|
|
0
|
if ( $h1->year == $h2->year |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
390
|
|
|
|
|
|
|
&& $h1->month == $h2->month |
391
|
|
|
|
|
|
|
&& $h1->day == $h2->day |
392
|
|
|
|
|
|
|
&& $h2->hour < 12 |
393
|
|
|
|
|
|
|
&& $s2 eq 'x' ) |
394
|
|
|
|
|
|
|
{ |
395
|
|
|
|
|
|
|
|
396
|
|
|
|
|
|
|
# we inferred the 12 hour period of the second endpoint incorrectly; |
397
|
|
|
|
|
|
|
# it was in the evening rather than morning |
398
|
0
|
|
|
|
|
0
|
$h2->add( hours => 12 ); |
399
|
|
|
|
|
|
|
} |
400
|
|
|
|
|
|
|
else { |
401
|
0
|
|
|
|
|
0
|
croak "dates in \"$phrase\" are out of order"; |
402
|
|
|
|
|
|
|
} |
403
|
|
|
|
|
|
|
} |
404
|
441
|
|
|
|
|
44612
|
return $h1, $h2, $count == 2; |
405
|
|
|
|
|
|
|
} |
406
|
|
|
|
|
|
|
} |
407
|
0
|
|
|
|
|
0
|
croak "cannot parse \"$phrase\" as a date expression"; |
408
|
|
|
|
|
|
|
} |
409
|
|
|
|
|
|
|
|
410
|
|
|
|
|
|
|
# if the sole expression is a unit identifier, infer the modifier 'this' |
411
|
|
|
|
|
|
|
sub infer_modifier { |
412
|
441
|
|
|
441
|
0
|
657
|
my ( $h1, $h2 ) = @_; |
413
|
441
|
50
|
100
|
|
|
1732
|
if ( keys %$h1 == 2 && keys %$h2 == 2 && $h1->{period} && $h2->{period} ) { |
|
|
|
66
|
|
|
|
|
|
|
|
66
|
|
|
|
|
414
|
3
|
|
|
|
|
8
|
$h1->{modifier} = $h2->{modifier} = 'this'; |
415
|
|
|
|
|
|
|
} |
416
|
|
|
|
|
|
|
} |
417
|
|
|
|
|
|
|
|
418
|
|
|
|
|
|
|
# pulls time expression -- 11:00 am, e.g. -- out of hash and converts it |
419
|
|
|
|
|
|
|
# to a series of units |
420
|
|
|
|
|
|
|
sub extract_time { |
421
|
461
|
|
|
461
|
0
|
690
|
my ( $h, $is_start ) = @_; |
422
|
461
|
|
|
|
|
737
|
my $time = $h->{time}; |
423
|
461
|
100
|
|
|
|
900
|
if ( defined $time ) { |
424
|
416
|
|
|
|
|
719
|
delete $h->{time}; |
425
|
|
|
|
|
|
|
|
426
|
416
|
|
|
|
|
770
|
return daytime($time); |
427
|
|
|
|
|
|
|
} |
428
|
|
|
|
|
|
|
else { |
429
|
|
|
|
|
|
|
|
430
|
|
|
|
|
|
|
#return default values |
431
|
45
|
100
|
|
|
|
309
|
return $is_start |
432
|
|
|
|
|
|
|
? ( hour => 0, minute => 0, second => 0, suffix => 'a' ) |
433
|
|
|
|
|
|
|
: ( hour => 23, minute => 59, second => 59, suffix => 'p' ); |
434
|
|
|
|
|
|
|
} |
435
|
|
|
|
|
|
|
} |
436
|
|
|
|
|
|
|
|
437
|
|
|
|
|
|
|
# produces interpretation of date expression consistent with a fixed end date |
438
|
|
|
|
|
|
|
sub fixed_end { |
439
|
1
|
|
|
1
|
0
|
2
|
my ( $h1, $h2 ) = @_; |
440
|
1
|
|
|
|
|
3
|
$h2 = fix_date($h2); |
441
|
1
|
50
|
|
|
|
163
|
if ( is_fixed($h1) ) { |
442
|
0
|
|
|
|
|
0
|
$h1 = fix_date( $h1, 1 ); |
443
|
|
|
|
|
|
|
} |
444
|
|
|
|
|
|
|
else { |
445
|
1
|
|
|
|
|
4
|
my ( $unit, $amt ) = time_unit($h1); |
446
|
1
|
|
|
|
|
4
|
$h1 = decontextualized_date( $h1, 1 ); |
447
|
1
|
50
|
|
|
|
7
|
if ( ref $h1 eq 'DateTime' ) { |
448
|
1
|
|
|
|
|
5
|
while ( DateTime->compare( $h1, $h2 ) > 0 ) { |
449
|
3
|
|
|
|
|
1443
|
$h1->subtract( $unit => $amt ); |
450
|
|
|
|
|
|
|
} |
451
|
|
|
|
|
|
|
} |
452
|
|
|
|
|
|
|
else { |
453
|
|
|
|
|
|
|
|
454
|
|
|
|
|
|
|
# we just have a floating weekday |
455
|
0
|
|
|
|
|
0
|
$h1 = adjust_weekday( $h1, $h2 ); |
456
|
|
|
|
|
|
|
} |
457
|
|
|
|
|
|
|
} |
458
|
1
|
|
|
|
|
664
|
return ( $h1, $h2 ); |
459
|
|
|
|
|
|
|
} |
460
|
|
|
|
|
|
|
|
461
|
|
|
|
|
|
|
# picks a day of the week before a given date |
462
|
|
|
|
|
|
|
sub adjust_weekday { |
463
|
81
|
|
|
81
|
0
|
117
|
my ( $ref, $date ) = @_; |
464
|
|
|
|
|
|
|
my $delta = $ref->{day_of_week} |
465
|
81
|
|
50
|
|
|
223
|
|| die 'should always be day_of_week key at this point'; |
466
|
81
|
|
|
|
|
227
|
my $d = $date->clone; |
467
|
81
|
|
|
|
|
909
|
$delta = $date->day_of_week - $delta; |
468
|
81
|
50
|
|
|
|
365
|
$delta += 7 if $delta <= 0; |
469
|
81
|
|
|
|
|
223
|
$d->subtract( days => $delta ); |
470
|
81
|
|
|
|
|
48299
|
return $d; |
471
|
|
|
|
|
|
|
} |
472
|
|
|
|
|
|
|
|
473
|
|
|
|
|
|
|
# determines the finest grained unit of time by which a given date can be modified |
474
|
|
|
|
|
|
|
sub time_unit { |
475
|
172
|
|
|
172
|
0
|
217
|
my $h = shift; |
476
|
172
|
100
|
|
|
|
358
|
if ( $h->{type} eq 'numeric' ) { |
477
|
82
|
50
|
|
|
|
288
|
return 'years' => 1 if exists $h->{month}; |
478
|
0
|
|
|
|
|
0
|
return 'months' => 1; |
479
|
|
|
|
|
|
|
} |
480
|
|
|
|
|
|
|
else { |
481
|
90
|
100
|
|
|
|
200
|
if ( my $period = $h->{period} ) { |
482
|
2
|
|
|
|
|
5
|
for ($period) { |
483
|
2
|
|
|
|
|
6
|
when ('mon') { return 'months' => 1 } |
|
0
|
|
|
|
|
0
|
|
484
|
2
|
|
|
|
|
3
|
when ('wee') { return 'weeks' => 1 } |
|
0
|
|
|
|
|
0
|
|
485
|
2
|
|
|
|
|
3
|
when ('pay') { return 'days' => pay_period_length() } |
|
2
|
|
|
|
|
7
|
|
486
|
|
|
|
|
|
|
} |
487
|
|
|
|
|
|
|
} |
488
|
|
|
|
|
|
|
else { |
489
|
88
|
100
|
|
|
|
205
|
return 'years' => 1 if exists $h->{month}; |
490
|
81
|
50
|
|
|
|
304
|
return 'weeks' => 1 if exists $h->{day}; |
491
|
0
|
|
|
|
|
0
|
return 'months' => 1; |
492
|
|
|
|
|
|
|
} |
493
|
|
|
|
|
|
|
} |
494
|
|
|
|
|
|
|
} |
495
|
|
|
|
|
|
|
|
496
|
|
|
|
|
|
|
# produces interpretation of date expression consistent with a fixed start date |
497
|
|
|
|
|
|
|
sub fixed_start { |
498
|
356
|
|
|
356
|
0
|
590
|
my ( $h1, $h2, $two_endpoints ) = @_; |
499
|
356
|
|
|
|
|
665
|
$h1 = fix_date( $h1, 1 ); |
500
|
356
|
100
|
100
|
|
|
57720
|
unless ( $two_endpoints || $h2->{type} ne 'numeric' ) { |
501
|
164
|
100
|
|
|
|
722
|
return $h1, $h1->clone if defined $h2->{day}; |
502
|
1
|
|
|
|
|
48
|
return $h1, $h1->clone->add( years => 1 )->subtract( days => 1 ); |
503
|
|
|
|
|
|
|
} |
504
|
192
|
100
|
|
|
|
392
|
if ( is_fixed($h2) ) { |
505
|
189
|
|
|
|
|
364
|
$h2 = fix_date($h2); |
506
|
|
|
|
|
|
|
} |
507
|
|
|
|
|
|
|
else { |
508
|
3
|
|
|
|
|
9
|
my ( $unit, $amt ) = time_unit($h2); |
509
|
3
|
|
|
|
|
11
|
$h2 = decontextualized_date($h2); |
510
|
3
|
100
|
|
|
|
16
|
$h2 = adjust_weekday( $h2, $h1 ) unless ref $h2 eq 'DateTime'; |
511
|
3
|
|
|
|
|
12
|
$h2->subtract( $unit => $amt ) while $h2 > $h1; |
512
|
3
|
|
|
|
|
3637
|
$h2->add( $unit => $amt ); |
513
|
|
|
|
|
|
|
} |
514
|
192
|
|
|
|
|
23675
|
return ( $h1, $h2 ); |
515
|
|
|
|
|
|
|
} |
516
|
|
|
|
|
|
|
|
517
|
|
|
|
|
|
|
# date relative to now not yet adjusted relative to its position in the span or |
518
|
|
|
|
|
|
|
# another fixed date |
519
|
|
|
|
|
|
|
sub decontextualized_date { |
520
|
172
|
|
|
172
|
0
|
246
|
my ( $h, $is_start ) = @_; |
521
|
|
|
|
|
|
|
return decontextualized_numeric_date( $h, $is_start ) |
522
|
172
|
100
|
|
|
|
454
|
if $h->{type} eq 'numeric'; |
523
|
90
|
|
|
|
|
181
|
for ( $h->{modifier} ) { |
524
|
90
|
|
|
|
|
123
|
when ('end') { $is_start = 0 } |
|
0
|
|
|
|
|
0
|
|
525
|
90
|
|
|
|
|
139
|
when ('beginning') { $is_start = 1 } |
|
0
|
|
|
|
|
0
|
|
526
|
|
|
|
|
|
|
} |
527
|
90
|
100
|
|
|
|
197
|
if ( my $period = $h->{period} ) { |
528
|
2
|
|
|
|
|
7
|
my $date = today; |
529
|
2
|
|
|
|
|
23
|
for ($period) { |
530
|
2
|
|
|
|
|
5
|
when ('mon') { |
531
|
0
|
|
|
|
|
0
|
$date->truncate( to => 'month' ); |
532
|
0
|
0
|
|
|
|
0
|
$date->add( months => 1 ) unless $is_start; |
533
|
|
|
|
|
|
|
} |
534
|
2
|
|
|
|
|
3
|
when ('wee') { |
535
|
0
|
|
|
|
|
0
|
$date->truncate( to => 'week' ); |
536
|
0
|
0
|
|
|
|
0
|
$date->subtract( days => 1 ) if sunday_begins_week; |
537
|
0
|
0
|
|
|
|
0
|
$date->add( weeks => 1 ) unless $is_start; |
538
|
|
|
|
|
|
|
} |
539
|
2
|
|
|
|
|
4
|
when ('pay') { |
540
|
2
|
|
|
|
|
6
|
my $days = |
541
|
|
|
|
|
|
|
$date->delta_days(start_pay_period)->in_units('days'); |
542
|
2
|
|
|
|
|
521
|
$days %= pay_period_length; |
543
|
2
|
|
|
|
|
8
|
$date->subtract( days => $days ); |
544
|
2
|
100
|
|
|
|
1196
|
$date->add( days => pay_period_length ) unless $is_start; |
545
|
|
|
|
|
|
|
} |
546
|
0
|
|
|
|
|
0
|
default { |
547
|
0
|
|
|
|
|
0
|
croak 'DEBUG' |
548
|
|
|
|
|
|
|
} |
549
|
|
|
|
|
|
|
} |
550
|
2
|
100
|
|
|
|
510
|
$date->subtract( days => 1 ) unless $is_start; |
551
|
2
|
|
|
|
|
577
|
return $date; |
552
|
|
|
|
|
|
|
} |
553
|
|
|
|
|
|
|
else { |
554
|
88
|
100
|
100
|
|
|
474
|
if ( exists $h->{day} && $h->{day} !~ /^\d++$/ ) { |
555
|
81
|
|
|
|
|
151
|
init_day_abbr(); |
556
|
81
|
|
|
|
|
229
|
$h->{day_of_week} = $day_abbr{ $h->{day} }; |
557
|
81
|
|
|
|
|
134
|
delete $h->{day}; |
558
|
81
|
|
|
|
|
196
|
return $h; |
559
|
|
|
|
|
|
|
} |
560
|
7
|
50
|
|
|
|
17
|
if ( exists $h->{month} ) { |
561
|
7
|
|
|
|
|
16
|
init_month_abbr(); |
562
|
7
|
|
|
|
|
20
|
$h->{month} = $month_abbr{ $h->{month} }; |
563
|
|
|
|
|
|
|
} |
564
|
7
|
|
|
|
|
16
|
return decontextualized_numeric_date( $h, $is_start ); |
565
|
|
|
|
|
|
|
} |
566
|
|
|
|
|
|
|
} |
567
|
|
|
|
|
|
|
|
568
|
|
|
|
|
|
|
sub decontextualized_numeric_date { |
569
|
89
|
|
|
89
|
0
|
143
|
my ( $h, $is_start ) = @_; |
570
|
89
|
|
|
|
|
220
|
my $date = today; |
571
|
89
|
|
|
|
|
947
|
delete $h->{type}; |
572
|
89
|
|
|
|
|
124
|
delete $h->{modifier}; |
573
|
89
|
|
33
|
|
|
381
|
$h->{year} //= $date->year; |
574
|
89
|
|
33
|
|
|
588
|
$h->{month} //= $date->month; |
575
|
89
|
|
|
|
|
145
|
my $day_unspecified = !exists $h->{day}; |
576
|
89
|
|
100
|
|
|
249
|
$date = DateTime->new( time_zone => tz(), %$h, day => $h->{day} // 1 ); |
577
|
|
|
|
|
|
|
|
578
|
89
|
100
|
100
|
|
|
15293
|
if ( !( exists $h->{day} || $is_start ) ) { |
579
|
2
|
|
|
|
|
11
|
$date->add( months => 1 ); |
580
|
2
|
|
|
|
|
1086
|
$date->subtract( days => 1 ); |
581
|
|
|
|
|
|
|
} |
582
|
89
|
|
|
|
|
1479
|
return $date; |
583
|
|
|
|
|
|
|
} |
584
|
|
|
|
|
|
|
|
585
|
|
|
|
|
|
|
sub fix_date { |
586
|
550
|
|
|
550
|
0
|
784
|
my ( $d, $is_start ) = @_; |
587
|
550
|
100
|
|
|
|
1473
|
if ( $d->{type} eq 'verbal' ) { |
588
|
363
|
100
|
|
|
|
918
|
if ( $d->{year} ) { |
|
|
100
|
|
|
|
|
|
589
|
247
|
|
|
|
|
424
|
init_month_abbr(); |
590
|
247
|
|
|
|
|
532
|
$d->{month} = $month_abbr{ $d->{month} }; |
591
|
247
|
|
|
|
|
389
|
delete $d->{type}; |
592
|
247
|
|
|
|
|
728
|
return DateTime->new( time_zone => tz(), %$d ); |
593
|
|
|
|
|
|
|
} |
594
|
|
|
|
|
|
|
elsif ( my $day = $d->{day} ) { |
595
|
95
|
|
|
|
|
299
|
my $date = today; |
596
|
95
|
100
|
|
|
|
1253
|
return $date if $day eq 'tod'; |
597
|
85
|
100
|
|
|
|
242
|
if ( $day eq 'yes' ) { |
|
|
100
|
|
|
|
|
|
598
|
2
|
|
|
|
|
9
|
$date->subtract( days => 1 ); |
599
|
2
|
|
|
|
|
1325
|
return $date; |
600
|
|
|
|
|
|
|
} |
601
|
|
|
|
|
|
|
elsif ( $day eq 'tom' ) { |
602
|
3
|
|
|
|
|
11
|
$date->add( days => 1 ); |
603
|
3
|
|
|
|
|
1478
|
return $date; |
604
|
|
|
|
|
|
|
} |
605
|
80
|
|
|
|
|
133
|
init_day_abbr(); |
606
|
80
|
|
|
|
|
167
|
my $day_num = $day_abbr{$day}; |
607
|
80
|
|
|
|
|
225
|
my $todays_num = $date->day_of_week; |
608
|
80
|
50
|
|
|
|
359
|
if ( $d->{modifier} eq 'this' ) { |
609
|
0
|
0
|
|
|
|
0
|
return $date if $day_num == $todays_num; |
610
|
0
|
0
|
|
|
|
0
|
my $delta = |
611
|
|
|
|
|
|
|
$day_num > $todays_num |
612
|
|
|
|
|
|
|
? $day_num - $todays_num |
613
|
|
|
|
|
|
|
: 7 - $todays_num + $day_num; |
614
|
0
|
|
|
|
|
0
|
$date->add( days => $delta ); |
615
|
0
|
|
|
|
|
0
|
return $date; |
616
|
|
|
|
|
|
|
} |
617
|
|
|
|
|
|
|
else { |
618
|
80
|
|
|
|
|
97
|
my $delta = 7; |
619
|
80
|
50
|
|
|
|
224
|
if ( $day_num < $todays_num ) { |
|
|
50
|
|
|
|
|
|
620
|
0
|
|
|
|
|
0
|
$delta = $todays_num - $day_num; |
621
|
|
|
|
|
|
|
} |
622
|
|
|
|
|
|
|
elsif ( $day_num > $todays_num ) { |
623
|
0
|
|
|
|
|
0
|
$delta = 7 - $day_num + $todays_num; |
624
|
|
|
|
|
|
|
} |
625
|
80
|
|
|
|
|
213
|
$date->subtract( days => $delta ); |
626
|
80
|
50
|
|
|
|
48090
|
$date->add( days => 14 ) if $d->{modifier} eq 'next'; |
627
|
80
|
|
|
|
|
197
|
return $date; |
628
|
|
|
|
|
|
|
} |
629
|
|
|
|
|
|
|
} |
630
|
|
|
|
|
|
|
|
631
|
21
|
100
|
|
|
|
58
|
if ( my $period = $d->{period} ) { |
632
|
17
|
|
|
|
|
56
|
my $date = today; |
633
|
17
|
100
|
|
|
|
213
|
if ( $d->{modifier} eq 'this' ) { |
634
|
10
|
|
|
|
|
21
|
for ($period) { |
635
|
10
|
|
|
|
|
22
|
when ('mon') { |
636
|
4
|
|
|
|
|
12
|
$date->truncate( to => 'month' ); |
637
|
4
|
100
|
|
|
|
874
|
$date->add( months => 1 ) unless $is_start; |
638
|
|
|
|
|
|
|
} |
639
|
6
|
|
|
|
|
9
|
when ('wee') { |
640
|
2
|
|
|
|
|
7
|
my $is_sunday = $date->day_of_week == 7; |
641
|
2
|
|
|
|
|
11
|
$date->truncate( to => 'week' ); |
642
|
2
|
50
|
|
|
|
1895
|
if (sunday_begins_week) { |
643
|
2
|
50
|
|
|
|
9
|
$date->subtract( days => $is_sunday ? -6 : 1 ); |
644
|
|
|
|
|
|
|
} |
645
|
2
|
100
|
|
|
|
1161
|
$date->add( weeks => 1 ) unless $is_start; |
646
|
|
|
|
|
|
|
} |
647
|
4
|
|
|
|
|
7
|
when ('yea') { |
648
|
2
|
|
|
|
|
7
|
$date->truncate( to => 'year' ); |
649
|
2
|
100
|
|
|
|
422
|
$date->add( years => 1 ) unless $is_start; |
650
|
|
|
|
|
|
|
} |
651
|
2
|
|
|
|
|
4
|
when ('pay') { |
652
|
2
|
|
|
|
|
7
|
my $days = |
653
|
|
|
|
|
|
|
$date->delta_days(start_pay_period)->in_units('days'); |
654
|
2
|
|
|
|
|
502
|
$days %= pay_period_length; |
655
|
2
|
|
|
|
|
8
|
$date->subtract( days => $days ); |
656
|
2
|
100
|
|
|
|
1162
|
$date->add( days => pay_period_length ) |
657
|
|
|
|
|
|
|
unless $is_start; |
658
|
|
|
|
|
|
|
} |
659
|
|
|
|
|
|
|
} |
660
|
10
|
100
|
|
|
|
2817
|
$date->subtract( days => 1 ) unless $is_start; |
661
|
|
|
|
|
|
|
} |
662
|
|
|
|
|
|
|
else { |
663
|
7
|
|
|
|
|
16
|
for ($period) { |
664
|
7
|
|
|
|
|
15
|
when ('mon') { |
665
|
0
|
|
|
|
|
0
|
$date->truncate( to => 'month' ); |
666
|
0
|
0
|
|
|
|
0
|
if ($is_start) { |
667
|
0
|
|
|
|
|
0
|
$date->subtract( months => 1 ); |
668
|
|
|
|
|
|
|
} |
669
|
|
|
|
|
|
|
else { |
670
|
0
|
|
|
|
|
0
|
$date->subtract( days => 1 ); |
671
|
|
|
|
|
|
|
} |
672
|
0
|
0
|
|
|
|
0
|
$date->add( months => 2 ) if $d->{modifier} eq 'next'; |
673
|
|
|
|
|
|
|
} |
674
|
7
|
|
|
|
|
12
|
when ('wee') { |
675
|
5
|
|
|
|
|
19
|
my $is_sunday = $date->day_of_week == 7; |
676
|
5
|
|
|
|
|
59
|
$date->truncate( to => 'week' ); |
677
|
5
|
50
|
|
|
|
4667
|
if (sunday_begins_week) { |
678
|
5
|
50
|
|
|
|
25
|
$date->subtract( days => $is_sunday ? -6 : 1 ); |
679
|
|
|
|
|
|
|
} |
680
|
5
|
100
|
|
|
|
4115
|
if ($is_start) { |
681
|
3
|
|
|
|
|
13
|
$date->subtract( weeks => 1 ); |
682
|
|
|
|
|
|
|
} |
683
|
|
|
|
|
|
|
else { |
684
|
2
|
|
|
|
|
10
|
$date->subtract( days => 1 ); |
685
|
|
|
|
|
|
|
} |
686
|
5
|
50
|
|
|
|
3747
|
$date->add( days => 14 ) if $d->{modifier} eq 'next'; |
687
|
|
|
|
|
|
|
} |
688
|
2
|
|
|
|
|
4
|
when ('yea') { |
689
|
2
|
|
|
|
|
7
|
$date->truncate( to => 'year' ); |
690
|
2
|
100
|
|
|
|
413
|
if ($is_start) { |
691
|
1
|
|
|
|
|
4
|
$date->subtract( years => 1 ); |
692
|
|
|
|
|
|
|
} |
693
|
|
|
|
|
|
|
else { |
694
|
1
|
|
|
|
|
5
|
$date->subtract( days => 1 ); |
695
|
|
|
|
|
|
|
} |
696
|
2
|
50
|
|
|
|
1311
|
$date->add( years => 2 ) if $d->{modifier} eq 'next'; |
697
|
|
|
|
|
|
|
} |
698
|
0
|
|
|
|
|
0
|
when ('pay') { |
699
|
0
|
|
|
|
|
0
|
my $days = |
700
|
|
|
|
|
|
|
$date->delta_days(start_pay_period)->in_units('days'); |
701
|
0
|
|
|
|
|
0
|
$days %= pay_period_length; |
702
|
0
|
|
|
|
|
0
|
$date->subtract( days => $days ); |
703
|
0
|
0
|
|
|
|
0
|
if ($is_start) { |
704
|
0
|
|
|
|
|
0
|
$date->subtract( days => pay_period_length ); |
705
|
|
|
|
|
|
|
} |
706
|
|
|
|
|
|
|
else { |
707
|
0
|
|
|
|
|
0
|
$date->subtract( days => 1 ); |
708
|
|
|
|
|
|
|
} |
709
|
|
|
|
|
|
|
$date->add( days => 2 * pay_period_length ) |
710
|
0
|
0
|
|
|
|
0
|
if $d->{modifier} eq 'next'; |
711
|
|
|
|
|
|
|
} |
712
|
|
|
|
|
|
|
} |
713
|
|
|
|
|
|
|
} |
714
|
17
|
|
|
|
|
2962
|
return $date; |
715
|
|
|
|
|
|
|
} |
716
|
|
|
|
|
|
|
|
717
|
4
|
|
|
|
|
9
|
init_month_abbr(); |
718
|
4
|
|
|
|
|
14
|
my $date = today; |
719
|
4
|
|
|
|
|
49
|
$date->truncate( to => 'month' ); |
720
|
4
|
|
|
|
|
850
|
my $month_num = $month_abbr{ $d->{month} }; |
721
|
4
|
|
|
|
|
11
|
my $todays_num = $date->month; |
722
|
4
|
100
|
|
|
|
27
|
if ( $d->{modifier} eq 'this' ) { |
723
|
2
|
|
|
|
|
4
|
my $delta = 0; |
724
|
2
|
50
|
|
|
|
9
|
if ( $todays_num > $month_num ) { |
|
|
50
|
|
|
|
|
|
725
|
0
|
|
|
|
|
0
|
$delta = 12 - $todays_num + $month_num; |
726
|
|
|
|
|
|
|
} |
727
|
|
|
|
|
|
|
elsif ( $todays_num < $month_num ) { |
728
|
2
|
|
|
|
|
3
|
$delta = $month_num - $todays_num; |
729
|
|
|
|
|
|
|
} |
730
|
2
|
100
|
|
|
|
8
|
$delta++ unless $is_start; |
731
|
2
|
|
|
|
|
6
|
$date->add( months => $delta ); |
732
|
|
|
|
|
|
|
} |
733
|
|
|
|
|
|
|
else { |
734
|
2
|
|
|
|
|
3
|
my $delta = 12; |
735
|
2
|
50
|
|
|
|
10
|
if ( $todays_num > $month_num ) { |
|
|
50
|
|
|
|
|
|
736
|
0
|
|
|
|
|
0
|
$delta = $todays_num - $month_num; |
737
|
|
|
|
|
|
|
} |
738
|
|
|
|
|
|
|
elsif ( $todays_num < $month_num ) { |
739
|
2
|
|
|
|
|
3
|
$delta -= $month_num - $todays_num; |
740
|
|
|
|
|
|
|
} |
741
|
2
|
100
|
|
|
|
5
|
$delta-- unless $is_start; |
742
|
2
|
|
|
|
|
7
|
$date->subtract( months => $delta ); |
743
|
|
|
|
|
|
|
} |
744
|
4
|
100
|
|
|
|
2703
|
$date->subtract( days => 1 ) unless $is_start; |
745
|
4
|
|
|
|
|
1165
|
return $date; |
746
|
|
|
|
|
|
|
} |
747
|
|
|
|
|
|
|
|
748
|
|
|
|
|
|
|
# numeric date |
749
|
187
|
|
|
|
|
272
|
delete $d->{type}; |
750
|
187
|
|
|
|
|
649
|
return DateTime->new( time_zone => tz(), %$d ); |
751
|
|
|
|
|
|
|
} |
752
|
|
|
|
|
|
|
|
753
|
|
|
|
|
|
|
# lazy initialization of verbal -> numeric month map |
754
|
|
|
|
|
|
|
sub init_month_abbr { |
755
|
260
|
100
|
|
260
|
0
|
1054
|
unless (%month_abbr) { |
756
|
2
|
|
|
|
|
15
|
my @months = qw(jan feb mar apr may jun jul aug sep oct nov dec); |
757
|
2
|
|
|
|
|
8
|
init_hash( \%month_abbr, \@months ); |
758
|
|
|
|
|
|
|
} |
759
|
|
|
|
|
|
|
} |
760
|
|
|
|
|
|
|
|
761
|
|
|
|
|
|
|
# lazy initialization of verbal -> numeric day of week map |
762
|
|
|
|
|
|
|
sub init_day_abbr { |
763
|
161
|
100
|
|
161
|
0
|
613
|
unless (%day_abbr) { |
764
|
1
|
|
|
|
|
5
|
my @days = qw(mon tue wed thu fri sat sun); |
765
|
1
|
|
|
|
|
4
|
init_hash( \%day_abbr, \@days ); |
766
|
|
|
|
|
|
|
} |
767
|
|
|
|
|
|
|
} |
768
|
|
|
|
|
|
|
|
769
|
|
|
|
|
|
|
# generic verbal -> numeric map generator |
770
|
|
|
|
|
|
|
sub init_hash { |
771
|
3
|
|
|
3
|
0
|
7
|
my ( $h, $units ) = @_; |
772
|
3
|
|
|
|
|
11
|
while (@$units) { |
773
|
31
|
|
|
|
|
42
|
my $i = @$units; |
774
|
31
|
|
|
|
|
45
|
my $u = pop @$units; |
775
|
31
|
|
|
|
|
103
|
$h->{$u} = $i; |
776
|
|
|
|
|
|
|
} |
777
|
|
|
|
|
|
|
} |
778
|
|
|
|
|
|
|
|
779
|
|
|
|
|
|
|
# produces interpretation of date expression such that neither date ends after |
780
|
|
|
|
|
|
|
# the present |
781
|
|
|
|
|
|
|
sub before_now { |
782
|
84
|
|
|
84
|
0
|
139
|
my ( $h1, $h2, $two_endpoints ) = @_; |
783
|
84
|
100
|
|
|
|
184
|
infer_missing( $h1, $h2 ) if $two_endpoints; |
784
|
84
|
|
|
|
|
249
|
my $now = today; |
785
|
84
|
|
|
|
|
1014
|
my ( $u1, $amt1, $u2, $amt2 ) = ( time_unit($h1), time_unit($h2) ); |
786
|
84
|
|
|
|
|
189
|
( $h1, $h2 ) = |
787
|
|
|
|
|
|
|
( decontextualized_date( $h1, 1 ), decontextualized_date($h2) ); |
788
|
84
|
100
|
|
|
|
258
|
$h2 = adjust_weekday( $h2, $now ) unless ref $h2 eq 'DateTime'; |
789
|
84
|
100
|
|
|
|
247
|
$h1 = adjust_weekday( $h1, $now ) unless ref $h1 eq 'DateTime'; |
790
|
84
|
|
|
|
|
282
|
while ( $now < $h2 ) { |
791
|
1
|
|
|
|
|
96
|
$h2->subtract( $u2 => $amt2 ); |
792
|
|
|
|
|
|
|
} |
793
|
84
|
|
|
|
|
8587
|
while ( $h2 < $h1 ) { |
794
|
1
|
|
|
|
|
89
|
$h1->subtract( $u1 => $amt1 ); |
795
|
|
|
|
|
|
|
} |
796
|
|
|
|
|
|
|
|
797
|
84
|
100
|
|
|
|
8288
|
if ($two_endpoints) { |
798
|
|
|
|
|
|
|
|
799
|
|
|
|
|
|
|
# move the two dates as close together as possible |
800
|
2
|
|
|
|
|
6
|
while ( $h1 < $h2 ) { |
801
|
2
|
|
|
|
|
172
|
$h2->subtract( $u2 => $amt2 ); |
802
|
|
|
|
|
|
|
} |
803
|
2
|
|
|
|
|
1524
|
$h2->add( $u2 => $amt2 ); |
804
|
|
|
|
|
|
|
} |
805
|
84
|
|
|
|
|
1448
|
return $h1, $h2; |
806
|
|
|
|
|
|
|
} |
807
|
|
|
|
|
|
|
|
808
|
|
|
|
|
|
|
# fill in missing fields in two date hashes, each using the other as context |
809
|
|
|
|
|
|
|
# this is a bit of a hack, but a natural hack |
810
|
|
|
|
|
|
|
sub infer_missing { |
811
|
2
|
|
|
2
|
0
|
4
|
my ( $h1, $h2 ) = @_; |
812
|
2
|
50
|
|
|
|
10
|
if ( $h1->{type} eq $h2->{type} ) { |
|
|
50
|
|
|
|
|
|
813
|
0
|
|
|
|
|
0
|
while ( my ( $k, $v ) = each %$h1 ) { |
814
|
0
|
|
0
|
|
|
0
|
$h2->{$k} //= $v; |
815
|
|
|
|
|
|
|
} |
816
|
0
|
|
|
|
|
0
|
while ( my ( $k, $v ) = each %$h2 ) { |
817
|
0
|
|
0
|
|
|
0
|
$h1->{$k} //= $v; |
818
|
|
|
|
|
|
|
} |
819
|
|
|
|
|
|
|
} |
820
|
|
|
|
|
|
|
elsif ( $h2->{type} eq 'numeric' ) { |
821
|
2
|
50
|
33
|
|
|
17
|
if ( $h1->{month} && !$h2->{month} ) { |
822
|
2
|
|
|
|
|
5
|
init_month_abbr(); |
823
|
2
|
|
|
|
|
7
|
$h2->{month} = $month_abbr{ $h1->{month} }; |
824
|
|
|
|
|
|
|
} |
825
|
|
|
|
|
|
|
} |
826
|
|
|
|
|
|
|
else { |
827
|
|
|
|
|
|
|
|
828
|
|
|
|
|
|
|
# I don't think we have any problems in this case |
829
|
|
|
|
|
|
|
} |
830
|
|
|
|
|
|
|
} |
831
|
|
|
|
|
|
|
|
832
|
|
|
|
|
|
|
# normalizes string values |
833
|
|
|
|
|
|
|
sub normalize { |
834
|
465
|
|
|
465
|
0
|
674
|
my $h = shift; |
835
|
465
|
|
|
|
|
597
|
delete $h->{debug}; |
836
|
465
|
100
|
|
|
|
1433
|
if ( $h->{type} eq 'verbal' ) { |
837
|
236
|
|
|
|
|
433
|
for my $key (qw(day month period)) { |
838
|
708
|
100
|
|
|
|
1883
|
if ( my $value = $h->{$key} ) { |
839
|
362
|
100
|
|
|
|
1216
|
next if $value =~ /\d/; |
840
|
236
|
|
|
|
|
384
|
$value = lc $value; |
841
|
236
|
100
|
|
|
|
524
|
if ( $value =~ /^p/ ) { |
842
|
3
|
50
|
|
|
|
10
|
croak 'pay period not defined' |
843
|
|
|
|
|
|
|
unless defined start_pay_period; |
844
|
3
|
|
|
|
|
521
|
$h->{$key} = 'pay'; |
845
|
|
|
|
|
|
|
} |
846
|
|
|
|
|
|
|
else { |
847
|
233
|
|
|
|
|
659
|
$h->{$key} = substr $value, 0, 3; |
848
|
|
|
|
|
|
|
} |
849
|
|
|
|
|
|
|
} |
850
|
|
|
|
|
|
|
} |
851
|
236
|
|
100
|
|
|
1132
|
for ( $h->{modifier} || '' ) { |
852
|
236
|
|
|
|
|
337
|
when (/beg/) { $h->{modifier} = 'beginning' } |
|
0
|
|
|
|
|
0
|
|
853
|
236
|
|
|
|
|
315
|
when (/end/) { $h->{modifier} = 'end' } |
|
0
|
|
|
|
|
0
|
|
854
|
236
|
|
|
|
|
319
|
when (/las/) { $h->{modifier} = 'last' } |
|
45
|
|
|
|
|
161
|
|
855
|
191
|
|
|
|
|
249
|
when (/thi/) { $h->{modifier} = 'this' } |
|
3
|
|
|
|
|
12
|
|
856
|
188
|
|
|
|
|
397
|
when (/nex/) { $h->{modifier} = 'next' } |
|
0
|
|
|
|
|
0
|
|
857
|
|
|
|
|
|
|
} |
858
|
|
|
|
|
|
|
} |
859
|
|
|
|
|
|
|
} |
860
|
|
|
|
|
|
|
|
861
|
|
|
|
|
|
|
# whether the particular date expression refers to a fixed |
862
|
|
|
|
|
|
|
# rather than relative date |
863
|
|
|
|
|
|
|
sub is_fixed { |
864
|
719
|
|
|
719
|
0
|
931
|
my $h = shift; |
865
|
|
|
|
|
|
|
return 1 |
866
|
719
|
100
|
|
|
|
2142
|
if exists $h->{year}; |
867
|
289
|
100
|
|
|
|
685
|
if ( $h->{type} eq 'verbal' ) { |
868
|
207
|
100
|
|
|
|
487
|
if ( exists $h->{modifier} ) { |
869
|
101
|
50
|
|
|
|
672
|
return 1 if $h->{modifier} =~ /this|last|next/; |
870
|
|
|
|
|
|
|
} |
871
|
106
|
100
|
|
|
|
244
|
if ( exists $h->{day} ) { |
872
|
100
|
100
|
|
|
|
450
|
return 1 if $h->{day} =~ /yes|tod|tom/; |
873
|
|
|
|
|
|
|
} |
874
|
|
|
|
|
|
|
} |
875
|
173
|
|
|
|
|
418
|
return 0; |
876
|
|
|
|
|
|
|
} |
877
|
|
|
|
|
|
|
|
878
|
|
|
|
|
|
|
1; |
879
|
|
|
|
|
|
|
|
880
|
|
|
|
|
|
|
__END__ |