pangel-chronic 0.3.0.3 → 0.3.10

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 (47) hide show
  1. data/{README.rdoc → README.txt} +7 -22
  2. data/lib/chronic.rb +89 -12
  3. data/lib/chronic/chronic.rb +260 -301
  4. data/lib/chronic/grabber.rb +23 -23
  5. data/lib/chronic/handlers.rb +538 -557
  6. data/lib/chronic/ordinal.rb +36 -35
  7. data/lib/chronic/pointer.rb +24 -26
  8. data/lib/chronic/repeater.rb +128 -138
  9. data/lib/chronic/repeaters/repeater_day.rb +51 -51
  10. data/lib/chronic/repeaters/repeater_day_name.rb +50 -52
  11. data/lib/chronic/repeaters/repeater_day_portion.rb +93 -93
  12. data/lib/chronic/repeaters/repeater_fortnight.rb +66 -66
  13. data/lib/chronic/repeaters/repeater_hour.rb +56 -57
  14. data/lib/chronic/repeaters/repeater_minute.rb +56 -56
  15. data/lib/chronic/repeaters/repeater_month.rb +71 -62
  16. data/lib/chronic/repeaters/repeater_month_name.rb +95 -95
  17. data/lib/chronic/repeaters/repeater_season.rb +142 -142
  18. data/lib/chronic/repeaters/repeater_season_name.rb +42 -42
  19. data/lib/chronic/repeaters/repeater_second.rb +40 -40
  20. data/lib/chronic/repeaters/repeater_time.rb +124 -123
  21. data/lib/chronic/repeaters/repeater_week.rb +70 -70
  22. data/lib/chronic/repeaters/repeater_weekday.rb +76 -76
  23. data/lib/chronic/repeaters/repeater_weekend.rb +63 -63
  24. data/lib/chronic/repeaters/repeater_year.rb +63 -63
  25. data/lib/chronic/scalar.rb +89 -70
  26. data/lib/chronic/separator.rb +88 -88
  27. data/lib/chronic/time_zone.rb +23 -20
  28. data/lib/numerizer/numerizer.rb +93 -94
  29. data/test/suite.rb +2 -2
  30. data/test/test_Chronic.rb +47 -47
  31. data/test/test_Handler.rb +106 -106
  32. data/test/test_Numerizer.rb +47 -49
  33. data/test/test_RepeaterDayName.rb +48 -48
  34. data/test/test_RepeaterFortnight.rb +59 -59
  35. data/test/test_RepeaterHour.rb +61 -64
  36. data/test/test_RepeaterMonth.rb +43 -43
  37. data/test/test_RepeaterMonthName.rb +53 -53
  38. data/test/test_RepeaterTime.rb +68 -68
  39. data/test/test_RepeaterWeek.rb +59 -59
  40. data/test/test_RepeaterWeekday.rb +53 -53
  41. data/test/test_RepeaterWeekend.rb +71 -71
  42. data/test/test_RepeaterYear.rb +59 -59
  43. data/test/test_Span.rb +19 -28
  44. data/test/test_Time.rb +46 -46
  45. data/test/test_Token.rb +22 -22
  46. data/test/test_parsing.rb +726 -792
  47. metadata +6 -10
@@ -1,91 +1,91 @@
1
1
  module Chronic
2
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
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
14
90
 
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|@)$/i => :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$/i => :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$/i => :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
91
+ end
@@ -1,23 +1,26 @@
1
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
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
9
 
