chronic_2011 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. data/.gitignore +6 -0
  2. data/HISTORY.md +4 -0
  3. data/LICENSE +21 -0
  4. data/README.md +180 -0
  5. data/Rakefile +46 -0
  6. data/chronic.gemspec +18 -0
  7. data/lib/chronic.rb +117 -0
  8. data/lib/chronic/chronic.rb +346 -0
  9. data/lib/chronic/grabber.rb +33 -0
  10. data/lib/chronic/handler.rb +88 -0
  11. data/lib/chronic/handlers.rb +553 -0
  12. data/lib/chronic/mini_date.rb +38 -0
  13. data/lib/chronic/numerizer.rb +121 -0
  14. data/lib/chronic/ordinal.rb +47 -0
  15. data/lib/chronic/pointer.rb +32 -0
  16. data/lib/chronic/repeater.rb +142 -0
  17. data/lib/chronic/repeaters/repeater_day.rb +53 -0
  18. data/lib/chronic/repeaters/repeater_day_name.rb +52 -0
  19. data/lib/chronic/repeaters/repeater_day_portion.rb +108 -0
  20. data/lib/chronic/repeaters/repeater_fortnight.rb +71 -0
  21. data/lib/chronic/repeaters/repeater_hour.rb +58 -0
  22. data/lib/chronic/repeaters/repeater_minute.rb +58 -0
  23. data/lib/chronic/repeaters/repeater_month.rb +79 -0
  24. data/lib/chronic/repeaters/repeater_month_name.rb +94 -0
  25. data/lib/chronic/repeaters/repeater_season.rb +109 -0
  26. data/lib/chronic/repeaters/repeater_season_name.rb +43 -0
  27. data/lib/chronic/repeaters/repeater_second.rb +42 -0
  28. data/lib/chronic/repeaters/repeater_time.rb +128 -0
  29. data/lib/chronic/repeaters/repeater_week.rb +74 -0
  30. data/lib/chronic/repeaters/repeater_weekday.rb +85 -0
  31. data/lib/chronic/repeaters/repeater_weekend.rb +66 -0
  32. data/lib/chronic/repeaters/repeater_year.rb +77 -0
  33. data/lib/chronic/scalar.rb +116 -0
  34. data/lib/chronic/season.rb +26 -0
  35. data/lib/chronic/separator.rb +94 -0
  36. data/lib/chronic/span.rb +31 -0
  37. data/lib/chronic/tag.rb +36 -0
  38. data/lib/chronic/time_zone.rb +32 -0
  39. data/lib/chronic/token.rb +47 -0
  40. data/test/helper.rb +12 -0
  41. data/test/test_chronic.rb +148 -0
  42. data/test/test_daylight_savings.rb +118 -0
  43. data/test/test_handler.rb +104 -0
  44. data/test/test_mini_date.rb +32 -0
  45. data/test/test_numerizer.rb +72 -0
  46. data/test/test_parsing.rb +977 -0
  47. data/test/test_repeater_day_name.rb +51 -0
  48. data/test/test_repeater_day_portion.rb +254 -0
  49. data/test/test_repeater_fortnight.rb +62 -0
  50. data/test/test_repeater_hour.rb +68 -0
  51. data/test/test_repeater_minute.rb +34 -0
  52. data/test/test_repeater_month.rb +50 -0
  53. data/test/test_repeater_month_name.rb +56 -0
  54. data/test/test_repeater_season.rb +40 -0
  55. data/test/test_repeater_time.rb +70 -0
  56. data/test/test_repeater_week.rb +62 -0
  57. data/test/test_repeater_weekday.rb +55 -0
  58. data/test/test_repeater_weekend.rb +74 -0
  59. data/test/test_repeater_year.rb +69 -0
  60. data/test/test_span.rb +23 -0
  61. data/test/test_token.rb +25 -0
  62. metadata +156 -0
