chronic 0.4.1 → 0.6.5

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 (64) hide show
  1. data/.gemtest +0 -0
  2. data/.gitignore +5 -0
  3. data/.yardopts +3 -0
  4. data/HISTORY.md +75 -2
  5. data/README.md +10 -5
  6. data/Rakefile +12 -106
  7. data/chronic.gemspec +12 -78
  8. data/lib/chronic/chronic.rb +271 -101
  9. data/lib/chronic/grabber.rb +12 -3
  10. data/lib/chronic/handler.rb +90 -0
  11. data/lib/chronic/handlers.rb +247 -295
  12. data/lib/chronic/mini_date.rb +15 -5
  13. data/lib/chronic/numerizer.rb +3 -2
  14. data/lib/chronic/ordinal.rb +15 -4
  15. data/lib/chronic/pointer.rb +13 -5
  16. data/lib/chronic/repeater.rb +31 -15
  17. data/lib/chronic/repeaters/repeater_day.rb +6 -7
  18. data/lib/chronic/repeaters/repeater_day_name.rb +1 -2
  19. data/lib/chronic/repeaters/repeater_day_portion.rb +12 -13
  20. data/lib/chronic/repeaters/repeater_fortnight.rb +2 -3
  21. data/lib/chronic/repeaters/repeater_hour.rb +7 -8
  22. data/lib/chronic/repeaters/repeater_minute.rb +6 -7
  23. data/lib/chronic/repeaters/repeater_month.rb +22 -11
  24. data/lib/chronic/repeaters/repeater_month_name.rb +16 -24
  25. data/lib/chronic/repeaters/repeater_season.rb +10 -29
  26. data/lib/chronic/repeaters/repeater_season_name.rb +2 -4
  27. data/lib/chronic/repeaters/repeater_second.rb +0 -1
  28. data/lib/chronic/repeaters/repeater_time.rb +19 -20
  29. data/lib/chronic/repeaters/repeater_week.rb +0 -1
  30. data/lib/chronic/repeaters/repeater_weekday.rb +1 -2
  31. data/lib/chronic/repeaters/repeater_weekend.rb +0 -1
  32. data/lib/chronic/repeaters/repeater_year.rb +29 -18
  33. data/lib/chronic/scalar.rb +29 -1
  34. data/lib/chronic/season.rb +37 -0
  35. data/lib/chronic/separator.rb +24 -7
  36. data/lib/chronic/tag.rb +12 -1
  37. data/lib/chronic/time_zone.rb +14 -5
  38. data/lib/chronic/token.rb +14 -4
  39. data/lib/chronic.rb +66 -89
  40. data/test/helper.rb +1 -1
  41. data/test/test_Chronic.rb +97 -3
  42. data/test/test_DaylightSavings.rb +1 -1
  43. data/test/test_Handler.rb +1 -6
  44. data/test/test_MiniDate.rb +3 -3
  45. data/test/test_Numerizer.rb +1 -1
  46. data/test/test_RepeaterDayName.rb +1 -1
  47. data/test/test_RepeaterFortnight.rb +1 -1
  48. data/test/test_RepeaterHour.rb +1 -1
  49. data/test/test_RepeaterMinute.rb +1 -1
  50. data/test/test_RepeaterMonth.rb +5 -1
  51. data/test/test_RepeaterMonthName.rb +1 -1
  52. data/test/test_RepeaterSeason.rb +40 -0
  53. data/test/test_RepeaterTime.rb +1 -1
  54. data/test/test_RepeaterWeek.rb +1 -1
  55. data/test/test_RepeaterWeekday.rb +1 -1
  56. data/test/test_RepeaterWeekend.rb +1 -1
  57. data/test/test_RepeaterYear.rb +8 -1
  58. data/test/test_Span.rb +1 -1
  59. data/test/test_Token.rb +1 -1
  60. data/test/test_parsing.rb +228 -117
  61. metadata +30 -32
  62. data/Manifest.txt +0 -59
  63. data/benchmark/benchmark.rb +0 -13
  64. data/test/test_Time.rb +0 -49
@@ -2,17 +2,27 @@ module Chronic
2
2
  class MiniDate
3
3
  attr_accessor :month, :day
4
4
 
5
+ def self.from_time(time)
6
+ new(time.month, time.day)
7
+ end
8
+
5
9
  def initialize(month, day)
