chronic_2001 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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