chronic 0.9.1 → 0.10.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.
- checksums.yaml +4 -4
- data/.gitignore +4 -4
- data/HISTORY.md +12 -0
- data/README.md +8 -0
- data/Rakefile +28 -8
- data/chronic.gemspec +7 -5
- data/lib/chronic.rb +10 -10
- data/lib/chronic/date.rb +82 -0
- data/lib/chronic/handler.rb +34 -25
- data/lib/chronic/handlers.rb +22 -3
- data/lib/chronic/ordinal.rb +22 -20
- data/lib/chronic/parser.rb +31 -26
- data/lib/chronic/repeater.rb +18 -18
- data/lib/chronic/repeaters/repeater_day.rb +4 -3
- data/lib/chronic/repeaters/repeater_day_name.rb +5 -4
- data/lib/chronic/repeaters/repeater_day_portion.rb +4 -3
- data/lib/chronic/repeaters/repeater_fortnight.rb +4 -3
- data/lib/chronic/repeaters/repeater_hour.rb +4 -3
- data/lib/chronic/repeaters/repeater_minute.rb +4 -3
- data/lib/chronic/repeaters/repeater_month.rb +5 -4
- data/lib/chronic/repeaters/repeater_month_name.rb +4 -3
- data/lib/chronic/repeaters/repeater_season.rb +5 -3
- data/lib/chronic/repeaters/repeater_second.rb +4 -3
- data/lib/chronic/repeaters/repeater_time.rb +35 -25
- data/lib/chronic/repeaters/repeater_week.rb +4 -3
- data/lib/chronic/repeaters/repeater_weekday.rb +4 -3
- data/lib/chronic/repeaters/repeater_weekend.rb +4 -3
- data/lib/chronic/repeaters/repeater_year.rb +5 -4
- data/lib/chronic/scalar.rb +34 -69
- data/lib/chronic/separator.rb +107 -8
- data/lib/chronic/sign.rb +49 -0
- data/lib/chronic/tag.rb +5 -4
- data/lib/chronic/time.rb +40 -0
- data/test/helper.rb +2 -2
- data/test/test_chronic.rb +4 -4
- data/test/test_handler.rb +20 -0
- data/test/test_parsing.rb +72 -3
- data/test/test_repeater_time.rb +18 -0
- metadata +24 -6
@@ -8,8 +8,10 @@ module Chronic
|
|
8
8
|
:winter => Season.new(MiniDate.new(12,22), MiniDate.new(3,19))
|
9
9
|
}
|
10
10
|
|
11
|
-
def initialize(type)
|
11
|
+
def initialize(type, options = {})
|
12
12
|
super
|
13
|
+
@next_season_start = nil
|
14
|
+
@next_season_end = nil
|
13
15
|
end
|
14
16
|
|
15
17
|
def next(pointer)
|
@@ -63,7 +65,7 @@ module Chronic
|
|
63
65
|
private
|
64
66
|
|
65
67
|
def find_next_season_span(direction, next_season)
|
66
|
-
unless @next_season_start
|
68
|
+
unless @next_season_start || @next_season_end
|
67
69
|
@next_season_start = Chronic.construct(@now.year, @now.month, @now.day)
|
68
70
|
@next_season_end = Chronic.construct(@now.year, @now.month, @now.day)
|
69
71
|
end
|
@@ -106,4 +108,4 @@ module Chronic
|
|
106
108
|
)
|
107
109
|
end
|
108
110
|
end
|
109
|
-
end
|
111
|
+
end
|
@@ -2,8 +2,9 @@ module Chronic
|
|
2
2
|
class RepeaterSecond < Repeater #:nodoc:
|
3
3
|
SECOND_SECONDS = 1 # haha, awesome
|
4
4
|
|
5
|
-
def initialize(type)
|
5
|
+
def initialize(type, options = {})
|
6
6
|
super
|
7
|
+
@second_start = nil
|
7
8
|
end
|
8
9
|
|
9
10
|
def next(pointer = :future)
|
@@ -11,7 +12,7 @@ module Chronic
|
|
11
12
|
|
12
13
|
direction = pointer == :future ? 1 : -1
|
13
14
|
|
14
|
-
|
15
|
+
unless @second_start
|
15
16
|
@second_start = @now + (direction * SECOND_SECONDS)
|
16
17
|
else
|
17
18
|
@second_start += SECOND_SECONDS * direction
|
@@ -39,4 +40,4 @@ module Chronic
|
|
39
40
|
super << '-second'
|
40
41
|
end
|
41
42
|
end
|
42
|
-
end
|
43
|
+
end
|
@@ -26,31 +26,41 @@ module Chronic
|
|
26
26
|
|
27
27
|
end
|
28
28
|
|
29
|
-
def initialize(time)
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
raise("Time cannot exceed six digits")
|
29
|
+
def initialize(time, options = {})
|
30
|
+
@current_time = nil
|
31
|
+
@options = options
|
32
|
+
time_parts = time.split(':')
|
33
|
+
raise ArgumentError, "Time cannot have more than 4 groups of ':'" if time_parts.count > 4
|
34
|
+
|
35
|
+
if time_parts.first.length > 2 and time_parts.count == 1
|
36
|
+
if time_parts.first.length > 4
|
37
|
+
second_index = time_parts.first.length - 2
|
38
|
+
time_parts.insert(1, time_parts.first[second_index..time_parts.first.length])
|
39
|
+
time_parts[0] = time_parts.first[0..second_index - 1]
|
40
|
+
end
|
41
|
+
minute_index = time_parts.first.length - 2
|
42
|
+
time_parts.insert(1, time_parts.first[minute_index..time_parts.first.length])
|
43
|
+
time_parts[0] = time_parts.first[0..minute_index - 1]
|
44
|
+
end
|
45
|
+
|
46
|
+
ambiguous = false
|
47
|
+
hours = time_parts.first.to_i
|
48
|
+
|
49
|
+
if @options[:hours24].nil? or (not @options[:hours24].nil? and @options[:hours24] != true)
|
50
|
+
ambiguous = true if (time_parts.first.length == 1 and hours > 0) or (hours >= 10 and hours <= 12) or (@options[:hours24] == false and hours > 0)
|
51
|
+
hours = 0 if hours == 12 and ambiguous
|
53
52
|
end
|
53
|
+
|
54
|
+
hours *= 60 * 60
|
55
|
+
minutes = 0
|
56
|
+
seconds = 0
|
57
|
+
subseconds = 0
|
58
|
+
|
59
|
+
minutes = time_parts[1].to_i * 60 if time_parts.count > 1
|
60
|
+
seconds = time_parts[2].to_i if time_parts.count > 2
|
61
|
+
subseconds = time_parts[3].to_f / (10 ** time_parts[3].length) if time_parts.count > 3
|
62
|
+
|
63
|
+
@type = Tick.new(hours + minutes + seconds + subseconds, ambiguous)
|
54
64
|
end
|
55
65
|
|
56
66
|
# Return the next past or future Span for the time that this Repeater represents
|
@@ -125,4 +135,4 @@ module Chronic
|
|
125
135
|
super << '-time-' << @type.to_s
|
126
136
|
end
|
127
137
|
end
|
128
|
-
end
|
138
|
+
end
|
@@ -2,14 +2,15 @@ module Chronic
|
|
2
2
|
class RepeaterWeek < Repeater #:nodoc:
|
3
3
|
WEEK_SECONDS = 604800 # (7 * 24 * 60 * 60)
|
4
4
|
|
5
|
-
def initialize(type)
|
5
|
+
def initialize(type, options = {})
|
6
6
|
super
|
7
|
+
@current_week_start = nil
|
7
8
|
end
|
8
9
|
|
9
10
|
def next(pointer)
|
10
11
|
super
|
11
12
|
|
12
|
-
|
13
|
+
unless @current_week_start
|
13
14
|
case pointer
|
14
15
|
when :future
|
15
16
|
sunday_repeater = RepeaterDayName.new(:sunday)
|
@@ -71,4 +72,4 @@ module Chronic
|
|
71
72
|
super << '-week'
|
72
73
|
end
|
73
74
|
end
|
74
|
-
end
|
75
|
+
end
|
@@ -11,8 +11,9 @@ module Chronic
|
|
11
11
|
:saturday => 6
|
12
12
|
}
|
13
13
|
|
14
|
-
def initialize(type)
|
14
|
+
def initialize(type, options = {})
|
15
15
|
super
|
16
|
+
@current_weekday_start = nil
|
16
17
|
end
|
17
18
|
|
18
19
|
def next(pointer)
|
@@ -20,7 +21,7 @@ module Chronic
|
|
20
21
|
|
21
22
|
direction = pointer == :future ? 1 : -1
|
22
23
|
|
23
|
-
|
24
|
+
unless @current_weekday_start
|
24
25
|
@current_weekday_start = Chronic.construct(@now.year, @now.month, @now.day)
|
25
26
|
@current_weekday_start += direction * DAY_SECONDS
|
26
27
|
|
@@ -82,4 +83,4 @@ module Chronic
|
|
82
83
|
DAYS[sym] || raise("Invalid symbol specified")
|
83
84
|
end
|
84
85
|
end
|
85
|
-
end
|
86
|
+
end
|
@@ -2,14 +2,15 @@ module Chronic
|
|
2
2
|
class RepeaterWeekend < Repeater #:nodoc:
|
3
3
|
WEEKEND_SECONDS = 172_800 # (2 * 24 * 60 * 60)
|
4
4
|
|
5
|
-
def initialize(type)
|
5
|
+
def initialize(type, options = {})
|
6
6
|
super
|
7
|
+
@current_week_start = nil
|
7
8
|
end
|
8
9
|
|
9
10
|
def next(pointer)
|
10
11
|
super
|
11
12
|
|
12
|
-
|
13
|
+
unless @current_week_start
|
13
14
|
case pointer
|
14
15
|
when :future
|
15
16
|
saturday_repeater = RepeaterDayName.new(:saturday)
|
@@ -63,4 +64,4 @@ module Chronic
|
|
63
64
|
super << '-weekend'
|
64
65
|
end
|
65
66
|
end
|
66
|
-
end
|
67
|
+
end
|
@@ -2,14 +2,15 @@ module Chronic
|
|
2
2
|
class RepeaterYear < Repeater #:nodoc:
|
3
3
|
YEAR_SECONDS = 31536000 # 365 * 24 * 60 * 60
|
4
4
|
|
5
|
-
def initialize(type)
|
5
|
+
def initialize(type, options = {})
|
6
6
|
super
|
7
|
+
@current_year_start = nil
|
7
8
|
end
|
8
9
|
|
9
10
|
def next(pointer)
|
10
11
|
super
|
11
12
|
|
12
|
-
|
13
|
+
unless @current_year_start
|
13
14
|
case pointer
|
14
15
|
when :future
|
15
16
|
@current_year_start = Chronic.construct(@now.year + 1)
|
@@ -67,11 +68,11 @@ module Chronic
|
|
67
68
|
end
|
68
69
|
|
69
70
|
def month_days(year, month)
|
70
|
-
if Date.leap?(year)
|
71
|
+
if ::Date.leap?(year)
|
71
72
|
RepeaterMonth::MONTH_DAYS_LEAP[month - 1]
|
72
73
|
else
|
73
74
|
RepeaterMonth::MONTH_DAYS[month - 1]
|
74
75
|
end
|
75
76
|
end
|
76
77
|
end
|
77
|
-
end
|
78
|
+
end
|
data/lib/chronic/scalar.rb
CHANGED
@@ -11,88 +11,53 @@ module Chronic
|
|
11
11
|
# Returns an Array of tokens.
|
12
12
|
def self.scan(tokens, options)
|
13
13
|
tokens.each_index do |i|
|
14
|
-
|
15
|
-
|
16
|
-
if
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
14
|
+
token = tokens[i]
|
15
|
+
post_token = tokens[i + 1]
|
16
|
+
if token.word =~ /^\d+$/
|
17
|
+
scalar = token.word.to_i
|
18
|
+
token.tag(Scalar.new(scalar))
|
19
|
+
token.tag(ScalarSubsecond.new(scalar)) if Chronic::Time::could_be_subsecond?(scalar)
|
20
|
+
token.tag(ScalarSecond.new(scalar)) if Chronic::Time::could_be_second?(scalar)
|
21
|
+
token.tag(ScalarMinute.new(scalar)) if Chronic::Time::could_be_minute?(scalar)
|
22
|
+
token.tag(ScalarHour.new(scalar)) if Chronic::Time::could_be_hour?(scalar)
|
23
|
+
unless post_token and DAY_PORTIONS.include?(post_token.word)
|
24
|
+
token.tag(ScalarDay.new(scalar)) if Chronic::Date::could_be_day?(scalar)
|
25
|
+
token.tag(ScalarMonth.new(scalar)) if Chronic::Date::could_be_month?(scalar)
|
26
|
+
if Chronic::Date::could_be_year?(scalar)
|
27
|
+
year = Chronic::Date::make_year(scalar, options[:ambiguous_year_future_bias])
|
28
|
+
token.tag(ScalarYear.new(year.to_i))
|
29
|
+
end
|
30
|
+
end
|
29
31
|
end
|
30
32
|
end
|
31
33
|
end
|
32
34
|
|
33
|
-
|
34
|
-
|
35
|
-
#
|
36
|
-
# Returns a new Scalar object.
|
37
|
-
def self.scan_for_days(token, post_token)
|
38
|
-
if token.word =~ /^\d\d?$/
|
39
|
-
toi = token.word.to_i
|
40
|
-
unless toi > 31 || toi < 1 || (post_token && DAY_PORTIONS.include?(post_token.word))
|
41
|
-
return ScalarDay.new(toi)
|
42
|
-
end
|
43
|
-
end
|
35
|
+
def to_s
|
36
|
+
'scalar'
|
44
37
|
end
|
38
|
+
end
|
45
39
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
# Returns a new Scalar object.
|
50
|
-
def self.scan_for_months(token, post_token)
|
51
|
-
if token.word =~ /^\d\d?$/
|
52
|
-
toi = token.word.to_i
|
53
|
-
unless toi > 12 || toi < 1 || (post_token && DAY_PORTIONS.include?(post_token.word))
|
54
|
-
return ScalarMonth.new(toi)
|
55
|
-
end
|
56
|
-
end
|
40
|
+
class ScalarSubsecond < Scalar #:nodoc:
|
41
|
+
def to_s
|
42
|
+
super << '-subsecond-' << @type.to_s
|
57
43
|
end
|
44
|
+
end
|
58
45
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
#
|
63
|
-
# Returns a new Scalar object.
|
64
|
-
def self.scan_for_years(token, post_token, options)
|
65
|
-
if token.word =~ /^([1-9]\d)?\d\d?$/
|
66
|
-
unless post_token && DAY_PORTIONS.include?(post_token.word)
|
67
|
-
year = make_year(token.word.to_i, options[:ambiguous_year_future_bias])
|
68
|
-
return ScalarYear.new(year.to_i)
|
69
|
-
end
|
70
|
-
end
|
46
|
+
class ScalarSecond < Scalar #:nodoc:
|
47
|
+
def to_s
|
48
|
+
super << '-second-' << @type.to_s
|
71
49
|
end
|
50
|
+
end
|
72
51
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
# bias - The Integer amount of future years to bias.
|
77
|
-
#
|
78
|
-
# Examples:
|
79
|
-
#
|
80
|
-
# make_year(96, 50) #=> 1996
|
81
|
-
# make_year(79, 20) #=> 2079
|
82
|
-
# make_year(00, 50) #=> 2000
|
83
|
-
#
|
84
|
-
# Returns The Integer 4 digit year.
|
85
|
-
def self.make_year(year, bias)
|
86
|
-
return year if year.to_s.size > 2
|
87
|
-
start_year = Chronic.time_class.now.year - bias
|
88
|
-
century = (start_year / 100) * 100
|
89
|
-
full_year = century + year
|
90
|
-
full_year += 100 if full_year < start_year
|
91
|
-
full_year
|
52
|
+
class ScalarMinute < Scalar #:nodoc:
|
53
|
+
def to_s
|
54
|
+
super << '-minute-' << @type.to_s
|
92
55
|
end
|
56
|
+
end
|
93
57
|
|
58
|
+
class ScalarHour < Scalar #:nodoc:
|
94
59
|
def to_s
|
95
|
-
'
|
60
|
+
super << '-hour-' << @type.to_s
|
96
61
|
end
|
97
62
|
end
|
98
63
|
|
data/lib/chronic/separator.rb
CHANGED
@@ -11,11 +11,18 @@ module Chronic
|
|
11
11
|
def self.scan(tokens, options)
|
12
12
|
tokens.each do |token|
|
13
13
|
if t = scan_for_commas(token) then token.tag(t); next end
|
14
|
-
if t =
|
14
|
+
if t = scan_for_dots(token) then token.tag(t); next end
|
15
|
+
if t = scan_for_colon(token) then token.tag(t); next end
|
16
|
+
if t = scan_for_space(token) then token.tag(t); next end
|
17
|
+
if t = scan_for_slash(token) then token.tag(t); next end
|
18
|
+
if t = scan_for_dash(token) then token.tag(t); next end
|
19
|
+
if t = scan_for_quote(token) then token.tag(t); next end
|
15
20
|
if t = scan_for_at(token) then token.tag(t); next end
|
16
21
|
if t = scan_for_in(token) then token.tag(t); next end
|
17
22
|
if t = scan_for_on(token) then token.tag(t); next end
|
18
23
|
if t = scan_for_and(token) then token.tag(t); next end
|
24
|
+
if t = scan_for_t(token) then token.tag(t); next end
|
25
|
+
if t = scan_for_w(token) then token.tag(t); next end
|
19
26
|
end
|
20
27
|
end
|
21
28
|
|
@@ -28,12 +35,47 @@ module Chronic
|
|
28
35
|
|
29
36
|
# token - The Token object we want to scan.
|
30
37
|
#
|
31
|
-
# Returns a new
|
32
|
-
def self.
|
33
|
-
scan_for token,
|
38
|
+
# Returns a new SeparatorDot object.
|
39
|
+
def self.scan_for_dots(token)
|
40
|
+
scan_for token, SeparatorDot, { /^\.$/ => :dot }
|
41
|
+
end
|
42
|
+
|
43
|
+
# token - The Token object we want to scan.
|
44
|
+
#
|
45
|
+
# Returns a new SeparatorColon object.
|
46
|
+
def self.scan_for_colon(token)
|
47
|
+
scan_for token, SeparatorColon, { /^:$/ => :colon }
|
48
|
+
end
|
49
|
+
|
50
|
+
# token - The Token object we want to scan.
|
51
|
+
#
|
52
|
+
# Returns a new SeparatorSpace object.
|
53
|
+
def self.scan_for_space(token)
|
54
|
+
scan_for token, SeparatorSpace, { /^ $/ => :space }
|
55
|
+
end
|
56
|
+
|
57
|
+
# token - The Token object we want to scan.
|
58
|
+
#
|
59
|
+
# Returns a new SeparatorSlash object.
|
60
|
+
def self.scan_for_slash(token)
|
61
|
+
scan_for token, SeparatorSlash, { /^\/$/ => :slash }
|
62
|
+
end
|
63
|
+
|
64
|
+
# token - The Token object we want to scan.
|
65
|
+
#
|
66
|
+
# Returns a new SeparatorDash object.
|
67
|
+
def self.scan_for_dash(token)
|
68
|
+
scan_for token, SeparatorDash, { /^-$/ => :dash }
|
69
|
+
end
|
70
|
+
|
71
|
+
# token - The Token object we want to scan.
|
72
|
+
#
|
73
|
+
# Returns a new SeparatorQuote object.
|
74
|
+
def self.scan_for_quote(token)
|
75
|
+
scan_for token, SeparatorQuote,
|
34
76
|
{
|
35
|
-
|
36
|
-
|
77
|
+
/^'$/ => :single_quote,
|
78
|
+
/^"$/ => :double_quote
|
37
79
|
}
|
38
80
|
end
|
39
81
|
|
@@ -65,6 +107,20 @@ module Chronic
|
|
65
107
|
scan_for token, SeparatorAnd, { /^and$/ => :and }
|
66
108
|
end
|
67
109
|
|
110
|
+
# token - The Token object we want to scan.
|
111
|
+
#
|
112
|
+
# Returns a new SeperatorT Object object.
|
113
|
+
def self.scan_for_t(token)
|
114
|
+
scan_for token, SeparatorT, { /^t$/ => :T }
|
115
|
+
end
|
116
|
+
|
117
|
+
# token - The Token object we want to scan.
|
118
|
+
#
|
119
|
+
# Returns a new SeperatorW Object object.
|
120
|
+
def self.scan_for_w(token)
|
121
|
+
scan_for token, SeparatorW, { /^w$/ => :W }
|
122
|
+
end
|
123
|
+
|
68
124
|
def to_s
|
69
125
|
'separator'
|
70
126
|
end
|
@@ -76,9 +132,39 @@ module Chronic
|
|
76
132
|
end
|
77
133
|
end
|
78
134
|
|
79
|
-
class
|
135
|
+
class SeparatorDot < Separator #:nodoc:
|
80
136
|
def to_s
|
81
|
-
super << '-
|
137
|
+
super << '-dot'
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
class SeparatorColon < Separator #:nodoc:
|
142
|
+
def to_s
|
143
|
+
super << '-colon'
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
class SeparatorSpace < Separator #:nodoc:
|
148
|
+
def to_s
|
149
|
+
super << '-space'
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
class SeparatorSlash < Separator #:nodoc:
|
154
|
+
def to_s
|
155
|
+
super << '-slash'
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
class SeparatorDash < Separator #:nodoc:
|
160
|
+
def to_s
|
161
|
+
super << '-dash'
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
class SeparatorQuote < Separator #:nodoc:
|
166
|
+
def to_s
|
167
|
+
super << '-quote-' << @type.to_s
|
82
168
|
end
|
83
169
|
end
|
84
170
|
|
@@ -105,4 +191,17 @@ module Chronic
|
|
105
191
|
super << '-and'
|
106
192
|
end
|
107
193
|
end
|
194
|
+
|
195
|
+
class SeparatorT < Separator #:nodoc:
|
196
|
+
def to_s
|
197
|
+
super << '-T'
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
class SeparatorW < Separator #:nodoc:
|
202
|
+
def to_s
|
203
|
+
super << '-W'
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
108
207
|
end
|