chronic 0.6.3 → 0.10.2

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 (67) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -3
  3. data/.travis.yml +8 -0
  4. data/HISTORY.md +76 -0
  5. data/README.md +47 -42
  6. data/Rakefile +30 -12
  7. data/chronic.gemspec +10 -4
  8. data/lib/chronic/date.rb +82 -0
  9. data/lib/chronic/grabber.rb +9 -7
  10. data/lib/chronic/handler.rb +47 -40
  11. data/lib/chronic/handlers.rb +240 -28
  12. data/lib/chronic/numerizer.rb +11 -2
  13. data/lib/chronic/ordinal.rb +28 -23
  14. data/lib/chronic/parser.rb +268 -0
  15. data/lib/chronic/pointer.rb +9 -7
  16. data/lib/chronic/repeater.rb +58 -48
  17. data/lib/chronic/repeaters/repeater_day.rb +4 -3
  18. data/lib/chronic/repeaters/repeater_day_name.rb +5 -4
  19. data/lib/chronic/repeaters/repeater_day_portion.rb +33 -18
  20. data/lib/chronic/repeaters/repeater_fortnight.rb +4 -3
  21. data/lib/chronic/repeaters/repeater_hour.rb +4 -3
  22. data/lib/chronic/repeaters/repeater_minute.rb +4 -3
  23. data/lib/chronic/repeaters/repeater_month.rb +5 -4
  24. data/lib/chronic/repeaters/repeater_month_name.rb +4 -3
  25. data/lib/chronic/repeaters/repeater_season.rb +5 -3
  26. data/lib/chronic/repeaters/repeater_second.rb +4 -3
  27. data/lib/chronic/repeaters/repeater_time.rb +35 -25
  28. data/lib/chronic/repeaters/repeater_week.rb +4 -3
  29. data/lib/chronic/repeaters/repeater_weekday.rb +4 -3
  30. data/lib/chronic/repeaters/repeater_weekend.rb +4 -3
  31. data/lib/chronic/repeaters/repeater_year.rb +6 -5
  32. data/lib/chronic/scalar.rb +40 -68
  33. data/lib/chronic/season.rb +1 -12
  34. data/lib/chronic/separator.rb +142 -23
  35. data/lib/chronic/sign.rb +49 -0
  36. data/lib/chronic/span.rb +2 -2
  37. data/lib/chronic/tag.rb +10 -15
  38. data/lib/chronic/time.rb +40 -0
  39. data/lib/chronic/time_zone.rb +9 -7
  40. data/lib/chronic/token.rb +16 -10
  41. data/lib/chronic.rb +109 -70
  42. data/test/helper.rb +8 -2
  43. data/test/{test_Chronic.rb → test_chronic.rb} +69 -34
  44. data/test/{test_DaylightSavings.rb → test_daylight_savings.rb} +1 -1
  45. data/test/{test_Handler.rb → test_handler.rb} +38 -14
  46. data/test/{test_MiniDate.rb → test_mini_date.rb} +9 -9
  47. data/test/{test_Numerizer.rb → test_numerizer.rb} +16 -2
  48. data/test/test_parsing.rb +392 -22
  49. data/test/{test_RepeaterDayName.rb → test_repeater_day_name.rb} +1 -1
  50. data/test/test_repeater_day_portion.rb +254 -0
  51. data/test/{test_RepeaterFortnight.rb → test_repeater_fortnight.rb} +1 -1
  52. data/test/{test_RepeaterHour.rb → test_repeater_hour.rb} +1 -1
  53. data/test/{test_RepeaterMinute.rb → test_repeater_minute.rb} +1 -1
  54. data/test/{test_RepeaterMonth.rb → test_repeater_month.rb} +1 -1
  55. data/test/{test_RepeaterMonthName.rb → test_repeater_month_name.rb} +1 -1
  56. data/test/{test_RepeaterSeason.rb → test_repeater_season.rb} +1 -1
  57. data/test/{test_RepeaterTime.rb → test_repeater_time.rb} +19 -1
  58. data/test/{test_RepeaterWeek.rb → test_repeater_week.rb} +1 -1
  59. data/test/{test_RepeaterWeekday.rb → test_repeater_weekday.rb} +1 -1
  60. data/test/{test_RepeaterWeekend.rb → test_repeater_weekend.rb} +1 -1
  61. data/test/{test_RepeaterYear.rb → test_repeater_year.rb} +1 -1
  62. data/test/{test_Span.rb → test_span.rb} +2 -2
  63. data/test/{test_Token.rb → test_token.rb} +1 -1
  64. metadata +129 -87
  65. data/.gemtest +0 -0
  66. data/.yardopts +0 -3
  67. data/lib/chronic/chronic.rb +0 -323
