chronic_2011 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,109 @@
1
+ module Chronic
2
+ class RepeaterSeason < Repeater #:nodoc:
3
+ SEASON_SECONDS = 7_862_400 # 91 * 24 * 60 * 60
4
+ SEASONS = {
5
+ :spring => Season.new(MiniDate.new(3,20), MiniDate.new(6,20)),
6
+ :summer => Season.new(MiniDate.new(6,21), MiniDate.new(9,22)),
7
+ :autumn => Season.new(MiniDate.new(9,23), MiniDate.new(12,21)),
8
+ :winter => Season.new(MiniDate.new(12,22), MiniDate.new(3,19))
9
+ }
10
+
11
+ def initialize(type)
12
+ super
13
+ end
14
+
15
+ def next(pointer)
16
+ super
17
+
18
+ direction = pointer == :future ? 1 : -1
19
+ next_season = Season.find_next_season(find_current_season(MiniDate.from_time(@now)), direction)
20
+
21
+ find_next_season_span(direction, next_season)
22
+ end
23
+
24
+ def this(pointer = :future)
25
+ super
26
+
27
+ direction = pointer == :future ? 1 : -1
28
+
29
+ today = Chronic.construct(@now.year, @now.month, @now.day)
30
+ this_ssn = find_current_season(MiniDate.from_time(@now))
31
+ case pointer
32
+ when :past
33
+ this_ssn_start = today + direction * num_seconds_til_start(this_ssn, direction)
34
+ this_ssn_end = today
35
+ when :future
36
+ this_ssn_start = today + RepeaterDay::DAY_SECONDS
37
+ this_ssn_end = today + direction * num_seconds_til_end(this_ssn, direction)
38
+ when :none
39
+ this_ssn_start = today + direction * num_seconds_til_start(this_ssn, direction)
40
+ this_ssn_end = today + direction * num_seconds_til_end(this_ssn, direction)
41
+ end
42
+
43
+ construct_season(this_ssn_start, this_ssn_end)
44
+ end
45
+
46
+ def offset(span, amount, pointer)
47
+ Span.new(offset_by(span.begin, amount, pointer), offset_by(span.end, amount, pointer))
48
+ end
49
+
50
+ def offset_by(time, amount, pointer)
51
+ direction = pointer == :future ? 1 : -1
52
+ time + amount * direction * SEASON_SECONDS
53
+ end
54
+
55
+ def width
56
+ SEASON_SECONDS
57
+ end
58
+
59
+ def to_s
60
+ super << '-season'
61
+ end
62
+
63
+ private
64
+
65
+ def find_next_season_span(direction, next_season)
66
+ unless @next_season_start or @next_season_end
67
+ @next_season_start = Chronic.construct(@now.year, @now.month, @now.day)
68
+ @next_season_end = Chronic.construct(@now.year, @now.month, @now.day)
69
+ end
70
+
71
+ @next_season_start += direction * num_seconds_til_start(next_season, direction)
72
+ @next_season_end += direction * num_seconds_til_end(next_season, direction)
73
+
74
+ construct_season(@next_season_start, @next_season_end)
75
+ end
76
+
77
+ def find_current_season(md)
78
+ [:spring, :summer, :autumn, :winter].find do |season|
79
+ md.is_between?(SEASONS[season].start, SEASONS[season].end)
80
+ end
81
+ end
82
+
83
+ def num_seconds_til(goal, direction)
84
+ start = Chronic.construct(@now.year, @now.month, @now.day)
85
+ seconds = 0
86
+
87
+ until MiniDate.from_time(start + direction * seconds).equals?(goal)
88
+ seconds += RepeaterDay::DAY_SECONDS
89
+ end
90
+
91
+ seconds
92
+ end
93
+
94
+ def num_seconds_til_start(season_symbol, direction)
95
+ num_seconds_til(SEASONS[season_symbol].start, direction)
96
+ end
97
+
98
+ def num_seconds_til_end(season_symbol, direction)
99
+ num_seconds_til(SEASONS[season_symbol].end, direction)
100
+ end
101
+
102
+ def construct_season(start, finish)
103
+ Span.new(
104
+ Chronic.construct(start.year, start.month, start.day),
105
+ Chronic.construct(finish.year, finish.month, finish.day)
106
+ )
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,43 @@
1
+ module Chronic
2
+ class RepeaterSeasonName < RepeaterSeason #:nodoc:
3
+ SEASON_SECONDS = 7_862_400 # 91 * 24 * 60 * 60
4
+ DAY_SECONDS = 86_400 # (24 * 60 * 60)
5
+
6
+ def next(pointer)
7
+ direction = pointer == :future ? 1 : -1
8
+ find_next_season_span(direction, @type)
9
+ end
10
+
11
+ def this(pointer = :future)
12
+ direction = pointer == :future ? 1 : -1
13
+
14
+ today = Chronic.construct(@now.year, @now.month, @now.day)
15
+ goal_ssn_start = today + direction * num_seconds_til_start(@type, direction)
16
+ goal_ssn_end = today + direction * num_seconds_til_end(@type, direction)
17
+ curr_ssn = find_current_season(MiniDate.from_time(@now))
18
+ case pointer
19
+ when :past
20
+ this_ssn_start = goal_ssn_start
21
+ this_ssn_end = (curr_ssn == @type) ? today : goal_ssn_end
22
+ when :future
23
+ this_ssn_start = (curr_ssn == @type) ? today + RepeaterDay::DAY_SECONDS : goal_ssn_start
24
+ this_ssn_end = goal_ssn_end
25
+ when :none
26
+ this_ssn_start = goal_ssn_start
27
+ this_ssn_end = goal_ssn_end
28
+ end
29
+
30
+ construct_season(this_ssn_start, this_ssn_end)
31
+ end
32
+
33
+ def offset(span, amount, pointer)
34
+ Span.new(offset_by(span.begin, amount, pointer), offset_by(span.end, amount, pointer))
35
+ end
36
+
37
+ def offset_by(time, amount, pointer)
38
+ direction = pointer == :future ? 1 : -1
39
+ time + amount * direction * RepeaterYear::YEAR_SECONDS
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,42 @@
1
+ module Chronic
2
+ class RepeaterSecond < Repeater #:nodoc:
3
+ SECOND_SECONDS = 1 # haha, awesome
4
+
5
+ def initialize(type)
6
+ super
7
+ end
8
+
9
+ def next(pointer = :future)
10
+ super
11
+
12
+ direction = pointer == :future ? 1 : -1
13
+
14
+ if !@second_start
15
+ @second_start = @now + (direction * SECOND_SECONDS)
16
+ else
17
+ @second_start += SECOND_SECONDS * direction
18
+ end
19
+
20
+ Span.new(@second_start, @second_start + SECOND_SECONDS)
21
+ end
22
+
23
+ def this(pointer = :future)
24
+ super
25
+
26
+ Span.new(@now, @now + 1)
27
+ end
28
+
29
+ def offset(span, amount, pointer)
30
+ direction = pointer == :future ? 1 : -1
31
+ span + direction * amount * SECOND_SECONDS
32
+ end
33
+
34
+ def width
35
+ SECOND_SECONDS
36
+ end
37
+
38
+ def to_s
39
+ super << '-second'
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,128 @@
1
+ module Chronic
2
+ class RepeaterTime < Repeater #:nodoc:
3
+ class Tick #:nodoc:
4
+ attr_accessor :time
5
+
6
+ def initialize(time, ambiguous = false)
7
+ @time = time
8
+ @ambiguous = ambiguous
9
+ end
10
+
11
+ def ambiguous?
12
+ @ambiguous
13
+ end
14
+
15
+ def *(other)
16
+ Tick.new(@time * other, @ambiguous)
17
+ end
18
+
19
+ def to_f
20
+ @time.to_f
21
+ end
22
+
23
+ def to_s
24
+ @time.to_s + (@ambiguous ? '?' : '')
25
+ end
26
+
27
+ end
28
+
29
+ def initialize(time)
30
+ t = time.gsub(/\:/, '')
31
+
32
+ @type =
33
+ case t.size
34
+ when 1..2
35
+ hours = t.to_i
36
+ Tick.new((hours == 12 ? 0 : hours) * 60 * 60, true)
37
+ when 3
38
+ hours = t[0..0].to_i
39
+ ambiguous = hours > 0
40
+ Tick.new((hours * 60 * 60) + (t[1..2].to_i * 60), ambiguous)
41
+ when 4
42
+ ambiguous = time =~ /:/ && t[0..0].to_i != 0 && t[0..1].to_i <= 12
43
+ hours = t[0..1].to_i
44
+ hours == 12 ? Tick.new(0 * 60 * 60 + t[2..3].to_i * 60, ambiguous) : Tick.new(hours * 60 * 60 + t[2..3].to_i * 60, ambiguous)
45
+ when 5
46
+ Tick.new(t[0..0].to_i * 60 * 60 + t[1..2].to_i * 60 + t[3..4].to_i, true)
47
+ when 6
48
+ ambiguous = time =~ /:/ && t[0..0].to_i != 0 && t[0..1].to_i <= 12
49
+ hours = t[0..1].to_i
50
+ hours == 12 ? Tick.new(0 * 60 * 60 + t[2..3].to_i * 60 + t[4..5].to_i, ambiguous) : Tick.new(hours * 60 * 60 + t[2..3].to_i * 60 + t[4..5].to_i, ambiguous)
51
+ else
52
+ raise("Time cannot exceed six digits")
53
+ end
54
+ end
55
+
56
+ # Return the next past or future Span for the time that this Repeater represents
57
+ # pointer - Symbol representing which temporal direction to fetch the next day
58
+ # must be either :past or :future
59
+ def next(pointer)
60
+ super
61
+
62
+ half_day = 60 * 60 * 12
63
+ full_day = 60 * 60 * 24
64
+
65
+ first = false
66
+
67
+ unless @current_time
68
+ first = true
69
+ midnight = Chronic.time_class.local(@now.year, @now.month, @now.day)
70
+
71
+ yesterday_midnight = midnight - full_day
72
+ tomorrow_midnight = midnight + full_day
73
+
74
+ offset_fix = midnight.gmt_offset - tomorrow_midnight.gmt_offset
75
+ tomorrow_midnight += offset_fix
76
+
77
+ catch :done do
78
+ if pointer == :future
79
+ if @type.ambiguous?
80
+ [midnight + @type.time + offset_fix, midnight + half_day + @type.time + offset_fix, tomorrow_midnight + @type.time].each do |t|
81
+ (@current_time = t; throw :done) if t >= @now
82
+ end
83
+ else
84
+ [midnight + @type.time + offset_fix, tomorrow_midnight + @type.time].each do |t|
85
+ (@current_time = t; throw :done) if t >= @now
86
+ end
87
+ end
88
+ else # pointer == :past
89
+ if @type.ambiguous?
90
+ [midnight + half_day + @type.time + offset_fix, midnight + @type.time + offset_fix, yesterday_midnight + @type.time + half_day].each do |t|
91
+ (@current_time = t; throw :done) if t <= @now
92
+ end
93
+ else
94
+ [midnight + @type.time + offset_fix, yesterday_midnight + @type.time].each do |t|
95
+ (@current_time = t; throw :done) if t <= @now
96
+ end
97
+ end
98
+ end
99
+ end
100
+
101
+ @current_time || raise("Current time cannot be nil at this point")
102
+ end
103
+
104
+ unless first
105
+ increment = @type.ambiguous? ? half_day : full_day
106
+ @current_time += pointer == :future ? increment : -increment
107
+ end
108
+
109
+ Span.new(@current_time, @current_time + width)
110
+ end
111
+
112
+ def this(context = :future)
113
+ super
114
+
115
+ context = :future if context == :none
116
+
117
+ self.next(context)
118
+ end
119
+
120
+ def width
121
+ 1
122
+ end
123
+
124
+ def to_s
125
+ super << '-time-' << @type.to_s
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,74 @@
1
+ module Chronic
2
+ class RepeaterWeek < Repeater #:nodoc:
3
+ WEEK_SECONDS = 604800 # (7 * 24 * 60 * 60)
4
+
5
+ def initialize(type)
6
+ super
7
+ end
8
+
9
+ def next(pointer)
10
+ super
11
+
12
+ if !@current_week_start
13
+ case pointer
14
+ when :future
15
+ sunday_repeater = RepeaterDayName.new(:sunday)
16
+ sunday_repeater.start = @now
17
+ next_sunday_span = sunday_repeater.next(:future)
18
+ @current_week_start = next_sunday_span.begin
19
+ when :past
20
+ sunday_repeater = RepeaterDayName.new(:sunday)
21
+ sunday_repeater.start = (@now + RepeaterDay::DAY_SECONDS)
22
+ sunday_repeater.next(:past)
23
+ last_sunday_span = sunday_repeater.next(:past)
24
+ @current_week_start = last_sunday_span.begin
25
+ end
26
+ else
27
+ direction = pointer == :future ? 1 : -1
28
+ @current_week_start += direction * WEEK_SECONDS
29
+ end
30
+
31
+ Span.new(@current_week_start, @current_week_start + WEEK_SECONDS)
32
+ end
33
+
34
+ def this(pointer = :future)
35
+ super
36
+
37
+ case pointer
38
+ when :future
39
+ this_week_start = Chronic.time_class.local(@now.year, @now.month, @now.day, @now.hour) + RepeaterHour::HOUR_SECONDS
40
+ sunday_repeater = RepeaterDayName.new(:sunday)
41
+ sunday_repeater.start = @now
42
+ this_sunday_span = sunday_repeater.this(:future)
43
+ this_week_end = this_sunday_span.begin
44
+ Span.new(this_week_start, this_week_end)
45
+ when :past
46
+ this_week_end = Chronic.time_class.local(@now.year, @now.month, @now.day, @now.hour)
47
+ sunday_repeater = RepeaterDayName.new(:sunday)
48
+ sunday_repeater.start = @now
49
+ last_sunday_span = sunday_repeater.next(:past)
50
+ this_week_start = last_sunday_span.begin
51
+ Span.new(this_week_start, this_week_end)
52
+ when :none
53
+ sunday_repeater = RepeaterDayName.new(:sunday)
54
+ sunday_repeater.start = @now
55
+ last_sunday_span = sunday_repeater.next(:past)
56
+ this_week_start = last_sunday_span.begin
57
+ Span.new(this_week_start, this_week_start + WEEK_SECONDS)
58
+ end
59
+ end
60
+
61
+ def offset(span, amount, pointer)
62
+ direction = pointer == :future ? 1 : -1
63
+ span + direction * amount * WEEK_SECONDS
64
+ end
65
+
66
+ def width
67
+ WEEK_SECONDS
68
+ end
69
+
70
+ def to_s
71
+ super << '-week'
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,85 @@
1
+ module Chronic
2
+ class RepeaterWeekday < Repeater #:nodoc:
3
+ DAY_SECONDS = 86400 # (24 * 60 * 60)
4
+ DAYS = {
5
+ :sunday => 0,
6
+ :monday => 1,
7
+ :tuesday => 2,
8
+ :wednesday => 3,
9
+ :thursday => 4,
10
+ :friday => 5,
11
+ :saturday => 6
12
+ }
13
+
14
+ def initialize(type)
15
+ super
16
+ end
17
+
18
+ def next(pointer)
19
+ super
20
+
21
+ direction = pointer == :future ? 1 : -1
22
+
23
+ if !@current_weekday_start
24
+ @current_weekday_start = Chronic.construct(@now.year, @now.month, @now.day)
25
+ @current_weekday_start += direction * DAY_SECONDS
26
+
27
+ until is_weekday?(@current_weekday_start.wday)
28
+ @current_weekday_start += direction * DAY_SECONDS
29
+ end
30
+ else
31
+ loop do
32
+ @current_weekday_start += direction * DAY_SECONDS
33
+ break if is_weekday?(@current_weekday_start.wday)
34
+ end
35
+ end
36
+
37
+ Span.new(@current_weekday_start, @current_weekday_start + DAY_SECONDS)
38
+ end
39
+
40
+ def this(pointer = :future)
41
+ super
42
+
43
+ case pointer
44
+ when :past
45
+ self.next(:past)
46
+ when :future, :none
47
+ self.next(:future)
48
+ end
49
+ end
50
+
51
+ def offset(span, amount, pointer)
52
+ direction = pointer == :future ? 1 : -1
53
+
54
+ num_weekdays_passed = 0; offset = 0
55
+ until num_weekdays_passed == amount
56
+ offset += direction * DAY_SECONDS
57
+ num_weekdays_passed += 1 if is_weekday?((span.begin+offset).wday)
58
+ end
59
+
60
+ span + offset
61
+ end
62
+
63
+ def width
64
+ DAY_SECONDS
65
+ end
66
+
67
+ def to_s
68
+ super << '-weekday'
69
+ end
70
+
71
+ private
72
+
73
+ def is_weekend?(day)
74
+ day == symbol_to_number(:saturday) || day == symbol_to_number(:sunday)
75
+ end
76
+
77
+ def is_weekday?(day)
78
+ !is_weekend?(day)
79
+ end
80
+
81
+ def symbol_to_number(sym)
82
+ DAYS[sym] || raise("Invalid symbol specified")
83
+ end
84
+ end
85
+ end