6
- raise(InvalidArgumentException, "1..12 are valid months") unless (1..12).include?(month)
10
+ unless (1..12).include?(month)
11
+ raise ArgumentError, "1..12 are valid months"
12
+ end
13
+
7
14
  @month = month
8
15
  @day = day
9
16
  end
10
17
 
11
18
  def is_between?(md_start, md_end)
12
- return false if (@month==md_start.month && @month==md_end.month && (@day < md_start.day || @day > md_end.day))
13
- return true if (@month == md_start.month and @day >= md_start.day) ||
14
- (@month == md_end.month and @day <= md_end.day)
19
+ return false if (@month == md_start.month && @month == md_end.month) &&
20
+ (@day < md_start.day || @day > md_end.day)
21
+ return true if (@month == md_start.month && @day >= md_start.day) ||
22
+ (@month == md_end.month && @day <= md_end.day)
23
+
15
24
  i = (md_start.month % 12) + 1
25
+
16
26
  until i == md_end.month
17
27
  return true if @month == i
18
28
  i = (i % 12) + 1
@@ -22,7 +32,7 @@ module Chronic
22
32
  end
23
33
 
24
34
  def equals?(other)
25
- @month == other.month and day == other.day
35
+ @month == other.month and @day == other.day
26
36
  end
27
37
  end
28
38
  end
@@ -107,14 +107,15 @@ module Chronic
107
107
 
108
108
  def andition(string)
109
109
  sc = StringScanner.new(string)
110
- while(sc.scan_until(/<num>(\d+)( | and )<num>(\d+)(?=[^\w]|$)/i))
110
+
111
+ while sc.scan_until(/<num>(\d+)( | and )<num>(\d+)(?=[^\w]|$)/i)
111
112
  if sc[2] =~ /and/ || sc[1].size > sc[3].size
112
113
  string[(sc.pos - sc.matched_size)..(sc.pos-1)] = '<num>' + (sc[1].to_i + sc[3].to_i).to_s
113
114
  sc.reset
114
115
  end
115
116
  end
116
117
  end
117
- end
118
118
 
119
+ end
119
120
  end
120
121
  end
@@ -1,16 +1,27 @@
1
1
  module Chronic
2
- class Ordinal < Tag #:nodoc:
2
+ class Ordinal < Tag
3
+
4
+ # Scan an Array of {Token}s and apply any necessary Ordinal tags to
5
+ # each token
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
3
10
  def self.scan(tokens, options)
4
- tokens.each_index do |i|
5
- if t = scan_for_ordinals(tokens[i]) then tokens[i].tag(t) end
6
- if t = scan_for_days(tokens[i]) then tokens[i].tag(t) end
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
7
14
  end
8
15
  end
9
16
 
17
+ # @param [Token] token
18
+ # @return [Ordinal, nil]
10
19
  def self.scan_for_ordinals(token)
11
20
  Ordinal.new($1.to_i) if token.word =~ /^(\d*)(st|nd|rd|th)$/
12
21
  end
13
22
 
23
+ # @param [Token] token
24
+ # @return [OrdinalDay, nil]
14
25
  def self.scan_for_days(token)
15
26
  if token.word =~ /^(\d*)(st|nd|rd|th)$/
16
27
  unless $1.to_i > 31 || $1.to_i < 1
@@ -1,17 +1,25 @@
1
1
  module Chronic
2
- class Pointer < Tag #:nodoc:
2
+ class Pointer < Tag
3
+
4
+ # Scan an Array of {Token}s and apply any necessary Pointer tags to
5
+ # each token
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
3
10
  def self.scan(tokens, options)
4
- tokens.each_index do |i|
5
- if t = scan_for_all(tokens[i]) then tokens[i].tag(t) end
11
+ tokens.each do |token|
12
+ if t = scan_for_all(token) then token.tag(t) end
6
13
  end
7
14
  end
8
15
 
16
+ # @param [Token] token
17
+ # @return [Pointer, nil]
9
18
  def self.scan_for_all(token)
10
19
  scan_for token, self,
11
20
  {
12
21
  /\bpast\b/ => :past,
13
- /\bfuture\b/ => :future,
14
- /\bin\b/ => :future
22
+ /\b(?:future|in)\b/ => :future,
15
23
  }
16
24
  end
17
25
 
@@ -1,16 +1,25 @@
1
1
  module Chronic