@@ -4,7 +4,7 @@ module Chronic
4
4
 
5
5
  # Handle month/day
6
6
  def handle_m_d(month, day, time_tokens, options)
7
- month.start = Chronic.now
7
+ month.start = self.now
8
8
  span = month.this(options[:context])
9
9
  year, month = span.begin.year, span.begin.month
10
10
  day_start = Chronic.time_class.local(year, month, day)
@@ -17,7 +17,7 @@ module Chronic
17
17
  month = tokens[0].get_tag(RepeaterMonthName)
18
18
  day = tokens[1].get_tag(ScalarDay).type
19
19
 
20
- return if month_overflow?(Chronic.now.year, month.index, day)
20
+ return if month_overflow?(self.now.year, month.index, day)
21
21
 
22
22
  handle_m_d(month, day, tokens[2..tokens.size], options)
23
23
  end
@@ -34,7 +34,7 @@ module Chronic
34
34
  token_range = 0..0
35
35
  end
36
36
 
37
- return if month_overflow?(Chronic.now.year, month.index, day)
37
+ return if month_overflow?(self.now.year, month.index, day)
38
38
 
39
39
  handle_m_d(month, day, tokens[token_range], options)
40
40
  end
@@ -44,17 +44,50 @@ module Chronic
44
44
  month = tokens[0].get_tag(RepeaterMonthName)
45
45
  day = tokens[1].get_tag(OrdinalDay).type
46
46
 
47
- return if month_overflow?(Chronic.now.year, month.index, day)
47
+ return if month_overflow?(self.now.year, month.index, day)
48
48
 
49
49
  handle_m_d(month, day, tokens[2..tokens.size], options)
50
50
  end
51
51
 
52
+ # Handle ordinal this month
53
+ def handle_od_rm(tokens, options)
54
+ day = tokens[0].get_tag(OrdinalDay).type
55
+ month = tokens[2].get_tag(RepeaterMonth)
56
+ handle_m_d(month, day, tokens[3..tokens.size], options)
57
+ end
58
+
52
59
  # Handle ordinal-day/repeater-month-name
53
60
  def handle_od_rmn(tokens, options)
54
61
  month = tokens[1].get_tag(RepeaterMonthName)
55
62
  day = tokens[0].get_tag(OrdinalDay).type
56
63
 
57
- return if month_overflow?(Chronic.now.year, month.index, day)
64
+ return if month_overflow?(self.now.year, month.index, day)
65
+
66
+ handle_m_d(month, day, tokens[2..tokens.size], options)
67
+ end
68
+
69
+ def handle_sy_rmn_od(tokens, options)
70
+ year = tokens[0].get_tag(ScalarYear).type
71
+ month = tokens[1].get_tag(RepeaterMonthName).index
72
+ day = tokens[2].get_tag(OrdinalDay).type
73
+ time_tokens = tokens.last(tokens.size - 3)
74
+
75
+ return if month_overflow?(year, month, day)
76
+
77
+ begin
78
+ day_start = Chronic.time_class.local(year, month, day)
79
+ day_or_time(day_start, time_tokens, options)
80
+ rescue ArgumentError
81
+ nil
82
+ end
83
+ end
84
+
85
+ # Handle scalar-day/repeater-month-name
86
+ def handle_sd_rmn(tokens, options)
87
+ month = tokens[1].get_tag(RepeaterMonthName)
88
+ day = tokens[0].get_tag(ScalarDay).type
89
+
90
+ return if month_overflow?(self.now.year, month.index, day)
58
91
 
59
92
  handle_m_d(month, day, tokens[2..tokens.size], options)
60
93
  end
@@ -71,7 +104,7 @@ module Chronic
71
104
  token_range = 0..0
72
105
  end
73
106
 
74
- return if month_overflow?(Chronic.now.year, month.index, day)
107
+ return if month_overflow?(self.now.year, month.index, day)
75
108
 