@@ -0,0 +1,33 @@
1
+ module Chronic
2
+ class Grabber < Tag
3
+
4
+ # Scan an Array of Tokens and apply any necessary Grabber tags to
5
+ # each token.
6
+ #
7
+ # tokens - An Array of Token objects to scan.
8
+ # options - The Hash of options specified in Chronic::parse.
9
+ #
10
+ # Returns an Array of Token objects.
11
+ def self.scan(tokens, options)
12
+ tokens.each do |token|
13
+ if t = scan_for_all(token) then token.tag(t); next end
14
+ end
15
+ end
16
+
17
+ # token - The Token object to scan.
18
+ #
19
+ # Returns a new Grabber object.
20
+ def self.scan_for_all(token)
21
+ scan_for token, self,
22
+ {
23
+ /last/ => :last,
24
+ /this/ => :this,
25
+ /next/ => :next
26
+ }
27
+ end
28
+
29
+ def to_s
30
+ 'grabber-' << @type.to_s
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,88 @@
1
+ module Chronic
2
+ class Handler
3
+
4
+ attr_reader :pattern
5
+
6
+ attr_reader :handler_method
7
+
8
+ # pattern - An Array of patterns to match tokens against.
9
+ # handler_method - A Symbole representing the method to be invoked
10
+ # when patterns are matched.
11
+ def initialize(pattern, handler_method)
12
+ @pattern = pattern
13
+ @handler_method = handler_method
14
+ end
15
+
16
+ # tokens - An Array of tokens to process.
17
+ # definitions - A Hash of definitions to check against.
18
+ #
19
+ # Returns true if a match is found.
20
+ def match(tokens, definitions)
21
+ token_index = 0
22
+
23
+ @pattern.each do |element|
24
+ name = element.to_s
25
+ optional = name[-1, 1] == '?'
26
+ name = name.chop if optional
27
+
28
+ case element
29
+ when Symbol
30
+ if tags_match?(name, tokens, token_index)
31
+ token_index += 1
32
+ next
33
+ else
34
+ if optional
35
+ next
36
+ else
37
+ return false
38
+ end
39
+ end
40
+ when String
41
+ return true if optional && token_index == tokens.size
42
+
43
+ if definitions.key?(name.to_sym)
44
+ sub_handlers = definitions[name.to_sym]
45
+ else
46
+ raise ChronicPain, "Invalid subset #{name} specified"
47
+ end
48
+
49
+ sub_handlers.each do |sub_handler|
50
+ return true if sub_handler.match(tokens[token_index..tokens.size], definitions)
51
+ end
52
+ else
53
+ raise ChronicPain, "Invalid match type: #{element.class}"
54
+ end
55
+ end
56
+
57
+ return false if token_index != tokens.size
58
+ return true
59
+ end
60
+
61
+ def invoke(type, tokens, options)
62
+ if Chronic.debug
63
+ puts "-#{type}"
64
+ puts "Handler: #{@handler_method}"
65
+ end
66
+
67
+ Handlers.send(@handler_method, tokens, options)
68
+ end
69
+
70
+ # other - The other Handler object to compare.
71
+ #
72
+ # Returns true if these Handlers match.
73
+ def ==(other)
74
+ @pattern == other.pattern
75
+ end
76
+
77
+ private
78
+
79
+ def tags_match?(name, tokens, token_index)
80
+ klass = Chronic.const_get(name.to_s.gsub(/(?:^|_)(.)/) { $1.upcase })
81
+
82
+ if tokens[token_index]
83
+ !tokens[token_index].tags.select { |o| o.kind_of?(klass) }.empty?
84
+ end
85
+ end
86
+
87
+ end
88
+ end
@@ -0,0 +1,553 @@
1
+ module Chronic
2
+ module Handlers
3
+ module_function
4
+
5
+ # Handle month/day
6
+ def handle_m_d(month, day, time_tokens, options)
7
+ month.start = Chronic.now
8
+ span = month.this(options[:context])
9
+ year, month = span.begin.year, span.begin.month
10
+ day_start = Chronic.time_class.local(year, month, day)
11
+
12
+ day_or_time(day_start, time_tokens, options)
13
+ end
14
+
15
+ # Handle repeater-month-name/scalar-day
16
+ def handle_rmn_sd(tokens, options)
17
+ month = tokens[0].get_tag(RepeaterMonthName)
18
+ day = tokens[1].get_tag(ScalarDay).type
19
+
20
+ return if month_overflow?(Chronic.now.year, month.index, day)
21
+
22
+ handle_m_d(month, day, tokens[2..tokens.size], options)
23
+ end
24
+
25
+ # Handle repeater-month-name/scalar-day with separator-on
26
+ def handle_rmn_sd_on(tokens, options)
27
+ if tokens.size > 3
28
+ month = tokens[2].get_tag(RepeaterMonthName)
29
+ day = tokens[3].get_tag(ScalarDay).type
30
+ token_range = 0..1
31
+ else
32
+ month = tokens[1].get_tag(RepeaterMonthName)
33
+ day = tokens[2].get_tag(ScalarDay).type
34
+ token_range = 0..0
35
+ end
36
+
37
+ return if month_overflow?(Chronic.now.year, month.index, day)
38
+
39
+ handle_m_d(month, day, tokens[token_range], options)
40
+ end
41
+
42
+ # Handle repeater-month-name/ordinal-day
43
+ def handle_rmn_od(tokens, options)
44
+ month = tokens[0].get_tag(RepeaterMonthName)
45
+ day = tokens[1].get_tag(OrdinalDay).type
46
+
47
+ return if month_overflow?(Chronic.now.year, month.index, day)
48
+
49
+ handle_m_d(month, day, tokens[2..tokens.size], options)
50
+ end
51
+
52
+ # Handle ordinal-day/repeater-month-name
53
+ def handle_od_rmn(tokens, options)
54
+ month = tokens[1].get_tag(RepeaterMonthName)
55
+ day = tokens[0].get_tag(OrdinalDay).type
56
+
57
+ return if month_overflow?(Chronic.now.year, month.index, day)
58
+
59
+ handle_m_d(month, day, tokens[2..tokens.size], options)
60
+ end
61
+
62
+ def handle_sy_rmn_od(tokens, options)
63
+ year = tokens[0].get_tag(ScalarYear).type
64
+ month = tokens[1].get_tag(RepeaterMonthName).index
65
+ day = tokens[2].get_tag(OrdinalDay).type
66
+ time_tokens = tokens.last(tokens.size - 3)
67
+
68
+ return if month_overflow?(year, month, day)
69
+
70
+ begin
71
+ day_start = Chronic.time_class.local(year, month, day)
72
+ day_or_time(day_start, time_tokens, options)
73
+ rescue ArgumentError
74
+ nil
75
+ end
76
+ end
77
+
78
+ # Handle scalar-day/repeater-month-name
79
+ def handle_sd_rmn(tokens, options)
80
+ month = tokens[1].get_tag(RepeaterMonthName)
81
+ day = tokens[0].get_tag(ScalarDay).type
82
+
83
+ return if month_overflow?(Chronic.now.year, month.index, day)
84
+
85
+ handle_m_d(month, day, tokens[2..tokens.size], options)
86
+ end
87
+
88
+ # Handle repeater-month-name/ordinal-day with separator-on
89
+ def handle_rmn_od_on(tokens, options)
90
+ if tokens.size > 3
91
+ month = tokens[2].get_tag(RepeaterMonthName)
92
+ day = tokens[3].get_tag(OrdinalDay).type
93
+ token_range = 0..1
94
+ else
95
+ month = tokens[1].get_tag(RepeaterMonthName)
96
+ day = tokens[2].get_tag(OrdinalDay).type
97
+ token_range = 0..0
98
+ end
99
+
100
+ return if month_overflow?(Chronic.now.year, month.index, day)
101
+
102
+ handle_m_d(month, day, tokens[token_range], options)
103
+ end
104
+
105
+ # Handle repeater-month-name/scalar-year
106
+ def handle_rmn_sy(tokens, options)
107
+ month = tokens[0].get_tag(RepeaterMonthName).index
108
+ year = tokens[1].get_tag(ScalarYear).type
109
+
110
+ if month == 12
111
+ next_month_year = year + 1
112
+ next_month_month = 1
113
+ else
114
+ next_month_year = year
115
+ next_month_month = month + 1
116
+ end
117
+
118
+ begin
119
+ end_time = Chronic.time_class.local(next_month_year, next_month_month)
120
+ Span.new(Chronic.time_class.local(year, month), end_time)
121
+ rescue ArgumentError
122
+ nil
123
+ end
124
+ end
125
+
126
+ # Handle generic timestamp (ruby 1.8)
127
+ def handle_rdn_rmn_sd_t_tz_sy(tokens, options)
128
+ t = Chronic.time_class.parse(options[:text])
129
+ Span.new(t, t + 1)
130
+ end
131
+
132
+ # Handle generic timestamp (ruby 1.9)
133
+ def handle_sy_sm_sd_t_tz(tokens, options)
134
+ t = Chronic.time_class.parse(options[:text])
135
+ Span.new(t, t + 1)
136
+ end
137
+
138
+ # Handle repeater-month-name/scalar-day/scalar-year
139
+ def handle_rmn_sd_sy(tokens, options)
140
+ month = tokens[0].get_tag(RepeaterMonthName).index
141
+ day = tokens[1].get_tag(ScalarDay).type
142
+ year = tokens[2].get_tag(ScalarYear).type
143
+ time_tokens = tokens.last(tokens.size - 3)
144
+
145
+ return if month_overflow?(year, month, day)
146
+
147
+ begin
148
+ day_start = Chronic.time_class.local(year, month, day)
149
+ day_or_time(day_start, time_tokens, options)
150
+ rescue ArgumentError
151
+ nil
152
+ end
153
+ end
154
+
155
+ # Handle repeater-month-name/ordinal-day/scalar-year
156
+ def handle_rmn_od_sy(tokens, options)
157
+ month = tokens[0].get_tag(RepeaterMonthName).index
158
+ day = tokens[1].get_tag(OrdinalDay).type
159
+ year = tokens[2].get_tag(ScalarYear).type
160
+ time_tokens = tokens.last(tokens.size - 3)
161
+
162
+ return if month_overflow?(year, month, day)
163
+
164
+ begin
165
+ day_start = Chronic.time_class.local(year, month, day)
166
+ day_or_time(day_start, time_tokens, options)
167
+ rescue ArgumentError
168
+ nil
169
+ end
170
+ end
171
+
172
+ # Handle oridinal-day/repeater-month-name/scalar-year
173
+ def handle_od_rmn_sy(tokens, options)
174
+ day = tokens[0].get_tag(OrdinalDay).type
175
+ month = tokens[1].get_tag(RepeaterMonthName).index
176
+ year = tokens[2].get_tag(ScalarYear).type
177
+ time_tokens = tokens.last(tokens.size - 3)
178
+
179
+ return if month_overflow?(year, month, day)
180
+
181
+ begin
182
+ day_start = Chronic.time_class.local(year, month, day)
183
+ day_or_time(day_start, time_tokens, options)
184
+ rescue ArgumentError
185
+ nil
186
+ end
187
+ end
188
+
189
+ # Handle scalar-day/repeater-month-name/scalar-year
190
+ def handle_sd_rmn_sy(tokens, options)
191
+ new_tokens = [tokens[1], tokens[0], tokens[2]]
192
+ time_tokens = tokens.last(tokens.size - 3)
193
+ handle_rmn_sd_sy(new_tokens + time_tokens, options)
194
+ end
195
+
196
+ # Handle scalar-month/scalar-day/scalar-year (endian middle)
197
+ def handle_sm_sd_sy(tokens, options)
198
+ month = tokens[0].get_tag(ScalarMonth).type
199
+ day = tokens[1].get_tag(ScalarDay).type
200
+ year = tokens[2].get_tag(ScalarYear).type
201
+ time_tokens = tokens.last(tokens.size - 3)
202
+
203
+ return if month_overflow?(year, month, day)
204
+
205
+ begin
206
+ day_start = Chronic.time_class.local(year, month, day)
207
+ day_or_time(day_start, time_tokens, options)
208
+ rescue ArgumentError
209
+ nil
210
+ end
211
+ end
212
+
213
+ # Handle scalar-day/scalar-month/scalar-year (endian little)
214
+ def handle_sd_sm_sy(tokens, options)
215
+ new_tokens = [tokens[1], tokens[0], tokens[2]]
216
+ time_tokens = tokens.last(tokens.size - 3)
217
+ handle_sm_sd_sy(new_tokens + time_tokens, options)
218
+ end
219
+
220
+ # Handle scalar-year/scalar-month/scalar-day
221
+ def handle_sy_sm_sd(tokens, options)
222
+ new_tokens = [tokens[1], tokens[2], tokens[0]]
223
+ time_tokens = tokens.last(tokens.size - 3)
224
+ handle_sm_sd_sy(new_tokens + time_tokens, options)
225
+ end
226
+
227
+ # Handle scalar-day/scalar-month AND scalar-month/scalar-day
228
+ def handle_sm_sd(tokens, options)
229
+ month = tokens[0].get_tag(ScalarMonth).type
230
+ day = tokens[1].get_tag(ScalarDay).type
231
+ year = Chronic.now.year
232
+
233
+ if Array(options[:endian_precedence]).first == :little
234
+ day, month = month, day
235
+ end
236
+
237
+ return if month_overflow?(year, month, day)
238
+
239
+ begin
240
+ start_time = Chronic.time_class.local(year, month, day)
241
+ end_time = Chronic.time_class.local(year, month, day + 1)
242
+ Span.new(start_time, end_time)
243
+ rescue ArgumentError
244
+ nil
245
+ end
246
+ end
247
+
248
+ # Handle scalar-month/scalar-year
249
+ def handle_sm_sy(tokens, options)
250
+ month = tokens[0].get_tag(ScalarMonth).type
251
+ year = tokens[1].get_tag(ScalarYear).type
252
+
253
+ if month == 12
254
+ next_month_year = year + 1
255
+ next_month_month = 1
256
+ else
257
+ next_month_year = year
258
+ next_month_month = month + 1
259
+ end
260
+
261
+ begin
262
+ end_time = Chronic.time_class.local(next_month_year, next_month_month)
263
+ Span.new(Chronic.time_class.local(year, month), end_time)
264
+ rescue ArgumentError
265
+ nil
266
+ end
267
+ end
268
+
269
+ # Handle RepeaterDayName RepeaterMonthName OrdinalDay
270
+ def handle_rdn_rmn_od(tokens, options)
271
+ month = tokens[1].get_tag(RepeaterMonthName)
272
+ day = tokens[2].get_tag(OrdinalDay).type
273
+ year = Chronic.now.year
274
+
275
+ return if month_overflow?(year, month.index, day)
276
+
277
+ begin
278
+ start_time = Chronic.time_class.local(year, month.index, day)
279
+ end_time = time_with_rollover(year, month.index, day + 1)
280
+ Span.new(start_time, end_time)
281
+ rescue ArgumentError
282
+ nil
283
+ end
284
+ end
285
+
286
+ # Handle RepeaterDayName RepeaterMonthName ScalarDay
287
+ def handle_rdn_rmn_sd(tokens, options)
288
+ month = tokens[1].get_tag(RepeaterMonthName)
289
+ day = tokens[2].get_tag(ScalarDay).type
290
+ year = Chronic.now.year
291
+
292
+ return if month_overflow?(year, month.index, day)
293
+
294
+ begin
295
+ start_time = Chronic.time_class.local(year, month.index, day)
296
+ end_time = time_with_rollover(year, month.index, day + 1)
297
+ Span.new(start_time, end_time)
298
+ rescue ArgumentError
299
+ nil
300
+ end
301
+ end
302
+
303
+ # Handle RepeaterDayName RepeaterMonthName ScalarDay ScalarYear
304
+ def handle_rdn_rmn_sd_sy(tokens, options)
305
+ month = tokens[1].get_tag(RepeaterMonthName)
306
+ day = tokens[2].get_tag(ScalarDay).type
307
+ year = tokens[3].get_tag(ScalarYear).type
308
+
309
+ return if month_overflow?(year, month.index, day)
310
+
311
+ begin
312
+ start_time = Chronic.time_class.local(year, month.index, day)
313
+ end_time = time_with_rollover(year, month.index, day + 1)
314
+ Span.new(start_time, end_time)
315
+ rescue ArgumentError
316
+ nil
317
+ end
318
+ end
319
+
320
+ # anchors
321
+
322
+ # Handle repeaters
323
+ def handle_r(tokens, options)
324
+ dd_tokens = dealias_and_disambiguate_times(tokens, options)
325
+ get_anchor(dd_tokens, options)
326
+ end
327
+
328
+ # Handle repeater/grabber/repeater
329
+ def handle_r_g_r(tokens, options)
330
+ new_tokens = [tokens[1], tokens[0], tokens[2]]
331
+ handle_r(new_tokens, options)
332
+ end
333
+
334
+ # arrows
335
+
336
+ # Handle scalar/repeater/pointer helper
337
+ def handle_srp(tokens, span, options)
338
+ distance = tokens[0].get_tag(Scalar).type
339
+ repeater = tokens[1].get_tag(Repeater)
340
+ pointer = tokens[2].get_tag(Pointer).type
341
+
342
+ repeater.offset(span, distance, pointer)
343
+ end
344
+
345
+ # Handle scalar/repeater/pointer
346
+ def handle_s_r_p(tokens, options)
347
+ repeater = tokens[1].get_tag(Repeater)
348
+ span = Span.new(Chronic.now, Chronic.now + 1)
349
+
350
+ handle_srp(tokens, span, options)
351
+ end
352
+
353
+ # Handle pointer/scalar/repeater
354
+ def handle_p_s_r(tokens, options)
355
+ new_tokens = [tokens[1], tokens[2], tokens[0]]
356
+ handle_s_r_p(new_tokens, options)
357
+ end
358
+
359
+ # Handle scalar/repeater/pointer/anchor
360
+ def handle_s_r_p_a(tokens, options)
361
+ anchor_span = get_anchor(tokens[3..tokens.size - 1], options)
362
+ handle_srp(tokens, anchor_span, options)
363
+ end
364
+
365
+ # narrows
366
+
367
+ # Handle oridinal repeaters
368
+ def handle_orr(tokens, outer_span, options)
369
+ repeater = tokens[1].get_tag(Repeater)
370
+ repeater.start = outer_span.begin - 1
371
+ ordinal = tokens[0].get_tag(Ordinal).type
372
+ span = nil
373
+
374
+ ordinal.times do
375
+ span = repeater.next(:future)
376
+
377
+ if span.begin >= outer_span.end
378
+ span = nil
379
+ break
380
+ end
381
+ end
382
+
383
+ span
384
+ end
385
+
386
+ # Handle ordinal/repeater/separator/repeater
387
+ def handle_o_r_s_r(tokens, options)
388
+ outer_span = get_anchor([tokens[3]], options)
389
+ handle_orr(tokens[0..1], outer_span, options)
390
+ end
391
+
392
+ # Handle ordinal/repeater/grabber/repeater
393
+ def handle_o_r_g_r(tokens, options)
394
+ outer_span = get_anchor(tokens[2..3], options)
395
+ handle_orr(tokens[0..1], outer_span, options)
396
+ end
397
+
398
+ # support methods
399
+
400
+ def day_or_time(day_start, time_tokens, options)
401
+ outer_span = Span.new(day_start, day_start + (24 * 60 * 60))
402
+
403
+ if !time_tokens.empty?
404
+ Chronic.now = outer_span.begin
405
+ get_anchor(dealias_and_disambiguate_times(time_tokens, options), options)
406
+ else
407
+ outer_span
408
+ end
409
+ end
410
+
411
+ def get_anchor(tokens, options)
412
+ grabber = Grabber.new(:this)
413
+ pointer = :future
414
+
415
+ repeaters = get_repeaters(tokens)
416
+ repeaters.size.times { tokens.pop }
417
+
418
+ if tokens.first && tokens.first.get_tag(Grabber)
419
+ grabber = tokens.shift.get_tag(Grabber)
420
+ end
421
+
422
+ head = repeaters.shift
423
+ head.start = Chronic.now
424
+
425
+ case grabber.type
426
+ when :last
427
+ outer_span = head.next(:past)
428
+ when :this
429
+ if options[:context] != :past and repeaters.size > 0
430
+ outer_span = head.this(:none)
431
+ else
432
+ outer_span = head.this(options[:context])
433
+ end
434
+ when :next
435
+ outer_span = head.next(:future)
436
+ else
437
+ raise ChronicPain, "Invalid grabber"
438
+ end
439
+
440
+ if Chronic.debug
441
+ puts "Handler-class: #{head.class}"
442
+ puts "--#{outer_span}"
443
+ end
444
+
445
+ find_within(repeaters, outer_span, pointer)
446
+ end
447
+
448
+ def get_repeaters(tokens)
449
+ tokens.map { |token| token.get_tag(Repeater) }.compact.sort.reverse
450
+ end
451
+
452
+ def month_overflow?(year, month, day)
453
+ if Date.leap?(year)
454
+ day > RepeaterMonth::MONTH_DAYS_LEAP[month - 1]
455
+ else
456
+ day > RepeaterMonth::MONTH_DAYS[month - 1]
457
+ end
458
+ rescue ArgumentError
459
+ false
460
+ end
461
+
462
+ # Recursively finds repeaters within other repeaters.
463
+ # Returns a Span representing the innermost time span
464
+ # or nil if no repeater union could be found
465
+ def find_within(tags, span, pointer)
466
+ puts "--#{span}" if Chronic.debug
467
+ return span if tags.empty?
468
+
469
+ head = tags.shift
470
+ head.start = (pointer == :future ? span.begin : span.end)
471
+ h = head.this(:none)
472
+
473
+ if span.cover?(h.begin) || span.cover?(h.end)
474
+ find_within(tags, h, pointer)
475
+ end
476
+ end
477
+
478
+ def time_with_rollover(year, month, day)
479
+ date_parts =
480
+ if month_overflow?(year, month, day)
481
+ if month == 12
482
+ [year + 1, 1, 1]
483
+ else
484
+ [year, month + 1, 1]
485
+ end
486
+ else
487
+ [year, month, day]
488
+ end
489
+ Chronic.time_class.local(*date_parts)
490
+ end
491
+
492
+ def dealias_and_disambiguate_times(tokens, options)
493
+ # handle aliases of am/pm
494
+ # 5:00 in the morning -> 5:00 am
495
+ # 7:00 in the evening -> 7:00 pm
496
+
497
+ day_portion_index = nil
498
+ tokens.each_with_index do |t, i|
499
+ if t.get_tag(RepeaterDayPortion)
500
+ day_portion_index = i
501
+ break
502
+ end
503
+ end
504
+
505
+ time_index = nil
506
+ tokens.each_with_index do |t, i|
507
+ if t.get_tag(RepeaterTime)
508
+ time_index = i
509
+ break
510
+ end
511
+ end
512
+
513
+ if day_portion_index && time_index
514
+ t1 = tokens[day_portion_index]
515
+ t1tag = t1.get_tag(RepeaterDayPortion)
516
+
517
+ case t1tag.type
518
+ when :morning
519
+ puts '--morning->am' if Chronic.debug
520
+ t1.untag(RepeaterDayPortion)
521
+ t1.tag(RepeaterDayPortion.new(:am))
522
+ when :afternoon, :evening, :night
523
+ puts "--#{t1tag.type}->pm" if Chronic.debug
524
+ t1.untag(RepeaterDayPortion)
525
+ t1.tag(RepeaterDayPortion.new(:pm))
526
+ end
527
+ end
528
+
529
+ # handle ambiguous times if :ambiguous_time_range is specified
530
+ if options[:ambiguous_time_range] != :none
531
+ ambiguous_tokens = []
532
+
533
+ tokens.each_with_index do |token, i|
534
+ ambiguous_tokens << token
535
+ next_token = tokens[i + 1]
536
+
537
+ if token.get_tag(RepeaterTime) && token.get_tag(RepeaterTime).type.ambiguous? && (!next_token || !next_token.get_tag(RepeaterDayPortion))
538
+ distoken = Token.new('disambiguator')
539
+
540
+ distoken.tag(RepeaterDayPortion.new(options[:ambiguous_time_range]))
541
+ ambiguous_tokens << distoken
542
+ end
543
+ end
544
+
545
+ tokens = ambiguous_tokens
546
+ end
547
+
548
+ tokens
549
+ end
550
+
551
+ end
552
+
553
+ end