2
- class Repeater < Tag #:nodoc:
2
+ class Repeater < Tag
3
+
4
+ # Scan an Array of {Token}s and apply any necessary Repeater tags to
5
+ # each token
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
3
10
  def self.scan(tokens, options)
4
- tokens.each_index do |i|
5
- if t = scan_for_season_names(tokens[i]) then tokens[i].tag(t); next end
6
- if t = scan_for_month_names(tokens[i]) then tokens[i].tag(t); next end
7
- if t = scan_for_day_names(tokens[i]) then tokens[i].tag(t); next end
8
- if t = scan_for_day_portions(tokens[i]) then tokens[i].tag(t); next end
9
- if t = scan_for_times(tokens[i]) then tokens[i].tag(t); next end
10
- if t = scan_for_units(tokens[i]) then tokens[i].tag(t); next end
11
+ tokens.each do |token|
12
+ if t = scan_for_season_names(token) then token.tag(t); next end
13
+ if t = scan_for_month_names(token) then token.tag(t); next end
14
+ if t = scan_for_day_names(token) then token.tag(t); next end
15
+ if t = scan_for_day_portions(token) then token.tag(t); next end
16
+ if t = scan_for_times(token) then token.tag(t); next end
17
+ if t = scan_for_units(token) then token.tag(t); next end
11
18
  end
12
19
  end
13
20
 
21
+ # @param [Token] token
22
+ # @return [RepeaterSeasonName, nil]
14
23
  def self.scan_for_season_names(token)
15
24
  scan_for token, RepeaterSeasonName,
16
25
  {
@@ -21,6 +30,8 @@ module Chronic
21
30
  }
22
31
  end
23
32
 
33
+ # @param [Token] token
34
+ # @return [RepeaterMonthName, nil]
24
35
  def self.scan_for_month_names(token)
25
36
  scan_for token, RepeaterMonthName,
26
37
  {
@@ -39,22 +50,23 @@ module Chronic
39
50
  }
40
51
  end
41
52
 
53
+ # @param [Token] token
54
+ # @return [RepeaterDayName, nil]
42
55
  def self.scan_for_day_names(token)
43
56
  scan_for token, RepeaterDayName,
44
57
  {
45
58
  /^m[ou]n(day)?$/ => :monday,
46
- /^t(ue|eu|oo|u|)s(day)?$/ => :tuesday,
47
- /^tue$/ => :tuesday,
48
- /^we(dnes|nds|nns)day$/ => :wednesday,
49
- /^wed$/ => :wednesday,
50
- /^th(urs|ers)day$/ => :thursday,
51
- /^thu(rs)?$/ => :thursday,
59
+ /^t(ue|eu|oo|u|)s?(day)?$/ => :tuesday,
60
+ /^we(d|dnes|nds|nns)(day)?$/ => :wednesday,
61
+ /^th(u(?:rs)?|ers)(day)?$/ => :thursday,
52
62
  /^fr[iy](day)?$/ => :friday,
53
63
  /^sat(t?[ue]rday)?$/ => :saturday,
54
64
  /^su[nm](day)?$/ => :sunday
55
65
  }
56
66
  end
57
67
 
68
+ # @param [Token] token
69
+ # @return [RepeaterDayPortion, nil]
58
70
  def self.scan_for_day_portions(token)
59
71
  scan_for token, RepeaterDayPortion,
60
72
  {
@@ -67,10 +79,14 @@ module Chronic
67
79
  }
68
80
  end
69
81
 
82
+ # @param [Token] token
83
+ # @return [RepeaterTime, nil]
70
84
  def self.scan_for_times(token)
71
85
  scan_for token, RepeaterTime, /^\d{1,2}(:?\d{2})?([\.:]?\d{2})?$/
72
86
  end
73
87
 
88
+ # @param [Token] token
89
+ # @return [Repeater] A new instance of a subclass of Repeater
74
90
  def self.scan_for_units(token)
