chronic 0.4.1 → 0.4.2
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 +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.
|