chronic 0.4.1 → 0.4.2
Sign up to get free protection for your applications and to get access to all the features.
- data/HISTORY.md +9 -2
- data/Rakefile +0 -8
- data/chronic.gemspec +1 -1
- data/lib/chronic.rb +60 -47
- data/lib/chronic/chronic.rb +221 -90
- data/lib/chronic/grabber.rb +10 -1
- data/lib/chronic/handlers.rb +44 -186
- data/lib/chronic/mini_date.rb +6 -2
- data/lib/chronic/ordinal.rb +12 -1
- data/lib/chronic/pointer.rb +10 -1
- data/lib/chronic/repeater.rb +20 -1
- data/lib/chronic/repeaters/repeater_month.rb +13 -1
- data/lib/chronic/scalar.rb +29 -1
- data/lib/chronic/separator.rb +18 -1
- data/lib/chronic/time_zone.rb +10 -1
- data/lib/chronic/token.rb +14 -4
- data/test/test_Chronic.rb +52 -2
- data/test/test_RepeaterMonth.rb +4 -0
- metadata +2 -2
data/lib/chronic/pointer.rb
CHANGED
@@ -1,11 +1,20 @@
|
|
1
1
|
module Chronic
|
2
|
-
class Pointer < Tag
|
2
|
+
class Pointer < Tag
|
3
|
+
|
4
|
+
# Scan an Array of {Token}s and apply any necessary Pointer tags to
|
5
|
+
# each token
|
6
|
+
#
|
7
|
+
# @param [Array<Token>] tokens Array of tokens to scan
|
8
|
+
# @param [Hash] options Options specified in {Chronic.parse}
|
9
|
+
# @return [Array] list of tokens
|
3
10
|
def self.scan(tokens, options)
|
4
11
|
tokens.each_index do |i|
|
5
12
|
if t = scan_for_all(tokens[i]) then tokens[i].tag(t) end
|
6
13
|
end
|
7
14
|
end
|
8
15
|
|
16
|
+
# @param [Token] token
|
17
|
+
# @return [Pointer, nil]
|
9
18
|
def self.scan_for_all(token)
|
10
19
|
scan_for token, self,
|
11
20
|
{
|
data/lib/chronic/repeater.rb
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
module Chronic
|
2
|
-
class Repeater < Tag
|
2
|
+
class Repeater < Tag
|
3
|
+
|
4
|
+
# Scan an Array of {Token}s and apply any necessary Repeater tags to
|
5
|
+
# each token
|
6
|
+
#
|
7
|
+
# @param [Array<Token>] tokens Array of tokens to scan
|
8
|
+
# @param [Hash] options Options specified in {Chronic.parse}
|
9
|
+
# @return [Array] list of tokens
|
3
10
|
def self.scan(tokens, options)
|
4
11
|
tokens.each_index do |i|
|
5
12
|
if t = scan_for_season_names(tokens[i]) then tokens[i].tag(t); next end
|
@@ -11,6 +18,8 @@ module Chronic
|
|
11
18
|
end
|
12
19
|
end
|
13
20
|
|
21
|
+
# @param [Token] token
|
22
|
+
# @return [RepeaterSeasonName, nil]
|
14
23
|
def self.scan_for_season_names(token)
|
15
24
|
scan_for token, RepeaterSeasonName,
|
16
25
|
{
|
@@ -21,6 +30,8 @@ module Chronic
|
|
21
30
|
}
|
22
31
|
end
|
23
32
|
|
33
|
+
# @param [Token] token
|
34
|
+
# @return [RepeaterMonthName, nil]
|
24
35
|
def self.scan_for_month_names(token)
|
25
36
|
scan_for token, RepeaterMonthName,
|
26
37
|
{
|
@@ -39,6 +50,8 @@ module Chronic
|
|
39
50
|
}
|
40
51
|
end
|
41
52
|
|
53
|
+
# @param [Token] token
|
54
|
+
# @return [RepeaterDayName, nil]
|
42
55
|
def self.scan_for_day_names(token)
|
43
56
|
scan_for token, RepeaterDayName,
|
44
57
|
{
|
@@ -55,6 +68,8 @@ module Chronic
|
|
55
68
|
}
|
56
69
|
end
|
57
70
|
|
71
|
+
# @param [Token] token
|
72
|
+
# @return [RepeaterDayPortion, nil]
|
58
73
|
def self.scan_for_day_portions(token)
|
59
74
|
scan_for token, RepeaterDayPortion,
|
60
75
|
{
|
@@ -67,10 +82,14 @@ module Chronic
|
|
67
82
|
}
|
68
83
|
end
|
69
84
|
|
85
|
+
# @param [Token] token
|
86
|
+
# @return [RepeaterTime, nil]
|
70
87
|
def self.scan_for_times(token)
|
71
88
|
scan_for token, RepeaterTime, /^\d{1,2}(:?\d{2})?([\.:]?\d{2})?$/
|
72
89
|
end
|
73
90
|
|
91
|
+
# @param [Token] token
|
92
|
+
# @return [Repeater] A new instance of a subclass of Repeater
|
74
93
|
def self.scan_for_units(token)
|
75
94
|
{
|
76
95
|
/^years?$/ => :year,
|
@@ -2,6 +2,8 @@ module Chronic
|
|
2
2
|
class RepeaterMonth < Repeater #:nodoc:
|
3
3
|
MONTH_SECONDS = 2_592_000 # 30 * 24 * 60 * 60
|
4
4
|
YEAR_MONTHS = 12
|
5
|
+
MONTH_DAYS = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
6
|
+
MONTH_DAYS_LEAP = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
5
7
|
|
6
8
|
def initialize(type)
|
7
9
|
super
|
@@ -54,7 +56,11 @@ module Chronic
|
|
54
56
|
new_year += 1
|
55
57
|
new_month -= YEAR_MONTHS
|
56
58
|
end
|
57
|
-
|
59
|
+
|
60
|
+
days = month_days(new_year, new_month)
|
61
|
+
new_day = time.day > days ? days : time.day
|
62
|
+
|
63
|
+
Time.construct(new_year, new_month, new_day, time.hour, time.min, time.sec)
|
58
64
|
end
|
59
65
|
|
60
66
|
def width
|
@@ -64,5 +70,11 @@ module Chronic
|
|
64
70
|
def to_s
|
65
71
|
super << '-month'
|
66
72
|
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def month_days(year, month)
|
77
|
+
Date.leap?(year) ? MONTH_DAYS_LEAP[month - 1] : MONTH_DAYS[month - 1]
|
78
|
+
end
|
67
79
|
end
|
68
80
|
end
|
data/lib/chronic/scalar.rb
CHANGED
@@ -1,7 +1,13 @@
|
|
1
1
|
module Chronic
|
2
|
-
class Scalar < Tag
|
2
|
+
class Scalar < Tag
|
3
3
|
DAY_PORTIONS = %w( am pm morning afternoon evening night )
|
4
4
|
|
5
|
+
# Scan an Array of {Token}s and apply any necessary Scalar tags to
|
6
|
+
# each token
|
7
|
+
#
|
8
|
+
# @param [Array<Token>] tokens Array of tokens to scan
|
9
|
+
# @param [Hash] options Options specified in {Chronic.parse}
|
10
|
+
# @return [Array] list of tokens
|
5
11
|
def self.scan(tokens, options)
|
6
12
|
tokens.each_index do |i|
|
7
13
|
if t = scan_for_scalars(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
|
@@ -11,6 +17,9 @@ module Chronic
|
|
11
17
|
end
|
12
18
|
end
|
13
19
|
|
20
|
+
# @param [Token] token
|
21
|
+
# @param [Token] post_token
|
22
|
+
# @return [Scalar, nil]
|
14
23
|
def self.scan_for_scalars(token, post_token)
|
15
24
|
if token.word =~ /^\d*$/
|
16
25
|
unless post_token && DAY_PORTIONS.include?(post_token.word)
|
@@ -19,6 +28,9 @@ module Chronic
|
|
19
28
|
end
|
20
29
|
end
|
21
30
|
|
31
|
+
# @param [Token] token
|
32
|
+
# @param [Token] post_token
|
33
|
+
# @return [ScalarDay, nil]
|
22
34
|
def self.scan_for_days(token, post_token)
|
23
35
|
if token.word =~ /^\d\d?$/
|
24
36
|
toi = token.word.to_i
|
@@ -28,6 +40,9 @@ module Chronic
|
|
28
40
|
end
|
29
41
|
end
|
30
42
|
|
43
|
+
# @param [Token] token
|
44
|
+
# @param [Token] post_token
|
45
|
+
# @return [ScalarMonth, nil]
|
31
46
|
def self.scan_for_months(token, post_token)
|
32
47
|
if token.word =~ /^\d\d?$/
|
33
48
|
toi = token.word.to_i
|
@@ -37,6 +52,10 @@ module Chronic
|
|
37
52
|
end
|
38
53
|
end
|
39
54
|
|
55
|
+
# @param [Token] token
|
56
|
+
# @param [Token] post_token
|
57
|
+
# @param [Hash] options Options specified in {Chronic.parse}
|
58
|
+
# @return [ScalarYear, nil]
|
40
59
|
def self.scan_for_years(token, post_token, options)
|
41
60
|
if token.word =~ /^([1-9]\d)?\d\d?$/
|
42
61
|
unless post_token && DAY_PORTIONS.include?(post_token.word)
|
@@ -47,6 +66,15 @@ module Chronic
|
|
47
66
|
end
|
48
67
|
|
49
68
|
# Build a year from a 2 digit suffix
|
69
|
+
#
|
70
|
+
# @example
|
71
|
+
# make_year(96, 50) #=> 1996
|
72
|
+
# make_year(79, 20) #=> 2079
|
73
|
+
# make_year(00, 50) #=> 2000
|
74
|
+
#
|
75
|
+
# @param [Integer] year The two digit year to build from
|
76
|
+
# @param [Integer] bias The amount of future years to bias
|
77
|
+
# @return [Integer] The 4 digit year
|
50
78
|
def self.make_year(year, bias)
|
51
79
|
return year if year.to_s.size > 2
|
52
80
|
start_year = Chronic.time_class.now.year - bias
|
data/lib/chronic/separator.rb
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
module Chronic
|
2
|
-
class Separator < Tag
|
2
|
+
class Separator < Tag
|
3
|
+
|
4
|
+
# Scan an Array of {Token}s and apply any necessary Separator tags to
|
5
|
+
# each token
|
6
|
+
#
|
7
|
+
# @param [Array<Token>] tokens Array of tokens to scan
|
8
|
+
# @param [Hash] options Options specified in {Chronic.parse}
|
9
|
+
# @return [Array] list of tokens
|
3
10
|
def self.scan(tokens, options)
|
4
11
|
tokens.each_index do |i|
|
5
12
|
if t = scan_for_commas(tokens[i]) then tokens[i].tag(t); next end
|
@@ -10,10 +17,14 @@ module Chronic
|
|
10
17
|
end
|
11
18
|
end
|
12
19
|
|
20
|
+
# @param [Token] token
|
21
|
+
# @return [SeparatorComma, nil]
|
13
22
|
def self.scan_for_commas(token)
|
14
23
|
scan_for token, SeparatorComma, { /^,$/ => :comma }
|
15
24
|
end
|
16
25
|
|
26
|
+
# @param [Token] token
|
27
|
+
# @return [SeparatorSlashOrDash, nil]
|
17
28
|
def self.scan_for_slash_or_dash(token)
|
18
29
|
scan_for token, SeparatorSlashOrDash,
|
19
30
|
{
|
@@ -22,14 +33,20 @@ module Chronic
|
|
22
33
|
}
|
23
34
|
end
|
24
35
|
|
36
|
+
# @param [Token] token
|
37
|
+
# @return [SeparatorAt, nil]
|
25
38
|
def self.scan_for_at(token)
|
26
39
|
scan_for token, SeparatorAt, { /^(at|@)$/ => :at }
|
27
40
|
end
|
28
41
|
|
42
|
+
# @param [Token] token
|
43
|
+
# @return [SeparatorIn, nil]
|
29
44
|
def self.scan_for_in(token)
|
30
45
|
scan_for token, SeparatorIn, { /^in$/ => :in }
|
31
46
|
end
|
32
47
|
|
48
|
+
# @param [Token] token
|
49
|
+
# @return [SeparatorOn, nil]
|
33
50
|
def self.scan_for_on(token)
|
34
51
|
scan_for token, SeparatorOn, { /^on$/ => :on }
|
35
52
|
end
|
data/lib/chronic/time_zone.rb
CHANGED
@@ -1,11 +1,20 @@
|
|
1
1
|
module Chronic
|
2
|
-
class TimeZone < Tag
|
2
|
+
class TimeZone < Tag
|
3
|
+
|
4
|
+
# Scan an Array of {Token}s and apply any necessary TimeZone tags to
|
5
|
+
# each token
|
6
|
+
#
|
7
|
+
# @param [Array<Token>] tokens Array of tokens to scan
|
8
|
+
# @param [Hash] options Options specified in {Chronic.parse}
|
9
|
+
# @return [Array] list of tokens
|
3
10
|
def self.scan(tokens, options)
|
4
11
|
tokens.each_index do |i|
|
5
12
|
if t = scan_for_all(tokens[i]) then tokens[i].tag(t); next end
|
6
13
|
end
|
7
14
|
end
|
8
15
|
|
16
|
+
# @param [Token] token
|
17
|
+
# @return [TimeZone, nil]
|
9
18
|
def self.scan_for_all(token)
|
10
19
|
scan_for token, self,
|
11
20
|
{
|
data/lib/chronic/token.rb
CHANGED
@@ -1,6 +1,11 @@
|
|
1
1
|
module Chronic
|
2
|
-
class Token
|
3
|
-
|
2
|
+
class Token
|
3
|
+
|
4
|
+
# @return [String] The word this Token represents
|
5
|
+
attr_accessor :word
|
6
|
+
|
7
|
+
# @return [Array] A list of tag associated with this Token
|
8
|
+
attr_accessor :tags
|
4
9
|
|
5
10
|
def initialize(word)
|
6
11
|
@word = word
|
@@ -8,21 +13,26 @@ module Chronic
|
|
8
13
|
end
|
9
14
|
|
10
15
|
# Tag this token with the specified tag
|
16
|
+
#
|
17
|
+
# @param [Tag] new_tag A instance of {Tag} or one of its subclasses
|
11
18
|
def tag(new_tag)
|
12
19
|
@tags << new_tag
|
13
20
|
end
|
14
21
|
|
15
22
|
# Remove all tags of the given class
|
23
|
+
#
|
24
|
+
# @param [Class] The tag class to remove
|
16
25
|
def untag(tag_class)
|
17
26
|
@tags.delete_if { |m| m.kind_of? tag_class }
|
18
27
|
end
|
19
28
|
|
20
|
-
#
|
29
|
+
# @return [Boolean] true if this token has any tags
|
21
30
|
def tagged?
|
22
31
|
@tags.size > 0
|
23
32
|
end
|
24
33
|
|
25
|
-
#
|
34
|
+
# @param [Class] tag_class The tag class to search for
|
35
|
+
# @return [Tag] The first Tag that matches the given class
|
26
36
|
def get_tag(tag_class)
|
27
37
|
@tags.find { |m| m.kind_of? tag_class }
|
28
38
|
end
|
data/test/test_Chronic.rb
CHANGED
@@ -21,7 +21,7 @@ class TestChronic < Test::Unit::TestCase
|
|
21
21
|
|
22
22
|
assert_equal :morning, tokens[1].tags[0].type
|
23
23
|
|
24
|
-
tokens = Chronic.dealias_and_disambiguate_times(tokens, {})
|
24
|
+
tokens = Chronic::Handlers.dealias_and_disambiguate_times(tokens, {})
|
25
25
|
|
26
26
|
assert_equal :am, tokens[1].tags[0].type
|
27
27
|
assert_equal 2, tokens.size
|
@@ -34,7 +34,7 @@ class TestChronic < Test::Unit::TestCase
|
|
34
34
|
|
35
35
|
assert_equal :morning, tokens[1].tags[0].type
|
36
36
|
|
37
|
-
tokens = Chronic.dealias_and_disambiguate_times(tokens, {})
|
37
|
+
tokens = Chronic::Handlers.dealias_and_disambiguate_times(tokens, {})
|
38
38
|
|
39
39
|
assert_equal :morning, tokens[1].tags[0].type
|
40
40
|
assert_equal 2, tokens.size
|
@@ -51,4 +51,54 @@ class TestChronic < Test::Unit::TestCase
|
|
51
51
|
assert_equal Time.local(2006, 11, 16), Chronic.guess(span)
|
52
52
|
end
|
53
53
|
|
54
|
+
def test_now
|
55
|
+
Chronic.parse('now', :now => Time.local(2006, 01))
|
56
|
+
assert_equal Time.local(2006, 01), Chronic.now
|
57
|
+
|
58
|
+
Chronic.parse('now', :now => Time.local(2007, 01))
|
59
|
+
assert_equal Time.local(2007, 01), Chronic.now
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_endian_definitions
|
63
|
+
# middle, little
|
64
|
+
endians = [
|
65
|
+
Chronic::Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_day, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sm_sd_sy),
|
66
|
+
Chronic::Handler.new([:scalar_day, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sd_sm_sy)
|
67
|
+
]
|
68
|
+
|
69
|
+
assert_equal endians, Chronic.definitions[:endian]
|
70
|
+
|
71
|
+
defs = Chronic.definitions(:endian_precedence => :little)
|
72
|
+
assert_equal endians.reverse, defs[:endian]
|
73
|
+
|
74
|
+
defs = Chronic.definitions(:endian_precedence => [:little, :middle])
|
75
|
+
assert_equal endians.reverse, defs[:endian]
|
76
|
+
|
77
|
+
assert_raises(Chronic::InvalidArgumentException) do
|
78
|
+
Chronic.definitions(:endian_precedence => :invalid)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_passing_options
|
83
|
+
assert_raises(Chronic::InvalidArgumentException) do
|
84
|
+
Chronic.parse('now', :invalid => :option)
|
85
|
+
end
|
86
|
+
|
87
|
+
assert_raises(Chronic::InvalidArgumentException) do
|
88
|
+
Chronic.parse('now', :context => :invalid_context)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_debug
|
93
|
+
require 'stringio'
|
94
|
+
$stdout = StringIO.new
|
95
|
+
Chronic.debug = true
|
96
|
+
|
97
|
+
Chronic.parse 'now'
|
98
|
+
assert $stdout.string.include?('this(grabber-this)')
|
99
|
+
ensure
|
100
|
+
$stdout = STDOUT
|
101
|
+
Chronic.debug = false
|
102
|
+
end
|
103
|
+
|
54
104
|
end
|
data/test/test_RepeaterMonth.rb
CHANGED
@@ -23,6 +23,10 @@ class TestRepeaterMonth < Test::Unit::TestCase
|
|
23
23
|
|
24
24
|
time = Chronic::RepeaterMonth.new(:month).offset_by(@now, 10, :past)
|
25
25
|
assert_equal Time.local(2005, 10, 16, 14), time
|
26
|
+
|
27
|
+
time = Chronic::RepeaterMonth.new(:month).offset_by(Time.local(2010, 3, 29), 1, :past)
|
28
|
+
assert_equal 2, time.month
|
29
|
+
assert_equal 28, time.day
|
26
30
|
end
|
27
31
|
|
28
32
|
def test_offset
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: chronic
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.4.
|
5
|
+
version: 0.4.2
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Tom Preston-Werner
|
@@ -11,7 +11,7 @@ autorequire:
|
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
13
|
|
14
|
-
date: 2011-06-
|
14
|
+
date: 2011-06-07 00:00:00 Z
|
15
15
|
dependencies: []
|
16
16
|
|
17
17
|
description: Chronic is a natural language date/time parser written in pure Ruby.
|