75
91
  {
76
92
  /^years?$/ => :year,
@@ -100,7 +116,7 @@ module Chronic
100
116
 
101
117
  # returns the width (in seconds or months) of this repeatable.
102
118
  def width
103
- raise("Repeatable#width must be overridden in subclasses")
119
+ raise("Repeater#width must be overridden in subclasses")
104
120
  end
105
121
 
106
122
  # returns the next occurance of this repeatable.
@@ -4,7 +4,6 @@ module Chronic
4
4
 
5
5
  def initialize(type)
6
6
  super
7
- @current_day_start = nil
8
7
  end
9
8
 
10
9
  def next(pointer)
@@ -25,14 +24,14 @@ module Chronic
25
24
 
26
25
  case pointer
27
26
  when :future
28
- day_begin = Time.construct(@now.year, @now.month, @now.day, @now.hour)
29
- day_end = Time.construct(@now.year, @now.month, @now.day) + DAY_SECONDS
27
+ day_begin = Chronic.construct(@now.year, @now.month, @now.day, @now.hour)
28
+ day_end = Chronic.construct(@now.year, @now.month, @now.day) + DAY_SECONDS
30
29
  when :past
31
- day_begin = Time.construct(@now.year, @now.month, @now.day)
32
- day_end = Time.construct(@now.year, @now.month, @now.day, @now.hour)
30
+ day_begin = Chronic.construct(@now.year, @now.month, @now.day)
31
+ day_end = Chronic.construct(@now.year, @now.month, @now.day, @now.hour)
33
32
  when :none
34
- day_begin = Time.construct(@now.year, @now.month, @now.day)
35
- day_end = Time.construct(@now.year, @now.month, @now.day) + DAY_SECONDS
33
+ day_begin = Chronic.construct(@now.year, @now.month, @now.day)
34
+ day_end = Chronic.construct(@now.year, @now.month, @now.day) + DAY_SECONDS
36
35
  end
37
36
 
38
37
  Span.new(day_begin, day_end)
@@ -4,7 +4,6 @@ module Chronic
4
4
 
5
5
  def initialize(type)
6
6
  super
7
- @current_day_start = nil
8
7
  end
9
8
 
10
9
  def next(pointer)
@@ -25,7 +24,7 @@ module Chronic
25
24
  @current_date += direction * 7
26
25
  end
27
26
  next_date = @current_date.succ
28
- Span.new(Time.construct(@current_date.year, @current_date.month, @current_date.day), Time.construct(next_date.year, next_date.month, next_date.day))
27
+ Span.new(Chronic.construct(@current_date.year, @current_date.month, @current_date.day), Chronic.construct(next_date.year, next_date.month, next_date.day))
29
28
  end
30
29
 
31
30
  def this(pointer = :future)
@@ -3,15 +3,14 @@ module Chronic
3
3
  PORTIONS = {
4
4
  :am => 0..(12 * 60 * 60 - 1),
5
5
  :pm => (12 * 60 * 60)..(24 * 60 * 60 - 1),
6
- :morning => (6 * 60 * 60)..(12 * 60 * 60), # 6am-12am,
7
- :afternoon =>(13 * 60 * 60)..(17 * 60 * 60), # 1pm-5pm,
8
- :evening => (17 * 60 * 60)..(20 * 60 * 60), # 5pm-8pm,
9
- :night =>(20 * 60 * 60)..(24 * 60 * 60), # 8pm-12pm
6
+ :morning => (6 * 60 * 60)..(12 * 60 * 60), # 6am-12am,
7
+ :afternoon => (13 * 60 * 60)..(17 * 60 * 60), # 1pm-5pm,
8
+ :evening => (17 * 60 * 60)..(20 * 60 * 60), # 5pm-8pm,
9
+ :night => (20 * 60 * 60)..(24 * 60 * 60), # 8pm-12pm
10
10
  }
11
11
 
12
12
  def initialize(type)
13
13
  super
14
- @current_span = nil
15
14
 
16
15
  if type.kind_of? Integer
17
16
  @range = (@type * 60 * 60)..((@type + 12) * 60 * 60)
@@ -29,27 +28,27 @@ module Chronic
29
28
  full_day = 60 * 60 * 24
30
29
 
31
30
  if !@current_span
32
- now_seconds = @now - Time.construct(@now.year, @now.month, @now.day)
31
+ now_seconds = @now - Chronic.construct(@now.year, @now.month, @now.day)
33
32
  if now_seconds < @range.begin
34
33
  case pointer
35
34
  when :future
36
- range_start = Time.construct(@now.year, @now.month, @now.day) + @range.begin
35
+ range_start = Chronic.construct(@now.year, @now.month, @now.day) + @range.begin
37
36
  when :past
38
- range_start = Time.construct(@now.year, @now.month, @now.day) - full_day + @range.begin
37
+ range_start = Chronic.construct(@now.year, @now.month, @now.day) - full_day + @range.begin
39
38
  end
40
39
  elsif now_seconds > @range.end
41
40
  case pointer
42
41
  when :future
43
- range_start = Time.construct(@now.year, @now.month, @now.day) + full_day + @range.begin
42
+ range_start = Chronic.construct(@now.year, @now.month, @now.day) + full_day + @range.begin
44
43
  when :past
45
- range_start = Time.construct(@now.year, @now.month, @now.day) + @range.begin
44
+ range_start = Chronic.construct(@now.year, @now.month, @now.day) + @range.begin
46
45
  end
47
46
  else
48
47
  case pointer
49
48
  when :future
50
- range_start = Time.construct(@now.year, @now.month, @now.day) + full_day + @range.begin
49
+ range_start = Chronic.construct(@now.year, @now.month, @now.day) + full_day + @range.begin
51
50
  when :past
52
- range_start = Time.construct(@now.year, @now.month, @now.day) - full_day + @range.begin
51
+ range_start = Chronic.construct(@now.year, @now.month, @now.day) - full_day + @range.begin
53
52
  end
54
53
  end
55
54
 
@@ -67,7 +66,7 @@ module Chronic
67
66
  def this(context = :future)
68
67
  super
69
68
 
70
- range_start = Time.construct(@now.year, @now.month, @now.day) + @range.begin
69
+ range_start = Chronic.construct(@now.year, @now.month, @now.day) + @range.begin
71
70
  @current_span = Span.new(range_start, range_start + (@range.end - @range.begin))
72
71
  end
73
72
 
@@ -4,7 +4,6 @@ module Chronic
4
4
 
5
5
  def initialize(type)
6
6
  super
7
- @current_fortnight_start = nil
8
7
  end
9
8
 
10
9
  def next(pointer)
@@ -39,7 +38,7 @@ module Chronic
39
38
 
40
39
  case pointer
41
40
  when :future
42
- this_fortnight_start = Time.construct(@now.year, @now.month, @now.day, @now.hour) + RepeaterHour::HOUR_SECONDS
41
+ this_fortnight_start = Chronic.construct(@now.year, @now.month, @now.day, @now.hour) + RepeaterHour::HOUR_SECONDS
43
42
  sunday_repeater = RepeaterDayName.new(:sunday)
44
43
  sunday_repeater.start = @now
45
44
  sunday_repeater.this(:future)
@@ -47,7 +46,7 @@ module Chronic
47
46
  this_fortnight_end = this_sunday_span.begin
48
47
  Span.new(this_fortnight_start, this_fortnight_end)
49
48
  when :past
50
- this_fortnight_end = Time.construct(@now.year, @now.month, @now.day, @now.hour)
49
+ this_fortnight_end = Chronic.construct(@now.year, @now.month, @now.day, @now.hour)
51
50
  sunday_repeater = RepeaterDayName.new(:sunday)
52
51
  sunday_repeater.start = @now
53
52
  last_sunday_span = sunday_repeater.next(:past)
@@ -4,7 +4,6 @@ module Chronic
4
4
 
5
5
  def initialize(type)
6
6
  super
7
- @current_hour_start = nil
8
7
  end
9
8
 
10
9
  def next(pointer)
@@ -13,9 +12,9 @@ module Chronic
13
12
  if !@current_hour_start
14
13
  case pointer
15
14
  when :future
16
- @current_hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour + 1)
15
+ @current_hour_start = Chronic.construct(@now.year, @now.month, @now.day, @now.hour + 1)
17
16
  when :past