10
- def self.scan_for_all(token)
11
- scanner = {/[PMCE][DS]T/i => :tz,
12
- /(tzminus)?\d{4}/i => :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
10
+ def self.scan_for_all(token)
11
+ if RUBY_VERSION =~ /1\.9\./
12
+ scanner = {/[PMCE][DS]T/i => :tz}
13
+ else
14
+ scanner = {/[PMCE][DS]T/i => :tz, /(tzminus)?[01]\d[304][05]/ => :tz}
15
+ end
16
+ scanner.keys.each do |scanner_item|
17
+ return self.new(scanner[scanner_item]) if scanner_item =~ token.word
18
+ end
19
+ return nil
20
+ end
18
21
 
19
- def to_s
20
- 'timezone'
21
- end
22
- end
23
- end
22
+ def to_s
23
+ 'timezone'
24
+ end
25
+ end
26
+ end
@@ -2,97 +2,96 @@ require 'strscan'
2
2
 
3
3
  class Numerizer
4
4
 
5
- DIRECT_NUMS = [
6
- ['eleven', '11'],
7
- ['twelve', '12'],
8
- ['thirteen', '13'],
9
- ['fourteen', '14'],
10
- ['fifteen', '15'],
11
- ['sixteen', '16'],
12
- ['seventeen', '17'],
13
- ['eighteen', '18'],
14
- ['nineteen', '19'],
15
- ['ninteen', '19'], # Common mis-spelling
16
- ['zero', '0'],
17
- ['one', '1'],
18
- ['two', '2'],
19
- ['three', '3'],
20
- ['four(\W|$)', '4\1'], # The weird regex is so that it matches four but not fourty
21
- ['five', '5'],
22
- ['six(\W|$)', '6\1'],
23
- ['seven(\W|$)', '7\1'],
24
- ['eight(\W|$)', '8\1'],
25
- ['nine(\W|$)', '9\1'],
26
- ['ten', '10'],
27
- ['\ba[\b^$]', '1'] # doesn't make sense for an 'a' at the end to be a 1
28
- ]
29
-
30
- TEN_PREFIXES = [ ['twenty', 20],
31
- ['thirty', 30],
32
- ['forty', 40],
33
- ['fourty', 40], # Common misspelling
34
- ['fifty', 50],
35
- ['sixty', 60],
36
- ['seventy', 70],
37
- ['eighty', 80],
38
- ['ninety', 90]
39
- ]
40
-
41
- BIG_PREFIXES = [ ['hundred', 100],
42
- ['thousand', 1000],
43
- ['million', 1_000_000],
44
- ['billion', 1_000_000_000],
45
- ['trillion', 1_000_000_000_000],
46
- ]
47
-
48
- def self.numerize(string)
49
- string = string.dup
50
-
51
- # preprocess
52
- string.gsub!(/ +|([^\d])-([^\d])/, '\1 \2') # will mutilate hyphenated-words but shouldn't matter for date extraction
53
- string.gsub!(/a half/, 'haAlf') # take the 'a' out so it doesn't turn into a 1, save the half for the end
54
-
55
- # easy/direct replacements
56
-
57
- DIRECT_NUMS.each do |dn|
58
- string.gsub!(/#{dn[0]}/i, '<num>' + dn[1])
59
- end
60
-
61
- # ten, twenty, etc.
62
-
63
- TEN_PREFIXES.each do |tp|
64
- string.gsub!(/(?:#{tp[0]}) *<num>(\d(?=[^\d]|$))*/i) { '<num>' + (tp[1] + $1.to_i).to_s }
65
- end
66
-
67
- TEN_PREFIXES.each do |tp|
68
- string.gsub!(/#{tp[0]}/i) { '<num>' + tp[1].to_s }
69
- end
70
-
71
- # hundreds, thousands, millions, etc.
72
-
73
- BIG_PREFIXES.each do |bp|
74
- string.gsub!(/(?:<num>)?(\d*) *#{bp[0]}/i) { '<num>' + (bp[1] * $1.to_i).to_s}
75
- andition(string)
76
- end
77
-
78
- # fractional addition
79
- # I'm not combining this with the previous block as using float addition complicates the strings
80
- # (with extraneous .0's and such )
81
- string.gsub!(/(\d+)(?: | and |-)*haAlf/i) { ($1.to_f + 0.5).to_s }
82
-
83
- string.gsub(/<num>/, '')
84
- end
85
-
86
- private
87
-
88
- def self.andition(string)
89
- sc = StringScanner.new(string)
90
- while(sc.scan_until(/<num>(\d+)( | and )<num>(\d+)(?=[^\w]|$)/i))
91
- if sc[2] =~ /and/ || sc[1].size > sc[3].size
92
- string[(sc.pos - sc.matched_size)..(sc.pos-1)] = '<num>' + (sc[1].to_i + sc[3].to_i).to_s
93
- sc.reset
94
- end
95
- end
96
- end
97
-
98
- end
5
+ DIRECT_NUMS = [
6
+ ['eleven', '11'],
7
+ ['twelve', '12'],
8
+ ['thirteen', '13'],
9
+ ['fourteen', '14'],
10
+ ['fifteen', '15'],
11
+ ['sixteen', '16'],
12
+ ['seventeen', '17'],
13
+ ['eighteen', '18'],
14
+ ['nineteen', '19'],
15
+ ['ninteen', '19'], # Common mis-spelling
16
+ ['zero', '0'],
17
+ ['one', '1'],
18
+ ['two', '2'],
19
+ ['three', '3'],
20
+ ['four(\W|$)', '4\1'], # The weird regex is so that it matches four but not fourty
21
+ ['five', '5'],
22
+ ['six(\W|$)', '6\1'],
23
+ ['seven(\W|$)', '7\1'],
24
+ ['eight(\W|$)', '8\1'],
25
+ ['nine(\W|$)', '9\1'],
26
+ ['ten', '10'],
27
+ ['\ba[\b^$]', '1'] # doesn't make sense for an 'a' at the end to be a 1
28
+ ]
29
+
30
+ TEN_PREFIXES = [ ['twenty', 20],
31
+ ['thirty', 30],
32
+ ['fourty', 40],
33
+ ['fifty', 50],
34
+ ['sixty', 60],
35
+ ['seventy', 70],
36
+ ['eighty', 80],
37
+ ['ninety', 90]
38
+ ]
39
+
40
+ BIG_PREFIXES = [ ['hundred', 100],
41
+ ['thousand', 1000],
42
+ ['million', 1_000_000],
43
+ ['billion', 1_000_000_000],
44
+ ['trillion', 1_000_000_000_000],
45
+ ]
46
+
47
+ def self.numerize(string)
48
+ string = string.dup
49
+
50
+ # preprocess
51
+ string.gsub!(/ +|([^\d])-([^\d])/, '\1 \2') # will mutilate hyphenated-words but shouldn't matter for date extraction
52
+ string.gsub!(/a half/, 'haAlf') # take the 'a' out so it doesn't turn into a 1, save the half for the end
53
+
54
+ # easy/direct replacements
55
+
56
+ DIRECT_NUMS.each do |dn|
57
+ string.gsub!(/#{dn[0]}/i, '<num>' + dn[1])
58
+ end
59
+
60
+ # ten, twenty, etc.
61
+
62
+ TEN_PREFIXES.each do |tp|
63
+ string.gsub!(/(?:#{tp[0]}) *<num>(\d(?=[^\d]|$))*/i) { '<num>' + (tp[1] + $1.to_i).to_s }
64
+ end
65
+
66
+ TEN_PREFIXES.each do |tp|
67
+ string.gsub!(/#{tp[0]}/i) { '<num>' + tp[1].to_s }
68
+ end
69
+
70
+ # hundreds, thousands, millions, etc.
71
+
72
+ BIG_PREFIXES.each do |bp|
73
+ string.gsub!(/(?:<num>)?(\d*) *#{bp[0]}/i) { '<num>' + (bp[1] * $1.to_i).to_s}
74
+ andition(string)
75
+ end
76
+
77
+ # fractional addition
78
+ # I'm not combining this with the previous block as using float addition complicates the strings
79
+ # (with extraneous .0's and such )
80
+ string.gsub!(/(\d+)(?: | and |-)*haAlf/i) { ($1.to_f + 0.5).to_s }
81
+
82
+ string.gsub(/<num>/, '')
83
+ end
84
+
85
+ private
86
+
87
+ def self.andition(string)
88
+ sc = StringScanner.new(string)
89
+ while(sc.scan_until(/<num>(\d+)( | and )<num>(\d+)(?=[^\w]|$)/i))
90
+ if sc[2] =~ /and/ || sc[1].size > sc[3].size
91
+ string[(sc.pos - sc.matched_size)..(sc.pos-1)] = '<num>' + (sc[1].to_i + sc[3].to_i).to_s
92
+ sc.reset
93
+ end
94
+ end
95
+ end
96
+
97
+ end
@@ -3,7 +3,7 @@ require 'test/unit'
3
3
  tests = Dir["#{File.dirname(__FILE__)}/test_*.rb"]
4
4
  tests.delete_if { |o| o =~ /test_parsing/ }
5
5
  tests.each do |file|
6
- require file
6
+ require file
7
7
  end
8
8
 
9
- require File.dirname(__FILE__) + '/test_parsing.rb'
9
+ require File.dirname(__FILE__) + '/test_parsing.rb'
@@ -1,50 +1,50 @@
1
- require File.dirname(__FILE__) + '/../lib/chronic'
1
+ require 'chronic'
2
2
  require 'test/unit'
3
3
 
4
4
  class TestChronic < Test::Unit::TestCase
5
-
6
- def setup
7
- # Wed Aug 16 14:00:00 UTC 2006
8
- @now = Time.local(2006, 8, 16, 14, 0, 0, 0)
9
- end
10
-
11
- def test_post_normalize_am_pm_aliases
12
- # affect wanted patterns
13
-
14
- tokens = [Chronic::Token.new("5:00"), Chronic::Token.new("morning")]
15
- tokens[0].tag(Chronic::RepeaterTime.new("5:00"))
16
- tokens[1].tag(Chronic::RepeaterDayPortion.new(:morning))
17
-
18
- assert_equal :morning, tokens[1].tags[0].type
19
-
20
- tokens = Chronic.dealias_and_disambiguate_times(tokens, {})
21
-
22
- assert_equal :am, tokens[1].tags[0].type
23
- assert_equal 2, tokens.size
24
-
25
- # don't affect unwanted patterns
26
-
27
- tokens = [Chronic::Token.new("friday"), Chronic::Token.new("morning")]
28
- tokens[0].tag(Chronic::RepeaterDayName.new(:friday))
29
- tokens[1].tag(Chronic::RepeaterDayPortion.new(:morning))
30
-
31
- assert_equal :morning, tokens[1].tags[0].type
32
-
33
- tokens = Chronic.dealias_and_disambiguate_times(tokens, {})
34
-
35
- assert_equal :morning, tokens[1].tags[0].type
36
- assert_equal 2, tokens.size
37
- end
38
-
39
- def test_guess
40
- span = Chronic::Span.new(Time.local(2006, 8, 16, 0), Time.local(2006, 8, 17, 0))
41
- assert_equal Time.local(2006, 8, 16, 12), Chronic.guess(span)
42
-
43
- span = Chronic::Span.new(Time.local(2006, 8, 16, 0), Time.local(2006, 8, 17, 0, 0, 1))
44
- assert_equal Time.local(2006, 8, 16, 12), Chronic.guess(span)
45
-
46
- span = Chronic::Span.new(Time.local(2006, 11), Time.local(2006, 12))
47
- assert_equal Time.local(2006, 11, 16), Chronic.guess(span)
48
- end
49
-
50
- end
5
+
6
+ def setup
7
+ # Wed Aug 16 14:00:00 UTC 2006
8
+ @now = Time.local(2006, 8, 16, 14, 0, 0, 0)
9
+ end
10
+
11
+ def test_post_normalize_am_pm_aliases
12
+ # affect wanted patterns
13
+
14
+ tokens = [Chronic::Token.new("5:00"), Chronic::Token.new("morning")]
15
+ tokens[0].tag(Chronic::RepeaterTime.new("5:00"))
16
+ tokens[1].tag(Chronic::RepeaterDayPortion.new(:morning))
17
+
18
+ assert_equal :morning, tokens[1].tags[0].type
19
+
20
+ tokens = Chronic.dealias_and_disambiguate_times(tokens, {})
21
+
22
+ assert_equal :am, tokens[1].tags[0].type
23
+ assert_equal 2, tokens.size
24
+
25
+ # don't affect unwanted patterns
26
+
27
+ tokens = [Chronic::Token.new("friday"), Chronic::Token.new("morning")]
28
+ tokens[0].tag(Chronic::RepeaterDayName.new(:friday))
29
+ tokens[1].tag(Chronic::RepeaterDayPortion.new(:morning))
30
+
31
+ assert_equal :morning, tokens[1].tags[0].type
32
+
33
+ tokens = Chronic.dealias_and_disambiguate_times(tokens, {})
34
+
35
+ assert_equal :morning, tokens[1].tags[0].type
36
+ assert_equal 2, tokens.size
37
+ end
38
+
39
+ def test_guess
40
+ span = Chronic::Span.new(Time.local(2006, 8, 16, 0), Time.local(2006, 8, 17, 0))
41
+ assert_equal Time.local(2006, 8, 16, 12), Chronic.guess(span)
42
+
43
+ span = Chronic::Span.new(Time.local(2006, 8, 16, 0), Time.local(2006, 8, 17, 0, 0, 1))
44
+ assert_equal Time.local(2006, 8, 16, 12), Chronic.guess(span)
45
+
46
+ span = Chronic::Span.new(Time.local(2006, 11), Time.local(2006, 12))
47
+ assert_equal Time.local(2006, 11, 16), Chronic.guess(span)
48
+ end
49
+
50
+ end