slaxor-chronic 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/HISTORY.md +76 -0
  2. data/LICENSE +21 -0
  3. data/Manifest.txt +47 -0
  4. data/README.md +165 -0
  5. data/Rakefile +146 -0
  6. data/benchmark/benchmark.rb +13 -0
  7. data/lib/chronic.rb +127 -0
  8. data/lib/chronic/chronic.rb +249 -0
  9. data/lib/chronic/grabber.rb +26 -0
  10. data/lib/chronic/handlers.rb +524 -0
  11. data/lib/chronic/numerizer/numerizer.rb +97 -0
  12. data/lib/chronic/ordinal.rb +40 -0
  13. data/lib/chronic/pointer.rb +27 -0
  14. data/lib/chronic/repeater.rb +129 -0
  15. data/lib/chronic/repeaters/repeater_day.rb +52 -0
  16. data/lib/chronic/repeaters/repeater_day_name.rb +51 -0
  17. data/lib/chronic/repeaters/repeater_day_portion.rb +94 -0
  18. data/lib/chronic/repeaters/repeater_fortnight.rb +70 -0
  19. data/lib/chronic/repeaters/repeater_hour.rb +57 -0
  20. data/lib/chronic/repeaters/repeater_minute.rb +57 -0
  21. data/lib/chronic/repeaters/repeater_month.rb +66 -0
  22. data/lib/chronic/repeaters/repeater_month_name.rb +98 -0
  23. data/lib/chronic/repeaters/repeater_season.rb +150 -0
  24. data/lib/chronic/repeaters/repeater_season_name.rb +45 -0
  25. data/lib/chronic/repeaters/repeater_second.rb +41 -0
  26. data/lib/chronic/repeaters/repeater_time.rb +124 -0
  27. data/lib/chronic/repeaters/repeater_week.rb +73 -0
  28. data/lib/chronic/repeaters/repeater_weekday.rb +77 -0
  29. data/lib/chronic/repeaters/repeater_weekend.rb +65 -0
  30. data/lib/chronic/repeaters/repeater_year.rb +64 -0
  31. data/lib/chronic/scalar.rb +76 -0
  32. data/lib/chronic/separator.rb +91 -0
  33. data/lib/chronic/time_zone.rb +23 -0
  34. data/slaxor-chronic.gemspec +85 -0
  35. data/test/helper.rb +7 -0
  36. data/test/test_Chronic.rb +49 -0
  37. data/test/test_DaylightSavings.rb +118 -0
  38. data/test/test_Handler.rb +109 -0
  39. data/test/test_Numerizer.rb +51 -0
  40. data/test/test_RepeaterDayName.rb +51 -0
  41. data/test/test_RepeaterFortnight.rb +62 -0
  42. data/test/test_RepeaterHour.rb +64 -0
  43. data/test/test_RepeaterMinute.rb +34 -0
  44. data/test/test_RepeaterMonth.rb +46 -0
  45. data/test/test_RepeaterMonthName.rb +56 -0
  46. data/test/test_RepeaterTime.rb +70 -0
  47. data/test/test_RepeaterWeek.rb +62 -0
  48. data/test/test_RepeaterWeekday.rb +55 -0
  49. data/test/test_RepeaterWeekend.rb +74 -0
  50. data/test/test_RepeaterYear.rb +62 -0
  51. data/test/test_Span.rb +23 -0
  52. data/test/test_Time.rb +49 -0
  53. data/test/test_Token.rb +25 -0
  54. data/test/test_parsing.rb +710 -0
  55. metadata +138 -0
