chronic_2011 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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