76
109
  handle_m_d(month, day, tokens[token_range], options)
77
110
  end
@@ -98,15 +131,11 @@ module Chronic
98
131
  end
99
132
 
100
133
  # Handle generic timestamp (ruby 1.8)
101
- def handle_rdn_rmn_sd_t_tz_sy(tokens, options)
102
- t = Chronic.time_class.parse(options[:text])
103
- Span.new(t, t + 1)
104
- end
105
-
106
- # Handle generic timestamp (ruby 1.9)
107
- def handle_sy_sm_sd_t_tz(tokens, options)
134
+ def handle_generic(tokens, options)
108
135
  t = Chronic.time_class.parse(options[:text])
109
136
  Span.new(t, t + 1)
137
+ rescue ArgumentError => e
138
+ raise e unless e.message =~ /out of range/
110
139
  end
111
140
 
112
141
  # Handle repeater-month-name/scalar-day/scalar-year
@@ -198,11 +227,32 @@ module Chronic
198
227
  handle_sm_sd_sy(new_tokens + time_tokens, options)
199
228
  end
200
229
 
201
- # Handle scalar-month/scalar-year
202
- def handle_sm_sy(tokens, options)
230
+ # Handle scalar-month/scalar-day
231
+ def handle_sm_sd(tokens, options)
203
232
  month = tokens[0].get_tag(ScalarMonth).type
204
- year = tokens[1].get_tag(ScalarYear).type
233
+ day = tokens[1].get_tag(ScalarDay).type
234
+ year = self.now.year
235
+ time_tokens = tokens.last(tokens.size - 2)
236
+
237
+ return if month_overflow?(year, month, day)
238
+
239
+ begin
240
+ day_start = Chronic.time_class.local(year, month, day)
241
+ day_start = Chronic.time_class.local(year + 1, month, day) if options[:context] == :future && day_start < now
242
+ day_or_time(day_start, time_tokens, options)
243
+ rescue ArgumentError
244
+ nil
245
+ end
246
+ end
247
+
248
+ # Handle scalar-day/scalar-month
249
+ def handle_sd_sm(tokens, options)
250
+ new_tokens = [tokens[1], tokens[0]]
251
+ time_tokens = tokens.last(tokens.size - 2)
252
+ handle_sm_sd(new_tokens + time_tokens, options)
253
+ end
205
254
 
255
+ def handle_year_and_month(year, month)
206
256
  if month == 12
207
257
  next_month_year = year + 1
208
258
  next_month_month = 1
@@ -219,6 +269,143 @@ module Chronic
219
269
  end
220
270
  end
221
271
 
