pangel-chronic 0.3.0.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 (47) hide show
  1. data/README.rdoc +182 -0
  2. data/lib/chronic/chronic.rb +303 -0
  3. data/lib/chronic/grabber.rb +26 -0
  4. data/lib/chronic/handlers.rb +560 -0
  5. data/lib/chronic/ordinal.rb +39 -0
  6. data/lib/chronic/pointer.rb +29 -0
  7. data/lib/chronic/repeater.rb +139 -0
  8. data/lib/chronic/repeaters/repeater_day.rb +52 -0
  9. data/lib/chronic/repeaters/repeater_day_name.rb +53 -0
  10. data/lib/chronic/repeaters/repeater_day_portion.rb +94 -0
  11. data/lib/chronic/repeaters/repeater_fortnight.rb +70 -0
  12. data/lib/chronic/repeaters/repeater_hour.rb +58 -0
  13. data/lib/chronic/repeaters/repeater_minute.rb +57 -0
  14. data/lib/chronic/repeaters/repeater_month.rb +66 -0
  15. data/lib/chronic/repeaters/repeater_month_name.rb +98 -0
  16. data/lib/chronic/repeaters/repeater_season.rb +150 -0
  17. data/lib/chronic/repeaters/repeater_season_name.rb +45 -0
  18. data/lib/chronic/repeaters/repeater_second.rb +41 -0
  19. data/lib/chronic/repeaters/repeater_time.rb +124 -0
  20. data/lib/chronic/repeaters/repeater_week.rb +73 -0
  21. data/lib/chronic/repeaters/repeater_weekday.rb +77 -0
  22. data/lib/chronic/repeaters/repeater_weekend.rb +65 -0
  23. data/lib/chronic/repeaters/repeater_year.rb +64 -0
  24. data/lib/chronic/scalar.rb +76 -0
  25. data/lib/chronic/separator.rb +91 -0
  26. data/lib/chronic/time_zone.rb +23 -0
  27. data/lib/chronic.rb +57 -0
  28. data/lib/numerizer/numerizer.rb +98 -0
  29. data/test/suite.rb +9 -0
  30. data/test/test_Chronic.rb +50 -0
  31. data/test/test_Handler.rb +110 -0
  32. data/test/test_Numerizer.rb +54 -0
  33. data/test/test_RepeaterDayName.rb +52 -0
  34. data/test/test_RepeaterFortnight.rb +63 -0
  35. data/test/test_RepeaterHour.rb +68 -0
  36. data/test/test_RepeaterMonth.rb +47 -0
  37. data/test/test_RepeaterMonthName.rb +57 -0
  38. data/test/test_RepeaterTime.rb +72 -0
  39. data/test/test_RepeaterWeek.rb +63 -0
  40. data/test/test_RepeaterWeekday.rb +56 -0
  41. data/test/test_RepeaterWeekend.rb +75 -0
  42. data/test/test_RepeaterYear.rb +63 -0
  43. data/test/test_Span.rb +33 -0
  44. data/test/test_Time.rb +50 -0
  45. data/test/test_Token.rb +26 -0
  46. data/test/test_parsing.rb +797 -0
  47. metadata +111 -0
