chronic 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 (41) hide show
  1. data/README +119 -0
  2. data/lib/chronic.rb +30 -0
  3. data/lib/chronic/chronic.rb +242 -0
  4. data/lib/chronic/grabber.rb +26 -0
  5. data/lib/chronic/handlers.rb +405 -0
  6. data/lib/chronic/ordinal.rb +40 -0
  7. data/lib/chronic/pointer.rb +27 -0
  8. data/lib/chronic/repeater.rb +114 -0
  9. data/lib/chronic/repeaters/repeater_day.rb +40 -0
  10. data/lib/chronic/repeaters/repeater_day_name.rb +41 -0
  11. data/lib/chronic/repeaters/repeater_day_portion.rb +93 -0
  12. data/lib/chronic/repeaters/repeater_fortnight.rb +64 -0
  13. data/lib/chronic/repeaters/repeater_hour.rb +52 -0
  14. data/lib/chronic/repeaters/repeater_minute.rb +21 -0
  15. data/lib/chronic/repeaters/repeater_month.rb +54 -0
  16. data/lib/chronic/repeaters/repeater_month_name.rb +82 -0
  17. data/lib/chronic/repeaters/repeater_season.rb +23 -0
  18. data/lib/chronic/repeaters/repeater_season_name.rb +24 -0
  19. data/lib/chronic/repeaters/repeater_second.rb +34 -0
  20. data/lib/chronic/repeaters/repeater_time.rb +106 -0
  21. data/lib/chronic/repeaters/repeater_week.rb +62 -0
  22. data/lib/chronic/repeaters/repeater_weekend.rb +11 -0
  23. data/lib/chronic/repeaters/repeater_year.rb +55 -0
  24. data/lib/chronic/scalar.rb +74 -0
  25. data/lib/chronic/separator.rb +76 -0
  26. data/test/parse_numbers.rb +50 -0
  27. data/test/suite.rb +9 -0
  28. data/test/test_Chronic.rb +50 -0
  29. data/test/test_Handler.rb +110 -0
  30. data/test/test_RepeaterDayName.rb +52 -0
  31. data/test/test_RepeaterFortnight.rb +63 -0
  32. data/test/test_RepeaterHour.rb +65 -0
  33. data/test/test_RepeaterMonth.rb +47 -0
  34. data/test/test_RepeaterMonthName.rb +57 -0
  35. data/test/test_RepeaterTime.rb +72 -0
  36. data/test/test_RepeaterWeek.rb +63 -0
  37. data/test/test_RepeaterYear.rb +63 -0
  38. data/test/test_Span.rb +24 -0
  39. data/test/test_Token.rb +26 -0
  40. data/test/test_parsing.rb +472 -0
  41. metadata +87 -0
