chronic 0.8.0 → 0.9.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 +11 -0
- data/README.md +38 -45
- data/lib/chronic.rb +97 -69
- data/lib/chronic/handler.rb +7 -7
- data/lib/chronic/handlers.rb +68 -21
- data/lib/chronic/{chronic.rb → parser.rb} +52 -134
- data/lib/chronic/repeater.rb +11 -11
- data/lib/chronic/separator.rb +15 -1
- data/test/test_chronic.rb +9 -27
- data/test/test_handler.rb +17 -13
- data/test/test_numerizer.rb +1 -1
- data/test/test_parsing.rb +112 -18
- metadata +4 -3
data/HISTORY.md
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
# 0.9.0 / 2012-12-21
|
2
|
+
|
3
|
+
* Implement Chronic::Parser class and create an instance of this class
|
4
|
+
instead of leaving all data in the class level of Chronic
|
5
|
+
* Various bug fixes
|
6
|
+
* Add support for excel date formats (#149, @jmondo)
|
7
|
+
* Added support for time expressions such as '10 till' or 'half
|
8
|
+
past two' (#146, @chicagogrooves)
|
9
|
+
* Add support for RepeaterDayName, RepeaterMonthName,
|
10
|
+
Ordinal/ScalarDay and Time (#153, @kareemk)
|
11
|
+
|
1
12
|
# 0.8.0 / 2012-09-16
|
2
13
|
|
3
14
|
* Support parsing "<ordinal> of this month" (#109)
|
data/README.md
CHANGED
@@ -1,60 +1,47 @@
|
|
1
1
|
Chronic
|
2
2
|
=======
|
3
3
|
|
4
|
-
## DESCRIPTION
|
5
|
-
|
6
4
|
Chronic is a natural language date/time parser written in pure Ruby. See below
|
7
5
|
for the wide variety of formats Chronic will parse.
|
8
6
|
|
7
|
+
## Installation
|
9
8
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
$ [sudo] gem install chronic
|
15
|
-
|
16
|
-
### GitHub
|
9
|
+
```
|
10
|
+
$ gem install chronic
|
11
|
+
```
|
17
12
|
|
18
|
-
|
19
|
-
$ cd chronic && gem build chronic.gemspec
|
20
|
-
$ gem install chronic-<version>.gem
|
13
|
+
## Usage
|
21
14
|
|
15
|
+
```ruby
|
16
|
+
require 'chronic'
|
22
17
|
|
23
|
-
|
18
|
+
Time.now #=> Sun Aug 27 23:18:25 PDT 2006
|
24
19
|
|
25
|
-
|
26
|
-
|
20
|
+
Chronic.parse('tomorrow')
|
21
|
+
#=> Mon Aug 28 12:00:00 PDT 2006
|
27
22
|
|
28
|
-
|
23
|
+
Chronic.parse('monday', :context => :past)
|
24
|
+
#=> Mon Aug 21 12:00:00 PDT 2006
|
29
25
|
|
30
|
-
|
26
|
+
Chronic.parse('this tuesday 5:00')
|
27
|
+
#=> Tue Aug 29 17:00:00 PDT 2006
|
31
28
|
|
32
|
-
|
33
|
-
|
29
|
+
Chronic.parse('this tuesday 5:00', :ambiguous_time_range => :none)
|
30
|
+
#=> Tue Aug 29 05:00:00 PDT 2006
|
34
31
|
|
35
|
-
|
36
|
-
|
32
|
+
Chronic.parse('may 27th', :now => Time.local(2000, 1, 1))
|
33
|
+
#=> Sat May 27 12:00:00 PDT 2000
|
37
34
|
|
38
|
-
|
39
|
-
|
35
|
+
Chronic.parse('may 27th', :guess => false)
|
36
|
+
#=> Sun May 27 00:00:00 PDT 2007..Mon May 28 00:00:00 PDT 2007
|
40
37
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
Chronic.parse('may 27th', :now => Time.local(2000, 1, 1))
|
45
|
-
#=> Sat May 27 12:00:00 PDT 2000
|
46
|
-
|
47
|
-
Chronic.parse('may 27th', :guess => false)
|
48
|
-
#=> Sun May 27 00:00:00 PDT 2007..Mon May 28 00:00:00 PDT 2007
|
49
|
-
|
50
|
-
Chronic.parse('6/4/2012', :endian_precedence => :little)
|
51
|
-
#=> Fri Apr 06 00:00:00 PDT 2012
|
52
|
-
|
38
|
+
Chronic.parse('6/4/2012', :endian_precedence => :little)
|
39
|
+
#=> Fri Apr 06 00:00:00 PDT 2012
|
40
|
+
```
|
53
41
|
|
54
42
|
See `Chronic.parse` for detailed usage instructions.
|
55
43
|
|
56
|
-
|
57
|
-
## EXAMPLES
|
44
|
+
## Examples
|
58
45
|
|
59
46
|
Chronic can parse a huge variety of date and time formats. Following is a
|
60
47
|
small sample of strings that will be properly parsed. Parsing is case
|
@@ -68,12 +55,17 @@ Simple
|
|
68
55
|
* friday 13:00
|
69
56
|
* mon 2:35
|
70
57
|
* 4pm
|
58
|
+
* 10 to 8
|
59
|
+
* 10 past 2
|
60
|
+
* half past 2
|
71
61
|
* 6 in the morning
|
72
62
|
* friday 1pm
|
73
63
|
* sat 7 in the evening
|
74
64
|
* yesterday
|
75
65
|
* today
|
76
66
|
* tomorrow
|
67
|
+
* last week
|
68
|
+
* next week
|
77
69
|
* this tuesday
|
78
70
|
* next month
|
79
71
|
* last winter
|
@@ -139,7 +131,7 @@ Specific Times (many of the above with an added time)
|
|
139
131
|
* etc
|
140
132
|
|
141
133
|
|
142
|
-
##
|
134
|
+
## Time Zones
|
143
135
|
|
144
136
|
Chronic allows you to set which Time class to use when constructing times. By
|
145
137
|
default, the built in Ruby time class creates times in your system's local
|
@@ -147,13 +139,14 @@ time zone. You can set this to something like ActiveSupport's
|
|
147
139
|
[TimeZone](http://api.rubyonrails.org/classes/ActiveSupport/TimeZone.html)
|
148
140
|
class to get full time zone support.
|
149
141
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
142
|
+
```
|
143
|
+
>> Time.zone = "UTC"
|
144
|
+
>> Chronic.time_class = Time.zone
|
145
|
+
>> Chronic.parse("June 15 2006 at 5:45 AM")
|
146
|
+
=> Thu, 15 Jun 2006 05:45:00 UTC +00:00
|
147
|
+
```
|
155
148
|
|
156
|
-
##
|
149
|
+
## Limitations
|
157
150
|
|
158
151
|
Chronic uses Ruby's built in Time class for all time storage and computation.
|
159
152
|
Because of this, only times that the Time class can handle will be properly
|
@@ -161,7 +154,7 @@ parsed. Parsing for times outside of this range will simply return nil.
|
|
161
154
|
Support for a wider range of times is planned for a future release.
|
162
155
|
|
163
156
|
|
164
|
-
##
|
157
|
+
## Contribute
|
165
158
|
|
166
159
|
If you'd like to hack on Chronic, start by forking the repo on GitHub:
|
167
160
|
|
data/lib/chronic.rb
CHANGED
@@ -1,6 +1,42 @@
|
|
1
1
|
require 'time'
|
2
2
|
require 'date'
|
3
3
|
|
4
|
+
require 'chronic/parser'
|
5
|
+
|
6
|
+
require 'chronic/handler'
|
7
|
+
require 'chronic/handlers'
|
8
|
+
require 'chronic/mini_date'
|
9
|
+
require 'chronic/tag'
|
10
|
+
require 'chronic/span'
|
11
|
+
require 'chronic/token'
|
12
|
+
require 'chronic/grabber'
|
13
|
+
require 'chronic/pointer'
|
14
|
+
require 'chronic/scalar'
|
15
|
+
require 'chronic/ordinal'
|
16
|
+
require 'chronic/ordinal'
|
17
|
+
require 'chronic/separator'
|
18
|
+
require 'chronic/time_zone'
|
19
|
+
require 'chronic/numerizer'
|
20
|
+
require 'chronic/season'
|
21
|
+
|
22
|
+
require 'chronic/repeater'
|
23
|
+
require 'chronic/repeaters/repeater_year'
|
24
|
+
require 'chronic/repeaters/repeater_season'
|
25
|
+
require 'chronic/repeaters/repeater_season_name'
|
26
|
+
require 'chronic/repeaters/repeater_month'
|
27
|
+
require 'chronic/repeaters/repeater_month_name'
|
28
|
+
require 'chronic/repeaters/repeater_fortnight'
|
29
|
+
require 'chronic/repeaters/repeater_week'
|
30
|
+
require 'chronic/repeaters/repeater_weekend'
|
31
|
+
require 'chronic/repeaters/repeater_weekday'
|
32
|
+
require 'chronic/repeaters/repeater_day'
|
33
|
+
require 'chronic/repeaters/repeater_day_name'
|
34
|
+
require 'chronic/repeaters/repeater_day_portion'
|
35
|
+
require 'chronic/repeaters/repeater_hour'
|
36
|
+
require 'chronic/repeaters/repeater_minute'
|
37
|
+
require 'chronic/repeaters/repeater_second'
|
38
|
+
require 'chronic/repeaters/repeater_time'
|
39
|
+
|
4
40
|
# Parse natural language dates and times into Time or Chronic::Span objects.
|
5
41
|
#
|
6
42
|
# Examples:
|
@@ -14,20 +50,8 @@ require 'date'
|
|
14
50
|
#
|
15
51
|
# Chronic.parse('monday', :context => :past)
|
16
52
|
# #=> Mon Aug 21 12:00:00 PDT 2006
|
17
|
-
#
|
18
|
-
# Chronic.parse('this tuesday 5:00')
|
19
|
-
# #=> Tue Aug 29 17:00:00 PDT 2006
|
20
|
-
#
|
21
|
-
# Chronic.parse('this tuesday 5:00', :ambiguous_time_range => :none)
|
22
|
-
# #=> Tue Aug 29 05:00:00 PDT 2006
|
23
|
-
#
|
24
|
-
# Chronic.parse('may 27th', :now => Time.local(2000, 1, 1))
|
25
|
-
# #=> Sat May 27 12:00:00 PDT 2000
|
26
|
-
#
|
27
|
-
# Chronic.parse('may 27th', :guess => false)
|
28
|
-
# #=> Sun May 27 00:00:00 PDT 2007..Mon May 28 00:00:00 PDT 2007
|
29
53
|
module Chronic
|
30
|
-
VERSION = "0.
|
54
|
+
VERSION = "0.9.0"
|
31
55
|
|
32
56
|
class << self
|
33
57
|
|
@@ -46,72 +70,76 @@ module Chronic
|
|
46
70
|
#
|
47
71
|
# Returns The Time class Chronic uses internally.
|
48
72
|
attr_accessor :time_class
|
49
|
-
|
50
|
-
# The current Time Chronic is using to base from.
|
51
|
-
#
|
52
|
-
# Examples:
|
53
|
-
#
|
54
|
-
# Time.now #=> 2011-06-06 14:13:43 +0100
|
55
|
-
# Chronic.parse('yesterday') #=> 2011-06-05 12:00:00 +0100
|
56
|
-
#
|
57
|
-
# now = Time.local(2025, 12, 24)
|
58
|
-
# Chronic.parse('tomorrow', :now => now) #=> 2025-12-25 12:00:00 +0000
|
59
|
-
#
|
60
|
-
# Returns a Time object.
|
61
|
-
attr_accessor :now
|
62
73
|
end
|
63
74
|
|
64
75
|
self.debug = false
|
65
76
|
self.time_class = Time
|
66
77
|
|
67
|
-
autoload :Handler, 'chronic/handler'
|
68
|
-
autoload :Handlers, 'chronic/handlers'
|
69
|
-
autoload :MiniDate, 'chronic/mini_date'
|
70
|
-
autoload :Tag, 'chronic/tag'
|
71
|
-
autoload :Span, 'chronic/span'
|
72
|
-
autoload :Token, 'chronic/token'
|
73
|
-
autoload :Grabber, 'chronic/grabber'
|
74
|
-
autoload :Pointer, 'chronic/pointer'
|
75
|
-
autoload :Scalar, 'chronic/scalar'
|
76
|
-
autoload :Ordinal, 'chronic/ordinal'
|
77
|
-
autoload :OrdinalDay, 'chronic/ordinal'
|
78
|
-
autoload :Separator, 'chronic/separator'
|
79
|
-
autoload :TimeZone, 'chronic/time_zone'
|
80
|
-
autoload :Numerizer, 'chronic/numerizer'
|
81
|
-
autoload :Season, 'chronic/season'
|
82
|
-
|
83
|
-
autoload :Repeater, 'chronic/repeater'
|
84
|
-
autoload :RepeaterYear, 'chronic/repeaters/repeater_year'
|
85
|
-
autoload :RepeaterSeason, 'chronic/repeaters/repeater_season'
|
86
|
-
autoload :RepeaterSeasonName, 'chronic/repeaters/repeater_season_name'
|
87
|
-
autoload :RepeaterMonth, 'chronic/repeaters/repeater_month'
|
88
|
-
autoload :RepeaterMonthName, 'chronic/repeaters/repeater_month_name'
|
89
|
-
autoload :RepeaterFortnight, 'chronic/repeaters/repeater_fortnight'
|
90
|
-
autoload :RepeaterWeek, 'chronic/repeaters/repeater_week'
|
91
|
-
autoload :RepeaterWeekend, 'chronic/repeaters/repeater_weekend'
|
92
|
-
autoload :RepeaterWeekday, 'chronic/repeaters/repeater_weekday'
|
93
|
-
autoload :RepeaterDay, 'chronic/repeaters/repeater_day'
|
94
|
-
autoload :RepeaterDayName, 'chronic/repeaters/repeater_day_name'
|
95
|
-
autoload :RepeaterDayPortion, 'chronic/repeaters/repeater_day_portion'
|
96
|
-
autoload :RepeaterHour, 'chronic/repeaters/repeater_hour'
|
97
|
-
autoload :RepeaterMinute, 'chronic/repeaters/repeater_minute'
|
98
|
-
autoload :RepeaterSecond, 'chronic/repeaters/repeater_second'
|
99
|
-
autoload :RepeaterTime, 'chronic/repeaters/repeater_time'
|
100
78
|
|
101
|
-
|
79
|
+
# Parses a string containing a natural language date or time.
|
80
|
+
#
|
81
|
+
# If the parser can find a date or time, either a Time or Chronic::Span
|
82
|
+
# will be returned (depending on the value of `:guess`). If no
|
83
|
+
# date or time can be found, `nil` will be returned.
|
84
|
+
#
|
85
|
+
# text - The String text to parse.
|
86
|
+
# opts - An optional Hash of configuration options passed to Parser::new.
|
87
|
+
def self.parse(text, options = {})
|
88
|
+
Parser.new(options).parse(text)
|
89
|
+
end
|
102
90
|
|
103
|
-
|
91
|
+
# Construct a new time object determining possible month overflows
|
92
|
+
# and leap years.
|
93
|
+
#
|
94
|
+
# year - Integer year.
|
95
|
+
# month - Integer month.
|
96
|
+
# day - Integer day.
|
97
|
+
# hour - Integer hour.
|
98
|
+
# minute - Integer minute.
|
99
|
+
# second - Integer second.
|
100
|
+
#
|
101
|
+
# Returns a new Time object constructed from these params.
|
102
|
+
def self.construct(year, month = 1, day = 1, hour = 0, minute = 0, second = 0)
|
103
|
+
if second >= 60
|
104
|
+
minute += second / 60
|
105
|
+
second = second % 60
|
106
|
+
end
|
104
107
|
|
105
|
-
|
108
|
+
if minute >= 60
|
109
|
+
hour += minute / 60
|
110
|
+
minute = minute % 60
|
111
|
+
end
|
106
112
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
113
|
+
if hour >= 24
|
114
|
+
day += hour / 24
|
115
|
+
hour = hour % 24
|
116
|
+
end
|
117
|
+
|
118
|
+
# determine if there is a day overflow. this is complicated by our crappy calendar
|
119
|
+
# system (non-constant number of days per month)
|
120
|
+
day <= 56 || raise("day must be no more than 56 (makes month resolution easier)")
|
121
|
+
if day > 28
|
122
|
+
# no month ever has fewer than 28 days, so only do this if necessary
|
123
|
+
leap_year_month_days = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
124
|
+
common_year_month_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
125
|
+
days_this_month = Date.leap?(year) ? leap_year_month_days[month - 1] : common_year_month_days[month - 1]
|
126
|
+
if day > days_this_month
|
127
|
+
month += day / days_this_month
|
128
|
+
day = day % days_this_month
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
if month > 12
|
133
|
+
if month % 12 == 0
|
134
|
+
year += (month - 12) / 12
|
135
|
+
month = 12
|
136
|
+
else
|
137
|
+
year += month / 12
|
138
|
+
month = month % 12
|
139
|
+
end
|
140
|
+
end
|
111
141
|
|
112
|
-
|
113
|
-
warn "Time.to_minidate will be deprecated in version 0.7.0. Please use Chronic::MiniDate.from_time(time) instead"
|
114
|
-
Chronic::MiniDate.from_time(self)
|
142
|
+
Chronic.time_class.local(year, month, day, hour, minute, second)
|
115
143
|
end
|
116
144
|
|
117
145
|
end
|
data/lib/chronic/handler.rb
CHANGED
@@ -6,8 +6,8 @@ module Chronic
|
|
6
6
|
attr_reader :handler_method
|
7
7
|
|
8
8
|
# pattern - An Array of patterns to match tokens against.
|
9
|
-
# handler_method - A
|
10
|
-
# when
|
9
|
+
# handler_method - A Symbol representing the method to be invoked
|
10
|
+
# when a pattern matches.
|
11
11
|
def initialize(pattern, handler_method)
|
12
12
|
@pattern = pattern
|
13
13
|
@handler_method = handler_method
|
@@ -43,14 +43,14 @@ module Chronic
|
|
43
43
|
if definitions.key?(name.to_sym)
|
44
44
|
sub_handlers = definitions[name.to_sym]
|
45
45
|
else
|
46
|
-
raise
|
46
|
+
raise "Invalid subset #{name} specified"
|
47
47
|
end
|
48
48
|
|
49
49
|
sub_handlers.each do |sub_handler|
|
50
50
|
return true if sub_handler.match(tokens[token_index..tokens.size], definitions)
|
51
51
|
end
|
52
52
|
else
|
53
|
-
raise
|
53
|
+
raise "Invalid match type: #{element.class}"
|
54
54
|
end
|
55
55
|
end
|
56
56
|
|
@@ -58,13 +58,13 @@ module Chronic
|
|
58
58
|
return true
|
59
59
|
end
|
60
60
|
|
61
|
-
def invoke(type, tokens, options)
|
61
|
+
def invoke(type, tokens, parser, options)
|
62
62
|
if Chronic.debug
|
63
63
|
puts "-#{type}"
|
64
64
|
puts "Handler: #{@handler_method}"
|
65
65
|
end
|
66
66
|
|
67
|
-
|
67
|
+
parser.send(@handler_method, tokens, options)
|
68
68
|
end
|
69
69
|
|
70
70
|
# other - The other Handler object to compare.
|
@@ -85,4 +85,4 @@ module Chronic
|
|
85
85
|
end
|
86
86
|
|
87
87
|
end
|
88
|
-
end
|
88
|
+
end
|
data/lib/chronic/handlers.rb
CHANGED
@@ -4,7 +4,7 @@ module Chronic
|
|
4
4
|
|
5
5
|
# Handle month/day
|
6
6
|
def handle_m_d(month, day, time_tokens, options)
|
7
|
-
month.start =
|
7
|
+
month.start = self.now
|
8
8
|
span = month.this(options[:context])
|
9
9
|
year, month = span.begin.year, span.begin.month
|
10
10
|
day_start = Chronic.time_class.local(year, month, day)
|
@@ -17,7 +17,7 @@ module Chronic
|
|
17
17
|
month = tokens[0].get_tag(RepeaterMonthName)
|
18
18
|
day = tokens[1].get_tag(ScalarDay).type
|
19
19
|
|
20
|
-
return if month_overflow?(
|
20
|
+
return if month_overflow?(self.now.year, month.index, day)
|
21
21
|
|
22
22
|
handle_m_d(month, day, tokens[2..tokens.size], options)
|
23
23
|
end
|
@@ -34,7 +34,7 @@ module Chronic
|
|
34
34
|
token_range = 0..0
|
35
35
|
end
|
36
36
|
|
37
|
-
return if month_overflow?(
|
37
|
+
return if month_overflow?(self.now.year, month.index, day)
|
38
38
|
|
39
39
|
handle_m_d(month, day, tokens[token_range], options)
|
40
40
|
end
|
@@ -44,7 +44,7 @@ module Chronic
|
|
44
44
|
month = tokens[0].get_tag(RepeaterMonthName)
|
45
45
|
day = tokens[1].get_tag(OrdinalDay).type
|
46
46
|
|
47
|
-
return if month_overflow?(
|
47
|
+
return if month_overflow?(self.now.year, month.index, day)
|
48
48
|
|
49
49
|
handle_m_d(month, day, tokens[2..tokens.size], options)
|
50
50
|
end
|
@@ -61,7 +61,7 @@ module Chronic
|
|
61
61
|
month = tokens[1].get_tag(RepeaterMonthName)
|
62
62
|
day = tokens[0].get_tag(OrdinalDay).type
|
63
63
|
|
64
|
-
return if month_overflow?(
|
64
|
+
return if month_overflow?(self.now.year, month.index, day)
|
65
65
|
|
66
66
|
handle_m_d(month, day, tokens[2..tokens.size], options)
|
67
67
|
end
|
@@ -87,7 +87,7 @@ module Chronic
|
|
87
87
|
month = tokens[1].get_tag(RepeaterMonthName)
|
88
88
|
day = tokens[0].get_tag(ScalarDay).type
|
89
89
|
|
90
|
-
return if month_overflow?(
|
90
|
+
return if month_overflow?(self.now.year, month.index, day)
|
91
91
|
|
92
92
|
handle_m_d(month, day, tokens[2..tokens.size], options)
|
93
93
|
end
|
@@ -104,7 +104,7 @@ module Chronic
|
|
104
104
|
token_range = 0..0
|
105
105
|
end
|
106
106
|
|
107
|
-
return if month_overflow?(
|
107
|
+
return if month_overflow?(self.now.year, month.index, day)
|
108
108
|
|
109
109
|
handle_m_d(month, day, tokens[token_range], options)
|
110
110
|
end
|
@@ -229,13 +229,14 @@ module Chronic
|
|
229
229
|
def handle_sm_sd(tokens, options)
|
230
230
|
month = tokens[0].get_tag(ScalarMonth).type
|
231
231
|
day = tokens[1].get_tag(ScalarDay).type
|
232
|
-
year =
|
232
|
+
year = self.now.year
|
233
233
|
time_tokens = tokens.last(tokens.size - 2)
|
234
234
|
|
235
235
|
return if month_overflow?(year, month, day)
|
236
236
|
|
237
237
|
begin
|
238
238
|
day_start = Chronic.time_class.local(year, month, day)
|
239
|
+
day_start = Chronic.time_class.local(year + 1, month, day) if options[:context] == :future && day_start < now
|
239
240
|
day_or_time(day_start, time_tokens, options)
|
240
241
|
rescue ArgumentError
|
241
242
|
nil
|
@@ -274,14 +275,46 @@ module Chronic
|
|
274
275
|
def handle_rdn_rmn_od(tokens, options)
|
275
276
|
month = tokens[1].get_tag(RepeaterMonthName)
|
276
277
|
day = tokens[2].get_tag(OrdinalDay).type
|
277
|
-
|
278
|
+
time_tokens = tokens.last(tokens.size - 3)
|
279
|
+
year = self.now.year
|
278
280
|
|
279
281
|
return if month_overflow?(year, month.index, day)
|
280
282
|
|
281
283
|
begin
|
282
|
-
|
283
|
-
|
284
|
-
|
284
|
+
if time_tokens.empty?
|
285
|
+
start_time = Chronic.time_class.local(year, month.index, day)
|
286
|
+
end_time = time_with_rollover(year, month.index, day + 1)
|
287
|
+
Span.new(start_time, end_time)
|
288
|
+
else
|
289
|
+
day_start = Chronic.time_class.local(year, month.index, day)
|
290
|
+
day_or_time(day_start, time_tokens, options)
|
291
|
+
end
|
292
|
+
rescue ArgumentError
|
293
|
+
nil
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
# Handle RepeaterDayName OrdinalDay
|
298
|
+
def handle_rdn_od(tokens, options)
|
299
|
+
day = tokens[1].get_tag(OrdinalDay).type
|
300
|
+
time_tokens = tokens.last(tokens.size - 2)
|
301
|
+
year = self.now.year
|
302
|
+
month = self.now.month
|
303
|
+
if options[:context] == :future
|
304
|
+
self.now.day > day ? month += 1 : month
|
305
|
+
end
|
306
|
+
|
307
|
+
return if month_overflow?(year, month, day)
|
308
|
+
|
309
|
+
begin
|
310
|
+
if time_tokens.empty?
|
311
|
+
start_time = Chronic.time_class.local(year, month, day)
|
312
|
+
end_time = time_with_rollover(year, month, day + 1)
|
313
|
+
Span.new(start_time, end_time)
|
314
|
+
else
|
315
|
+
day_start = Chronic.time_class.local(year, month, day)
|
316
|
+
day_or_time(day_start, time_tokens, options)
|
317
|
+
end
|
285
318
|
rescue ArgumentError
|
286
319
|
nil
|
287
320
|
end
|
@@ -291,14 +324,20 @@ module Chronic
|
|
291
324
|
def handle_rdn_rmn_sd(tokens, options)
|
292
325
|
month = tokens[1].get_tag(RepeaterMonthName)
|
293
326
|
day = tokens[2].get_tag(ScalarDay).type
|
294
|
-
|
327
|
+
time_tokens = tokens.last(tokens.size - 3)
|
328
|
+
year = self.now.year
|
295
329
|
|
296
330
|
return if month_overflow?(year, month.index, day)
|
297
331
|
|
298
332
|
begin
|
299
|
-
|
300
|
-
|
301
|
-
|
333
|
+
if time_tokens.empty?
|
334
|
+
start_time = Chronic.time_class.local(year, month.index, day)
|
335
|
+
end_time = time_with_rollover(year, month.index, day + 1)
|
336
|
+
Span.new(start_time, end_time)
|
337
|
+
else
|
338
|
+
day_start = Chronic.time_class.local(year, month.index, day)
|
339
|
+
day_or_time(day_start, time_tokens, options)
|
340
|
+
end
|
302
341
|
rescue ArgumentError
|
303
342
|
nil
|
304
343
|
end
|
@@ -332,7 +371,8 @@ module Chronic
|
|
332
371
|
end_time = Chronic.time_class.local(year, month, day + 1, h, m, s)
|
333
372
|
else
|
334
373
|
time = Chronic.time_class.local(year, month, day)
|
335
|
-
|
374
|
+
day += 1 unless day >= 31
|
375
|
+
end_time = Chronic.time_class.local(year, month, day)
|
336
376
|
end
|
337
377
|
Span.new(time, end_time)
|
338
378
|
end
|
@@ -365,7 +405,7 @@ module Chronic
|
|
365
405
|
# Handle scalar/repeater/pointer
|
366
406
|
def handle_s_r_p(tokens, options)
|
367
407
|
repeater = tokens[1].get_tag(Repeater)
|
368
|
-
span = Span.new(
|
408
|
+
span = Span.new(self.now, self.now + 1)
|
369
409
|
|
370
410
|
handle_srp(tokens, span, options)
|
371
411
|
end
|
@@ -382,6 +422,13 @@ module Chronic
|
|
382
422
|
handle_srp(tokens, anchor_span, options)
|
383
423
|
end
|
384
424
|
|
425
|
+
def handle_s_r_a_s_r_p_a(tokens, options)
|
426
|
+
anchor_span = get_anchor(tokens[4..tokens.size - 1], options)
|
427
|
+
|
428
|
+
span = handle_srp(tokens[0..1]+tokens[4..6], anchor_span, options)
|
429
|
+
handle_srp(tokens[2..3]+tokens[4..6], span, options)
|
430
|
+
end
|
431
|
+
|
385
432
|
# narrows
|
386
433
|
|
387
434
|
# Handle oridinal repeaters
|
@@ -421,7 +468,7 @@ module Chronic
|
|
421
468
|
outer_span = Span.new(day_start, day_start + (24 * 60 * 60))
|
422
469
|
|
423
470
|
if !time_tokens.empty?
|
424
|
-
|
471
|
+
self.now = outer_span.begin
|
425
472
|
get_anchor(dealias_and_disambiguate_times(time_tokens, options), options)
|
426
473
|
else
|
427
474
|
outer_span
|
@@ -439,7 +486,7 @@ module Chronic
|
|
439
486
|
end
|
440
487
|
|
441
488
|
head = repeaters.shift
|
442
|
-
head.start =
|
489
|
+
head.start = self.now
|
443
490
|
|
444
491
|
case grabber.type
|
445
492
|
when :last
|
@@ -453,7 +500,7 @@ module Chronic
|
|
453
500
|
when :next
|
454
501
|
outer_span = head.next(:future)
|
455
502
|
else
|
456
|
-
raise
|
503
|
+
raise "Invalid grabber"
|
457
504
|
end
|
458
505
|
|
459
506
|
if Chronic.debug
|