@@ -0,0 +1,139 @@
1
+ class Chronic::Repeater < Chronic::Tag #:nodoc:
2
+ def self.scan(tokens, options)
3
+ # for each token
4
+ tokens.each_index do |i|
5
+ if t = self.scan_for_season_names(tokens[i]) then tokens[i].tag(t); next end
6
+ if t = self.scan_for_month_names(tokens[i]) then tokens[i].tag(t); next end
7
+ if t = self.scan_for_day_names(tokens[i]) then tokens[i].tag(t); next end
8
+ if t = self.scan_for_day_portions(tokens[i]) then tokens[i].tag(t); next end
9
+ if t = self.scan_for_times(tokens[i], options) then tokens[i].tag(t); next end
10
+ if t = self.scan_for_units(tokens[i]) then tokens[i].tag(t); next end
11
+ if t = self.scan_for_decades(tokens[i]) then tokens[i].tag(t); next end
12
+ end
13
+ tokens
14
+ end
15
+
16
+ def self.scan_for_season_names(token)
17
+ scanner = {/^springs?$/i => :spring,
18
+ /^summers?$/i => :summer,
19
+ /^(autumn)|(fall)s?$/i => :autumn,
20
+ /^winters?$/i => :winter}
21
+ scanner.keys.each do |scanner_item|
22
+ return Chronic::RepeaterSeasonName.new(scanner[scanner_item]) if scanner_item =~ token.word
23
+ end
24
+
25
+ return nil
26
+ end
27
+
28
+ def self.scan_for_month_names(token)
29
+ scanner = {/^jan\.?(uary)?$/i => :january,
30
+ /^feb\.?(ruary)?$/i => :february,
31
+ /^mar\.?(ch)?$/i => :march,
32
+ /^apr\.?(il)?$/i => :april,
33
+ /^may$/i => :may,
34
+ /^jun\.?e?$/i => :june,
35
+ /^jul\.?y?$/i => :july,
36
+ /^aug\.?(ust)?$/i => :august,
37
+ /^sep\.?(t\.?|tember)?$/i => :september,
38
+ /^oct\.?(ober)?$/i => :october,
39
+ /^nov\.?(ember)?$/i => :november,
40
+ /^dec\.?(ember)?$/i => :december}
41
+ scanner.keys.each do |scanner_item|
42
+ return Chronic::RepeaterMonthName.new(scanner[scanner_item]) if scanner_item =~ token.word
43
+ end
44
+ return nil
45
+ end
46
+
47
+ def self.scan_for_day_names(token)
48
+ scanner = {/^m[ou]n(day)?$/i => :monday,
49
+ /^t(ue|eu|oo|u|)s(day)?$/i => :tuesday,
50
+ /^tue$/i => :tuesday,
51
+ /^we(dnes|nds|nns)day$/i => :wednesday,
52
+ /^wed$/i => :wednesday,
53
+ /^th(urs|ers)day$/i => :thursday,
54
+ /^thu$/i => :thursday,
55
+ /^fr[iy](day)?$/i => :friday,
56
+ /^sat(t?[ue]rday)?$/i => :saturday,
57
+ /^su[nm](day)?$/i => :sunday}
58
+ scanner.keys.each do |scanner_item|
59
+ return Chronic::RepeaterDayName.new(scanner[scanner_item]) if scanner_item =~ token.word
60
+ end
61
+ return nil
62
+ end
63
+
64
+ def self.scan_for_day_portions(token)
65
+ puts "scan_for_day_portions -- #{token}" if Chronic.debug
66
+ scanner = {/^ams?$/i => :am,
67
+ /^pms?$/i => :pm,
68
+ /^mornings?$/i => :morning,
69
+ /^afternoons?$/i => :afternoon,
70
+ /^evenings?$/i => :evening,
71
+ /^(night|nite)s?$/i => :night}
72
+ scanner.keys.each do |scanner_item|
73
+ return Chronic::RepeaterDayPortion.new(scanner[scanner_item]) if scanner_item =~ token.word
74
+ end
75
+ return nil
76
+ end
77
+
78
+ def self.scan_for_times(token, options)
79
+ if token.word =~ /^\d{1,2}(:?\d{2})?([\.:]?\d{2})?$/
80
+ return Chronic::RepeaterTime.new(token.word, options)
81
+ end
82
+ return nil
83
+ end
84
+
85
+ def self.scan_for_decades(token)
86
+ if token.word =~ Chronic::RepeaterDecade::DECADE_PATTERN
87
+ return Chronic::RepeaterDecade.new(token.word)
88
+ end
89
+ return nil
90
+ end
91
+
92
+ def self.scan_for_units(token)
93
+ scanner = {/^years?$/i => :year,
94
+ /^seasons?$/i => :season,
95
+ /^months?$/i => :month,
96
+ /^fortnights?$/i => :fortnight,
97
+ /^weeks?$/i => :week,
98
+ /^weekends?$/i => :weekend,
99
+ /^(week|business)days?$/i => :weekday,
100
+ /^days?$/i => :day,
101
+ /^hours?$/i => :hour,
102
+ /^minutes?$/i => :minute,
103
+ /^seconds?$/i => :second,
104
+ }
105
+ scanner.keys.each do |scanner_item|
106
+ if scanner_item =~ token.word
107
+ klass_name = 'Chronic::Repeater' + scanner[scanner_item].to_s.capitalize
108
+ klass = eval(klass_name)
109
+ return klass.new(scanner[scanner_item])
110
+ end
111
+ end
112
+ return nil
113
+ end
114
+
115
+ def <=>(other)
116
+ width <=> other.width
117
+ end
118
+
119
+ # returns the width (in seconds or months) of this repeatable.
120
+ def width
121
+ raise("Repeatable#width must be overridden in subclasses")
122
+ end
123
+
124
+ # returns the next occurance of this repeatable.
125
+ def next(pointer)
126
+ !@now.nil? || raise("Start point must be set before calling #next")
127
+ [:future, :none, :past].include?(pointer) || raise("First argument 'pointer' must be one of :past or :future")
128
+ #raise("Repeatable#next must be overridden in subclasses")
129
+ end
130
+
131
+ def this(pointer)
132
+ !@now.nil? || raise("Start point must be set before calling #this")
133
+ [:future, :past, :none].include?(pointer) || raise("First argument 'pointer' must be one of :past, :future, :none")
134
+ end
135
+
136
+ def to_s
137
+ 'repeater'
138
+ end
139
+ end
@@ -0,0 +1,52 @@
1
+ class Chronic::RepeaterDay < Chronic::Repeater #:nodoc:
2
+ DAY_SECONDS = 86_400 # (24 * 60 * 60)
3
+
4
+ def initialize(type)
5
+ super
6
+ @current_day_start = nil
7
+ end
8
+
9
+ def next(pointer)
10
+ super
11
+
12
+ if !@current_day_start
13
+ @current_day_start = Chronic.time_class.local(@now.year, @now.month, @now.day)
14
+ end
15
+
16
+ direction = pointer == :future ? 1 : -1
17
+ @current_day_start += direction * DAY_SECONDS
18
+
19
+ Chronic::Span.new(@current_day_start, @current_day_start + DAY_SECONDS)
20
+ end
21
+
22
+ def this(pointer = :future)
23
+ super
24
+
25
+ case pointer
26
+ when :future
27
+ day_begin = Time.construct(@now.year, @now.month, @now.day, @now.hour + 1)
28
+ day_end = Time.construct(@now.year, @now.month, @now.day) + DAY_SECONDS
29
+ when :past
30
+ day_begin = Time.construct(@now.year, @now.month, @now.day)
31
+ day_end = Time.construct(@now.year, @now.month, @now.day, @now.hour)
32
+ when :none
33
+ day_begin = Time.construct(@now.year, @now.month, @now.day)
34
+ day_end = Time.construct(@now.year, @now.month, @now.day) + DAY_SECONDS
35
+ end
36
+
37
+ Chronic::Span.new(day_begin, day_end)
38
+ end
39
+
40
+ def offset(span, amount, pointer)
41
+ direction = pointer == :future ? 1 : -1
42
+ span + direction * amount * DAY_SECONDS
43
+ end
44
+
45
+ def width
46
+ DAY_SECONDS
47
+ end
48
+
49
+ def to_s
50
+ super << '-day'
51
+ end
52
+ end
@@ -0,0 +1,53 @@
1
+ require 'date'
2
+ class Chronic::RepeaterDayName < Chronic::Repeater #:nodoc:
3
+ DAY_SECONDS = 86400 # (24 * 60 * 60)
4
+
5
+ def initialize(type)
6
+ super
7
+ @current_date = nil
8
+ end
9
+
10
+ def next(pointer)
11
+ super
12
+
13
+ direction = pointer == :future ? 1 : -1
14
+
15
+ unless @current_date
16
+ @current_date = DateTime.new(y=@now.year, m=@now.month, d=@now.day)
17
+ @current_date += direction
18
+ day_num = symbol_to_number(@type)
19
+
20
+ while @current_date.wday != day_num
21
+ @current_date += direction
22
+ end
23
+ else
24
+ @current_date += direction * 7
25
+ end
26
+ next_date = @current_date.succ
27
+ Chronic::Span.new(Chronic.time_class.local(@current_date.year, @current_date.month, @current_date.day),
28
+ Chronic.time_class.local(next_date.year, next_date.month, next_date.day))
29
+ end
30
+
31
+
32
+ def this(pointer = :future)
33
+ super
34
+
35
+ pointer = :future if pointer == :none
36
+ self.next(pointer)
37
+ end
38
+
39
+ def width
40
+ DAY_SECONDS
41
+ end
42
+
43
+ def to_s
44
+ super << '-dayname-' << @type.to_s
45
+ end
46
+
47
+ private
48
+
49
+ def symbol_to_number(sym)
50
+ lookup = {:sunday => 0, :monday => 1, :tuesday => 2, :wednesday => 3, :thursday => 4, :friday => 5, :saturday => 6}
51
+ lookup[sym] || raise("Invalid symbol specified")
52
+ end
53
+ end
@@ -0,0 +1,94 @@
1
+ class Chronic::RepeaterDayPortion < Chronic::Repeater #:nodoc:
2
+ @@morning = (6 * 60 * 60)..(12 * 60 * 60) # 6am-12am
3
+ @@afternoon = (13 * 60 * 60)..(17 * 60 * 60) # 1pm-5pm
4
+ @@evening = (17 * 60 * 60)..(20 * 60 * 60) # 5pm-8pm
5
+ @@night = (20 * 60 * 60)..(24 * 60 * 60) # 8pm-12pm
6
+
7
+ def initialize(type)
8
+ super
9
+ @current_span = nil
10
+
11
+ if type.kind_of? Integer
12
+ @range = (@type * 60 * 60)..((@type + 12) * 60 * 60)
13
+ else
14
+ lookup = {:am => 0..(12 * 60 * 60 - 1),
15
+ :pm => (12 * 60 * 60)..(24 * 60 * 60 - 1),
16
+ :morning => @@morning,
17
+ :afternoon => @@afternoon,
18
+ :evening => @@evening,
19
+ :night => @@night}
20
+ @range = lookup[type]
21
+ lookup[type] || raise("Invalid type '#{type}' for RepeaterDayPortion")
22
+ end
23
+ @range || raise("Range should have been set by now")
24
+ end
25
+
26
+ def next(pointer)
27
+ super
28
+
29
+ full_day = 60 * 60 * 24
30
+
31
+ if !@current_span
32
+ now_seconds = @now - Time.construct(@now.year, @now.month, @now.day)
33
+ if now_seconds < @range.begin
34
+ case pointer
35
+ when :future
36
+ range_start = Time.construct(@now.year, @now.month, @now.day) + @range.begin
37
+ when :past
38
+ range_start = Time.construct(@now.year, @now.month, @now.day) - full_day + @range.begin
39
+ end
40
+ elsif now_seconds > @range.end
41
+ case pointer
42
+ when :future
43
+ range_start = Time.construct(@now.year, @now.month, @now.day) + full_day + @range.begin
44
+ when :past
45
+ range_start = Time.construct(@now.year, @now.month, @now.day) + @range.begin
46
+ end
47
+ else
48
+ case pointer
49
+ when :future
50
+ range_start = Time.construct(@now.year, @now.month, @now.day) + full_day + @range.begin
51
+ when :past
52
+ range_start = Time.construct(@now.year, @now.month, @now.day) - full_day + @range.begin
53
+ end
54
+ end
55
+
56
+ @current_span = Chronic::Span.new(range_start, range_start + (@range.end - @range.begin))
57
+ else
58
+ case pointer
59
+ when :future
60
+ @current_span += full_day
61
+ when :past
62
+ @current_span -= full_day
63
+ end
64
+ end
65
+ end
66
+
67
+ def this(context = :future)
68
+ super
69
+
70
+ range_start = Time.construct(@now.year, @now.month, @now.day) + @range.begin
71
+ @current_span = Chronic::Span.new(range_start, range_start + (@range.end - @range.begin))
72
+ end
73
+
74
+ def offset(span, amount, pointer)
75
+ @now = span.begin
76
+ portion_span = self.next(pointer)
77
+ direction = pointer == :future ? 1 : -1
78
+ portion_span + (direction * (amount - 1) * Chronic::RepeaterDay::DAY_SECONDS)
79
+ end
80
+
81
+ def width
82
+ @range || raise("Range has not been set")
83
+ return @current_span.width if @current_span
84
+ if @type.kind_of? Integer
85
+ return (12 * 60 * 60)
86
+ else
87
+ @range.end - @range.begin
88
+ end
89
+ end
90
+
91
+ def to_s
92
+ super << '-dayportion-' << @type.to_s
93
+ end
94
+ end
@@ -0,0 +1,70 @@
1
+ class Chronic::RepeaterFortnight < Chronic::Repeater #:nodoc:
2
+ FORTNIGHT_SECONDS = 1_209_600 # (14 * 24 * 60 * 60)
3
+
4
+ def initialize(type)
5
+ super
6
+ @current_fortnight_start = nil
7
+ end
8
+
9
+ def next(pointer)
10
+ super
11
+
12
+ if !@current_fortnight_start
13
+ case pointer
14
+ when :future
15
+ sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
16
+ sunday_repeater.start = @now
17
+ next_sunday_span = sunday_repeater.next(:future)
18
+ @current_fortnight_start = next_sunday_span.begin
19
+ when :past
20
+ sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
21
+ sunday_repeater.start = (@now + Chronic::RepeaterDay::DAY_SECONDS)
22
+ 2.times { sunday_repeater.next(:past) }
23
+ last_sunday_span = sunday_repeater.next(:past)
24
+ @current_fortnight_start = last_sunday_span.begin
25
+ end
26
+ else
27
+ direction = pointer == :future ? 1 : -1
28
+ @current_fortnight_start += direction * FORTNIGHT_SECONDS
29
+ end
30
+
31
+ Chronic::Span.new(@current_fortnight_start, @current_fortnight_start + FORTNIGHT_SECONDS)
32
+ end
33
+
34
+ def this(pointer = :future)
35
+ super
36
+
37
+ pointer = :future if pointer == :none
38
+
39
+ case pointer
40
+ when :future
41
+ this_fortnight_start = Time.construct(@now.year, @now.month, @now.day, @now.hour) + Chronic::RepeaterHour::HOUR_SECONDS
42
+ sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
43
+ sunday_repeater.start = @now
44
+ sunday_repeater.this(:future)
45
+ this_sunday_span = sunday_repeater.this(:future)
46
+ this_fortnight_end = this_sunday_span.begin
47
+ Chronic::Span.new(this_fortnight_start, this_fortnight_end)
48
+ when :past
49
+ this_fortnight_end = Time.construct(@now.year, @now.month, @now.day, @now.hour)
50
+ sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
51
+ sunday_repeater.start = @now
52
+ last_sunday_span = sunday_repeater.next(:past)
53
+ this_fortnight_start = last_sunday_span.begin
54
+ Chronic::Span.new(this_fortnight_start, this_fortnight_end)
55
+ end
56
+ end
57
+
58
+ def offset(span, amount, pointer)
59
+ direction = pointer == :future ? 1 : -1
60
+ span + direction * amount * FORTNIGHT_SECONDS
61
+ end
62
+
63
+ def width
64
+ FORTNIGHT_SECONDS
65
+ end
66
+
67
+ def to_s
68
+ super << '-fortnight'
69
+ end
70
+ end
@@ -0,0 +1,58 @@
1
+ class Chronic::RepeaterHour < Chronic::Repeater #:nodoc:
2
+ HOUR_SECONDS = 3600 # 60 * 60
3
+
4
+ def initialize(type)
5
+ super
6
+ @current_hour_start = nil
7
+ end
8
+
9
+ def next(pointer)
10
+ super
11
+
12
+ if !@current_hour_start
13
+ case pointer
14
+ when :future
15
+ @current_hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour + 1)
16
+ when :past
17
+ @current_hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour - 1)
18
+ end
19
+ else
20
+ direction = pointer == :future ? 1 : -1
21
+ @current_hour_start += direction * HOUR_SECONDS
22
+ end
23
+
24
+ Chronic::Span.new(@current_hour_start, @current_hour_start + HOUR_SECONDS)
25
+ end
26
+
27
+ def this(pointer = :future)
28
+ super
29
+
30
+ case pointer
31
+ when :future
32
+ hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min + 1)
33
+ hour_end = Time.construct(@now.year, @now.month, @now.day, @now.hour + 1)
34
+ when :past
35
+ hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour)
36
+ hour_end = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min)
37
+ when :none
38
+ hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour)
39
+ hour_end = hour_start + HOUR_SECONDS
40
+ end
41
+
42
+ Chronic::Span.new(hour_start, hour_end)
43
+ end
44
+
45
+ def offset(span, amount, pointer)
46
+ direction = pointer == :future ? 1 : -1
47
+ p [span, amount, pointer] if Chronic.debug
48
+ span + direction * amount * HOUR_SECONDS
49
+ end
50
+
51
+ def width
52
+ HOUR_SECONDS
53
+ end
54
+
55
+ def to_s
56
+ super << '-hour'
57
+ end
58
+ end
@@ -0,0 +1,57 @@
1
+ class Chronic::RepeaterMinute < Chronic::Repeater #:nodoc:
2
+ MINUTE_SECONDS = 60
3
+
4
+ def initialize(type)
5
+ super
6
+ @current_minute_start = nil
7
+ end
8
+
9
+ def next(pointer = :future)
10
+ super
11
+
12
+ if !@current_minute_start
13
+ case pointer
14
+ when :future
15
+ @current_minute_start = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min + 1)
16
+ when :past
17
+ @current_minute_start = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min - 1)
18
+ end
19
+ else
20
+ direction = pointer == :future ? 1 : -1
21
+ @current_minute_start += direction * MINUTE_SECONDS
22
+ end
23
+
24
+ Chronic::Span.new(@current_minute_start, @current_minute_start + MINUTE_SECONDS)
25
+ end
26
+
27
+ def this(pointer = :future)
28
+ super
29
+
30
+ case pointer
31
+ when :future
32
+ minute_begin = @now
33
+ minute_end = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min)
34
+ when :past
35
+ minute_begin = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min)
36
+ minute_end = @now
37
+ when :none
38
+ minute_begin = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min)
39
+ minute_end = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min) + MINUTE_SECONDS
40
+ end
41
+
42
+ Chronic::Span.new(minute_begin, minute_end)
43
+ end
44
+
45
+ def offset(span, amount, pointer)
46
+ direction = pointer == :future ? 1 : -1
47
+ span + direction * amount * MINUTE_SECONDS
48
+ end
49
+
50
+ def width
51
+ MINUTE_SECONDS
52
+ end
53
+
54
+ def to_s
55
+ super << '-minute'
56
+ end
57
+ end
@@ -0,0 +1,66 @@
1
+ class Chronic::RepeaterMonth < Chronic::Repeater #:nodoc:
2
+ MONTH_SECONDS = 2_592_000 # 30 * 24 * 60 * 60
3
+ YEAR_MONTHS = 12
4
+
5
+ def initialize(type)
6
+ super
7
+ @current_month_start = nil
8
+ end
9
+
10
+ def next(pointer)
11
+ super
12
+
13
+ if !@current_month_start
14
+ @current_month_start = offset_by(Time.construct(@now.year, @now.month), 1, pointer)
15
+ else
16
+ @current_month_start = offset_by(Time.construct(@current_month_start.year, @current_month_start.month), 1, pointer)
17
+ end
18
+
19
+ Chronic::Span.new(@current_month_start, Time.construct(@current_month_start.year, @current_month_start.month + 1))
20
+ end
21
+
22
+ def this(pointer = :future)
23
+ super
24
+
25
+ case pointer
26
+ when :future
27
+ month_start = Time.construct(@now.year, @now.month, @now.day + 1)
28
+ month_end = self.offset_by(Time.construct(@now.year, @now.month), 1, :future)
29
+ when :past
30
+ month_start = Time.construct(@now.year, @now.month)
31
+ month_end = Time.construct(@now.year, @now.month, @now.day)
32
+ when :none
33
+ month_start = Time.construct(@now.year, @now.month)
34
+ month_end = self.offset_by(Time.construct(@now.year, @now.month), 1, :future)
35
+ end
36
+
37
+ Chronic::Span.new(month_start, month_end)
38
+ end
39
+
40
+ def offset(span, amount, pointer)
41
+ Chronic::Span.new(offset_by(span.begin, amount, pointer), offset_by(span.end, amount, pointer))
42
+ end
43
+
44
+ def offset_by(time, amount, pointer)
45
+ direction = pointer == :future ? 1 : -1
46
+
47
+ amount_years = direction * amount / YEAR_MONTHS
48
+ amount_months = direction * amount % YEAR_MONTHS
49
+
50
+ new_year = time.year + amount_years
51
+ new_month = time.month + amount_months
52
+ if new_month > YEAR_MONTHS
53
+ new_year += 1
54
+ new_month -= YEAR_MONTHS
55
+ end
56
+ Time.construct(new_year, new_month, time.day, time.hour, time.min, time.sec)
57
+ end
58
+
59
+ def width
60
+ MONTH_SECONDS
61
+ end
62
+
63
+ def to_s
64
+ super << '-month'
65
+ end
66
+ end
@@ -0,0 +1,98 @@
1
+ class Chronic::RepeaterMonthName < Chronic::Repeater #:nodoc:
2
+ MONTH_SECONDS = 2_592_000 # 30 * 24 * 60 * 60
3
+
4
+ def initialize(type)
5
+ super
6
+ @current_month_begin = nil
7
+ end
8
+
9
+ def next(pointer)
10
+ super
11
+
12
+ if !@current_month_begin
13
+ target_month = symbol_to_number(@type)
14
+ case pointer
15
+ when :future
16
+ if @now.month < target_month
17
+ @current_month_begin = Time.construct(@now.year, target_month)
18
+ elsif @now.month > target_month
19
+ @current_month_begin = Time.construct(@now.year + 1, target_month)
20
+ end
21
+ when :none
22
+ if @now.month <= target_month
23
+ @current_month_begin = Time.construct(@now.year, target_month)
24
+ elsif @now.month > target_month
25
+ @current_month_begin = Time.construct(@now.year + 1, target_month)
26
+ end
27
+ when :past
28
+ if @now.month > target_month
29
+ @current_month_begin = Time.construct(@now.year, target_month)
30
+ elsif @now.month < target_month
31
+ @current_month_begin = Time.construct(@now.year - 1, target_month)
32
+ end
33
+ end
34
+ @current_month_begin || raise("Current month should be set by now")
35
+ else
36
+ case pointer
37
+ when :future
38
+ @current_month_begin = Time.construct(@current_month_begin.year + 1, @current_month_begin.month)
39
+ when :past
40
+ @current_month_begin = Time.construct(@current_month_begin.year - 1, @current_month_begin.month)
41
+ end
42
+ end
43
+
44
+ cur_month_year = @current_month_begin.year
45
+ cur_month_month = @current_month_begin.month
46
+
47
+ if cur_month_month == 12
48
+ next_month_year = cur_month_year + 1
49
+ next_month_month = 1
50
+ else
51
+ next_month_year = cur_month_year
52
+ next_month_month = cur_month_month + 1
53
+ end
54
+
55
+ Chronic::Span.new(@current_month_begin, Time.construct(next_month_year, next_month_month))
56
+ end
57
+
58
+ def this(pointer = :future)
59
+ super
60
+
61
+ case pointer
62
+ when :past
63
+ self.next(pointer)
64
+ when :future, :none
65
+ self.next(:none)
66
+ end
67
+ end
68
+
69
+ def width
70
+ MONTH_SECONDS
71
+ end
72
+
73
+ def index
74
+ symbol_to_number(@type)
75
+ end
76
+
77
+ def to_s
78
+ super << '-monthname-' << @type.to_s
79
+ end
80
+
81
+ private
82
+
83
+ def symbol_to_number(sym)
84
+ lookup = {:january => 1,
85
+ :february => 2,
86
+ :march => 3,
87
+ :april => 4,
88
+ :may => 5,
89
+ :june => 6,
90
+ :july => 7,
91
+ :august => 8,
92
+ :september => 9,
93
+ :october => 10,
94
+ :november => 11,
95
+ :december => 12}
96
+ lookup[sym] || raise("Invalid symbol specified")
97
+ end
98
+ end