272
+ # Handle scalar-month/scalar-year
273
+ def handle_sm_sy(tokens, options)
274
+ month = tokens[0].get_tag(ScalarMonth).type
275
+ year = tokens[1].get_tag(ScalarYear).type
276
+ handle_year_and_month(year, month)
277
+ end
278
+
279
+ # Handle scalar-year/scalar-month
280
+ def handle_sy_sm(tokens, options)
281
+ year = tokens[0].get_tag(ScalarYear).type
282
+ month = tokens[1].get_tag(ScalarMonth).type
283
+ handle_year_and_month(year, month)
284
+ end
285
+
286
+ # Handle RepeaterDayName RepeaterMonthName OrdinalDay
287
+ def handle_rdn_rmn_od(tokens, options)
288
+ month = tokens[1].get_tag(RepeaterMonthName)
289
+ day = tokens[2].get_tag(OrdinalDay).type
290
+ time_tokens = tokens.last(tokens.size - 3)
291
+ year = self.now.year
292
+
293
+ return if month_overflow?(year, month.index, day)
294
+
295
+ begin
296
+ if time_tokens.empty?
297
+ start_time = Chronic.time_class.local(year, month.index, day)
298
+ end_time = time_with_rollover(year, month.index, day + 1)
299
+ Span.new(start_time, end_time)
300
+ else
301
+ day_start = Chronic.time_class.local(year, month.index, day)
302
+ day_or_time(day_start, time_tokens, options)
303
+ end
304
+ rescue ArgumentError
305
+ nil
306
+ end
307
+ end
308
+
309
+ # Handle RepeaterDayName RepeaterMonthName OrdinalDay ScalarYear
310
+ def handle_rdn_rmn_od_sy(tokens, options)
311
+ month = tokens[1].get_tag(RepeaterMonthName)
312
+ day = tokens[2].get_tag(OrdinalDay).type
313
+ year = tokens[3].get_tag(ScalarYear).type
314
+
315
+ return if month_overflow?(year, month.index, day)
316
+
317
+ begin
318
+ start_time = Chronic.time_class.local(year, month.index, day)
319
+ end_time = time_with_rollover(year, month.index, day + 1)
320
+ Span.new(start_time, end_time)
321
+ rescue ArgumentError
322
+ nil
323
+ end
324
+ end
325
+
326
+ # Handle RepeaterDayName OrdinalDay
327
+ def handle_rdn_od(tokens, options)
328
+ day = tokens[1].get_tag(OrdinalDay).type
329
+ time_tokens = tokens.last(tokens.size - 2)
330
+ year = self.now.year
331
+ month = self.now.month
332
+ if options[:context] == :future
333
+ self.now.day > day ? month += 1 : month
334
+ end
335
+
336
+ return if month_overflow?(year, month, day)
337
+
338
+ begin
339
+ if time_tokens.empty?
340
+ start_time = Chronic.time_class.local(year, month, day)
341
+ end_time = time_with_rollover(year, month, day + 1)
342
+ Span.new(start_time, end_time)
343
+ else
344
+ day_start = Chronic.time_class.local(year, month, day)
345
+ day_or_time(day_start, time_tokens, options)
346
+ end
347
+ rescue ArgumentError
348
+ nil
349
+ end
350
+ end
351
+
352
+ # Handle RepeaterDayName RepeaterMonthName ScalarDay
353
+ def handle_rdn_rmn_sd(tokens, options)
354
+ month = tokens[1].get_tag(RepeaterMonthName)
355
+ day = tokens[2].get_tag(ScalarDay).type
356
+ time_tokens = tokens.last(tokens.size - 3)
357
+ year = self.now.year
358
+
359
+ return if month_overflow?(year, month.index, day)
360
+
361
+ begin
362
+ if time_tokens.empty?
363
+ start_time = Chronic.time_class.local(year, month.index, day)
364
+ end_time = time_with_rollover(year, month.index, day + 1)
365
+ Span.new(start_time, end_time)
366
+ else
367
+ day_start = Chronic.time_class.local(year, month.index, day)
368
+ day_or_time(day_start, time_tokens, options)
369
+ end
370
+ rescue ArgumentError
371
+ nil
372
+ end
373
+ end
374
+
375
+ # Handle RepeaterDayName RepeaterMonthName ScalarDay ScalarYear
376
+ def handle_rdn_rmn_sd_sy(tokens, options)
377
+ month = tokens[1].get_tag(RepeaterMonthName)
378
+ day = tokens[2].get_tag(ScalarDay).type
379
+ year = tokens[3].get_tag(ScalarYear).type
380
+
381
+ return if month_overflow?(year, month.index, day)
382
+
383
+ begin
384
+ start_time = Chronic.time_class.local(year, month.index, day)
385
+ end_time = time_with_rollover(year, month.index, day + 1)
386
+ Span.new(start_time, end_time)
387
+ rescue ArgumentError
388
+ nil
389
+ end
390
+ end
391
+
392
+ def handle_sm_rmn_sy(tokens, options)
393
+ day = tokens[0].get_tag(ScalarDay).type
394
+ month = tokens[1].get_tag(RepeaterMonthName).index
395
+ year = tokens[2].get_tag(ScalarYear).type
396
+ if tokens.size > 3
397
+ time = get_anchor([tokens.last], options).begin
398
+ h, m, s = time.hour, time.min, time.sec
399
+ time = Chronic.time_class.local(year, month, day, h, m, s)
400
+ end_time = Chronic.time_class.local(year, month, day + 1, h, m, s)
401
+ else
402
+ time = Chronic.time_class.local(year, month, day)
403
+ day += 1 unless day >= 31
404
+ end_time = Chronic.time_class.local(year, month, day)
405
+ end
406
+ Span.new(time, end_time)
407
+ end
408
+
222
409
  # anchors
223
410
 
224
411
  # Handle repeaters
@@ -241,13 +428,12 @@ module Chronic
241
428
  repeater = tokens[1].get_tag(Repeater)
242
429
  pointer = tokens[2].get_tag(Pointer).type
