slaxor-chronic 0.3.1

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 (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