@@ -0,0 +1,76 @@
1
+ module Chronic
2
+
3
+ class Scalar < Tag #:nodoc:
4
+ def self.scan(tokens)
5
+ # for each token
6
+ tokens.each_index do |i|
7
+ if t = self.scan_for_scalars(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
8
+ if t = self.scan_for_days(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
9
+ if t = self.scan_for_months(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
10
+ if t = self.scan_for_years(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
11
+ end
12
+ tokens
13
+ end
14
+
15
+ def self.scan_for_scalars(token, post_token)
16
+ if token.word =~ /^\d*$/
17
+ unless post_token && %w{am pm morning afternoon evening night}.include?(post_token)
18
+ return Scalar.new(token.word.to_i)
19
+ end
20
+ end
21
+ return nil
22
+ end
23
+
24
+ def self.scan_for_days(token, post_token)
25
+ if token.word =~ /^\d\d?$/
26
+ toi = token.word.to_i
27
+ unless toi > 31 || toi < 1 || (post_token && %w{am pm morning afternoon evening night}.include?(post_token.word))
28
+ return ScalarDay.new(toi)
29
+ end
30
+ end
31
+ return nil
32
+ end
33
+
34
+ def self.scan_for_months(token, post_token)
35
+ if token.word =~ /^\d\d?$/
36
+ toi = token.word.to_i
37
+ unless toi > 12 || toi < 1 || (post_token && %w{am pm morning afternoon evening night}.include?(post_token.word))
38
+ return ScalarMonth.new(toi)
39
+ end
40
+ end
41
+ return nil
42
+ end
43
+
44
+ def self.scan_for_years(token, post_token)
45
+ if token.word =~ /^([1-9]\d)?\d\d?$/
46
+ unless post_token && %w{am pm morning afternoon evening night}.include?(post_token.word)
47
+ return ScalarYear.new(token.word.to_i)
48
+ end
49
+ end
50
+ return nil
51
+ end
52
+
53
+ def to_s
54
+ 'scalar'
55
+ end
56
+ end
57
+
58
+ class ScalarDay < Scalar #:nodoc:
59
+ def to_s
60
+ super << '-day-' << @type.to_s
61
+ end
62
+ end
63
+
64
+ class ScalarMonth < Scalar #:nodoc:
65
+ def to_s
66
+ super << '-month-' << @type.to_s
67
+ end
68
+ end
69
+
70
+ class ScalarYear < Scalar #:nodoc:
71
+ def to_s
72
+ super << '-year-' << @type.to_s
73
+ end
74
+ end
75
+
76
+ end
@@ -0,0 +1,91 @@
1
+ module Chronic
2
+
3
+ class Separator < Tag #:nodoc:
4
+ def self.scan(tokens)
5
+ tokens.each_index do |i|
6
+ if t = self.scan_for_commas(tokens[i]) then tokens[i].tag(t); next end
7
+ if t = self.scan_for_slash_or_dash(tokens[i]) then tokens[i].tag(t); next end
8
+ if t = self.scan_for_at(tokens[i]) then tokens[i].tag(t); next end
9
+ if t = self.scan_for_in(tokens[i]) then tokens[i].tag(t); next end
10
+ if t = self.scan_for_on(tokens[i]) then tokens[i].tag(t); next end
11
+ end
12
+ tokens
13
+ end
14
+
15
+ def self.scan_for_commas(token)
16
+ scanner = {/^,$/ => :comma}
17
+ scanner.keys.each do |scanner_item|
18
+ return SeparatorComma.new(scanner[scanner_item]) if scanner_item =~ token.word
19
+ end
20
+ return nil
21
+ end
22
+
23
+ def self.scan_for_slash_or_dash(token)
24
+ scanner = {/^-$/ => :dash,
25
+ /^\/$/ => :slash}
26
+ scanner.keys.each do |scanner_item|
27
+ return SeparatorSlashOrDash.new(scanner[scanner_item]) if scanner_item =~ token.word
28
+ end
29
+ return nil
30
+ end
31
+
32
+ def self.scan_for_at(token)
33
+ scanner = {/^(at|@)$/ => :at}
34
+ scanner.keys.each do |scanner_item|
35
+ return SeparatorAt.new(scanner[scanner_item]) if scanner_item =~ token.word
36
+ end
37
+ return nil
38
+ end
39
+
40
+ def self.scan_for_in(token)
41
+ scanner = {/^in$/ => :in}
42
+ scanner.keys.each do |scanner_item|
43
+ return SeparatorIn.new(scanner[scanner_item]) if scanner_item =~ token.word
44
+ end
45
+ return nil
46
+ end
47
+
48
+ def self.scan_for_on(token)
49
+ scanner = {/^on$/ => :on}
50
+ scanner.keys.each do |scanner_item|
51
+ return SeparatorOn.new(scanner[scanner_item]) if scanner_item =~ token.word
52
+ end
53
+ return nil
54
+ end
55
+
56
+ def to_s
57
+ 'separator'
58
+ end
59
+ end
60
+
61
+ class SeparatorComma < Separator #:nodoc:
62
+ def to_s
63
+ super << '-comma'
64
+ end
65
+ end
66
+
67
+ class SeparatorSlashOrDash < Separator #:nodoc:
68
+ def to_s
69
+ super << '-slashordash-' << @type.to_s
70
+ end
71
+ end
72
+
73
+ class SeparatorAt < Separator #:nodoc:
74
+ def to_s
75
+ super << '-at'
76
+ end
77
+ end
78
+
79
+ class SeparatorIn < Separator #:nodoc:
80
+ def to_s
81
+ super << '-in'
82
+ end
83
+ end
84
+
85
+ class SeparatorOn < Separator #:nodoc:
86
+ def to_s
87
+ super << '-on'
88
+ end
89
+ end
90
+
91
+ end
@@ -0,0 +1,23 @@
1
+ module Chronic
2
+ class TimeZone < Tag #:nodoc:
3
+ def self.scan(tokens)
4
+ tokens.each_index do |i|
5
+ if t = self.scan_for_all(tokens[i]) then tokens[i].tag(t); next end
6
+ end
7
+ tokens
8
+ end
9
+
10
+ def self.scan_for_all(token)
11
+ scanner = {/[PMCE][DS]T/i => :tz,
12
+ /(tzminus)?\d{4}/ => :tz}
13
+ scanner.keys.each do |scanner_item|
14
+ return self.new(scanner[scanner_item]) if scanner_item =~ token.word
15
+ end
16
+ return nil
17
+ end
18
+
19
+ def to_s
20
+ 'timezone'
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,85 @@
1
+ Gem::Specification.new do |s|
2
+ s.specification_version = 2 if s.respond_to? :specification_version=
3
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
4
+ s.rubygems_version = '1.3.5'
5
+
6
+ s.name = 'slaxor-chronic'
7
+ s.version = '0.3.1'
8
+ s.date = '2010-10-22'
9
+ s.rubyforge_project = 'slaxor-chronic'
10
+
11
+ s.summary = "Natural language date/time parsing with proper namespacing."
12
+ s.description = "Chronic is a natural language date/time parser written in pure Ruby. Use this if you get naming conflicts with the original"
13
+
14
+ s.authors = ["Tom Preston-Werner", "Sascha Teske"]
15
+ s.email = 'sascha.teske@gmail.com'
16
+ s.homepage = 'http://github.com/slaxor/chronic'
17
+
18
+ s.require_paths = %w[lib]
19
+
20
+ s.rdoc_options = ["--charset=UTF-8"]
21
+ s.extra_rdoc_files = %w[README.md HISTORY.md LICENSE]
22
+
23
+ # = MANIFEST =
24
+ s.files = %w[
25
+ HISTORY.md
26
+ LICENSE
27
+ Manifest.txt
28
+ README.md
29
+ Rakefile
30
+ benchmark/benchmark.rb
31
+ slaxor-chronic.gemspec
32
+ lib/chronic.rb
33
+ lib/chronic/chronic.rb
34
+ lib/chronic/grabber.rb
35
+ lib/chronic/handlers.rb
36
+ lib/chronic/numerizer/numerizer.rb
37
+ lib/chronic/ordinal.rb
38
+ lib/chronic/pointer.rb
39
+ lib/chronic/repeater.rb
40
+ lib/chronic/repeaters/repeater_day.rb
41
+ lib/chronic/repeaters/repeater_day_name.rb
42
+ lib/chronic/repeaters/repeater_day_portion.rb
43
+ lib/chronic/repeaters/repeater_fortnight.rb
44
+ lib/chronic/repeaters/repeater_hour.rb
45
+ lib/chronic/repeaters/repeater_minute.rb
46
+ lib/chronic/repeaters/repeater_month.rb
47
+ lib/chronic/repeaters/repeater_month_name.rb
48
+ lib/chronic/repeaters/repeater_season.rb
49
+ lib/chronic/repeaters/repeater_season_name.rb
50
+ lib/chronic/repeaters/repeater_second.rb
51
+ lib/chronic/repeaters/repeater_time.rb
52
+ lib/chronic/repeaters/repeater_week.rb
53
+ lib/chronic/repeaters/repeater_weekday.rb
54
+ lib/chronic/repeaters/repeater_weekend.rb
55
+ lib/chronic/repeaters/repeater_year.rb
56
+ lib/chronic/scalar.rb
57
+ lib/chronic/separator.rb
58
+ lib/chronic/time_zone.rb
59
+ test/helper.rb
60
+ test/test_Chronic.rb
61
+ test/test_DaylightSavings.rb
62
+ test/test_Handler.rb
63
+ test/test_Numerizer.rb
64
+ test/test_RepeaterDayName.rb
65
+ test/test_RepeaterFortnight.rb
66
+ test/test_RepeaterHour.rb
67
+ test/test_RepeaterMinute.rb
68
+ test/test_RepeaterMonth.rb
69
+ test/test_RepeaterMonthName.rb
70
+ test/test_RepeaterTime.rb
71
+ test/test_RepeaterWeek.rb
72
+ test/test_RepeaterWeekday.rb
73
+ test/test_RepeaterWeekend.rb
74
+ test/test_RepeaterYear.rb
75
+ test/test_Span.rb
76
+ test/test_Time.rb
77
+ test/test_Token.rb
78
+ test/test_parsing.rb
79
+ ]
80
+ # = MANIFEST =
81
+
82
+ ## Test files will be grabbed from the file list. Make sure the path glob
83
+ ## matches what you actually use.
84
+ s.test_files = s.files.select { |path| path =~ /^test\/test_.*\.rb/ }
85
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,7 @@
1
+ require 'test/unit'
2
+
3
+ dir = File.dirname(File.expand_path(__FILE__))
4
+ $LOAD_PATH.unshift(File.join(dir, '..', 'lib'))
5
+ $LOAD_PATH.unshift(dir)
6
+
7
+ require 'chronic'
@@ -0,0 +1,49 @@
1
+ require File.join(File.dirname(__FILE__), *%w[helper])
2
+
3
+ class TestChronic < Test::Unit::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_post_normalize_am_pm_aliases
11
+ # affect wanted patterns
12
+
13
+ tokens = [Chronic::Token.new("5:00"), Chronic::Token.new("morning")]
14
+ tokens[0].tag(Chronic::RepeaterTime.new("5:00"))
15
+ tokens[1].tag(Chronic::RepeaterDayPortion.new(:morning))
16
+
17
+ assert_equal :morning, tokens[1].tags[0].type
18
+
19
+ tokens = Chronic.dealias_and_disambiguate_times(tokens, {})
20
+
21
+ assert_equal :am, tokens[1].tags[0].type
22
+ assert_equal 2, tokens.size
23
+
24
+ # don't affect unwanted patterns
25
+
26
+ tokens = [Chronic::Token.new("friday"), Chronic::Token.new("morning")]
27
+ tokens[0].tag(Chronic::RepeaterDayName.new(:friday))
28
+ tokens[1].tag(Chronic::RepeaterDayPortion.new(:morning))
29
+
30
+ assert_equal :morning, tokens[1].tags[0].type
31
+
32
+ tokens = Chronic.dealias_and_disambiguate_times(tokens, {})
33
+
34
+ assert_equal :morning, tokens[1].tags[0].type
35
+ assert_equal 2, tokens.size
36
+ end
37
+
38
+ def test_guess
39
+ span = Chronic::Span.new(Time.local(2006, 8, 16, 0), Time.local(2006, 8, 17, 0))
40
+ assert_equal Time.local(2006, 8, 16, 12), Chronic.guess(span)
41
+
42
+ span = Chronic::Span.new(Time.local(2006, 8, 16, 0), Time.local(2006, 8, 17, 0, 0, 1))
43
+ assert_equal Time.local(2006, 8, 16, 12), Chronic.guess(span)
44
+
45
+ span = Chronic::Span.new(Time.local(2006, 11), Time.local(2006, 12))
46
+ assert_equal Time.local(2006, 11, 16), Chronic.guess(span)
47
+ end
48
+
49
+ end
@@ -0,0 +1,118 @@
1
+ require File.join(File.dirname(__FILE__), *%w[helper])
2
+
3
+ class TestDaylightSavings < Test::Unit::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