243
430
 
244
- repeater.offset(span, distance, pointer)
431
+ repeater.offset(span, distance, pointer) if repeater.respond_to?(:offset)
245
432
  end
246
433
 
247
434
  # Handle scalar/repeater/pointer
248
435
  def handle_s_r_p(tokens, options)
249
- repeater = tokens[1].get_tag(Repeater)
250
- span = Span.new(Chronic.now, Chronic.now + 1)
436
+ span = Span.new(self.now, self.now + 1)
251
437
 
252
438
  handle_srp(tokens, span, options)
253
439
  end
@@ -264,6 +450,13 @@ module Chronic
264
450
  handle_srp(tokens, anchor_span, options)
265
451
  end
266
452
 
453
+ def handle_s_r_a_s_r_p_a(tokens, options)
454
+ anchor_span = get_anchor(tokens[4..tokens.size - 1], options)
455
+
456
+ span = handle_srp(tokens[0..1]+tokens[4..6], anchor_span, options)
457
+ handle_srp(tokens[2..3]+tokens[4..6], span, options)
458
+ end
459
+
267
460
  # narrows
268
461
 
269
462
  # Handle oridinal repeaters
@@ -276,7 +469,7 @@ module Chronic
276
469
  ordinal.times do
277
470
  span = repeater.next(:future)
278
471
 
279
- if span.begin > outer_span.end
472
+ if span.begin >= outer_span.end
280
473
  span = nil
281
474
  break
282
475
  end
@@ -302,9 +495,9 @@ module Chronic
302
495
  def day_or_time(day_start, time_tokens, options)
303
496
  outer_span = Span.new(day_start, day_start + (24 * 60 * 60))
304
497
 
305
- if !time_tokens.empty?
306
- Chronic.now = outer_span.begin
307
- get_anchor(dealias_and_disambiguate_times(time_tokens, options), options)
498
+ unless time_tokens.empty?
499
+ self.now = outer_span.begin
500
+ get_anchor(dealias_and_disambiguate_times(time_tokens, options), options.merge(:context => :future))
308
501
  else
309
502
  outer_span
310
503
  end
@@ -313,7 +506,6 @@ module Chronic
313
506
  def get_anchor(tokens, options)
314
507
  grabber = Grabber.new(:this)
315
508
  pointer = :future
316
-
317
509
  repeaters = get_repeaters(tokens)
318
510
  repeaters.size.times { tokens.pop }
319
511
 
@@ -322,7 +514,7 @@ module Chronic
322
514
  end
323
515
 
324
516
  head = repeaters.shift
325
- head.start = Chronic.now
517
+ head.start = self.now
326
518
 
327
519
  case grabber.type
328
520
  when :last
@@ -336,10 +528,14 @@ module Chronic
336
528
  when :next
337
529
  outer_span = head.next(:future)
338
530
  else
339
- raise ChronicPain, "Invalid grabber"
531
+ raise "Invalid grabber"
532
+ end
533
+
534
+ if Chronic.debug
535
+ puts "Handler-class: #{head.class}"
536
+ puts "--#{outer_span}"
340
537
  end
341
538
 
342
- puts "--#{outer_span}" if Chronic.debug
343
539
  find_within(repeaters, outer_span, pointer)
344
540
  end
345
541
 
@@ -348,11 +544,13 @@ module Chronic
348
544
  end
349
545
 
350
546
  def month_overflow?(year, month, day)
351
- if Date.leap?(year)
547
+ if ::Date.leap?(year)
352
548
  day > RepeaterMonth::MONTH_DAYS_LEAP[month - 1]
353
549
  else
354
550
  day > RepeaterMonth::MONTH_DAYS[month - 1]
355
551
  end
552
+ rescue ArgumentError
553
+ false
356
554
  end
357
555
 
358
556
  # Recursively finds repeaters within other repeaters.
@@ -371,6 +569,20 @@ module Chronic
371
569
  end
372
570
  end
373
571
 
572
+ def time_with_rollover(year, month, day)
573
+ date_parts =
574
+ if month_overflow?(year, month, day)
575
+ if month == 12
576
+ [year + 1, 1, 1]
577
+ else
578
+ [year, month + 1, 1]
579
+ end
580
+ else
581
+ [year, month, day]
582
+ end
583
+ Chronic.time_class.local(*date_parts)
584
+ end
585
+
374
586
  def dealias_and_disambiguate_times(tokens, options)
