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,32 @@
1
+ module Chronic
2
+ class TimeZone < Tag
3
+
4
+ # Scan an Array of Token objects and apply any necessary TimeZone
5
+ # tags to each token.
6
+ #
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.
11
+ def self.scan(tokens, options)
12
+ tokens.each do |token|
13
+ if t = scan_for_all(token) then token.tag(t); next end
14
+ end
15
+ end
16
+
17
+ # token - The Token object we want to scan.
18
+ #
19
+ # Returns a new Pointer object.
20
+ def self.scan_for_all(token)
21
+ scan_for token, self,
22
+ {
23
+ /[PMCE][DS]T|UTC/i => :tz,
24
+ /(tzminus)?\d{2}:?\d{2}/ => :tz
25
+ }
26
+ end
27
+
28
+ def to_s
29
+ 'timezone'
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,47 @@
1
+ module Chronic
2
+ class Token
3
+
4
+ attr_accessor :word
5
+ attr_accessor :tags
6
+
7
+ def initialize(word)
8
+ @word = word
9
+ @tags = []
10
+ end
11
+
12
+ # Tag this token with the specified tag.
13
+ #
14
+ # new_tag - The new Tag object.
15
+ #
16
+ # Returns nothing.
17
+ def tag(new_tag)
18
+ @tags << new_tag
19
+ end
20
+
21
+ # Remove all tags of the given class.
22
+ #
23
+ # tag_class - The tag Class to remove.
24
+ #
25
+ # Returns nothing.
26
+ def untag(tag_class)
27
+ @tags.delete_if { |m| m.kind_of? tag_class }
28
+ end
29
+
30
+ # Returns true if this token has any tags.
31
+ def tagged?
32
+ @tags.size > 0
33
+ end
34
+
35
+ # tag_class - The tag Class to search for.
36
+ #
37
+ # Returns The first Tag that matches the given class.
38
+ def get_tag(tag_class)
39
+ @tags.find { |m| m.kind_of? tag_class }
40
+ end
41
+
42
+ # Print this Token in a pretty way
43
+ def to_s
44
+ @word << '(' << @tags.join(', ') << ') '
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,12 @@
1
+ unless defined? Chronic
2
+ $:.unshift File.expand_path('../../lib', __FILE__)
3
+ require 'chronic'
4
+ end
5
+
6
+ require 'minitest/autorun'
7
+
8
+ class TestCase < MiniTest::Unit::TestCase
9
+ def self.test(name, &block)
10
+ define_method("test_#{name.gsub(/\W/, '_')}", &block) if block
11
+ end
12
+ end
@@ -0,0 +1,148 @@
1
+ require 'helper'
2
+
3
+ class TestChronic < TestCase
4
+
5
+ def setup
6
+ # Wed Aug 16 14:00:00 UTC 2006
7
+ @now = Time.local(2006, 8, 16, 14, 0, 0, 0)
8
+ end
9
+
10
+ def test_pre_normalize
11
+ assert_equal Chronic.pre_normalize('12:55 pm'), Chronic.pre_normalize('12.55 pm')
12
+ end
13
+
14
+ def test_pre_normalize_numerized_string
15
+ string = 'two and a half years'
16
+ assert_equal Chronic::Numerizer.numerize(string), Chronic.pre_normalize(string)
17
+ end
18
+
19
+ def test_post_normalize_am_pm_aliases
20
+ # affect wanted patterns
21
+
22
+ tokens = [Chronic::Token.new("5:00"), Chronic::Token.new("morning")]
23
+ tokens[0].tag(Chronic::RepeaterTime.new("5:00"))
24
+ tokens[1].tag(Chronic::RepeaterDayPortion.new(:morning))
25
+
26
+ assert_equal :morning, tokens[1].tags[0].type
27
+
28
+ tokens = Chronic::Handlers.dealias_and_disambiguate_times(tokens, {})
29
+
30
+ assert_equal :am, tokens[1].tags[0].type
31
+ assert_equal 2, tokens.size
32
+
33
+ # don't affect unwanted patterns
34
+
35
+ tokens = [Chronic::Token.new("friday"), Chronic::Token.new("morning")]
36
+ tokens[0].tag(Chronic::RepeaterDayName.new(:friday))
37
+ tokens[1].tag(Chronic::RepeaterDayPortion.new(:morning))
38
+
39
+ assert_equal :morning, tokens[1].tags[0].type
40
+
41
+ tokens = Chronic::Handlers.dealias_and_disambiguate_times(tokens, {})
42
+
43
+ assert_equal :morning, tokens[1].tags[0].type
44
+ assert_equal 2, tokens.size
45
+ end
46
+
47
+ def test_guess
48
+ span = Chronic::Span.new(Time.local(2006, 8, 16, 0), Time.local(2006, 8, 17, 0))
49
+ assert_equal Time.local(2006, 8, 16, 12), Chronic.guess(span)
50
+
51
+ span = Chronic::Span.new(Time.local(2006, 8, 16, 0), Time.local(2006, 8, 17, 0, 0, 1))
52
+ assert_equal Time.local(2006, 8, 16, 12), Chronic.guess(span)
53
+
54
+ span = Chronic::Span.new(Time.local(2006, 11), Time.local(2006, 12))
55
+ assert_equal Time.local(2006, 11, 16), Chronic.guess(span)
56
+ end
57
+
58
+ def test_now
59
+ Chronic.parse('now', :now => Time.local(2006, 01))
60
+ assert_equal Time.local(2006, 01), Chronic.now
61
+
62
+ Chronic.parse('now', :now => Time.local(2007, 01))
63
+ assert_equal Time.local(2007, 01), Chronic.now
64
+ end
65
+
66
+ def test_endian_definitions
67
+ # middle, little
68
+ endians = [
69
+ Chronic::Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_day, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sm_sd_sy),
70
+ Chronic::Handler.new([:scalar_day, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sd_sm_sy)
71
+ ]
72
+
73
+ assert_equal endians, Chronic.definitions[:endian]
74
+
75
+ defs = Chronic.definitions(:endian_precedence => :little)
76
+ assert_equal endians.reverse, defs[:endian]
77
+
78
+ defs = Chronic.definitions(:endian_precedence => [:little, :middle])
79
+ assert_equal endians.reverse, defs[:endian]
80
+
81
+ assert_raises(ArgumentError) do
82
+ Chronic.definitions(:endian_precedence => :invalid)
83
+ end
84
+ end
85
+
86
+ def test_passing_options
87
+ assert_raises(ArgumentError) do
88
+ Chronic.parse('now', :invalid => :option)
89
+ end
90
+
91
+ assert_raises(ArgumentError) do
92
+ Chronic.parse('now', :context => :invalid_context)
93
+ end
94
+ end
95
+
96
+ def test_debug
97
+ require 'stringio'
98
+ $stdout = StringIO.new
99
+ Chronic.debug = true
100
+
101
+ Chronic.parse 'now'
102
+ assert $stdout.string.include?('this(grabber-this)')
103
+ ensure
104
+ $stdout = STDOUT
105
+ Chronic.debug = false
106
+ end
107
+
108
+ # Chronic.construct
109
+
110
+ def test_normal
111
+ assert_equal Time.local(2006, 1, 2, 0, 0, 0), Chronic.construct(2006, 1, 2, 0, 0, 0)
112
+ assert_equal Time.local(2006, 1, 2, 3, 0, 0), Chronic.construct(2006, 1, 2, 3, 0, 0)
113
+ assert_equal Time.local(2006, 1, 2, 3, 4, 0), Chronic.construct(2006, 1, 2, 3, 4, 0)
114
+ assert_equal Time.local(2006, 1, 2, 3, 4, 5), Chronic.construct(2006, 1, 2, 3, 4, 5)
115
+ end
116
+
117
+ def test_second_overflow
118
+ assert_equal Time.local(2006, 1, 1, 0, 1, 30), Chronic.construct(2006, 1, 1, 0, 0, 90)
119
+ assert_equal Time.local(2006, 1, 1, 0, 5, 0), Chronic.construct(2006, 1, 1, 0, 0, 300)
120
+ end
121
+
122
+ def test_minute_overflow
123
+ assert_equal Time.local(2006, 1, 1, 1, 30), Chronic.construct(2006, 1, 1, 0, 90)
124
+ assert_equal Time.local(2006, 1, 1, 5), Chronic.construct(2006, 1, 1, 0, 300)
125
+ end
126
+
127
+ def test_hour_overflow
128
+ assert_equal Time.local(2006, 1, 2, 12), Chronic.construct(2006, 1, 1, 36)
129
+ assert_equal Time.local(2006, 1, 7), Chronic.construct(2006, 1, 1, 144)
130
+ end
131
+
132
+ def test_day_overflow
133
+ assert_equal Time.local(2006, 2, 1), Chronic.construct(2006, 1, 32)
134
+ assert_equal Time.local(2006, 3, 5), Chronic.construct(2006, 2, 33)
135
+ assert_equal Time.local(2004, 3, 4), Chronic.construct(2004, 2, 33)
136
+ assert_equal Time.local(2000, 3, 4), Chronic.construct(2000, 2, 33)
137
+
138
+ assert_raises(RuntimeError) do
139
+ Chronic.construct(2006, 1, 57)
140
+ end
141
+ end
142
+
143
+ def test_month_overflow
144
+ assert_equal Time.local(2006, 1), Chronic.construct(2005, 13)
145
+ assert_equal Time.local(2005, 12), Chronic.construct(2000, 72)
146
+ end
147
+
148
+ end
@@ -0,0 +1,118 @@
1
+ require 'helper'
2
+
3
+ class TestDaylightSavings < TestCase
4
+
5
+ def setup
6
+ @begin_daylight_savings = Time.local(2008, 3, 9, 5, 0, 0, 0)
7
+ @end_daylight_savings = Time.local(2008, 11, 2, 5, 0, 0, 0)
8
+ end
9
+
10
+ def test_begin_past
11
+ # ambiguous - resolve to last night
12
+ t = Chronic::RepeaterTime.new('900')
13
+ t.start = @begin_daylight_savings
14
+ assert_equal Time.local(2008, 3, 8, 21), t.next(:past).begin
15
+
16
+ # ambiguous - resolve to this afternoon
17
+ t = Chronic::RepeaterTime.new('900')
18
+ t.start = Time.local(2008, 3, 9, 22, 0, 0, 0)
19
+ assert_equal Time.local(2008, 3, 9, 21), t.next(:past).begin
20
+
21
+ # ambiguous - resolve to this morning
22
+ t = Chronic::RepeaterTime.new('400')
23
+ t.start = @begin_daylight_savings
24
+ assert_equal Time.local(2008, 3, 9, 4), t.next(:past).begin
25
+
26
+ # unambiguous - resolve to today
27
+ t = Chronic::RepeaterTime.new('0400')
28
+ t.start = @begin_daylight_savings
29
+ assert_equal Time.local(2008, 3, 9, 4), t.next(:past).begin
30
+
31
+ # unambiguous - resolve to yesterday
32
+ t = Chronic::RepeaterTime.new('1300')
33
+ t.start = @begin_daylight_savings
34
+ assert_equal Time.local(2008, 3, 8, 13), t.next(:past).begin
35
+ end
36
+
37
+ def test_begin_future
38
+ # ambiguous - resolve to this morning
39
+ t = Chronic::RepeaterTime.new('900')
40
+ t.start = @begin_daylight_savings
41
+ assert_equal Time.local(2008, 3, 9, 9), t.next(:future).begin
42
+
43
+ # ambiguous - resolve to this afternoon
44
+ t = Chronic::RepeaterTime.new('900')
45
+ t.start = Time.local(2008, 3, 9, 13, 0, 0, 0)
46
+ assert_equal Time.local(2008, 3, 9, 21), t.next(:future).begin
47
+
48
+ # ambiguous - resolve to tomorrow
49
+ t = Chronic::RepeaterTime.new('900')
50
+ t.start = Time.local(2008, 3, 9, 22, 0, 0, 0)
51
+ assert_equal Time.local(2008, 3, 10, 9), t.next(:future).begin
52
+
53
+ # unambiguous - resolve to today
54
+ t = Chronic::RepeaterTime.new('0900')
55
+ t.start = @begin_daylight_savings
56
+ assert_equal Time.local(2008, 3, 9, 9), t.next(:future).begin
57
+
58
+ # unambiguous - resolve to tomorrow
59
+ t = Chronic::RepeaterTime.new('0400')
60
+ t.start = @begin_daylight_savings
61
+ assert_equal Time.local(2008, 3, 10, 4), t.next(:future).begin
62
+ end
63
+
64
+ def test_end_past
65
+ # ambiguous - resolve to last night
66
+ t = Chronic::RepeaterTime.new('900')
67
+ t.start = @end_daylight_savings
68
+ assert_equal Time.local(2008, 11, 1, 21), t.next(:past).begin
69
+
70
+ # ambiguous - resolve to this afternoon
71
+ t = Chronic::RepeaterTime.new('900')
72
+ t.start = Time.local(2008, 11, 2, 22, 0, 0, 0)
73
+ assert_equal Time.local(2008, 11, 2, 21), t.next(:past).begin
74
+
75
+ # ambiguous - resolve to this morning
76
+ t = Chronic::RepeaterTime.new('400')
77
+ t.start = @end_daylight_savings
78
+ assert_equal Time.local(2008, 11, 2, 4), t.next(:past).begin
79
+
80
+ # unambiguous - resolve to today
81
+ t = Chronic::RepeaterTime.new('0400')
82
+ t.start = @end_daylight_savings
83
+ assert_equal Time.local(2008, 11, 2, 4), t.next(:past).begin
84
+
85
+ # unambiguous - resolve to yesterday
86
+ t = Chronic::RepeaterTime.new('1300')
87
+ t.start = @end_daylight_savings
88
+ assert_equal Time.local(2008, 11, 1, 13), t.next(:past).begin
89
+ end
90
+
91
+ def test_end_future
92
+ # ambiguous - resolve to this morning
93
+ t = Chronic::RepeaterTime.new('900')
94
+ t.start = @end_daylight_savings
95
+ assert_equal Time.local(2008, 11, 2, 9), t.next(:future).begin
96
+
97
+ # ambiguous - resolve to this afternoon
98
+ t = Chronic::RepeaterTime.new('900')
99
+ t.start = Time.local(2008, 11, 2, 13, 0, 0, 0)
100
+ assert_equal Time.local(2008, 11, 2, 21), t.next(:future).begin
101
+
102
+ # ambiguous - resolve to tomorrow
103
+ t = Chronic::RepeaterTime.new('900')
104
+ t.start = Time.local(2008, 11, 2, 22, 0, 0, 0)
105
+ assert_equal Time.local(2008, 11, 3, 9), t.next(:future).begin
106
+
107
+ # unambiguous - resolve to today
108
+ t = Chronic::RepeaterTime.new('0900')
109
+ t.start = @end_daylight_savings
110
+ assert_equal Time.local(2008, 11, 2, 9), t.next(:future).begin
111
+
112
+ # unambiguous - resolve to tomorrow
113
+ t = Chronic::RepeaterTime.new('0400')
114
+ t.start = @end_daylight_savings
115
+ assert_equal Time.local(2008, 11, 3, 4), t.next(:future).begin
116
+ end
117
+
118
+ end
@@ -0,0 +1,104 @@
1
+ require 'helper'
2
+
3
+ class TestHandler < TestCase
4
+
5
+ def setup
6
+ # Wed Aug 16 14:00:00 UTC 2006
7
+ @now = Time.local(2006, 8, 16, 14, 0, 0, 0)
8
+ end
9
+
10
+ def test_handler_class_1
11
+ handler = Chronic::Handler.new([:repeater], :handler)
12
+
13
+ tokens = [Chronic::Token.new('friday')]
14
+ tokens[0].tag(Chronic::RepeaterDayName.new(:friday))
15
+
16
+ assert handler.match(tokens, Chronic.definitions)
17
+
18
+ tokens << Chronic::Token.new('afternoon')
19
+ tokens[1].tag(Chronic::RepeaterDayPortion.new(:afternoon))
20
+
21
+ assert !handler.match(tokens, Chronic.definitions)
22
+ end
23
+
24
+ def test_handler_class_2
25
+ handler = Chronic::Handler.new([:repeater, :repeater?], :handler)
26
+
27
+ tokens = [Chronic::Token.new('friday')]
28
+ tokens[0].tag(Chronic::RepeaterDayName.new(:friday))
29
+
30
+ assert handler.match(tokens, Chronic.definitions)
31
+
32
+ tokens << Chronic::Token.new('afternoon')
33
+ tokens[1].tag(Chronic::RepeaterDayPortion.new(:afternoon))
34
+
35
+ assert handler.match(tokens, Chronic.definitions)
36
+
37
+ tokens << Chronic::Token.new('afternoon')
38
+ tokens[2].tag(Chronic::RepeaterDayPortion.new(:afternoon))
39
+
40
+ assert !handler.match(tokens, Chronic.definitions)
41
+ end
42
+
43
+ def test_handler_class_3
44
+ handler = Chronic::Handler.new([:repeater, 'time?'], :handler)
45
+
46
+ tokens = [Chronic::Token.new('friday')]
47
+ tokens[0].tag(Chronic::RepeaterDayName.new(:friday))
48
+
49
+ assert handler.match(tokens, Chronic.definitions)
50
+
51
+ tokens << Chronic::Token.new('afternoon')
52
+ tokens[1].tag(Chronic::RepeaterDayPortion.new(:afternoon))
53
+
54
+ assert !handler.match(tokens, Chronic.definitions)
55
+ end
56
+
57
+ def test_handler_class_4
58
+ handler = Chronic::Handler.new([:repeater_month_name, :scalar_day, 'time?'], :handler)
59
+
60
+ tokens = [Chronic::Token.new('may')]
61
+ tokens[0].tag(Chronic::RepeaterMonthName.new(:may))
62
+
63
+ assert !handler.match(tokens, Chronic.definitions)
64
+
65
+ tokens << Chronic::Token.new('27')
66
+ tokens[1].tag(Chronic::ScalarDay.new(27))
67
+
68
+ assert handler.match(tokens, Chronic.definitions)
69
+ end
70
+
71
+ def test_handler_class_5
72
+ handler = Chronic::Handler.new([:repeater, 'time?'], :handler)
73
+
74
+ tokens = [Chronic::Token.new('friday')]
75
+ tokens[0].tag(Chronic::RepeaterDayName.new(:friday))
76
+
77
+ assert handler.match(tokens, Chronic.definitions)
78
+
79
+ tokens << Chronic::Token.new('5:00')
80
+ tokens[1].tag(Chronic::RepeaterTime.new('5:00'))
81
+
82
+ assert handler.match(tokens, Chronic.definitions)
83
+
84
+ tokens << Chronic::Token.new('pm')
85
+ tokens[2].tag(Chronic::RepeaterDayPortion.new(:pm))
86
+
87
+ assert handler.match(tokens, Chronic.definitions)
88
+ end
89
+
90
+ def test_handler_class_6
91
+ handler = Chronic::Handler.new([:scalar, :repeater, :pointer], :handler)
92
+
93
+ tokens = [Chronic::Token.new('3'),
94
+ Chronic::Token.new('years'),
95
+ Chronic::Token.new('past')]
96
+
97
+ tokens[0].tag(Chronic::Scalar.new(3))
98
+ tokens[1].tag(Chronic::RepeaterYear.new(:year))
99
+ tokens[2].tag(Chronic::Pointer.new(:past))
100
+
101
+ assert handler.match(tokens, Chronic.definitions)
102
+ end
103
+
104
+ end