@@ -0,0 +1,21 @@
1
+ class Chronic::RepeaterMinute < Chronic::Repeater #:nodoc:
2
+ MINUTE_SECONDS = 60
3
+
4
+ def this(pointer = :future)
5
+ minute_begin = Time.local(@now.year, @now.month, @now.day, @now.hour, @now.min)
6
+ Chronic::Span.new(minute_begin, minute_begin + MINUTE_SECONDS)
7
+ end
8
+
9
+ def offset(span, amount, pointer)
10
+ direction = pointer == :future ? 1 : -1
11
+ span + direction * amount * MINUTE_SECONDS
12
+ end
13
+
14
+ def width
15
+ MINUTE_SECONDS
16
+ end
17
+
18
+ def to_s
19
+ super << '-minute'
20
+ end
21
+ end
@@ -0,0 +1,54 @@
1
+ class Chronic::RepeaterMonth < Chronic::Repeater #:nodoc:
2
+ MONTH_SECONDS = 2_592_000 # 30 * 24 * 60 * 60
3
+ YEAR_MONTHS = 12
4
+
5
+ def next(pointer)
6
+ super
7
+
8
+ if !@current_month_start
9
+ @current_month_start = offset_by(Time.local(@now.year, @now.month), 1, pointer)
10
+ else
11
+ @current_month_start = offset_by(Time.local(@current_month_start.year, @current_month_start.month), 1, pointer)
12
+ end
13
+
14
+ Chronic::Span.new(@current_month_start, Time.local(@current_month_start.year, @current_month_start.month + 1))
15
+ end
16
+
17
+ def this(pointer = :future)
18
+ super
19
+
20
+ case pointer
21
+ when :future
22
+ month_start = Time.local(@now.year, @now.month, @now.day + 1)
23
+ month_end = self.offset_by(Time.local(@now.year, @now.month), 1, :future)
24
+ when :past
25
+ month_start = Time.local(@now.year, @now.month)
26
+ month_end = Time.local(@now.year, @now.month, @now.day)
27
+ end
28
+
29
+ Chronic::Span.new(month_start, month_end)
30
+ end
31
+
32
+ def offset(span, amount, pointer)
33
+ Chronic::Span.new(offset_by(span.begin, amount, pointer), offset_by(span.end, amount, pointer))
34
+ end
35
+
36
+ def offset_by(time, amount, pointer)
37
+ direction = pointer == :future ? 1 : -1
38
+
39
+ amount_years = direction * amount / YEAR_MONTHS
40
+ amount_months = direction * amount % YEAR_MONTHS
41
+
42
+ new_year = time.year + amount_years
43
+ new_month = time.month + amount_months
44
+ if new_month > YEAR_MONTHS
45
+ new_year += 1
46
+ new_month -= YEAR_MONTHS
47
+ end
48
+ Time.local(new_year, new_month, time.day, time.hour, time.min, time.sec)
49
+ end
50
+
51
+ def width
52
+ MONTH_SECONDS
53
+ end
54
+ end
@@ -0,0 +1,82 @@
1
+ class Chronic::RepeaterMonthName < Chronic::Repeater #:nodoc:
2
+ MONTH_SECONDS = 2_592_000 # 30 * 24 * 60 * 60
3
+
4
+ def next(pointer)
5
+ super
6
+
7
+ if !@current_month_begin
8
+ target_month = symbol_to_number(@type)
9
+ case pointer
10
+ when :future
11
+ if @now.month < target_month
12
+ @current_month_begin = Time.local(@now.year, target_month)
13
+ else @now.month > target_month
14
+ @current_month_begin = Time.local(@now.year + 1, target_month)
15
+ end
16
+ when :past
17
+ if @now.month > target_month
18
+ @current_month_begin = Time.local(@now.year, target_month)
19
+ else @now.month < target_month
20
+ @current_month_begin = Time.local(@now.year - 1, target_month)
21
+ end
22
+ end
23
+ @current_month_begin || raise("Current month should be set by now")
24
+ else
25
+ case pointer
26
+ when :future
27
+ @current_month_begin = Time.local(@current_month_begin.year + 1, @current_month_begin.month)
28
+ when :past
29
+ @current_month_begin = Time.local(@current_month_begin.year - 1, @current_month_begin.month)
30
+ end
31
+ end
32
+
33
+ cur_month_year = @current_month_begin.year
34
+ cur_month_month = @current_month_begin.month
35
+
36
+ if cur_month_month == 12
37
+ next_month_year = cur_month_year + 1
38
+ next_month_month = 1
39
+ else
40
+ next_month_year = cur_month_year
41
+ next_month_month = cur_month_month + 1
42
+ end
43
+
44
+ Chronic::Span.new(@current_month_begin, Time.local(next_month_year, next_month_month))
45
+ end
46
+
47
+ def this(pointer = :future)
48
+ super
49
+
50
+ self.next(pointer)
51
+ end
52
+
53
+ def width
54
+ MONTH_SECONDS
55
+ end
56
+
57
+ def index
58
+ symbol_to_number(@type)
59
+ end
60
+
61
+ def to_s
62
+ super << '-month-' << @type.to_s
63
+ end
64
+
65
+ private
66
+
67
+ def symbol_to_number(sym)
68
+ lookup = {:january => 1,
69
+ :february => 2,
70
+ :march => 3,
71
+ :april => 4,
72
+ :may => 5,
73
+ :june => 6,
74
+ :july => 7,
75
+ :august => 8,
76
+ :september => 9,
77
+ :october => 10,
78
+ :november => 11,
79
+ :december => 12}
80
+ lookup[sym] || raise("Invalid symbol specified")
81
+ end
82
+ end
@@ -0,0 +1,23 @@
1
+ class Chronic::RepeaterSeason < Chronic::Repeater #:nodoc:
2
+ SEASON_SECONDS = 7_862_400 # 91 * 24 * 60 * 60
3
+
4
+ def next(pointer)
5
+ super
6
+
7
+ raise 'Not implemented'
8
+ end
9
+
10
+ def this(pointer = :future)
11
+ super
12
+
13
+ raise 'Not implemented'
14
+ end
15
+
16
+ def width
17
+ SEASON_SECONDS
18
+ end
19
+
20
+ def to_s
21
+ super << '-season'
22
+ end
23
+ end
@@ -0,0 +1,24 @@
1
+ class Chronic::RepeaterSeasonName < Chronic::RepeaterSeason #:nodoc:
2
+ @summer = ['jul 21', 'sep 22']
3
+ @autumn = ['sep 23', 'dec 21']
4
+ @winter = ['dec 22', 'mar 19']
5
+ @spring = ['mar 20', 'jul 20']
6
+
7
+ def next(pointer)
8
+ super
9
+ raise 'Not implemented'
10
+ end
11
+
12
+ def this(pointer = :future)
13
+ super
14
+ raise 'Not implemented'
15
+ end
16
+
17
+ def width
18
+ (91 * 24 * 60 * 60)
19
+ end
20
+
21
+ def to_s
22
+ super << '-season-' << @type.to_s
23
+ end
24
+ end
@@ -0,0 +1,34 @@
1
+ class Chronic::RepeaterSecond < Chronic::Repeater #:nodoc:
2
+ SECOND_SECONDS = 1 # haha, awesome
3
+
4
+ def next(pointer = :future)
5
+ super
6
+
7
+ direction = pointer == :future ? 1 : -1
8
+
9
+ if !@second_start
10
+ @second_start = @now + (direction * SECOND_SECONDS)
11
+ else
12
+ @second_start += SECOND_SECONDS * direction
13
+ end
14
+
15
+ Chronic::Span.new(@second_start, @second_start + SECOND_SECONDS)
16
+ end
17
+
18
+ def this(pointer = :future)
19
+ Chronic::Span.new(@now, @now + 1)
20
+ end
21
+
22
+ def offset(span, amount, pointer)
23
+ direction = pointer == :future ? 1 : -1
24
+ span + direction * amount * SECOND_SECONDS
25
+ end
26
+
27
+ def width
28
+ SECOND_SECONDS
29
+ end
30
+
31
+ def to_s
32
+ super << '-second'
33
+ end
34
+ end
@@ -0,0 +1,106 @@
1
+ class Chronic::RepeaterTime < Chronic::Repeater #:nodoc:
2
+ class Tick #:nodoc:
3
+ attr_accessor :time
4
+
5
+ def initialize(time, ambiguous = false)
6
+ @time = time
7
+ @ambiguous = ambiguous
8
+ end
9
+
10
+ def ambiguous?
11
+ @ambiguous
12
+ end
13
+
14
+ def *(other)
15
+ Tick.new(@time * other, @ambiguous)
16
+ end
17
+
18
+ def to_f
19
+ @time.to_f
20
+ end
21
+
22
+ def to_s
23
+ @time.to_s + (@ambiguous ? '?' : '')
24
+ end
25
+ end
26
+
27
+ def initialize(time, options = {})
28
+ t = time.sub(/\:/, '')
29
+ @type =
30
+ if (1..2) === t.size
31
+ Tick.new(t.to_i * 60 * 60, true)
32
+ elsif t.size == 3
33
+ Tick.new((t[0..0].to_i * 60 * 60) + (t[1..2].to_i * 60), true)
34
+ elsif t.size == 4
35
+ ambiguous = time =~ /:/ && t[0..0].to_i != 0 && t[0..1].to_i <= 12
36
+ Tick.new(t[0..1].to_i * 60 * 60 + t[2..3].to_i * 60, ambiguous)
37
+ else
38
+ raise("Time cannot exceed four digits")
39
+ end
40
+ end
41
+
42
+ # Return the next past or future Span for the time that this Repeater represents
43
+ # pointer - Symbol representing which temporal direction to fetch the next day
44
+ # must be either :past or :future
45
+ def next(pointer)
46
+ super
47
+
48
+ half_day = 60 * 60 * 12
49
+ full_day = 60 * 60 * 24
50
+
51
+ first = false
52
+
53
+ unless @current_time
54
+ first = true
55
+ midnight = Time.local(@now.year, @now.month, @now.day)
56
+ yesterday_midnight = midnight - full_day
57
+ tomorrow_midnight = midnight + full_day
58
+
59
+ catch :done do
60
+ if pointer == :future
61
+ if @type.ambiguous?
62
+ [midnight + @type, midnight + half_day + @type, tomorrow_midnight + @type].each do |t|
63
+ (@current_time = t; throw :done) if t > @now
64
+ end
65
+ else
66
+ [midnight + @type, tomorrow_midnight + @type].each do |t|
67
+ (@current_time = t; throw :done) if t > @now
68
+ end
69
+ end
70
+ else # pointer == :past
71
+ if @type.ambiguous?
72
+ [midnight + half_day + @type, midnight + @type, yesterday_midnight + @type * 2].each do |t|
73
+ (@current_time = t; throw :done) if t < @now
74
+ end
75
+ else
76
+ [midnight + @type, yesterday_midnight + @type].each do |t|
77
+ (@current_time = t; throw :done) if t < @now
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+ @current_time || raise("Current time cannot be nil at this point")
84
+ end
85
+
86
+ unless first
87
+ increment = @type.ambiguous? ? half_day : full_day
88
+ @current_time += pointer == :future ? increment : -increment
89
+ end
90
+
91
+ Chronic::Span.new(@current_time, @current_time + width)
92
+ end
93
+
94
+ def this(context = :future)
95
+ [:future, :past].include?(context) || raise("First argument 'context' must be one of :past or :future")
96
+ self.next(context)
97
+ end
98
+
99
+ def width
100
+ 1
101
+ end
102
+
103
+ def to_s
104
+ super << '-time-' << @type.to_s
105
+ end
106
+ end
@@ -0,0 +1,62 @@
1
+ class Chronic::RepeaterWeek < Chronic::Repeater #:nodoc:
2
+ WEEK_SECONDS = 604800 # (7 * 24 * 60 * 60)
3
+
4
+ def next(pointer)
5
+ super
6
+
7
+ if !@current_week_start
8
+ case pointer
9
+ when :future
10
+ sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
11
+ sunday_repeater.start = @now
12
+ next_sunday_span = sunday_repeater.next(:future)
13
+ @current_week_start = next_sunday_span.begin
14
+ when :past
15
+ sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
16
+ sunday_repeater.start = (@now + Chronic::RepeaterDay::DAY_SECONDS)
17
+ sunday_repeater.next(:past)
18
+ last_sunday_span = sunday_repeater.next(:past)
19
+ @current_week_start = last_sunday_span.begin
20
+ end
21
+ else
22
+ direction = pointer == :future ? 1 : -1
23
+ @current_week_start += direction * WEEK_SECONDS
24
+ end
25
+
26
+ Chronic::Span.new(@current_week_start, @current_week_start + WEEK_SECONDS)
27
+ end
28
+
29
+ def this(pointer = :future)
30
+ super
31
+
32
+ case pointer
33
+ when :future
34
+ this_week_start = Time.local(@now.year, @now.month, @now.day, @now.hour) + Chronic::RepeaterHour::HOUR_SECONDS
35
+ sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
36
+ sunday_repeater.start = @now
37
+ this_sunday_span = sunday_repeater.this(:future)
38
+ this_week_end = this_sunday_span.begin
39
+ Chronic::Span.new(this_week_start, this_week_end)
40
+ when :past
41
+ this_week_end = Time.local(@now.year, @now.month, @now.day, @now.hour)
42
+ sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
43
+ sunday_repeater.start = @now
44
+ last_sunday_span = sunday_repeater.next(:past)
45
+ this_week_start = last_sunday_span.begin
46
+ Chronic::Span.new(this_week_start, this_week_end)
47
+ end
48
+ end
49
+
50
+ def offset(span, amount, pointer)
51
+ direction = pointer == :future ? 1 : -1
52
+ span + direction * amount * WEEK_SECONDS
53
+ end
54
+
55
+ def width
56
+ WEEK_SECONDS
57
+ end
58
+
59
+ def to_s
60
+ super << '-week'
61
+ end
62
+ end
@@ -0,0 +1,11 @@
1
+ class Chronic::RepeaterWeekend < Chronic::Repeater #:nodoc:
2
+ WEEKEND_SECONDS = 172_800 # (2 * 24 * 60 * 60)
3
+
4
+ def width
5
+ WEEKEND_SECONDS
6
+ end
7
+
8
+ def to_s
9
+ super << '-weekend'
10
+ end
11
+ end
@@ -0,0 +1,55 @@
1
+ class Chronic::RepeaterYear < Chronic::Repeater #:nodoc:
2
+
3
+ def next(pointer)
4
+ super
5
+
6
+ if !@current_year_start
7
+ case pointer
8
+ when :future
9
+ @current_year_start = Time.local(@now.year + 1)
10
+ when :past
11
+ @current_year_start = Time.local(@now.year - 1)
12
+ end
13
+ else
14
+ diff = pointer == :future ? 1 : -1
15
+ @current_year_start = Time.local(@current_year_start.year + diff)
16
+ end
17
+
18
+ Chronic::Span.new(@current_year_start, Time.local(@current_year_start.year + 1))
19
+ end
20
+
21
+ def this(pointer = :future)
22
+ super
23
+
24
+ case pointer
25
+ when :future
26
+ this_year_start = Time.local(@now.year, @now.month, @now.day) + Chronic::RepeaterDay::DAY_SECONDS
27
+ this_year_end = Time.local(@now.year + 1, 1, 1)
28
+ when :past
29
+ this_year_start = Time.local(@now.year, 1, 1)
30
+ this_year_end = Time.local(@now.year, @now.month, @now.day)
31
+ end
32
+
33
+ Chronic::Span.new(this_year_start, this_year_end)
34
+ end
35
+
36
+ def offset(span, amount, pointer)
37
+ direction = pointer == :future ? 1 : -1
38
+
39
+ sb = span.begin
40
+ new_begin = Time.local(sb.year + (amount * direction), sb.month, sb.day, sb.hour, sb.min, sb.sec)
41
+
42
+ se = span.end
43
+ new_end = Time.local(se.year + (amount * direction), se.month, se.day, se.hour, se.min, se.sec)
44
+
45
+ Chronic::Span.new(new_begin, new_end)
46
+ end
47
+
48
+ def width
49
+ (365 * 24 * 60 * 60)
50
+ end
51
+
52
+ def to_s
53
+ super << '-year'
54
+ end
55
+ end