18
- @current_hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour - 1)
17
+ @current_hour_start = Chronic.construct(@now.year, @now.month, @now.day, @now.hour - 1)
19
18
  end
20
19
  else
21
20
  direction = pointer == :future ? 1 : -1
@@ -30,13 +29,13 @@ module Chronic
30
29
 
31
30
  case pointer
32
31
  when :future
33
- hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min + 1)
34
- hour_end = Time.construct(@now.year, @now.month, @now.day, @now.hour + 1)
32
+ hour_start = Chronic.construct(@now.year, @now.month, @now.day, @now.hour, @now.min + 1)
33
+ hour_end = Chronic.construct(@now.year, @now.month, @now.day, @now.hour + 1)
35
34
  when :past
36
- hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour)
37
- hour_end = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min)
35
+ hour_start = Chronic.construct(@now.year, @now.month, @now.day, @now.hour)
36
+ hour_end = Chronic.construct(@now.year, @now.month, @now.day, @now.hour, @now.min)
38
37
  when :none
39
- hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour)
38
+ hour_start = Chronic.construct(@now.year, @now.month, @now.day, @now.hour)
40
39
  hour_end = hour_start + HOUR_SECONDS
41
40
  end
42
41
 
@@ -4,7 +4,6 @@ module Chronic
4
4
 