375
587
  # handle aliases of am/pm
376
588
  # 5:00 in the morning -> 5:00 am
@@ -37,7 +37,16 @@ module Chronic
37
37
  ['seventh', '7'],
38
38
  ['eighth', '8'],
39
39
  ['ninth', '9'],
40
- ['tenth', '10']
40
+ ['tenth', '10'],
41
+ ['twelfth', '12'],
42
+ ['twentieth', '20'],
43
+ ['thirtieth', '30'],
44
+ ['fourtieth', '40'],
45
+ ['fiftieth', '50'],
46
+ ['sixtieth', '60'],
47
+ ['seventieth', '70'],
48
+ ['eightieth', '80'],
49
+ ['ninetieth', '90']
41
50
  ]
42
51
 
43
52
  TEN_PREFIXES = [
@@ -90,7 +99,7 @@ module Chronic
90
99
  # hundreds, thousands, millions, etc.
91
100
 
92
101
  BIG_PREFIXES.each do |bp|
93
- string.gsub!(/(?:<num>)?(\d*) *#{bp[0]}/i) { '<num>' + (bp[1] * $1.to_i).to_s}
102
+ string.gsub!(/(?:<num>)?(\d*) *#{bp[0]}/i) { $1.empty? ? bp[1] : '<num>' + (bp[1] * $1.to_i).to_s}
94
103
  andition(string)
95
104
  end
96
105
 
@@ -1,31 +1,24 @@
1
1
  module Chronic
2
2
  class Ordinal < Tag
3
3
 
4
- # Scan an Array of {Token}s and apply any necessary Ordinal tags to
5
- # each token
4
+ # Scan an Array of Token objects and apply any necessary Ordinal
5
+ # tags to each token.
6
6
  #
7
- # @param [Array<Token>] tokens Array of tokens to scan
8
- # @param [Hash] options Options specified in {Chronic.parse}
9
- # @return [Array] list of tokens
7
+ # tokens - An Array of tokens to scan.
8
+ # options - The Hash of options specified in Chronic::parse.
9
+ #
10
+ # Returns an Array of tokens.
10
11
  def self.scan(tokens, options)
11
- tokens.each do |token|
12
- if t = scan_for_ordinals(token) then token.tag(t) end
13
- if t = scan_for_days(token) then token.tag(t) end
14
- end
15
- end
16
-
17
- # @param [Token] token
18
- # @return [Ordinal, nil]
19
- def self.scan_for_ordinals(token)
20
- Ordinal.new($1.to_i) if token.word =~ /^(\d*)(st|nd|rd|th)$/
21
- end
22
-
23
- # @param [Token] token
24
- # @return [OrdinalDay, nil]
25
- def self.scan_for_days(token)
26
- if token.word =~ /^(\d*)(st|nd|rd|th)$/
27
- unless $1.to_i > 31 || $1.to_i < 1
28
- OrdinalDay.new(token.word.to_i)
12
+ tokens.each_index do |i|
13
+ if tokens[i].word =~ /^(\d+)(st|nd|rd|th|\.)$/
14
+ ordinal = $1.to_i
15
+ tokens[i].tag(Ordinal.new(ordinal))
16
+ tokens[i].tag(OrdinalDay.new(ordinal)) if Chronic::Date::could_be_day?(ordinal)
17
+ tokens[i].tag(OrdinalMonth.new(ordinal)) if Chronic::Date::could_be_month?(ordinal)
18
+ if Chronic::Date::could_be_year?(ordinal)
19
+ year = Chronic::Date::make_year(ordinal, options[:ambiguous_year_future_bias])
20
+ tokens[i].tag(OrdinalYear.new(year.to_i))
21
+ end
29
22
  end
30
23
  end
31
24
  end
@@ -41,4 +34,16 @@ module Chronic
41
34
  end
42
35
  end
43
36
 
37
+ class OrdinalMonth < Ordinal #:nodoc:
38
+ def to_s
39
+ super << '-month-' << @type.to_s
40
+ end
41
+ end
42
+
43
+ class OrdinalYear < Ordinal #:nodoc:
44
+ def to_s
45
+ super << '-year-' << @type.to_s
46
+ end
47
+ end
48
+
44
49
  end