chronic 0.1.5 → 0.6.5
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/.gemtest +0 -0
- data/.gitignore +5 -0
- data/.yardopts +3 -0
- data/HISTORY.md +180 -0
- data/LICENSE +21 -0
- data/README.md +176 -0
- data/Rakefile +39 -32
- data/chronic.gemspec +17 -0
- data/lib/chronic/chronic.rb +294 -208
- data/lib/chronic/grabber.rb +22 -17
- data/lib/chronic/handler.rb +90 -0
- data/lib/chronic/handlers.rb +365 -278
- data/lib/chronic/mini_date.rb +38 -0
- data/lib/chronic/numerizer.rb +121 -0
- data/lib/chronic/ordinal.rb +23 -19
- data/lib/chronic/pointer.rb +20 -17
- data/lib/chronic/repeater.rb +126 -105
- data/lib/chronic/repeaters/repeater_day.rb +49 -43
- data/lib/chronic/repeaters/repeater_day_name.rb +48 -42
- data/lib/chronic/repeaters/repeater_day_portion.rb +81 -80
- data/lib/chronic/repeaters/repeater_fortnight.rb +59 -53
- data/lib/chronic/repeaters/repeater_hour.rb +49 -43
- data/lib/chronic/repeaters/repeater_minute.rb +55 -39
- data/lib/chronic/repeaters/repeater_month.rb +77 -55
- data/lib/chronic/repeaters/repeater_month_name.rb +83 -82
- data/lib/chronic/repeaters/repeater_season.rb +107 -21
- data/lib/chronic/repeaters/repeater_season_name.rb +41 -22
- data/lib/chronic/repeaters/repeater_second.rb +39 -33
- data/lib/chronic/repeaters/repeater_time.rb +117 -103
- data/lib/chronic/repeaters/repeater_week.rb +63 -57
- data/lib/chronic/repeaters/repeater_weekday.rb +85 -0
- data/lib/chronic/repeaters/repeater_weekend.rb +64 -9
- data/lib/chronic/repeaters/repeater_year.rb +70 -51
- data/lib/chronic/scalar.rb +64 -29
- data/lib/chronic/season.rb +37 -0
- data/lib/chronic/separator.rb +50 -38
- data/lib/chronic/span.rb +31 -0
- data/lib/chronic/tag.rb +42 -0
- data/lib/chronic/time_zone.rb +30 -0
- data/lib/chronic/token.rb +45 -0
- data/lib/chronic.rb +86 -22
- data/test/helper.rb +6 -0
- data/test/test_Chronic.rb +118 -20
- data/test/test_DaylightSavings.rb +118 -0
- data/test/test_Handler.rb +36 -42
- data/test/test_MiniDate.rb +32 -0
- data/test/test_Numerizer.rb +72 -0
- data/test/test_RepeaterDayName.rb +15 -16
- data/test/test_RepeaterFortnight.rb +16 -17
- data/test/test_RepeaterHour.rb +22 -19
- data/test/test_RepeaterMinute.rb +34 -0
- data/test/test_RepeaterMonth.rb +20 -17
- data/test/test_RepeaterMonthName.rb +17 -18
- data/test/test_RepeaterSeason.rb +40 -0
- data/test/test_RepeaterTime.rb +20 -22
- data/test/test_RepeaterWeek.rb +16 -17
- data/test/test_RepeaterWeekday.rb +55 -0
- data/test/test_RepeaterWeekend.rb +74 -0
- data/test/test_RepeaterYear.rb +24 -18
- data/test/test_Span.rb +5 -6
- data/test/test_Token.rb +5 -6
- data/test/test_parsing.rb +743 -338
- metadata +81 -54
- data/History.txt +0 -29
- data/Manifest.txt +0 -43
- data/README.txt +0 -149
- data/test/parse_numbers.rb +0 -50
- data/test/suite.rb +0 -9
data/lib/chronic/handlers.rb
CHANGED
|
@@ -1,112 +1,112 @@
|
|
|
1
1
|
module Chronic
|
|
2
|
+
module Handlers
|
|
3
|
+
module_function
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
Handler.new([:repeater_month_name, :scalar_day, :separator_at?, 'time?'], :handle_rmn_sd),
|
|
12
|
-
Handler.new([:repeater_month_name, :ordinal_day, :separator_at?, 'time?'], :handle_rmn_od),
|
|
13
|
-
Handler.new([:repeater_month_name, :scalar_year], :handle_rmn_sy),
|
|
14
|
-
Handler.new([:scalar_day, :repeater_month_name, :scalar_year, :separator_at?, 'time?'], :handle_sd_rmn_sy),
|
|
15
|
-
Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_day, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sm_sd_sy),
|
|
16
|
-
Handler.new([:scalar_day, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sd_sm_sy),
|
|
17
|
-
Handler.new([:scalar_year, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_day, :separator_at?, 'time?'], :handle_sy_sm_sd),
|
|
18
|
-
Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_year], :handle_sm_sy)],
|
|
19
|
-
|
|
20
|
-
:anchor => [Handler.new([:grabber?, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),
|
|
21
|
-
Handler.new([:repeater, :grabber, :repeater], :handle_r_g_r)],
|
|
22
|
-
|
|
23
|
-
:arrow => [Handler.new([:scalar, :repeater, :pointer], :handle_s_r_p),
|
|
24
|
-
Handler.new([:pointer, :scalar, :repeater], :handle_p_s_r),
|
|
25
|
-
Handler.new([:scalar, :repeater, :pointer, 'anchor'], :handle_s_r_p_a)],
|
|
26
|
-
|
|
27
|
-
:narrow => [Handler.new([:ordinal, :repeater, :separator_in, :repeater], :handle_o_r_s_r),
|
|
28
|
-
Handler.new([:ordinal, :repeater, :grabber, :repeater], :handle_o_r_g_r)]
|
|
29
|
-
}
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def tokens_to_span(tokens, options) #:nodoc:
|
|
33
|
-
# maybe it's a specific date
|
|
34
|
-
|
|
35
|
-
self.definitions[:date].each do |handler|
|
|
36
|
-
if handler.match(tokens, self.definitions)
|
|
37
|
-
good_tokens = tokens.select { |o| !o.get_tag Separator }
|
|
38
|
-
return self.send(handler.handler_method, good_tokens, options)
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
# I guess it's not a specific date, maybe it's just an anchor
|
|
43
|
-
|
|
44
|
-
self.definitions[:anchor].each do |handler|
|
|
45
|
-
if handler.match(tokens, self.definitions)
|
|
46
|
-
good_tokens = tokens.select { |o| !o.get_tag Separator }
|
|
47
|
-
return self.send(handler.handler_method, good_tokens, options)
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
# not an anchor, perhaps it's an arrow
|
|
52
|
-
|
|
53
|
-
self.definitions[:arrow].each do |handler|
|
|
54
|
-
if handler.match(tokens, self.definitions)
|
|
55
|
-
good_tokens = tokens.reject { |o| o.get_tag(SeparatorAt) || o.get_tag(SeparatorSlashOrDash) || o.get_tag(SeparatorComma) }
|
|
56
|
-
return self.send(handler.handler_method, good_tokens, options)
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
# not an arrow, let's hope it's an narrow
|
|
61
|
-
|
|
62
|
-
self.definitions[:narrow].each do |handler|
|
|
63
|
-
if handler.match(tokens, self.definitions)
|
|
64
|
-
#good_tokens = tokens.select { |o| !o.get_tag Separator }
|
|
65
|
-
return self.send(handler.handler_method, tokens, options)
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
# I guess you're out of luck!
|
|
70
|
-
return nil
|
|
5
|
+
# Handle month/day
|
|
6
|
+
def handle_m_d(month, day, time_tokens, options)
|
|
7
|
+
month.start = Chronic.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
|
+
|
|
12
|
+
day_or_time(day_start, time_tokens, options)
|
|
71
13
|
end
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
14
|
+
|
|
15
|
+
# Handle repeater-month-name/scalar-day
|
|
16
|
+
def handle_rmn_sd(tokens, options)
|
|
17
|
+
month = tokens[0].get_tag(RepeaterMonthName)
|
|
18
|
+
day = tokens[1].get_tag(ScalarDay).type
|
|
19
|
+
|
|
20
|
+
return if month_overflow?(Chronic.now.year, month.index, day)
|
|
21
|
+
|
|
22
|
+
handle_m_d(month, day, tokens[2..tokens.size], options)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Handle repeater-month-name/scalar-day with separator-on
|
|
26
|
+
def handle_rmn_sd_on(tokens, options)
|
|
27
|
+
if tokens.size > 3
|
|
28
|
+
month = tokens[2].get_tag(RepeaterMonthName)
|
|
29
|
+
day = tokens[3].get_tag(ScalarDay).type
|
|
30
|
+
token_range = 0..1
|
|
82
31
|
else
|
|
83
|
-
|
|
32
|
+
month = tokens[1].get_tag(RepeaterMonthName)
|
|
33
|
+
day = tokens[2].get_tag(ScalarDay).type
|
|
34
|
+
token_range = 0..0
|
|
84
35
|
end
|
|
36
|
+
|
|
37
|
+
return if month_overflow?(Chronic.now.year, month.index, day)
|
|
38
|
+
|
|
39
|
+
handle_m_d(month, day, tokens[token_range], options)
|
|
85
40
|
end
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
day_or_time(day_start, time_tokens, options)
|
|
41
|
+
|
|
42
|
+
# Handle repeater-month-name/ordinal-day
|
|
43
|
+
def handle_rmn_od(tokens, options)
|
|
44
|
+
month = tokens[0].get_tag(RepeaterMonthName)
|
|
45
|
+
day = tokens[1].get_tag(OrdinalDay).type
|
|
46
|
+
|
|
47
|
+
return if month_overflow?(Chronic.now.year, month.index, day)
|
|
48
|
+
|
|
49
|
+
handle_m_d(month, day, tokens[2..tokens.size], options)
|
|
96
50
|
end
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
51
|
+
|
|
52
|
+
# Handle ordinal-day/repeater-month-name
|
|
53
|
+
def handle_od_rmn(tokens, options)
|
|
54
|
+
month = tokens[1].get_tag(RepeaterMonthName)
|
|
55
|
+
day = tokens[0].get_tag(OrdinalDay).type
|
|
56
|
+
|
|
57
|
+
return if month_overflow?(Chronic.now.year, month.index, day)
|
|
58
|
+
|
|
59
|
+
handle_m_d(month, day, tokens[2..tokens.size], options)
|
|
100
60
|
end
|
|
101
|
-
|
|
102
|
-
def
|
|
103
|
-
|
|
61
|
+
|
|
62
|
+
def handle_sy_rmn_od(tokens, options)
|
|
63
|
+
year = tokens[0].get_tag(ScalarYear).type
|
|
64
|
+
month = tokens[1].get_tag(RepeaterMonthName).index
|
|
65
|
+
day = tokens[2].get_tag(OrdinalDay).type
|
|
66
|
+
time_tokens = tokens.last(tokens.size - 3)
|
|
67
|
+
|
|
68
|
+
return if month_overflow?(year, month, day)
|
|
69
|
+
|
|
70
|
+
begin
|
|
71
|
+
day_start = Chronic.time_class.local(year, month, day)
|
|
72
|
+
day_or_time(day_start, time_tokens, options)
|
|
73
|
+
rescue ArgumentError
|
|
74
|
+
nil
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Handle scalar-day/repeater-month-name
|
|
79
|
+
def handle_sd_rmn(tokens, options)
|
|
80
|
+
month = tokens[1].get_tag(RepeaterMonthName)
|
|
81
|
+
day = tokens[0].get_tag(ScalarDay).type
|
|
82
|
+
|
|
83
|
+
return if month_overflow?(Chronic.now.year, month.index, day)
|
|
84
|
+
|
|
85
|
+
handle_m_d(month, day, tokens[2..tokens.size], options)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Handle repeater-month-name/ordinal-day with separator-on
|
|
89
|
+
def handle_rmn_od_on(tokens, options)
|
|
90
|
+
if tokens.size > 3
|
|
91
|
+
month = tokens[2].get_tag(RepeaterMonthName)
|
|
92
|
+
day = tokens[3].get_tag(OrdinalDay).type
|
|
93
|
+
token_range = 0..1
|
|
94
|
+
else
|
|
95
|
+
month = tokens[1].get_tag(RepeaterMonthName)
|
|
96
|
+
day = tokens[2].get_tag(OrdinalDay).type
|
|
97
|
+
token_range = 0..0
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
return if month_overflow?(Chronic.now.year, month.index, day)
|
|
101
|
+
|
|
102
|
+
handle_m_d(month, day, tokens[token_range], options)
|
|
104
103
|
end
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
|
|
105
|
+
# Handle repeater-month-name/scalar-year
|
|
106
|
+
def handle_rmn_sy(tokens, options)
|
|
107
107
|
month = tokens[0].get_tag(RepeaterMonthName).index
|
|
108
108
|
year = tokens[1].get_tag(ScalarYear).type
|
|
109
|
-
|
|
109
|
+
|
|
110
110
|
if month == 12
|
|
111
111
|
next_month_year = year + 1
|
|
112
112
|
next_month_month = 1
|
|
@@ -114,66 +114,121 @@ module Chronic
|
|
|
114
114
|
next_month_year = year
|
|
115
115
|
next_month_month = month + 1
|
|
116
116
|
end
|
|
117
|
-
|
|
117
|
+
|
|
118
118
|
begin
|
|
119
|
-
|
|
119
|
+
end_time = Chronic.time_class.local(next_month_year, next_month_month)
|
|
120
|
+
Span.new(Chronic.time_class.local(year, month), end_time)
|
|
120
121
|
rescue ArgumentError
|
|
121
122
|
nil
|
|
122
123
|
end
|
|
123
124
|
end
|
|
124
|
-
|
|
125
|
-
|
|
125
|
+
|
|
126
|
+
# Handle generic timestamp (ruby 1.8)
|
|
127
|
+
def handle_rdn_rmn_sd_t_tz_sy(tokens, options)
|
|
128
|
+
t = Chronic.time_class.parse(options[:text])
|
|
129
|
+
Span.new(t, t + 1)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Handle generic timestamp (ruby 1.9)
|
|
133
|
+
def handle_sy_sm_sd_t_tz(tokens, options)
|
|
134
|
+
t = Chronic.time_class.parse(options[:text])
|
|
135
|
+
Span.new(t, t + 1)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Handle repeater-month-name/scalar-day/scalar-year
|
|
139
|
+
def handle_rmn_sd_sy(tokens, options)
|
|
126
140
|
month = tokens[0].get_tag(RepeaterMonthName).index
|
|
127
141
|
day = tokens[1].get_tag(ScalarDay).type
|
|
128
142
|
year = tokens[2].get_tag(ScalarYear).type
|
|
129
|
-
|
|
130
143
|
time_tokens = tokens.last(tokens.size - 3)
|
|
131
|
-
|
|
144
|
+
|
|
145
|
+
return if month_overflow?(year, month, day)
|
|
146
|
+
|
|
147
|
+
begin
|
|
148
|
+
day_start = Chronic.time_class.local(year, month, day)
|
|
149
|
+
day_or_time(day_start, time_tokens, options)
|
|
150
|
+
rescue ArgumentError
|
|
151
|
+
nil
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Handle repeater-month-name/ordinal-day/scalar-year
|
|
156
|
+
def handle_rmn_od_sy(tokens, options)
|
|
157
|
+
month = tokens[0].get_tag(RepeaterMonthName).index
|
|
158
|
+
day = tokens[1].get_tag(OrdinalDay).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
|
+
|
|
132
164
|
begin
|
|
133
|
-
day_start =
|
|
165
|
+
day_start = Chronic.time_class.local(year, month, day)
|
|
134
166
|
day_or_time(day_start, time_tokens, options)
|
|
135
167
|
rescue ArgumentError
|
|
136
168
|
nil
|
|
137
169
|
end
|
|
138
170
|
end
|
|
139
|
-
|
|
140
|
-
|
|
171
|
+
|
|
172
|
+
# Handle oridinal-day/repeater-month-name/scalar-year
|
|
173
|
+
def handle_od_rmn_sy(tokens, options)
|
|
174
|
+
day = tokens[0].get_tag(OrdinalDay).type
|
|
175
|
+
month = tokens[1].get_tag(RepeaterMonthName).index
|
|
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 scalar-day/repeater-month-name/scalar-year
|
|
190
|
+
def handle_sd_rmn_sy(tokens, options)
|
|
141
191
|
new_tokens = [tokens[1], tokens[0], tokens[2]]
|
|
142
192
|
time_tokens = tokens.last(tokens.size - 3)
|
|
143
|
-
|
|
193
|
+
handle_rmn_sd_sy(new_tokens + time_tokens, options)
|
|
144
194
|
end
|
|
145
|
-
|
|
146
|
-
|
|
195
|
+
|
|
196
|
+
# Handle scalar-month/scalar-day/scalar-year (endian middle)
|
|
197
|
+
def handle_sm_sd_sy(tokens, options)
|
|
147
198
|
month = tokens[0].get_tag(ScalarMonth).type
|
|
148
199
|
day = tokens[1].get_tag(ScalarDay).type
|
|
149
200
|
year = tokens[2].get_tag(ScalarYear).type
|
|
150
|
-
|
|
151
201
|
time_tokens = tokens.last(tokens.size - 3)
|
|
152
|
-
|
|
202
|
+
|
|
203
|
+
return if month_overflow?(year, month, day)
|
|
204
|
+
|
|
153
205
|
begin
|
|
154
|
-
day_start =
|
|
206
|
+
day_start = Chronic.time_class.local(year, month, day)
|
|
155
207
|
day_or_time(day_start, time_tokens, options)
|
|
156
208
|
rescue ArgumentError
|
|
157
209
|
nil
|
|
158
210
|
end
|
|
159
211
|
end
|
|
160
|
-
|
|
161
|
-
|
|
212
|
+
|
|
213
|
+
# Handle scalar-day/scalar-month/scalar-year (endian little)
|
|
214
|
+
def handle_sd_sm_sy(tokens, options)
|
|
162
215
|
new_tokens = [tokens[1], tokens[0], tokens[2]]
|
|
163
216
|
time_tokens = tokens.last(tokens.size - 3)
|
|
164
|
-
|
|
217
|
+
handle_sm_sd_sy(new_tokens + time_tokens, options)
|
|
165
218
|
end
|
|
166
|
-
|
|
167
|
-
|
|
219
|
+
|
|
220
|
+
# Handle scalar-year/scalar-month/scalar-day
|
|
221
|
+
def handle_sy_sm_sd(tokens, options)
|
|
168
222
|
new_tokens = [tokens[1], tokens[2], tokens[0]]
|
|
169
223
|
time_tokens = tokens.last(tokens.size - 3)
|
|
170
|
-
|
|
224
|
+
handle_sm_sd_sy(new_tokens + time_tokens, options)
|
|
171
225
|
end
|
|
172
|
-
|
|
173
|
-
|
|
226
|
+
|
|
227
|
+
# Handle scalar-month/scalar-year
|
|
228
|
+
def handle_sm_sy(tokens, options)
|
|
174
229
|
month = tokens[0].get_tag(ScalarMonth).type
|
|
175
230
|
year = tokens[1].get_tag(ScalarYear).type
|
|
176
|
-
|
|
231
|
+
|
|
177
232
|
if month == 12
|
|
178
233
|
next_month_year = year + 1
|
|
179
234
|
next_month_month = 1
|
|
@@ -181,232 +236,264 @@ module Chronic
|
|
|
181
236
|
next_month_year = year
|
|
182
237
|
next_month_month = month + 1
|
|
183
238
|
end
|
|
184
|
-
|
|
239
|
+
|
|
185
240
|
begin
|
|
186
|
-
|
|
241
|
+
end_time = Chronic.time_class.local(next_month_year, next_month_month)
|
|
242
|
+
Span.new(Chronic.time_class.local(year, month), end_time)
|
|
187
243
|
rescue ArgumentError
|
|
188
244
|
nil
|
|
189
245
|
end
|
|
190
246
|
end
|
|
191
|
-
|
|
247
|
+
|
|
248
|
+
# Handle RepeaterDayName RepeaterMonthName OrdinalDay
|
|
249
|
+
def handle_rdn_rmn_od(tokens, options)
|
|
250
|
+
month = tokens[1].get_tag(RepeaterMonthName)
|
|
251
|
+
day = tokens[2].get_tag(OrdinalDay).type
|
|
252
|
+
year = Chronic.now.year
|
|
253
|
+
|
|
254
|
+
return if month_overflow?(year, month.index, day)
|
|
255
|
+
|
|
256
|
+
begin
|
|
257
|
+
start_time = Chronic.time_class.local(year, month.index, day)
|
|
258
|
+
end_time = Chronic.time_class.local(year, month.index, day + 1)
|
|
259
|
+
Span.new(start_time, end_time)
|
|
260
|
+
rescue ArgumentError
|
|
261
|
+
nil
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# Handle RepeaterDayName RepeaterMonthName ScalarDay
|
|
266
|
+
def handle_rdn_rmn_sd(tokens, options)
|
|
267
|
+
month = tokens[1].get_tag(RepeaterMonthName)
|
|
268
|
+
day = tokens[2].get_tag(ScalarDay).type
|
|
269
|
+
year = Chronic.now.year
|
|
270
|
+
|
|
271
|
+
return if month_overflow?(year, month.index, day)
|
|
272
|
+
|
|
273
|
+
begin
|
|
274
|
+
start_time = Chronic.time_class.local(year, month.index, day)
|
|
275
|
+
end_time = Chronic.time_class.local(year, month.index, day + 1)
|
|
276
|
+
Span.new(start_time, end_time)
|
|
277
|
+
rescue ArgumentError
|
|
278
|
+
nil
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
|
|
192
282
|
# anchors
|
|
193
|
-
|
|
194
|
-
|
|
283
|
+
|
|
284
|
+
# Handle repeaters
|
|
285
|
+
def handle_r(tokens, options)
|
|
195
286
|
dd_tokens = dealias_and_disambiguate_times(tokens, options)
|
|
196
|
-
|
|
287
|
+
get_anchor(dd_tokens, options)
|
|
197
288
|
end
|
|
198
|
-
|
|
199
|
-
|
|
289
|
+
|
|
290
|
+
# Handle repeater/grabber/repeater
|
|
291
|
+
def handle_r_g_r(tokens, options)
|
|
200
292
|
new_tokens = [tokens[1], tokens[0], tokens[2]]
|
|
201
|
-
|
|
293
|
+
handle_r(new_tokens, options)
|
|
202
294
|
end
|
|
203
|
-
|
|
295
|
+
|
|
204
296
|
# arrows
|
|
205
|
-
|
|
206
|
-
|
|
297
|
+
|
|
298
|
+
# Handle scalar/repeater/pointer helper
|
|
299
|
+
def handle_srp(tokens, span, options)
|
|
207
300
|
distance = tokens[0].get_tag(Scalar).type
|
|
208
301
|
repeater = tokens[1].get_tag(Repeater)
|
|
209
302
|
pointer = tokens[2].get_tag(Pointer).type
|
|
210
|
-
|
|
303
|
+
|
|
211
304
|
repeater.offset(span, distance, pointer)
|
|
212
305
|
end
|
|
213
|
-
|
|
214
|
-
|
|
306
|
+
|
|
307
|
+
# Handle scalar/repeater/pointer
|
|
308
|
+
def handle_s_r_p(tokens, options)
|
|
215
309
|
repeater = tokens[1].get_tag(Repeater)
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
when [RepeaterYear, RepeaterSeason, RepeaterSeasonName, RepeaterMonth, RepeaterMonthName, RepeaterFortnight, RepeaterWeek].include?(repeater.class)
|
|
220
|
-
self.parse("this hour", :guess => false, :now => @now)
|
|
221
|
-
when [RepeaterWeekend, RepeaterDay, RepeaterDayName, RepeaterDayPortion, RepeaterHour].include?(repeater.class)
|
|
222
|
-
self.parse("this minute", :guess => false, :now => @now)
|
|
223
|
-
when [RepeaterMinute, RepeaterSecond].include?(repeater.class)
|
|
224
|
-
self.parse("this second", :guess => false, :now => @now)
|
|
225
|
-
else
|
|
226
|
-
raise(ChronicPain, "Invalid repeater: #{repeater.class}")
|
|
227
|
-
end
|
|
228
|
-
|
|
229
|
-
self.handle_srp(tokens, span, options)
|
|
310
|
+
span = Span.new(Chronic.now, Chronic.now + 1)
|
|
311
|
+
|
|
312
|
+
handle_srp(tokens, span, options)
|
|
230
313
|
end
|
|
231
|
-
|
|
232
|
-
|
|
314
|
+
|
|
315
|
+
# Handle pointer/scalar/repeater
|
|
316
|
+
def handle_p_s_r(tokens, options)
|
|
233
317
|
new_tokens = [tokens[1], tokens[2], tokens[0]]
|
|
234
|
-
|
|
318
|
+
handle_s_r_p(new_tokens, options)
|
|
235
319
|
end
|
|
236
|
-
|
|
237
|
-
|
|
320
|
+
|
|
321
|
+
# Handle scalar/repeater/pointer/anchor
|
|
322
|
+
def handle_s_r_p_a(tokens, options)
|
|
238
323
|
anchor_span = get_anchor(tokens[3..tokens.size - 1], options)
|
|
239
|
-
|
|
324
|
+
handle_srp(tokens, anchor_span, options)
|
|
240
325
|
end
|
|
241
|
-
|
|
326
|
+
|
|
242
327
|
# narrows
|
|
243
|
-
|
|
244
|
-
|
|
328
|
+
|
|
329
|
+
# Handle oridinal repeaters
|
|
330
|
+
def handle_orr(tokens, outer_span, options)
|
|
245
331
|
repeater = tokens[1].get_tag(Repeater)
|
|
246
332
|
repeater.start = outer_span.begin - 1
|
|
247
333
|
ordinal = tokens[0].get_tag(Ordinal).type
|
|
248
334
|
span = nil
|
|
335
|
+
|
|
249
336
|
ordinal.times do
|
|
250
337
|
span = repeater.next(:future)
|
|
251
|
-
|
|
338
|
+
|
|
339
|
+
if span.begin >= outer_span.end
|
|
252
340
|
span = nil
|
|
253
341
|
break
|
|
254
342
|
end
|
|
255
343
|
end
|
|
344
|
+
|
|
256
345
|
span
|
|
257
346
|
end
|
|
258
|
-
|
|
259
|
-
|
|
347
|
+
|
|
348
|
+
# Handle ordinal/repeater/separator/repeater
|
|
349
|
+
def handle_o_r_s_r(tokens, options)
|
|
260
350
|
outer_span = get_anchor([tokens[3]], options)
|
|
261
351
|
handle_orr(tokens[0..1], outer_span, options)
|
|
262
352
|
end
|
|
263
|
-
|
|
264
|
-
|
|
353
|
+
|
|
354
|
+
# Handle ordinal/repeater/grabber/repeater
|
|
355
|
+
def handle_o_r_g_r(tokens, options)
|
|
265
356
|
outer_span = get_anchor(tokens[2..3], options)
|
|
266
357
|
handle_orr(tokens[0..1], outer_span, options)
|
|
267
358
|
end
|
|
268
|
-
|
|
359
|
+
|
|
269
360
|
# support methods
|
|
270
|
-
|
|
271
|
-
def
|
|
361
|
+
|
|
362
|
+
def day_or_time(day_start, time_tokens, options)
|
|
363
|
+
outer_span = Span.new(day_start, day_start + (24 * 60 * 60))
|
|
364
|
+
|
|
365
|
+
if !time_tokens.empty?
|
|
366
|
+
Chronic.now = outer_span.begin
|
|
367
|
+
get_anchor(dealias_and_disambiguate_times(time_tokens, options), options)
|
|
368
|
+
else
|
|
369
|
+
outer_span
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
def get_anchor(tokens, options)
|
|
272
374
|
grabber = Grabber.new(:this)
|
|
273
375
|
pointer = :future
|
|
274
|
-
|
|
275
|
-
repeaters =
|
|
376
|
+
|
|
377
|
+
repeaters = get_repeaters(tokens)
|
|
276
378
|
repeaters.size.times { tokens.pop }
|
|
277
|
-
|
|
379
|
+
|
|
278
380
|
if tokens.first && tokens.first.get_tag(Grabber)
|
|
279
|
-
grabber = tokens.
|
|
280
|
-
tokens.pop
|
|
381
|
+
grabber = tokens.shift.get_tag(Grabber)
|
|
281
382
|
end
|
|
282
|
-
|
|
383
|
+
|
|
283
384
|
head = repeaters.shift
|
|
284
|
-
head.start =
|
|
285
|
-
|
|
385
|
+
head.start = Chronic.now
|
|
386
|
+
|
|
286
387
|
case grabber.type
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
end
|
|
295
|
-
when :next
|
|
296
|
-
outer_span = head.next(:future)
|
|
297
|
-
else raise(ChronicPain, "Invalid grabber")
|
|
298
|
-
end
|
|
299
|
-
|
|
300
|
-
puts "--#{outer_span}" if Chronic.debug
|
|
301
|
-
anchor = find_within(repeaters, outer_span, pointer)
|
|
302
|
-
end
|
|
303
|
-
|
|
304
|
-
def get_repeaters(tokens) #:nodoc:
|
|
305
|
-
repeaters = []
|
|
306
|
-
tokens.each do |token|
|
|
307
|
-
if t = token.get_tag(Repeater)
|
|
308
|
-
repeaters << t
|
|
388
|
+
when :last
|
|
389
|
+
outer_span = head.next(:past)
|
|
390
|
+
when :this
|
|
391
|
+
if options[:context] != :past and repeaters.size > 0
|
|
392
|
+
outer_span = head.this(:none)
|
|
393
|
+
else
|
|
394
|
+
outer_span = head.this(options[:context])
|
|
309
395
|
end
|
|
396
|
+
when :next
|
|
397
|
+
outer_span = head.next(:future)
|
|
398
|
+
else
|
|
399
|
+
raise ChronicPain, "Invalid grabber"
|
|
310
400
|
end
|
|
311
|
-
|
|
401
|
+
|
|
402
|
+
if Chronic.debug
|
|
403
|
+
puts "Handler-class: #{head.class}"
|
|
404
|
+
puts "--#{outer_span}"
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
find_within(repeaters, outer_span, pointer)
|
|
312
408
|
end
|
|
313
|
-
|
|
409
|
+
|
|
410
|
+
def get_repeaters(tokens)
|
|
411
|
+
tokens.map { |token| token.get_tag(Repeater) }.compact.sort.reverse
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
def month_overflow?(year, month, day)
|
|
415
|
+
if Date.leap?(year)
|
|
416
|
+
day > RepeaterMonth::MONTH_DAYS_LEAP[month - 1]
|
|
417
|
+
else
|
|
418
|
+
day > RepeaterMonth::MONTH_DAYS[month - 1]
|
|
419
|
+
end
|
|
420
|
+
end
|
|
421
|
+
|
|
314
422
|
# Recursively finds repeaters within other repeaters.
|
|
315
423
|
# Returns a Span representing the innermost time span
|
|
316
424
|
# or nil if no repeater union could be found
|
|
317
|
-
def find_within(tags, span, pointer)
|
|
425
|
+
def find_within(tags, span, pointer)
|
|
318
426
|
puts "--#{span}" if Chronic.debug
|
|
319
427
|
return span if tags.empty?
|
|
320
|
-
|
|
321
|
-
head
|
|
322
|
-
head.start = pointer == :future ? span.begin : span.end
|
|
428
|
+
|
|
429
|
+
head = tags.shift
|
|
430
|
+
head.start = (pointer == :future ? span.begin : span.end)
|
|
323
431
|
h = head.this(:none)
|
|
324
|
-
|
|
325
|
-
if span.
|
|
326
|
-
|
|
327
|
-
else
|
|
328
|
-
return nil
|
|
432
|
+
|
|
433
|
+
if span.cover?(h.begin) || span.cover?(h.end)
|
|
434
|
+
find_within(tags, h, pointer)
|
|
329
435
|
end
|
|
330
436
|
end
|
|
331
|
-
|
|
332
|
-
def dealias_and_disambiguate_times(tokens, options)
|
|
437
|
+
|
|
438
|
+
def dealias_and_disambiguate_times(tokens, options)
|
|
333
439
|
# handle aliases of am/pm
|
|
334
|
-
# 5:00 in the morning
|
|
335
|
-
# 7:00 in the evening
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
if
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
t1.tag(RepeaterDayPortion.new(:am))
|
|
343
|
-
elsif [:afternoon, :evening, :night].include?(t1tag.type)
|
|
344
|
-
t1.untag(RepeaterDayPortion)
|
|
345
|
-
t1.tag(RepeaterDayPortion.new(:pm))
|
|
346
|
-
end
|
|
440
|
+
# 5:00 in the morning -> 5:00 am
|
|
441
|
+
# 7:00 in the evening -> 7:00 pm
|
|
442
|
+
|
|
443
|
+
day_portion_index = nil
|
|
444
|
+
tokens.each_with_index do |t, i|
|
|
445
|
+
if t.get_tag(RepeaterDayPortion)
|
|
446
|
+
day_portion_index = i
|
|
447
|
+
break
|
|
347
448
|
end
|
|
348
449
|
end
|
|
349
|
-
|
|
350
|
-
|
|
450
|
+
|
|
451
|
+
time_index = nil
|
|
452
|
+
tokens.each_with_index do |t, i|
|
|
453
|
+
if t.get_tag(RepeaterTime)
|
|
454
|
+
time_index = i
|
|
455
|
+
break
|
|
456
|
+
end
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
if day_portion_index && time_index
|
|
460
|
+
t1 = tokens[day_portion_index]
|
|
461
|
+
t1tag = t1.get_tag(RepeaterDayPortion)
|
|
462
|
+
|
|
463
|
+
case t1tag.type
|
|
464
|
+
when :morning
|
|
465
|
+
puts '--morning->am' if Chronic.debug
|
|
466
|
+
t1.untag(RepeaterDayPortion)
|
|
467
|
+
t1.tag(RepeaterDayPortion.new(:am))
|
|
468
|
+
when :afternoon, :evening, :night
|
|
469
|
+
puts "--#{t1tag.type}->pm" if Chronic.debug
|
|
470
|
+
t1.untag(RepeaterDayPortion)
|
|
471
|
+
t1.tag(RepeaterDayPortion.new(:pm))
|
|
472
|
+
end
|
|
473
|
+
end
|
|
474
|
+
|
|
351
475
|
# handle ambiguous times if :ambiguous_time_range is specified
|
|
352
476
|
if options[:ambiguous_time_range] != :none
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
477
|
+
ambiguous_tokens = []
|
|
478
|
+
|
|
479
|
+
tokens.each_with_index do |token, i|
|
|
480
|
+
ambiguous_tokens << token
|
|
481
|
+
next_token = tokens[i + 1]
|
|
482
|
+
|
|
483
|
+
if token.get_tag(RepeaterTime) && token.get_tag(RepeaterTime).type.ambiguous? && (!next_token || !next_token.get_tag(RepeaterDayPortion))
|
|
358
484
|
distoken = Token.new('disambiguator')
|
|
485
|
+
|
|
359
486
|
distoken.tag(RepeaterDayPortion.new(options[:ambiguous_time_range]))
|
|
360
|
-
|
|
487
|
+
ambiguous_tokens << distoken
|
|
361
488
|
end
|
|
362
489
|
end
|
|
363
|
-
|
|
490
|
+
|
|
491
|
+
tokens = ambiguous_tokens
|
|
364
492
|
end
|
|
365
|
-
|
|
493
|
+
|
|
366
494
|
tokens
|
|
367
495
|
end
|
|
368
|
-
|
|
369
|
-
end
|
|
370
|
-
|
|
371
|
-
class Handler #:nodoc:
|
|
372
|
-
attr_accessor :pattern, :handler_method
|
|
373
|
-
|
|
374
|
-
def initialize(pattern, handler_method)
|
|
375
|
-
@pattern = pattern
|
|
376
|
-
@handler_method = handler_method
|
|
377
|
-
end
|
|
378
|
-
|
|
379
|
-
def constantize(name)
|
|
380
|
-
camel = name.to_s.gsub(/(^|_)(.)/) { $2.upcase }
|
|
381
|
-
::Chronic.module_eval(camel, __FILE__, __LINE__)
|
|
382
|
-
end
|
|
383
|
-
|
|
384
|
-
def match(tokens, definitions)
|
|
385
|
-
token_index = 0
|
|
386
|
-
@pattern.each do |element|
|
|
387
|
-
name = element.to_s
|
|
388
|
-
optional = name.reverse[0..0] == '?'
|
|
389
|
-
name = name.chop if optional
|
|
390
|
-
if element.instance_of? Symbol
|
|
391
|
-
klass = constantize(name)
|
|
392
|
-
match = tokens[token_index] && !tokens[token_index].tags.select { |o| o.kind_of?(klass) }.empty?
|
|
393
|
-
return false if !match && !optional
|
|
394
|
-
(token_index += 1; next) if match
|
|
395
|
-
next if !match && optional
|
|
396
|
-
elsif element.instance_of? String
|
|
397
|
-
return true if optional && token_index == tokens.size
|
|
398
|
-
sub_handlers = definitions[name.intern] || raise(ChronicPain, "Invalid subset #{name} specified")
|
|
399
|
-
sub_handlers.each do |sub_handler|
|
|
400
|
-
return true if sub_handler.match(tokens[token_index..tokens.size], definitions)
|
|
401
|
-
end
|
|
402
|
-
return false
|
|
403
|
-
else
|
|
404
|
-
raise(ChronicPain, "Invalid match type: #{element.class}")
|
|
405
|
-
end
|
|
406
|
-
end
|
|
407
|
-
return false if token_index != tokens.size
|
|
408
|
-
return true
|
|
409
|
-
end
|
|
496
|
+
|
|
410
497
|
end
|
|
411
|
-
|
|
412
|
-
end
|
|
498
|
+
|
|
499
|
+
end
|