5
5
  def initialize(type)
6
6
  super
7
- @current_minute_start = nil
8
7
  end
9
8
 
10
9
  def next(pointer = :future)
@@ -13,9 +12,9 @@ module Chronic
13
12
  if !@current_minute_start
14
13
  case pointer
15
14
  when :future
16
- @current_minute_start = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min + 1)
15
+ @current_minute_start = Chronic.construct(@now.year, @now.month, @now.day, @now.hour, @now.min + 1)
17
16
  when :past
18
- @current_minute_start = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min - 1)
17
+ @current_minute_start = Chronic.construct(@now.year, @now.month, @now.day, @now.hour, @now.min - 1)
19
18
  end
20
19
  else
21
20
  direction = pointer == :future ? 1 : -1
@@ -31,13 +30,13 @@ module Chronic
31
30
  case pointer
32
31
  when :future
33
32
  minute_begin = @now
34
- minute_end = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min)
33
+ minute_end = Chronic.construct(@now.year, @now.month, @now.day, @now.hour, @now.min)
35
34
  when :past
36
- minute_begin = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min)
35
+ minute_begin = Chronic.construct(@now.year, @now.month, @now.day, @now.hour, @now.min)
37
36
  minute_end = @now
38
37
  when :none
39
- minute_begin = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min)
40
- minute_end = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min) + MINUTE_SECONDS
38
+ minute_begin = Chronic.construct(@now.year, @now.month, @now.day, @now.hour, @now.min)
39
+ minute_end = Chronic.construct(@now.year, @now.month, @now.day, @now.hour, @now.min) + MINUTE_SECONDS
41
40
  end
42
41
 
43
42
  Span.new(minute_begin, minute_end)
@@ -2,22 +2,23 @@ module Chronic
2
2
  class RepeaterMonth < Repeater #:nodoc:
3
3
  MONTH_SECONDS = 2_592_000 # 30 * 24 * 60 * 60
4
4
  YEAR_MONTHS = 12
5
+ MONTH_DAYS = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
6
+ MONTH_DAYS_LEAP = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
5
7
 
6
8
  def initialize(type)
7
9
  super
8
- @current_month_start = nil
9
10
  end
10
11
 
11
12
  def next(pointer)
12
13
  super
13
14
 
14
15
  if !@current_month_start
15
- @current_month_start = offset_by(Time.construct(@now.year, @now.month), 1, pointer)
16
+ @current_month_start = offset_by(Chronic.construct(@now.year, @now.month), 1, pointer)
16
17
  else
17
- @current_month_start = offset_by(Time.construct(@current_month_start.year, @current_month_start.month), 1, pointer)
18
+ @current_month_start = offset_by(Chronic.construct(@current_month_start.year, @current_month_start.month), 1, pointer)
18
19
  end
19
20
 
20
- Span.new(@current_month_start, Time.construct(@current_month_start.year, @current_month_start.month + 1))
21
+ Span.new(@current_month_start, Chronic.construct(@current_month_start.year, @current_month_start.month + 1))
21
22
  end
22
23
 
23
24
  def this(pointer = :future)
@@ -25,14 +26,14 @@ module Chronic
25
26
 
26
27
  case pointer
27
28
  when :future
28
- month_start = Time.construct(@now.year, @now.month, @now.day + 1)
29
- month_end = self.offset_by(Time.construct(@now.year, @now.month), 1, :future)
29
+ month_start = Chronic.construct(@now.year, @now.month, @now.day + 1)
30
+ month_end = self.offset_by(Chronic.construct(@now.year, @now.month), 1, :future)
30
31
  when :past
31
- month_start = Time.construct(@now.year, @now.month)
32
- month_end = Time.construct(@now.year, @now.month, @now.day)
32
+ month_start = Chronic.construct(@now.year, @now.month)
33
+ month_end = Chronic.construct(@now.year, @now.month, @now.day)
33
34
  when :none
34
- month_start = Time.construct(@now.year, @now.month)
35
- month_end = self.offset_by(Time.construct(@now.year, @now.month), 1, :future)
35
+ month_start = Chronic.construct(@now.year, @now.month)
36
+ month_end = self.offset_by(Chronic.construct(@now.year, @now.month), 1, :future)
36
37
  end
