chronic 0.2.3 → 0.3.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.
- data/HISTORY.md +76 -0
- data/LICENSE +21 -0
- data/README.md +165 -0
- data/Rakefile +145 -18
- data/benchmark/benchmark.rb +13 -0
- data/chronic.gemspec +85 -0
- data/lib/chronic.rb +21 -19
- data/lib/chronic/chronic.rb +59 -49
- data/lib/chronic/grabber.rb +2 -2
- data/lib/chronic/handlers.rb +167 -112
- data/lib/{numerizer → chronic/numerizer}/numerizer.rb +17 -23
- data/lib/chronic/ordinal.rb +6 -6
- data/lib/chronic/pointer.rb +3 -3
- data/lib/chronic/repeater.rb +26 -12
- data/lib/chronic/repeaters/repeater_day.rb +17 -12
- data/lib/chronic/repeaters/repeater_day_name.rb +17 -12
- data/lib/chronic/repeaters/repeater_day_portion.rb +13 -12
- data/lib/chronic/repeaters/repeater_fortnight.rb +14 -9
- data/lib/chronic/repeaters/repeater_hour.rb +15 -10
- data/lib/chronic/repeaters/repeater_minute.rb +15 -10
- data/lib/chronic/repeaters/repeater_month.rb +20 -15
- data/lib/chronic/repeaters/repeater_month_name.rb +21 -16
- data/lib/chronic/repeaters/repeater_season.rb +136 -9
- data/lib/chronic/repeaters/repeater_season_name.rb +38 -17
- data/lib/chronic/repeaters/repeater_second.rb +15 -10
- data/lib/chronic/repeaters/repeater_time.rb +49 -42
- data/lib/chronic/repeaters/repeater_week.rb +16 -11
- data/lib/chronic/repeaters/repeater_weekday.rb +77 -0
- data/lib/chronic/repeaters/repeater_weekend.rb +14 -9
- data/lib/chronic/repeaters/repeater_year.rb +19 -13
- data/lib/chronic/scalar.rb +16 -14
- data/lib/chronic/separator.rb +25 -10
- data/lib/chronic/time_zone.rb +4 -3
- data/test/helper.rb +7 -0
- data/test/test_Chronic.rb +17 -18
- data/test/test_DaylightSavings.rb +118 -0
- data/test/test_Handler.rb +37 -38
- data/test/test_Numerizer.rb +8 -5
- data/test/test_RepeaterDayName.rb +15 -16
- data/test/test_RepeaterFortnight.rb +16 -17
- data/test/test_RepeaterHour.rb +18 -19
- data/test/test_RepeaterMinute.rb +34 -0
- data/test/test_RepeaterMonth.rb +16 -17
- data/test/test_RepeaterMonthName.rb +17 -18
- data/test/test_RepeaterTime.rb +20 -22
- data/test/test_RepeaterWeek.rb +16 -17
- data/test/test_RepeaterWeekday.rb +55 -0
- data/test/test_RepeaterWeekend.rb +21 -22
- data/test/test_RepeaterYear.rb +17 -18
- data/test/test_Span.rb +5 -6
- data/test/test_Time.rb +11 -12
- data/test/test_Token.rb +5 -6
- data/test/test_parsing.rb +300 -204
- metadata +74 -52
- data/History.txt +0 -53
- data/README.txt +0 -149
- data/test/suite.rb +0 -9
@@ -1,8 +1,14 @@
|
|
1
1
|
class Chronic::RepeaterYear < Chronic::Repeater #:nodoc:
|
2
|
-
|
2
|
+
YEAR_SECONDS = 31536000 # 365 * 24 * 60 * 60
|
3
|
+
|
4
|
+
def initialize(type)
|
5
|
+
super
|
6
|
+
@current_year_start = nil
|
7
|
+
end
|
8
|
+
|
3
9
|
def next(pointer)
|
4
10
|
super
|
5
|
-
|
11
|
+
|
6
12
|
if !@current_year_start
|
7
13
|
case pointer
|
8
14
|
when :future
|
@@ -14,13 +20,13 @@ class Chronic::RepeaterYear < Chronic::Repeater #:nodoc:
|
|
14
20
|
diff = pointer == :future ? 1 : -1
|
15
21
|
@current_year_start = Time.construct(@current_year_start.year + diff)
|
16
22
|
end
|
17
|
-
|
23
|
+
|
18
24
|
Chronic::Span.new(@current_year_start, Time.construct(@current_year_start.year + 1))
|
19
25
|
end
|
20
|
-
|
26
|
+
|
21
27
|
def this(pointer = :future)
|
22
28
|
super
|
23
|
-
|
29
|
+
|
24
30
|
case pointer
|
25
31
|
when :future
|
26
32
|
this_year_start = Time.construct(@now.year, @now.month, @now.day) + Chronic::RepeaterDay::DAY_SECONDS
|
@@ -32,27 +38,27 @@ class Chronic::RepeaterYear < Chronic::Repeater #:nodoc:
|
|
32
38
|
this_year_start = Time.construct(@now.year, 1, 1)
|
33
39
|
this_year_end = Time.construct(@now.year + 1, 1, 1)
|
34
40
|
end
|
35
|
-
|
41
|
+
|
36
42
|
Chronic::Span.new(this_year_start, this_year_end)
|
37
43
|
end
|
38
|
-
|
44
|
+
|
39
45
|
def offset(span, amount, pointer)
|
40
46
|
direction = pointer == :future ? 1 : -1
|
41
|
-
|
47
|
+
|
42
48
|
sb = span.begin
|
43
49
|
new_begin = Time.construct(sb.year + (amount * direction), sb.month, sb.day, sb.hour, sb.min, sb.sec)
|
44
|
-
|
50
|
+
|
45
51
|
se = span.end
|
46
52
|
new_end = Time.construct(se.year + (amount * direction), se.month, se.day, se.hour, se.min, se.sec)
|
47
|
-
|
53
|
+
|
48
54
|
Chronic::Span.new(new_begin, new_end)
|
49
55
|
end
|
50
|
-
|
56
|
+
|
51
57
|
def width
|
52
58
|
(365 * 24 * 60 * 60)
|
53
59
|
end
|
54
|
-
|
60
|
+
|
55
61
|
def to_s
|
56
62
|
super << '-year'
|
57
63
|
end
|
58
|
-
end
|
64
|
+
end
|
data/lib/chronic/scalar.rb
CHANGED
@@ -11,7 +11,7 @@ module Chronic
|
|
11
11
|
end
|
12
12
|
tokens
|
13
13
|
end
|
14
|
-
|
14
|
+
|
15
15
|
def self.scan_for_scalars(token, post_token)
|
16
16
|
if token.word =~ /^\d*$/
|
17
17
|
unless post_token && %w{am pm morning afternoon evening night}.include?(post_token)
|
@@ -20,55 +20,57 @@ module Chronic
|
|
20
20
|
end
|
21
21
|
return nil
|
22
22
|
end
|
23
|
-
|
23
|
+
|
24
24
|
def self.scan_for_days(token, post_token)
|
25
25
|
if token.word =~ /^\d\d?$/
|
26
|
-
|
27
|
-
|
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)
|
28
29
|
end
|
29
30
|
end
|
30
31
|
return nil
|
31
32
|
end
|
32
|
-
|
33
|
+
|
33
34
|
def self.scan_for_months(token, post_token)
|
34
35
|
if token.word =~ /^\d\d?$/
|
35
|
-
|
36
|
-
|
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)
|
37
39
|
end
|
38
40
|
end
|
39
41
|
return nil
|
40
42
|
end
|
41
|
-
|
43
|
+
|
42
44
|
def self.scan_for_years(token, post_token)
|
43
45
|
if token.word =~ /^([1-9]\d)?\d\d?$/
|
44
|
-
unless post_token && %w{am pm morning afternoon evening night}.include?(post_token)
|
46
|
+
unless post_token && %w{am pm morning afternoon evening night}.include?(post_token.word)
|
45
47
|
return ScalarYear.new(token.word.to_i)
|
46
48
|
end
|
47
49
|
end
|
48
50
|
return nil
|
49
51
|
end
|
50
|
-
|
52
|
+
|
51
53
|
def to_s
|
52
54
|
'scalar'
|
53
55
|
end
|
54
56
|
end
|
55
|
-
|
57
|
+
|
56
58
|
class ScalarDay < Scalar #:nodoc:
|
57
59
|
def to_s
|
58
60
|
super << '-day-' << @type.to_s
|
59
61
|
end
|
60
62
|
end
|
61
|
-
|
63
|
+
|
62
64
|
class ScalarMonth < Scalar #:nodoc:
|
63
65
|
def to_s
|
64
66
|
super << '-month-' << @type.to_s
|
65
67
|
end
|
66
68
|
end
|
67
|
-
|
69
|
+
|
68
70
|
class ScalarYear < Scalar #:nodoc:
|
69
71
|
def to_s
|
70
72
|
super << '-year-' << @type.to_s
|
71
73
|
end
|
72
74
|
end
|
73
75
|
|
74
|
-
end
|
76
|
+
end
|
data/lib/chronic/separator.rb
CHANGED
@@ -7,10 +7,11 @@ module Chronic
|
|
7
7
|
if t = self.scan_for_slash_or_dash(tokens[i]) then tokens[i].tag(t); next end
|
8
8
|
if t = self.scan_for_at(tokens[i]) then tokens[i].tag(t); next end
|
9
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
|
10
11
|
end
|
11
12
|
tokens
|
12
13
|
end
|
13
|
-
|
14
|
+
|
14
15
|
def self.scan_for_commas(token)
|
15
16
|
scanner = {/^,$/ => :comma}
|
16
17
|
scanner.keys.each do |scanner_item|
|
@@ -18,7 +19,7 @@ module Chronic
|
|
18
19
|
end
|
19
20
|
return nil
|
20
21
|
end
|
21
|
-
|
22
|
+
|
22
23
|
def self.scan_for_slash_or_dash(token)
|
23
24
|
scanner = {/^-$/ => :dash,
|
24
25
|
/^\/$/ => :slash}
|
@@ -27,7 +28,7 @@ module Chronic
|
|
27
28
|
end
|
28
29
|
return nil
|
29
30
|
end
|
30
|
-
|
31
|
+
|
31
32
|
def self.scan_for_at(token)
|
32
33
|
scanner = {/^(at|@)$/ => :at}
|
33
34
|
scanner.keys.each do |scanner_item|
|
@@ -35,7 +36,7 @@ module Chronic
|
|
35
36
|
end
|
36
37
|
return nil
|
37
38
|
end
|
38
|
-
|
39
|
+
|
39
40
|
def self.scan_for_in(token)
|
40
41
|
scanner = {/^in$/ => :in}
|
41
42
|
scanner.keys.each do |scanner_item|
|
@@ -43,34 +44,48 @@ module Chronic
|
|
43
44
|
end
|
44
45
|
return nil
|
45
46
|
end
|
46
|
-
|
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
|
+
|
47
56
|
def to_s
|
48
57
|
'separator'
|
49
58
|
end
|
50
59
|
end
|
51
|
-
|
60
|
+
|
52
61
|
class SeparatorComma < Separator #:nodoc:
|
53
62
|
def to_s
|
54
63
|
super << '-comma'
|
55
64
|
end
|
56
65
|
end
|
57
|
-
|
66
|
+
|
58
67
|
class SeparatorSlashOrDash < Separator #:nodoc:
|
59
68
|
def to_s
|
60
69
|
super << '-slashordash-' << @type.to_s
|
61
70
|
end
|
62
71
|
end
|
63
|
-
|
72
|
+
|
64
73
|
class SeparatorAt < Separator #:nodoc:
|
65
74
|
def to_s
|
66
75
|
super << '-at'
|
67
76
|
end
|
68
77
|
end
|
69
|
-
|
78
|
+
|
70
79
|
class SeparatorIn < Separator #:nodoc:
|
71
80
|
def to_s
|
72
81
|
super << '-in'
|
73
82
|
end
|
74
83
|
end
|
75
84
|
|
76
|
-
|
85
|
+
class SeparatorOn < Separator #:nodoc:
|
86
|
+
def to_s
|
87
|
+
super << '-on'
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
data/lib/chronic/time_zone.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
module Chronic
|
2
|
-
class TimeZone < Tag #:nodoc:
|
2
|
+
class TimeZone < Tag #:nodoc:
|
3
3
|
def self.scan(tokens)
|
4
4
|
tokens.each_index do |i|
|
5
5
|
if t = self.scan_for_all(tokens[i]) then tokens[i].tag(t); next end
|
@@ -8,7 +8,8 @@ module Chronic
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def self.scan_for_all(token)
|
11
|
-
scanner = {/[PMCE][DS]T/i => :tz
|
11
|
+
scanner = {/[PMCE][DS]T/i => :tz,
|
12
|
+
/(tzminus)?\d{4}/ => :tz}
|
12
13
|
scanner.keys.each do |scanner_item|
|
13
14
|
return self.new(scanner[scanner_item]) if scanner_item =~ token.word
|
14
15
|
end
|
@@ -19,4 +20,4 @@ module Chronic
|
|
19
20
|
'timezone'
|
20
21
|
end
|
21
22
|
end
|
22
|
-
end
|
23
|
+
end
|
data/test/helper.rb
ADDED
data/test/test_Chronic.rb
CHANGED
@@ -1,50 +1,49 @@
|
|
1
|
-
require
|
2
|
-
require 'test/unit'
|
1
|
+
require File.join(File.dirname(__FILE__), *%w[helper])
|
3
2
|
|
4
3
|
class TestChronic < Test::Unit::TestCase
|
5
|
-
|
4
|
+
|
6
5
|
def setup
|
7
6
|
# Wed Aug 16 14:00:00 UTC 2006
|
8
7
|
@now = Time.local(2006, 8, 16, 14, 0, 0, 0)
|
9
8
|
end
|
10
|
-
|
9
|
+
|
11
10
|
def test_post_normalize_am_pm_aliases
|
12
11
|
# affect wanted patterns
|
13
|
-
|
12
|
+
|
14
13
|
tokens = [Chronic::Token.new("5:00"), Chronic::Token.new("morning")]
|
15
14
|
tokens[0].tag(Chronic::RepeaterTime.new("5:00"))
|
16
15
|
tokens[1].tag(Chronic::RepeaterDayPortion.new(:morning))
|
17
|
-
|
16
|
+
|
18
17
|
assert_equal :morning, tokens[1].tags[0].type
|
19
|
-
|
18
|
+
|
20
19
|
tokens = Chronic.dealias_and_disambiguate_times(tokens, {})
|
21
|
-
|
20
|
+
|
22
21
|
assert_equal :am, tokens[1].tags[0].type
|
23
22
|
assert_equal 2, tokens.size
|
24
|
-
|
23
|
+
|
25
24
|
# don't affect unwanted patterns
|
26
|
-
|
25
|
+
|
27
26
|
tokens = [Chronic::Token.new("friday"), Chronic::Token.new("morning")]
|
28
27
|
tokens[0].tag(Chronic::RepeaterDayName.new(:friday))
|
29
28
|
tokens[1].tag(Chronic::RepeaterDayPortion.new(:morning))
|
30
|
-
|
29
|
+
|
31
30
|
assert_equal :morning, tokens[1].tags[0].type
|
32
|
-
|
31
|
+
|
33
32
|
tokens = Chronic.dealias_and_disambiguate_times(tokens, {})
|
34
|
-
|
33
|
+
|
35
34
|
assert_equal :morning, tokens[1].tags[0].type
|
36
35
|
assert_equal 2, tokens.size
|
37
36
|
end
|
38
|
-
|
37
|
+
|
39
38
|
def test_guess
|
40
39
|
span = Chronic::Span.new(Time.local(2006, 8, 16, 0), Time.local(2006, 8, 17, 0))
|
41
40
|
assert_equal Time.local(2006, 8, 16, 12), Chronic.guess(span)
|
42
|
-
|
41
|
+
|
43
42
|
span = Chronic::Span.new(Time.local(2006, 8, 16, 0), Time.local(2006, 8, 17, 0, 0, 1))
|
44
43
|
assert_equal Time.local(2006, 8, 16, 12), Chronic.guess(span)
|
45
|
-
|
44
|
+
|
46
45
|
span = Chronic::Span.new(Time.local(2006, 11), Time.local(2006, 12))
|
47
46
|
assert_equal Time.local(2006, 11, 16), Chronic.guess(span)
|
48
47
|
end
|
49
|
-
|
50
|
-
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
|