gitlab-chronic 0.10.3
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 +7 -0
- data/.gitignore +8 -0
- data/.gitlab-ci.yml +14 -0
- data/.travis.yml +10 -0
- data/Gemfile +3 -0
- data/HISTORY.md +252 -0
- data/LICENSE +21 -0
- data/README.md +188 -0
- data/Rakefile +68 -0
- data/chronic.gemspec +25 -0
- data/lib/chronic.rb +155 -0
- data/lib/chronic/date.rb +81 -0
- data/lib/chronic/definition.rb +128 -0
- data/lib/chronic/dictionary.rb +36 -0
- data/lib/chronic/handler.rb +97 -0
- data/lib/chronic/handlers.rb +672 -0
- data/lib/chronic/mini_date.rb +38 -0
- data/lib/chronic/parser.rb +222 -0
- data/lib/chronic/repeaters/repeater_day.rb +54 -0
- data/lib/chronic/repeaters/repeater_day_name.rb +53 -0
- data/lib/chronic/repeaters/repeater_day_portion.rb +109 -0
- data/lib/chronic/repeaters/repeater_fortnight.rb +72 -0
- data/lib/chronic/repeaters/repeater_hour.rb +59 -0
- data/lib/chronic/repeaters/repeater_minute.rb +59 -0
- data/lib/chronic/repeaters/repeater_month.rb +80 -0
- data/lib/chronic/repeaters/repeater_month_name.rb +95 -0
- data/lib/chronic/repeaters/repeater_quarter.rb +59 -0
- data/lib/chronic/repeaters/repeater_quarter_name.rb +40 -0
- data/lib/chronic/repeaters/repeater_season.rb +111 -0
- data/lib/chronic/repeaters/repeater_season_name.rb +43 -0
- data/lib/chronic/repeaters/repeater_second.rb +43 -0
- data/lib/chronic/repeaters/repeater_time.rb +159 -0
- data/lib/chronic/repeaters/repeater_week.rb +76 -0
- data/lib/chronic/repeaters/repeater_weekday.rb +86 -0
- data/lib/chronic/repeaters/repeater_weekend.rb +67 -0
- data/lib/chronic/repeaters/repeater_year.rb +78 -0
- data/lib/chronic/season.rb +26 -0
- data/lib/chronic/span.rb +31 -0
- data/lib/chronic/tag.rb +89 -0
- data/lib/chronic/tags/grabber.rb +29 -0
- data/lib/chronic/tags/ordinal.rb +52 -0
- data/lib/chronic/tags/pointer.rb +28 -0
- data/lib/chronic/tags/repeater.rb +160 -0
- data/lib/chronic/tags/scalar.rb +89 -0
- data/lib/chronic/tags/separator.rb +123 -0
- data/lib/chronic/tags/sign.rb +35 -0
- data/lib/chronic/tags/time_zone.rb +32 -0
- data/lib/chronic/time.rb +40 -0
- data/lib/chronic/token.rb +61 -0
- data/lib/chronic/tokenizer.rb +38 -0
- data/lib/chronic/version.rb +3 -0
- data/test/helper.rb +12 -0
- data/test/test_chronic.rb +203 -0
- data/test/test_daylight_savings.rb +122 -0
- data/test/test_handler.rb +128 -0
- data/test/test_mini_date.rb +32 -0
- data/test/test_parsing.rb +1537 -0
- data/test/test_repeater_day_name.rb +51 -0
- data/test/test_repeater_day_portion.rb +254 -0
- data/test/test_repeater_fortnight.rb +62 -0
- data/test/test_repeater_hour.rb +68 -0
- data/test/test_repeater_minute.rb +34 -0
- data/test/test_repeater_month.rb +50 -0
- data/test/test_repeater_month_name.rb +56 -0
- data/test/test_repeater_quarter.rb +70 -0
- data/test/test_repeater_quarter_name.rb +198 -0
- data/test/test_repeater_season.rb +40 -0
- data/test/test_repeater_time.rb +88 -0
- data/test/test_repeater_week.rb +115 -0
- data/test/test_repeater_weekday.rb +55 -0
- data/test/test_repeater_weekend.rb +74 -0
- data/test/test_repeater_year.rb +69 -0
- data/test/test_span.rb +23 -0
- data/test/test_token.rb +31 -0
- metadata +215 -0
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'chronic/definition'
|
2
|
+
|
3
|
+
module Chronic
|
4
|
+
# A collection of definitions
|
5
|
+
class Dictionary
|
6
|
+
attr_reader :defined_items, :options
|
7
|
+
|
8
|
+
def initialize(options = {})
|
9
|
+
@options = options
|
10
|
+
@defined_items = []
|
11
|
+
end
|
12
|
+
|
13
|
+
# returns a hash of each word's Definitions
|
14
|
+
def definitions
|
15
|
+
defined_items.each_with_object({}) do |word, defs|
|
16
|
+
word_type = "#{word.capitalize.to_s + 'Definitions'}"
|
17
|
+
defs[word] = Chronic.const_get(word_type).new(options).definitions
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class SpanDictionary < Dictionary
|
23
|
+
# Collection of SpanDefinitions
|
24
|
+
def initialize(options = {})
|
25
|
+
super
|
26
|
+
@defined_items = [:time,:date,:anchor,:arrow,:narrow,:endian]
|
27
|
+
end
|
28
|
+
|
29
|
+
# returns the definitions of a specific subclass of SpanDefinitions
|
30
|
+
# SpanDefinition#definitions returns an Hash of Handler instances
|
31
|
+
# arguments should come in as symbols
|
32
|
+
def [](handler_type=:symbol)
|
33
|
+
definitions[handler_type]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module Chronic
|
2
|
+
class Handler
|
3
|
+
|
4
|
+
attr_reader :pattern
|
5
|
+
|
6
|
+
attr_reader :handler_method
|
7
|
+
|
8
|
+
# pattern - An Array of patterns to match tokens against.
|
9
|
+
# handler_method - A Symbol representing the method to be invoked
|
10
|
+
# when a pattern matches.
|
11
|
+
def initialize(pattern, handler_method)
|
12
|
+
@pattern = pattern
|
13
|
+
@handler_method = handler_method
|
14
|
+
end
|
15
|
+
|
16
|
+
# tokens - An Array of tokens to process.
|
17
|
+
# definitions - A Hash of definitions to check against.
|
18
|
+
#
|
19
|
+
# Returns true if a match is found.
|
20
|
+
def match(tokens, definitions)
|
21
|
+
token_index = 0
|
22
|
+
@pattern.each do |elements|
|
23
|
+
was_optional = false
|
24
|
+
elements = [elements] unless elements.is_a?(Array)
|
25
|
+
|
26
|
+
elements.each_index do |i|
|
27
|
+
name = elements[i].to_s
|
28
|
+
optional = name[-1, 1] == '?'
|
29
|
+
name = name.chop if optional
|
30
|
+
|
31
|
+
case elements[i]
|
32
|
+
when Symbol
|
33
|
+
if tags_match?(name, tokens, token_index)
|
34
|
+
token_index += 1
|
35
|
+
break
|
36
|
+
else
|
37
|
+
if optional
|
38
|
+
was_optional = true
|
39
|
+
next
|
40
|
+
elsif i + 1 < elements.count
|
41
|
+
next
|
42
|
+
else
|
43
|
+
return false unless was_optional
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
when String
|
48
|
+
return true if optional && token_index == tokens.size
|
49
|
+
|
50
|
+
if definitions.key?(name.to_sym)
|
51
|
+
sub_handlers = definitions[name.to_sym]
|
52
|
+
else
|
53
|
+
raise "Invalid subset #{name} specified"
|
54
|
+
end
|
55
|
+
|
56
|
+
sub_handlers.each do |sub_handler|
|
57
|
+
return true if sub_handler.match(tokens[token_index..tokens.size], definitions)
|
58
|
+
end
|
59
|
+
else
|
60
|
+
raise "Invalid match type: #{elements[i].class}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
return false if token_index != tokens.size
|
67
|
+
return true
|
68
|
+
end
|
69
|
+
|
70
|
+
def invoke(type, tokens, parser, options)
|
71
|
+
if Chronic.debug
|
72
|
+
puts "-#{type}"
|
73
|
+
puts "Handler: #{@handler_method}"
|
74
|
+
end
|
75
|
+
|
76
|
+
parser.send(@handler_method, tokens, options)
|
77
|
+
end
|
78
|
+
|
79
|
+
# other - The other Handler object to compare.
|
80
|
+
#
|
81
|
+
# Returns true if these Handlers match.
|
82
|
+
def ==(other)
|
83
|
+
@pattern == other.pattern
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def tags_match?(name, tokens, token_index)
|
89
|
+
klass = Chronic.const_get(name.to_s.gsub(/(?:^|_)(.)/) { $1.upcase })
|
90
|
+
|
91
|
+
if tokens[token_index]
|
92
|
+
!tokens[token_index].tags.select { |o| o.kind_of?(klass) }.empty?
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,672 @@
|
|
1
|
+
module Chronic
|
2
|
+
module Handlers
|
3
|
+
module_function
|
4
|
+
|
5
|
+
# Handle month/day
|
6
|
+
def handle_m_d(month, day, time_tokens, options)
|
7
|
+
month.start = self.now
|
8
|
+
span = month.this(options[:context])
|
9
|
+
year, month = span.begin.year, span.begin.month
|
10
|
+
day_start = Chronic.time_class.local(year, month, day)
|
11
|
+
day_start = Chronic.time_class.local(year + 1, month, day) if options[:context] == :future && day_start < now
|
12
|
+
|
13
|
+
day_or_time(day_start, time_tokens, options)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Handle repeater-month-name/scalar-day
|
17
|
+
def handle_rmn_sd(tokens, options)
|
18
|
+
month = tokens[0].get_tag(RepeaterMonthName)
|
19
|
+
day = tokens[1].get_tag(ScalarDay).type
|
20
|
+
|
21
|
+
return if month_overflow?(self.now.year, month.index, day)
|
22
|
+
|
23
|
+
handle_m_d(month, day, tokens[2..tokens.size], options)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Handle repeater-month-name/scalar-day with separator-on
|
27
|
+
def handle_rmn_sd_on(tokens, options)
|
28
|
+
if tokens.size > 3
|
29
|
+
month = tokens[2].get_tag(RepeaterMonthName)
|
30
|
+
day = tokens[3].get_tag(ScalarDay).type
|
31
|
+
token_range = 0..1
|
32
|
+
else
|
33
|
+
month = tokens[1].get_tag(RepeaterMonthName)
|
34
|
+
day = tokens[2].get_tag(ScalarDay).type
|
35
|
+
token_range = 0..0
|
36
|
+
end
|
37
|
+
|
38
|
+
return if month_overflow?(self.now.year, month.index, day)
|
39
|
+
|
40
|
+
handle_m_d(month, day, tokens[token_range], options)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Handle repeater-month-name/ordinal-day
|
44
|
+
def handle_rmn_od(tokens, options)
|
45
|
+
month = tokens[0].get_tag(RepeaterMonthName)
|
46
|
+
day = tokens[1].get_tag(OrdinalDay).type
|
47
|
+
|
48
|
+
return if month_overflow?(self.now.year, month.index, day)
|
49
|
+
|
50
|
+
handle_m_d(month, day, tokens[2..tokens.size], options)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Handle ordinal this month
|
54
|
+
def handle_od_rm(tokens, options)
|
55
|
+
day = tokens[0].get_tag(OrdinalDay).type
|
56
|
+
month = tokens[2].get_tag(RepeaterMonth)
|
57
|
+
handle_m_d(month, day, tokens[3..tokens.size], options)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Handle ordinal-day/repeater-month-name
|
61
|
+
def handle_od_rmn(tokens, options)
|
62
|
+
month = tokens[1].get_tag(RepeaterMonthName)
|
63
|
+
day = tokens[0].get_tag(OrdinalDay).type
|
64
|
+
|
65
|
+
return if month_overflow?(self.now.year, month.index, day)
|
66
|
+
|
67
|
+
handle_m_d(month, day, tokens[2..tokens.size], options)
|
68
|
+
end
|
69
|
+
|
70
|
+
def handle_sy_rmn_od(tokens, options)
|
71
|
+
year = tokens[0].get_tag(ScalarYear).type
|
72
|
+
month = tokens[1].get_tag(RepeaterMonthName).index
|
73
|
+
day = tokens[2].get_tag(OrdinalDay).type
|
74
|
+
time_tokens = tokens.last(tokens.size - 3)
|
75
|
+
|
76
|
+
return if month_overflow?(year, month, day)
|
77
|
+
|
78
|
+
begin
|
79
|
+
day_start = Chronic.time_class.local(year, month, day)
|
80
|
+
day_or_time(day_start, time_tokens, options)
|
81
|
+
rescue ArgumentError
|
82
|
+
nil
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Handle scalar-day/repeater-month-name
|
87
|
+
def handle_sd_rmn(tokens, options)
|
88
|
+
month = tokens[1].get_tag(RepeaterMonthName)
|
89
|
+
day = tokens[0].get_tag(ScalarDay).type
|
90
|
+
|
91
|
+
return if month_overflow?(self.now.year, month.index, day)
|
92
|
+
|
93
|
+
handle_m_d(month, day, tokens[2..tokens.size], options)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Handle repeater-month-name/ordinal-day with separator-on
|
97
|
+
def handle_rmn_od_on(tokens, options)
|
98
|
+
if tokens.size > 3
|
99
|
+
month = tokens[2].get_tag(RepeaterMonthName)
|
100
|
+
day = tokens[3].get_tag(OrdinalDay).type
|
101
|
+
token_range = 0..1
|
102
|
+
else
|
103
|
+
month = tokens[1].get_tag(RepeaterMonthName)
|
104
|
+
day = tokens[2].get_tag(OrdinalDay).type
|
105
|
+
token_range = 0..0
|
106
|
+
end
|
107
|
+
|
108
|
+
return if month_overflow?(self.now.year, month.index, day)
|
109
|
+
|
110
|
+
handle_m_d(month, day, tokens[token_range], options)
|
111
|
+
end
|
112
|
+
|
113
|
+
# Handle scalar-year/repeater-quarter-name
|
114
|
+
def handle_sy_rqn(tokens, options)
|
115
|
+
handle_rqn_sy(tokens[0..1].reverse, options)
|
116
|
+
end
|
117
|
+
|
118
|
+
# Handle repeater-quarter-name/scalar-year
|
119
|
+
def handle_rqn_sy(tokens, options)
|
120
|
+
year = tokens[1].get_tag(ScalarYear).type
|
121
|
+
quarter_tag = tokens[0].get_tag(RepeaterQuarterName)
|
122
|
+
quarter_tag.start = Chronic.construct(year)
|
123
|
+
quarter_tag.this(:none)
|
124
|
+
end
|
125
|
+
|
126
|
+
# Handle repeater-month-name/scalar-year
|
127
|
+
def handle_rmn_sy(tokens, options)
|
128
|
+
month = tokens[0].get_tag(RepeaterMonthName).index
|
129
|
+
year = tokens[1].get_tag(ScalarYear).type
|
130
|
+
|
131
|
+
if month == 12
|
132
|
+
next_month_year = year + 1
|
133
|
+
next_month_month = 1
|
134
|
+
else
|
135
|
+
next_month_year = year
|
136
|
+
next_month_month = month + 1
|
137
|
+
end
|
138
|
+
|
139
|
+
begin
|
140
|
+
end_time = Chronic.time_class.local(next_month_year, next_month_month)
|
141
|
+
Span.new(Chronic.time_class.local(year, month), end_time)
|
142
|
+
rescue ArgumentError
|
143
|
+
nil
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Handle generic timestamp (ruby 1.8)
|
148
|
+
def handle_generic(tokens, options)
|
149
|
+
t = Chronic.time_class.parse(options[:text])
|
150
|
+
Span.new(t, t + 1)
|
151
|
+
rescue ArgumentError => e
|
152
|
+
raise e unless e.message =~ /out of range/
|
153
|
+
end
|
154
|
+
|
155
|
+
# Handle repeater-month-name/scalar-day/scalar-year
|
156
|
+
def handle_rmn_sd_sy(tokens, options)
|
157
|
+
month = tokens[0].get_tag(RepeaterMonthName).index
|
158
|
+
day = tokens[1].get_tag(ScalarDay).type
|
159
|
+
year = tokens[2].get_tag(ScalarYear).type
|
160
|
+
time_tokens = tokens.last(tokens.size - 3)
|
161
|
+
|
162
|
+
return if month_overflow?(year, month, day)
|
163
|
+
|
164
|
+
begin
|
165
|
+
day_start = Chronic.time_class.local(year, month, day)
|
166
|
+
day_or_time(day_start, time_tokens, options)
|
167
|
+
rescue ArgumentError
|
168
|
+
nil
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# Handle repeater-month-name/ordinal-day/scalar-year
|
173
|
+
def handle_rmn_od_sy(tokens, options)
|
174
|
+
month = tokens[0].get_tag(RepeaterMonthName).index
|
175
|
+
day = tokens[1].get_tag(OrdinalDay).type
|
176
|
+
year = tokens[2].get_tag(ScalarYear).type
|
177
|
+
time_tokens = tokens.last(tokens.size - 3)
|
178
|
+
|
179
|
+
return if month_overflow?(year, month, day)
|
180
|
+
|
181
|
+
begin
|
182
|
+
day_start = Chronic.time_class.local(year, month, day)
|
183
|
+
day_or_time(day_start, time_tokens, options)
|
184
|
+
rescue ArgumentError
|
185
|
+
nil
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
# Handle oridinal-day/repeater-month-name/scalar-year
|
190
|
+
def handle_od_rmn_sy(tokens, options)
|
191
|
+
day = tokens[0].get_tag(OrdinalDay).type
|
192
|
+
month = tokens[1].get_tag(RepeaterMonthName).index
|
193
|
+
year = tokens[2].get_tag(ScalarYear).type
|
194
|
+
time_tokens = tokens.last(tokens.size - 3)
|
195
|
+
|
196
|
+
return if month_overflow?(year, month, day)
|
197
|
+
|
198
|
+
begin
|
199
|
+
day_start = Chronic.time_class.local(year, month, day)
|
200
|
+
day_or_time(day_start, time_tokens, options)
|
201
|
+
rescue ArgumentError
|
202
|
+
nil
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
# Handle scalar-day/repeater-month-name/scalar-year
|
207
|
+
def handle_sd_rmn_sy(tokens, options)
|
208
|
+
new_tokens = [tokens[1], tokens[0], tokens[2]]
|
209
|
+
time_tokens = tokens.last(tokens.size - 3)
|
210
|
+
handle_rmn_sd_sy(new_tokens + time_tokens, options)
|
211
|
+
end
|
212
|
+
|
213
|
+
# Handle scalar-month/scalar-day/scalar-year (endian middle)
|
214
|
+
def handle_sm_sd_sy(tokens, options)
|
215
|
+
month = tokens[0].get_tag(ScalarMonth).type
|
216
|
+
day = tokens[1].get_tag(ScalarDay).type
|
217
|
+
year = tokens[2].get_tag(ScalarYear).type
|
218
|
+
time_tokens = tokens.last(tokens.size - 3)
|
219
|
+
|
220
|
+
return if month_overflow?(year, month, day)
|
221
|
+
|
222
|
+
begin
|
223
|
+
day_start = Chronic.time_class.local(year, month, day)
|
224
|
+
day_or_time(day_start, time_tokens, options)
|
225
|
+
rescue ArgumentError
|
226
|
+
nil
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
# Handle scalar-day/scalar-month/scalar-year (endian little)
|
231
|
+
def handle_sd_sm_sy(tokens, options)
|
232
|
+
new_tokens = [tokens[1], tokens[0], tokens[2]]
|
233
|
+
time_tokens = tokens.last(tokens.size - 3)
|
234
|
+
handle_sm_sd_sy(new_tokens + time_tokens, options)
|
235
|
+
end
|
236
|
+
|
237
|
+
# Handle scalar-year/scalar-month/scalar-day
|
238
|
+
def handle_sy_sm_sd(tokens, options)
|
239
|
+
new_tokens = [tokens[1], tokens[2], tokens[0]]
|
240
|
+
time_tokens = tokens.last(tokens.size - 3)
|
241
|
+
handle_sm_sd_sy(new_tokens + time_tokens, options)
|
242
|
+
end
|
243
|
+
|
244
|
+
# Handle scalar-month/scalar-day
|
245
|
+
def handle_sm_sd(tokens, options)
|
246
|
+
month = tokens[0].get_tag(ScalarMonth).type
|
247
|
+
day = tokens[1].get_tag(ScalarDay).type
|
248
|
+
year = self.now.year
|
249
|
+
time_tokens = tokens.last(tokens.size - 2)
|
250
|
+
|
251
|
+
return if month_overflow?(year, month, day)
|
252
|
+
|
253
|
+
begin
|
254
|
+
day_start = Chronic.time_class.local(year, month, day)
|
255
|
+
|
256
|
+
if options[:context] == :future && day_start < now
|
257
|
+
day_start = Chronic.time_class.local(year + 1, month, day)
|
258
|
+
elsif options[:context] == :past && day_start > now
|
259
|
+
day_start = Chronic.time_class.local(year - 1, month, day)
|
260
|
+
end
|
261
|
+
|
262
|
+
day_or_time(day_start, time_tokens, options)
|
263
|
+
rescue ArgumentError
|
264
|
+
nil
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
# Handle scalar-day/scalar-month
|
269
|
+
def handle_sd_sm(tokens, options)
|
270
|
+
new_tokens = [tokens[1], tokens[0]]
|
271
|
+
time_tokens = tokens.last(tokens.size - 2)
|
272
|
+
handle_sm_sd(new_tokens + time_tokens, options)
|
273
|
+
end
|
274
|
+
|
275
|
+
def handle_year_and_month(year, month)
|
276
|
+
if month == 12
|
277
|
+
next_month_year = year + 1
|
278
|
+
next_month_month = 1
|
279
|
+
else
|
280
|
+
next_month_year = year
|
281
|
+
next_month_month = month + 1
|
282
|
+
end
|
283
|
+
|
284
|
+
begin
|
285
|
+
end_time = Chronic.time_class.local(next_month_year, next_month_month)
|
286
|
+
Span.new(Chronic.time_class.local(year, month), end_time)
|
287
|
+
rescue ArgumentError
|
288
|
+
nil
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
# Handle scalar-month/scalar-year
|
293
|
+
def handle_sm_sy(tokens, options)
|
294
|
+
month = tokens[0].get_tag(ScalarMonth).type
|
295
|
+
year = tokens[1].get_tag(ScalarYear).type
|
296
|
+
handle_year_and_month(year, month)
|
297
|
+
end
|
298
|
+
|
299
|
+
# Handle scalar-year/scalar-month
|
300
|
+
def handle_sy_sm(tokens, options)
|
301
|
+
year = tokens[0].get_tag(ScalarYear).type
|
302
|
+
month = tokens[1].get_tag(ScalarMonth).type
|
303
|
+
handle_year_and_month(year, month)
|
304
|
+
end
|
305
|
+
|
306
|
+
# Handle RepeaterDayName RepeaterMonthName OrdinalDay
|
307
|
+
def handle_rdn_rmn_od(tokens, options)
|
308
|
+
month = tokens[1].get_tag(RepeaterMonthName)
|
309
|
+
day = tokens[2].get_tag(OrdinalDay).type
|
310
|
+
time_tokens = tokens.last(tokens.size - 3)
|
311
|
+
year = self.now.year
|
312
|
+
|
313
|
+
return if month_overflow?(year, month.index, day)
|
314
|
+
|
315
|
+
begin
|
316
|
+
if time_tokens.empty?
|
317
|
+
start_time = Chronic.time_class.local(year, month.index, day)
|
318
|
+
end_time = time_with_rollover(year, month.index, day + 1)
|
319
|
+
Span.new(start_time, end_time)
|
320
|
+
else
|
321
|
+
day_start = Chronic.time_class.local(year, month.index, day)
|
322
|
+
day_or_time(day_start, time_tokens, options)
|
323
|
+
end
|
324
|
+
rescue ArgumentError
|
325
|
+
nil
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
# Handle RepeaterDayName RepeaterMonthName OrdinalDay ScalarYear
|
330
|
+
def handle_rdn_rmn_od_sy(tokens, options)
|
331
|
+
month = tokens[1].get_tag(RepeaterMonthName)
|
332
|
+
day = tokens[2].get_tag(OrdinalDay).type
|
333
|
+
year = tokens[3].get_tag(ScalarYear).type
|
334
|
+
|
335
|
+
return if month_overflow?(year, month.index, day)
|
336
|
+
|
337
|
+
begin
|
338
|
+
start_time = Chronic.time_class.local(year, month.index, day)
|
339
|
+
end_time = time_with_rollover(year, month.index, day + 1)
|
340
|
+
Span.new(start_time, end_time)
|
341
|
+
rescue ArgumentError
|
342
|
+
nil
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
# Handle RepeaterDayName OrdinalDay
|
347
|
+
def handle_rdn_od(tokens, options)
|
348
|
+
day = tokens[1].get_tag(OrdinalDay).type
|
349
|
+
time_tokens = tokens.last(tokens.size - 2)
|
350
|
+
year = self.now.year
|
351
|
+
month = self.now.month
|
352
|
+
if options[:context] == :future
|
353
|
+
self.now.day > day ? month += 1 : month
|
354
|
+
end
|
355
|
+
|
356
|
+
return if month_overflow?(year, month, day)
|
357
|
+
|
358
|
+
begin
|
359
|
+
if time_tokens.empty?
|
360
|
+
start_time = Chronic.time_class.local(year, month, day)
|
361
|
+
end_time = time_with_rollover(year, month, day + 1)
|
362
|
+
Span.new(start_time, end_time)
|
363
|
+
else
|
364
|
+
day_start = Chronic.time_class.local(year, month, day)
|
365
|
+
day_or_time(day_start, time_tokens, options)
|
366
|
+
end
|
367
|
+
rescue ArgumentError
|
368
|
+
nil
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
# Handle RepeaterDayName RepeaterMonthName ScalarDay
|
373
|
+
def handle_rdn_rmn_sd(tokens, options)
|
374
|
+
month = tokens[1].get_tag(RepeaterMonthName)
|
375
|
+
day = tokens[2].get_tag(ScalarDay).type
|
376
|
+
time_tokens = tokens.last(tokens.size - 3)
|
377
|
+
year = self.now.year
|
378
|
+
|
379
|
+
return if month_overflow?(year, month.index, day)
|
380
|
+
|
381
|
+
begin
|
382
|
+
if time_tokens.empty?
|
383
|
+
start_time = Chronic.time_class.local(year, month.index, day)
|
384
|
+
end_time = time_with_rollover(year, month.index, day + 1)
|
385
|
+
Span.new(start_time, end_time)
|
386
|
+
else
|
387
|
+
day_start = Chronic.time_class.local(year, month.index, day)
|
388
|
+
day_or_time(day_start, time_tokens, options)
|
389
|
+
end
|
390
|
+
rescue ArgumentError
|
391
|
+
nil
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
# Handle RepeaterDayName RepeaterMonthName ScalarDay ScalarYear
|
396
|
+
def handle_rdn_rmn_sd_sy(tokens, options)
|
397
|
+
month = tokens[1].get_tag(RepeaterMonthName)
|
398
|
+
day = tokens[2].get_tag(ScalarDay).type
|
399
|
+
year = tokens[3].get_tag(ScalarYear).type
|
400
|
+
|
401
|
+
return if month_overflow?(year, month.index, day)
|
402
|
+
|
403
|
+
begin
|
404
|
+
start_time = Chronic.time_class.local(year, month.index, day)
|
405
|
+
end_time = time_with_rollover(year, month.index, day + 1)
|
406
|
+
Span.new(start_time, end_time)
|
407
|
+
rescue ArgumentError
|
408
|
+
nil
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
def handle_sm_rmn_sy(tokens, options)
|
413
|
+
day = tokens[0].get_tag(ScalarDay).type
|
414
|
+
month = tokens[1].get_tag(RepeaterMonthName).index
|
415
|
+
year = tokens[2].get_tag(ScalarYear).type
|
416
|
+
if tokens.size > 3
|
417
|
+
time = get_anchor([tokens.last], options).begin
|
418
|
+
h, m, s = time.hour, time.min, time.sec
|
419
|
+
time = Chronic.time_class.local(year, month, day, h, m, s)
|
420
|
+
end_time = Chronic.time_class.local(year, month, day + 1, h, m, s)
|
421
|
+
else
|
422
|
+
time = Chronic.time_class.local(year, month, day)
|
423
|
+
day += 1 unless day >= 31
|
424
|
+
end_time = Chronic.time_class.local(year, month, day)
|
425
|
+
end
|
426
|
+
Span.new(time, end_time)
|
427
|
+
end
|
428
|
+
|
429
|
+
# anchors
|
430
|
+
|
431
|
+
# Handle repeaters
|
432
|
+
def handle_r(tokens, options)
|
433
|
+
dd_tokens = dealias_and_disambiguate_times(tokens, options)
|
434
|
+
get_anchor(dd_tokens, options)
|
435
|
+
end
|
436
|
+
|
437
|
+
# Handle repeater/grabber/repeater
|
438
|
+
def handle_r_g_r(tokens, options)
|
439
|
+
new_tokens = [tokens[1], tokens[0], tokens[2]]
|
440
|
+
handle_r(new_tokens, options)
|
441
|
+
end
|
442
|
+
|
443
|
+
# arrows
|
444
|
+
|
445
|
+
# Handle scalar/repeater/pointer helper
|
446
|
+
def handle_srp(tokens, span, options)
|
447
|
+
distance = tokens[0].get_tag(Scalar).type
|
448
|
+
repeater = tokens[1].get_tag(Repeater)
|
449
|
+
pointer = tokens[2].get_tag(Pointer).type
|
450
|
+
|
451
|
+
repeater.offset(span, distance, pointer) if repeater.respond_to?(:offset)
|
452
|
+
end
|
453
|
+
|
454
|
+
# Handle scalar/repeater/pointer
|
455
|
+
def handle_s_r_p(tokens, options)
|
456
|
+
span = Span.new(self.now, self.now + 1)
|
457
|
+
|
458
|
+
handle_srp(tokens, span, options)
|
459
|
+
end
|
460
|
+
|
461
|
+
# Handle pointer/scalar/repeater
|
462
|
+
def handle_p_s_r(tokens, options)
|
463
|
+
new_tokens = [tokens[1], tokens[2], tokens[0]]
|
464
|
+
handle_s_r_p(new_tokens, options)
|
465
|
+
end
|
466
|
+
|
467
|
+
# Handle scalar/repeater/pointer/anchor
|
468
|
+
def handle_s_r_p_a(tokens, options)
|
469
|
+
anchor_span = get_anchor(tokens[3..tokens.size - 1], options)
|
470
|
+
handle_srp(tokens, anchor_span, options)
|
471
|
+
end
|
472
|
+
|
473
|
+
# Handle repeater/scalar/repeater/pointer
|
474
|
+
def handle_rmn_s_r_p(tokens, options)
|
475
|
+
handle_s_r_p_a(tokens[1..3] + tokens[0..0], options)
|
476
|
+
end
|
477
|
+
|
478
|
+
def handle_s_r_a_s_r_p_a(tokens, options)
|
479
|
+
anchor_span = get_anchor(tokens[4..tokens.size - 1], options)
|
480
|
+
|
481
|
+
span = handle_srp(tokens[0..1]+tokens[4..6], anchor_span, options)
|
482
|
+
handle_srp(tokens[2..3]+tokens[4..6], span, options)
|
483
|
+
end
|
484
|
+
|
485
|
+
# narrows
|
486
|
+
|
487
|
+
# Handle oridinal repeaters
|
488
|
+
def handle_orr(tokens, outer_span, options)
|
489
|
+
repeater = tokens[1].get_tag(Repeater)
|
490
|
+
repeater.start = outer_span.begin - 1
|
491
|
+
ordinal = tokens[0].get_tag(Ordinal).type
|
492
|
+
span = nil
|
493
|
+
|
494
|
+
ordinal.times do
|
495
|
+
span = repeater.next(:future)
|
496
|
+
|
497
|
+
if span.begin >= outer_span.end
|
498
|
+
span = nil
|
499
|
+
break
|
500
|
+
end
|
501
|
+
end
|
502
|
+
|
503
|
+
span
|
504
|
+
end
|
505
|
+
|
506
|
+
# Handle ordinal/repeater/separator/repeater
|
507
|
+
def handle_o_r_s_r(tokens, options)
|
508
|
+
outer_span = get_anchor([tokens[3]], options)
|
509
|
+
handle_orr(tokens[0..1], outer_span, options)
|
510
|
+
end
|
511
|
+
|
512
|
+
# Handle ordinal/repeater/grabber/repeater
|
513
|
+
def handle_o_r_g_r(tokens, options)
|
514
|
+
outer_span = get_anchor(tokens[2..3], options)
|
515
|
+
handle_orr(tokens[0..1], outer_span, options)
|
516
|
+
end
|
517
|
+
|
518
|
+
# support methods
|
519
|
+
|
520
|
+
def day_or_time(day_start, time_tokens, options)
|
521
|
+
outer_span = Span.new(day_start, day_start + (24 * 60 * 60))
|
522
|
+
|
523
|
+
unless time_tokens.empty?
|
524
|
+
self.now = outer_span.begin
|
525
|
+
get_anchor(dealias_and_disambiguate_times(time_tokens, options), options.merge(:context => :future))
|
526
|
+
else
|
527
|
+
outer_span
|
528
|
+
end
|
529
|
+
end
|
530
|
+
|
531
|
+
def get_anchor(tokens, options)
|
532
|
+
grabber = Grabber.new(:this)
|
533
|
+
pointer = :future
|
534
|
+
repeaters = get_repeaters(tokens)
|
535
|
+
repeaters.size.times { tokens.pop }
|
536
|
+
|
537
|
+
if tokens.first && tokens.first.get_tag(Grabber)
|
538
|
+
grabber = tokens.shift.get_tag(Grabber)
|
539
|
+
end
|
540
|
+
|
541
|
+
head = repeaters.shift
|
542
|
+
head.start = self.now
|
543
|
+
|
544
|
+
case grabber.type
|
545
|
+
when :last
|
546
|
+
outer_span = head.next(:past)
|
547
|
+
when :this
|
548
|
+
if options[:context] != :past and repeaters.size > 0
|
549
|
+
outer_span = head.this(:none)
|
550
|
+
else
|
551
|
+
outer_span = head.this(options[:context])
|
552
|
+
end
|
553
|
+
when :next
|
554
|
+
outer_span = head.next(:future)
|
555
|
+
else
|
556
|
+
raise 'Invalid grabber'
|
557
|
+
end
|
558
|
+
|
559
|
+
if Chronic.debug
|
560
|
+
puts "Handler-class: #{head.class}"
|
561
|
+
puts "--#{outer_span}"
|
562
|
+
end
|
563
|
+
|
564
|
+
find_within(repeaters, outer_span, pointer)
|
565
|
+
end
|
566
|
+
|
567
|
+
def get_repeaters(tokens)
|
568
|
+
tokens.map { |token| token.get_tag(Repeater) }.compact.sort.reverse
|
569
|
+
end
|
570
|
+
|
571
|
+
def month_overflow?(year, month, day)
|
572
|
+
if ::Date.leap?(year)
|
573
|
+
day > RepeaterMonth::MONTH_DAYS_LEAP[month - 1]
|
574
|
+
else
|
575
|
+
day > RepeaterMonth::MONTH_DAYS[month - 1]
|
576
|
+
end
|
577
|
+
rescue ArgumentError
|
578
|
+
false
|
579
|
+
end
|
580
|
+
|
581
|
+
# Recursively finds repeaters within other repeaters.
|
582
|
+
# Returns a Span representing the innermost time span
|
583
|
+
# or nil if no repeater union could be found
|
584
|
+
def find_within(tags, span, pointer)
|
585
|
+
puts "--#{span}" if Chronic.debug
|
586
|
+
return span if tags.empty?
|
587
|
+
|
588
|
+
head = tags.shift
|
589
|
+
head.start = (pointer == :future ? span.begin : span.end)
|
590
|
+
h = head.this(:none)
|
591
|
+
|
592
|
+
if span.cover?(h.begin) || span.cover?(h.end)
|
593
|
+
find_within(tags, h, pointer)
|
594
|
+
end
|
595
|
+
end
|
596
|
+
|
597
|
+
def time_with_rollover(year, month, day)
|
598
|
+
date_parts =
|
599
|
+
if month_overflow?(year, month, day)
|
600
|
+
if month == 12
|
601
|
+
[year + 1, 1, 1]
|
602
|
+
else
|
603
|
+
[year, month + 1, 1]
|
604
|
+
end
|
605
|
+
else
|
606
|
+
[year, month, day]
|
607
|
+
end
|
608
|
+
Chronic.time_class.local(*date_parts)
|
609
|
+
end
|
610
|
+
|
611
|
+
def dealias_and_disambiguate_times(tokens, options)
|
612
|
+
# handle aliases of am/pm
|
613
|
+
# 5:00 in the morning -> 5:00 am
|
614
|
+
# 7:00 in the evening -> 7:00 pm
|
615
|
+
|
616
|
+
day_portion_index = nil
|
617
|
+
tokens.each_with_index do |t, i|
|
618
|
+
if t.get_tag(RepeaterDayPortion)
|
619
|
+
day_portion_index = i
|
620
|
+
break
|
621
|
+
end
|
622
|
+
end
|
623
|
+
|
624
|
+
time_index = nil
|
625
|
+
tokens.each_with_index do |t, i|
|
626
|
+
if t.get_tag(RepeaterTime)
|
627
|
+
time_index = i
|
628
|
+
break
|
629
|
+
end
|
630
|
+
end
|
631
|
+
|
632
|
+
if day_portion_index && time_index
|
633
|
+
t1 = tokens[day_portion_index]
|
634
|
+
t1tag = t1.get_tag(RepeaterDayPortion)
|
635
|
+
|
636
|
+
case t1tag.type
|
637
|
+
when :morning
|
638
|
+
puts '--morning->am' if Chronic.debug
|
639
|
+
t1.untag(RepeaterDayPortion)
|
640
|
+
t1.tag(RepeaterDayPortion.new(:am))
|
641
|
+
when :afternoon, :evening, :night
|
642
|
+
puts "--#{t1tag.type}->pm" if Chronic.debug
|
643
|
+
t1.untag(RepeaterDayPortion)
|
644
|
+
t1.tag(RepeaterDayPortion.new(:pm))
|
645
|
+
end
|
646
|
+
end
|
647
|
+
|
648
|
+
# handle ambiguous times if :ambiguous_time_range is specified
|
649
|
+
if options[:ambiguous_time_range] != :none
|
650
|
+
ambiguous_tokens = []
|
651
|
+
|
652
|
+
tokens.each_with_index do |token, i|
|
653
|
+
ambiguous_tokens << token
|
654
|
+
next_token = tokens[i + 1]
|
655
|
+
|
656
|
+
if token.get_tag(RepeaterTime) && token.get_tag(RepeaterTime).type.ambiguous? && (!next_token || !next_token.get_tag(RepeaterDayPortion))
|
657
|
+
distoken = Token.new('disambiguator')
|
658
|
+
|
659
|
+
distoken.tag(RepeaterDayPortion.new(options[:ambiguous_time_range]))
|
660
|
+
ambiguous_tokens << distoken
|
661
|
+
end
|
662
|
+
end
|
663
|
+
|
664
|
+
tokens = ambiguous_tokens
|
665
|
+
end
|
666
|
+
|
667
|
+
tokens
|
668
|
+
end
|
669
|
+
|
670
|
+
end
|
671
|
+
|
672
|
+
end
|