37
38
 
38
39
  Span.new(month_start, month_end)
@@ -54,7 +55,11 @@ module Chronic
54
55
  new_year += 1
55
56
  new_month -= YEAR_MONTHS
56
57
  end
57
- Time.construct(new_year, new_month, time.day, time.hour, time.min, time.sec)
58
+
59
+ days = month_days(new_year, new_month)
60
+ new_day = time.day > days ? days : time.day
61
+
62
+ Chronic.construct(new_year, new_month, new_day, time.hour, time.min, time.sec)
58
63
  end
59
64
 
60
65
  def width
@@ -64,5 +69,11 @@ module Chronic
64
69
  def to_s
65
70
  super << '-month'
66
71
  end
72
+
73
+ private
74
+
75
+ def month_days(year, month)
76
+ Date.leap?(year) ? MONTH_DAYS_LEAP[month - 1] : MONTH_DAYS[month - 1]
77
+ end
67
78
  end
68
79
  end
@@ -18,41 +18,39 @@ module Chronic
18
18
 
19
19
  def initialize(type)
20
20
  super
21
- @current_month_begin = nil
22
21
  end
23
22
 
24
23
  def next(pointer)
25
24
  super
26
25
 
27
26
  if !@current_month_begin
28
- target_month = symbol_to_number(@type)
29
27
  case pointer
30
28
  when :future
31
- if @now.month < target_month
32
- @current_month_begin = Time.construct(@now.year, target_month)
33
- elsif @now.month > target_month
34
- @current_month_begin = Time.construct(@now.year + 1, target_month)
29
+ if @now.month < index
30
+ @current_month_begin = Chronic.construct(@now.year, index)
31
+ elsif @now.month > index
32
+ @current_month_begin = Chronic.construct(@now.year + 1, index)
35
33
  end
36
34
  when :none
37
- if @now.month <= target_month
38
- @current_month_begin = Time.construct(@now.year, target_month)
39
- elsif @now.month > target_month
40
- @current_month_begin = Time.construct(@now.year + 1, target_month)
35
+ if @now.month <= index
36
+ @current_month_begin = Chronic.construct(@now.year, index)
37
+ elsif @now.month > index
38
+ @current_month_begin = Chronic.construct(@now.year + 1, index)
41
39
  end
42
40
  when :past
43
- if @now.month >= target_month
44
- @current_month_begin = Time.construct(@now.year, target_month)
45
- elsif @now.month < target_month
46
- @current_month_begin = Time.construct(@now.year - 1, target_month)
41
+ if @now.month >= index
42
+ @current_month_begin = Chronic.construct(@now.year, index)
43
+ elsif @now.month < index
44
+ @current_month_begin = Chronic.construct(@now.year - 1, index)
47
45
  end
48
46
  end
49
47
  @current_month_begin || raise("Current month should be set by now")
50
48
  else
51
49
  case pointer
52
50
  when :future
53
- @current_month_begin = Time.construct(@current_month_begin.year + 1, @current_month_begin.month)
51
+ @current_month_begin = Chronic.construct(@current_month_begin.year + 1, @current_month_begin.month)
54
52
  when :past
55
- @current_month_begin = Time.construct(@current_month_begin.year - 1, @current_month_begin.month)
53
+ @current_month_begin = Chronic.construct(@current_month_begin.year - 1, @current_month_begin.month)
56
54
  end
57
55
  end
58
56
 
@@ -67,7 +65,7 @@ module Chronic
67
65
  next_month_month = cur_month_month + 1
68
66
  end
69
67
 
70
- Span.new(@current_month_begin, Time.construct(next_month_year, next_month_month))
68
+ Span.new(@current_month_begin, Chronic.construct(next_month_year, next_month_month))
71
69
  end
72
70
 
73
71
  def this(pointer = :future)
@@ -86,17 +84,11 @@ module Chronic
86
84
  end
87
85
 
88
86
  def index
89
- symbol_to_number(@type)
87
+ @index ||= MONTHS[@type]
90
88
  end
91
89
 
92
90
  def to_s
93
91
  super << '-monthname-' << @type.to_s
94
92
  end
95
-
96
- private
97
-
98
- def symbol_to_number(sym)
99
- MONTHS[sym] || raise("Invalid symbol specified")
100
- end
101
93
  end
102
94
  end