timing 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/timing/helpers.rb +58 -4
- data/lib/timing/natural_time_language.rb +2848 -0
- data/lib/timing/natural_time_language.treetop +227 -0
- data/lib/timing/natural_time_language_interpreters.rb +326 -0
- data/lib/timing/time_in_zone.rb +30 -2
- data/lib/timing/version.rb +1 -1
- data/lib/timing.rb +4 -0
- data/timing.gemspec +2 -0
- metadata +19 -2
@@ -0,0 +1,227 @@
|
|
1
|
+
module Timing
|
2
|
+
grammar NaturalTimeLanguage
|
3
|
+
|
4
|
+
rule expression
|
5
|
+
space* moment:moment space* zone_offset:zone_offset? space* <Expression>
|
6
|
+
end
|
7
|
+
|
8
|
+
rule moment
|
9
|
+
timestamp /
|
10
|
+
time_ago /
|
11
|
+
moment_at_time /
|
12
|
+
before_from_moment /
|
13
|
+
date_moment
|
14
|
+
end
|
15
|
+
|
16
|
+
rule date_moment
|
17
|
+
named_moment /
|
18
|
+
last_next_day_name /
|
19
|
+
day_month_name_year /
|
20
|
+
year_month_day /
|
21
|
+
beginning_end_interval
|
22
|
+
end
|
23
|
+
|
24
|
+
rule named_moment
|
25
|
+
now / today / tomorrow / yesterday
|
26
|
+
end
|
27
|
+
|
28
|
+
rule last_next_day_name
|
29
|
+
direction:last_next space* day_name:day_name <LastNextDayName>
|
30
|
+
end
|
31
|
+
|
32
|
+
rule day_month_name_year
|
33
|
+
day:integer space* month:month_name space* year:integer? space* <DayMonthNameYear>
|
34
|
+
end
|
35
|
+
|
36
|
+
rule year_month_day
|
37
|
+
year:integer '-' month:integer '-' day:integer <YearMonthDay>
|
38
|
+
end
|
39
|
+
|
40
|
+
rule beginning_end_interval
|
41
|
+
direction:beginning_end space* interval_type:interval <BeginningEndInterval>
|
42
|
+
end
|
43
|
+
|
44
|
+
rule time_ago
|
45
|
+
number:integer space* interval_type:interval space* 'ago'i <TimeAgo>
|
46
|
+
end
|
47
|
+
|
48
|
+
rule moment_at_time
|
49
|
+
moment:date_moment space* 'at'i space* time:hour_minute_second <MomentAtTime>
|
50
|
+
end
|
51
|
+
|
52
|
+
rule before_from_moment
|
53
|
+
number:integer space* interval_type:interval space* direction:before_from space* moment:moment <BeforeFromMoment>
|
54
|
+
end
|
55
|
+
|
56
|
+
rule timestamp
|
57
|
+
[\d]4..4 '-' [\d]2..2 '-' [\d]2..2 space* 'T'? space* [\d]2..2 ':' [\d]2..2 ':' [\d]2..2 <Timestamp>
|
58
|
+
end
|
59
|
+
|
60
|
+
rule now
|
61
|
+
'now'i <Now>
|
62
|
+
end
|
63
|
+
|
64
|
+
rule today
|
65
|
+
'today'i <Today>
|
66
|
+
end
|
67
|
+
|
68
|
+
rule tomorrow
|
69
|
+
'tomorrow'i <Tomorrow>
|
70
|
+
end
|
71
|
+
|
72
|
+
rule yesterday
|
73
|
+
'yesterday'i <Yesterday>
|
74
|
+
end
|
75
|
+
|
76
|
+
rule last_next
|
77
|
+
last / next
|
78
|
+
end
|
79
|
+
|
80
|
+
rule last
|
81
|
+
'last'i <LastNext>
|
82
|
+
end
|
83
|
+
|
84
|
+
rule next
|
85
|
+
'next'i <LastNext>
|
86
|
+
end
|
87
|
+
|
88
|
+
rule beginning_end
|
89
|
+
beginning_of / end_of
|
90
|
+
end
|
91
|
+
|
92
|
+
rule beginning_of
|
93
|
+
direction:'beginning'i space* 'of'i <BeginningEnd>
|
94
|
+
end
|
95
|
+
|
96
|
+
rule end_of
|
97
|
+
direction:'end'i space* 'of'i <BeginningEnd>
|
98
|
+
end
|
99
|
+
|
100
|
+
rule before_from
|
101
|
+
before / from
|
102
|
+
end
|
103
|
+
|
104
|
+
rule before
|
105
|
+
'before'i <BeforeFrom>
|
106
|
+
end
|
107
|
+
|
108
|
+
rule from
|
109
|
+
'from'i <BeforeFrom>
|
110
|
+
end
|
111
|
+
|
112
|
+
rule interval
|
113
|
+
second_interval /
|
114
|
+
minute_interval /
|
115
|
+
hour_interval /
|
116
|
+
day_interval /
|
117
|
+
week_interval /
|
118
|
+
month_interval /
|
119
|
+
year_interval
|
120
|
+
end
|
121
|
+
|
122
|
+
rule second_interval
|
123
|
+
'second'i 's'i? <SecondInterval>
|
124
|
+
end
|
125
|
+
|
126
|
+
rule minute_interval
|
127
|
+
'minute'i 's'i? <MinuteInterval>
|
128
|
+
end
|
129
|
+
|
130
|
+
rule hour_interval
|
131
|
+
'hour'i 's'i? <HourInterval>
|
132
|
+
end
|
133
|
+
|
134
|
+
rule day_interval
|
135
|
+
'day'i 's'i? <DayInterval>
|
136
|
+
end
|
137
|
+
|
138
|
+
rule week_interval
|
139
|
+
'week'i 's'i? <WeekInterval>
|
140
|
+
end
|
141
|
+
|
142
|
+
rule month_interval
|
143
|
+
'month'i 's'i? <MonthInterval>
|
144
|
+
end
|
145
|
+
|
146
|
+
rule year_interval
|
147
|
+
'year'i 's'i? <YearInterval>
|
148
|
+
end
|
149
|
+
|
150
|
+
rule day_name
|
151
|
+
long_day_name / short_day_name
|
152
|
+
end
|
153
|
+
|
154
|
+
rule long_day_name
|
155
|
+
'sunday'i <DayName> /
|
156
|
+
'monday'i <DayName> /
|
157
|
+
'tuesday'i <DayName> /
|
158
|
+
'wednesday'i <DayName> /
|
159
|
+
'thursday'i <DayName> /
|
160
|
+
'friday'i <DayName> /
|
161
|
+
'saturday'i <DayName>
|
162
|
+
end
|
163
|
+
|
164
|
+
rule short_day_name
|
165
|
+
'sun'i <DayName> /
|
166
|
+
'mon'i <DayName> /
|
167
|
+
'tue'i <DayName> /
|
168
|
+
'wed'i <DayName> /
|
169
|
+
'thu'i <DayName> /
|
170
|
+
'fri'i <DayName> /
|
171
|
+
'sat'i <DayName>
|
172
|
+
end
|
173
|
+
|
174
|
+
rule month_name
|
175
|
+
long_month_name / short_month_name
|
176
|
+
end
|
177
|
+
|
178
|
+
rule long_month_name
|
179
|
+
'january'i <MonthName> /
|
180
|
+
'february'i <MonthName> /
|
181
|
+
'march'i <MonthName> /
|
182
|
+
'april'i <MonthName> /
|
183
|
+
'may'i <MonthName> /
|
184
|
+
'june'i <MonthName> /
|
185
|
+
'july'i <MonthName> /
|
186
|
+
'august'i <MonthName> /
|
187
|
+
'september'i <MonthName> /
|
188
|
+
'october'i <MonthName> /
|
189
|
+
'november'i <MonthName> /
|
190
|
+
'december'i <MonthName>
|
191
|
+
end
|
192
|
+
|
193
|
+
rule short_month_name
|
194
|
+
'jan'i <MonthName> /
|
195
|
+
'feb'i <MonthName> /
|
196
|
+
'mar'i <MonthName> /
|
197
|
+
'apr'i <MonthName> /
|
198
|
+
'may'i <MonthName> /
|
199
|
+
'jun'i <MonthName> /
|
200
|
+
'jul'i <MonthName> /
|
201
|
+
'aug'i <MonthName> /
|
202
|
+
'sep'i <MonthName> /
|
203
|
+
'oct'i <MonthName> /
|
204
|
+
'nov'i <MonthName> /
|
205
|
+
'dec'i <MonthName>
|
206
|
+
end
|
207
|
+
|
208
|
+
rule zone_offset
|
209
|
+
[+-] [\d]2..2 ':'? [\d]2..2 <ZoneOffset>
|
210
|
+
end
|
211
|
+
|
212
|
+
rule hour_minute_second
|
213
|
+
[\d]2..2 ':' [\d]2..2 (':' [\d]2..2)? <HourMinuteSecond> /
|
214
|
+
'beginning' <HourMinuteSecond> /
|
215
|
+
'end' <HourMinuteSecond>
|
216
|
+
end
|
217
|
+
|
218
|
+
rule integer
|
219
|
+
[\d]+ <Int>
|
220
|
+
end
|
221
|
+
|
222
|
+
rule space
|
223
|
+
[\s\t\n]
|
224
|
+
end
|
225
|
+
|
226
|
+
end
|
227
|
+
end
|
@@ -0,0 +1,326 @@
|
|
1
|
+
module Timing
|
2
|
+
module NaturalTimeLanguage
|
3
|
+
|
4
|
+
class << self
|
5
|
+
|
6
|
+
def parse(expression)
|
7
|
+
parsed_expression = parser.parse expression
|
8
|
+
raise parser.failure_reason unless parsed_expression
|
9
|
+
parsed_expression.evaluate
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def parser
|
15
|
+
@parser ||= NaturalTimeLanguageParser.new
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
class Expression < Treetop::Runtime::SyntaxNode
|
21
|
+
def evaluate
|
22
|
+
moment.evaluate zone_offset.empty? ? nil : zone_offset.value
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Now < Treetop::Runtime::SyntaxNode
|
27
|
+
def evaluate(zone_offset)
|
28
|
+
TimeInZone.now zone_offset
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class Today < Treetop::Runtime::SyntaxNode
|
33
|
+
def evaluate(zone_offset)
|
34
|
+
TimeInZone.now(zone_offset).beginning_of_day
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class Yesterday < Treetop::Runtime::SyntaxNode
|
39
|
+
def evaluate(zone_offset)
|
40
|
+
TimeInZone.now(zone_offset).beginning_of_day - Interval.days(1)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class Tomorrow < Treetop::Runtime::SyntaxNode
|
45
|
+
def evaluate(zone_offset)
|
46
|
+
TimeInZone.now(zone_offset).beginning_of_day + Interval.days(1)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class LastNextDayName < Treetop::Runtime::SyntaxNode
|
51
|
+
def evaluate(zone_offset)
|
52
|
+
today = TimeInZone.now(zone_offset).beginning_of_day
|
53
|
+
|
54
|
+
if direction.last?
|
55
|
+
if today.wday > day_name.value
|
56
|
+
today - Interval.days(today.wday - day_name.value)
|
57
|
+
else
|
58
|
+
today - Interval.weeks(1) + Interval.days(day_name.value - today.wday)
|
59
|
+
end
|
60
|
+
else
|
61
|
+
if today.wday < day_name.value
|
62
|
+
today + Interval.days(day_name.value - today.wday)
|
63
|
+
else
|
64
|
+
today + Interval.weeks(1) - Interval.days(today.wday - day_name.value)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class BeginningEndInterval < Treetop::Runtime::SyntaxNode
|
71
|
+
def evaluate(zone_offset)
|
72
|
+
now = TimeInZone.now zone_offset
|
73
|
+
if direction.beginning?
|
74
|
+
interval_type.beginning_of now
|
75
|
+
else
|
76
|
+
interval_type.end_of now
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
class TimeAgo < Treetop::Runtime::SyntaxNode
|
82
|
+
def evaluate(zone_offset)
|
83
|
+
now = TimeInZone.now zone_offset
|
84
|
+
interval_type.time_ago now, number.value
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
class DayMonthNameYear < Treetop::Runtime::SyntaxNode
|
89
|
+
def evaluate(zone_offset)
|
90
|
+
now = TimeInZone.now zone_offset
|
91
|
+
yyyy = year.empty? ? now.year : year.value
|
92
|
+
mm = month.value.to_s.rjust(2, '0')
|
93
|
+
dd = day.value.to_s.rjust(2, '0')
|
94
|
+
TimeInZone.parse "#{yyyy}-#{mm}-#{dd} 00:00:00 #{now.zone_offset}"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
class YearMonthDay < Treetop::Runtime::SyntaxNode
|
99
|
+
def evaluate(zone_offset)
|
100
|
+
yyyy = year.value
|
101
|
+
mm = month.value.to_s.rjust(2, '0')
|
102
|
+
dd = day.value.to_s.rjust(2, '0')
|
103
|
+
TimeInZone.parse "#{yyyy}-#{mm}-#{dd} 00:00:00 #{zone_offset}"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
class MomentAtTime < Treetop::Runtime::SyntaxNode
|
108
|
+
def evaluate(zone_offset)
|
109
|
+
date = moment.evaluate zone_offset
|
110
|
+
TimeInZone.parse "#{date.strftime('%F')} #{time.value} #{date.zone_offset}"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
class BeforeFromMoment < Treetop::Runtime::SyntaxNode
|
115
|
+
def evaluate(zone_offset)
|
116
|
+
time = moment.evaluate zone_offset
|
117
|
+
if direction.before?
|
118
|
+
interval_type.time_ago time, number.value
|
119
|
+
else
|
120
|
+
interval_type.time_after time, number.value
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
class Timestamp < Treetop::Runtime::SyntaxNode
|
126
|
+
def evaluate(zone_offset)
|
127
|
+
TimeInZone.parse "#{text_value}#{zone_offset}"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
class BeginningEnd < Treetop::Runtime::SyntaxNode
|
132
|
+
def beginning?
|
133
|
+
direction.text_value.downcase == 'beginning'
|
134
|
+
end
|
135
|
+
|
136
|
+
def end?
|
137
|
+
direction.text_value.downcase == 'end'
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
class LastNext < Treetop::Runtime::SyntaxNode
|
142
|
+
def last?
|
143
|
+
text_value.downcase == 'last'
|
144
|
+
end
|
145
|
+
|
146
|
+
def next?
|
147
|
+
text_value.downcase == 'next'
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
class BeforeFrom < Treetop::Runtime::SyntaxNode
|
152
|
+
def before?
|
153
|
+
text_value.downcase == 'before'
|
154
|
+
end
|
155
|
+
|
156
|
+
def from?
|
157
|
+
text_value.downcase == 'from'
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
class SecondInterval < Treetop::Runtime::SyntaxNode
|
162
|
+
def time_ago(time, number)
|
163
|
+
time - Interval.seconds(number)
|
164
|
+
end
|
165
|
+
|
166
|
+
def time_after(time, number)
|
167
|
+
time + Interval.seconds(number)
|
168
|
+
end
|
169
|
+
|
170
|
+
def beginning_of(time)
|
171
|
+
raise 'Not supported'
|
172
|
+
end
|
173
|
+
|
174
|
+
def end_of(time)
|
175
|
+
raise 'Not supported'
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
class MinuteInterval < Treetop::Runtime::SyntaxNode
|
180
|
+
def time_ago(time, number)
|
181
|
+
time - Interval.minutes(number)
|
182
|
+
end
|
183
|
+
|
184
|
+
def time_after(time, number)
|
185
|
+
time + Interval.minutes(number)
|
186
|
+
end
|
187
|
+
|
188
|
+
def beginning_of(time)
|
189
|
+
raise 'Not supported'
|
190
|
+
end
|
191
|
+
|
192
|
+
def end_of(time)
|
193
|
+
raise 'Not supported'
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
class HourInterval < Treetop::Runtime::SyntaxNode
|
198
|
+
def time_ago(time, number)
|
199
|
+
time - Interval.hours(number)
|
200
|
+
end
|
201
|
+
|
202
|
+
def time_after(time, number)
|
203
|
+
time + Interval.hours(number)
|
204
|
+
end
|
205
|
+
|
206
|
+
def beginning_of(time)
|
207
|
+
time.beginning_of_hour
|
208
|
+
end
|
209
|
+
|
210
|
+
def end_of(time)
|
211
|
+
time.end_of_hour
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
class DayInterval < Treetop::Runtime::SyntaxNode
|
216
|
+
def time_ago(time, number)
|
217
|
+
time - Interval.days(number)
|
218
|
+
end
|
219
|
+
|
220
|
+
def time_after(time, number)
|
221
|
+
time + Interval.days(number)
|
222
|
+
end
|
223
|
+
|
224
|
+
def beginning_of(time)
|
225
|
+
time.beginning_of_day
|
226
|
+
end
|
227
|
+
|
228
|
+
def end_of(time)
|
229
|
+
time.end_of_day
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
class WeekInterval < Treetop::Runtime::SyntaxNode
|
234
|
+
def time_ago(time, number)
|
235
|
+
time - Interval.weeks(number)
|
236
|
+
end
|
237
|
+
|
238
|
+
def time_after(time, number)
|
239
|
+
time + Interval.weeks(number)
|
240
|
+
end
|
241
|
+
|
242
|
+
def beginning_of(time)
|
243
|
+
time.beginning_of_week
|
244
|
+
end
|
245
|
+
|
246
|
+
def end_of(time)
|
247
|
+
time.end_of_week
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
class MonthInterval < Treetop::Runtime::SyntaxNode
|
252
|
+
def time_ago(time, number)
|
253
|
+
time.months_ago(number)
|
254
|
+
end
|
255
|
+
|
256
|
+
def time_after(time, number)
|
257
|
+
time.months_after(number)
|
258
|
+
end
|
259
|
+
|
260
|
+
def beginning_of(time)
|
261
|
+
time.beginning_of_month
|
262
|
+
end
|
263
|
+
|
264
|
+
def end_of(time)
|
265
|
+
time.end_of_month
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
class YearInterval < Treetop::Runtime::SyntaxNode
|
270
|
+
def time_ago(time, number)
|
271
|
+
time.years_ago(number)
|
272
|
+
end
|
273
|
+
|
274
|
+
def time_after(time, number)
|
275
|
+
time.years_after(number)
|
276
|
+
end
|
277
|
+
|
278
|
+
def beginning_of(time)
|
279
|
+
time.beginning_of_year
|
280
|
+
end
|
281
|
+
|
282
|
+
def end_of(time)
|
283
|
+
time.end_of_year
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
class DayName < Treetop::Runtime::SyntaxNode
|
288
|
+
SHORT_NAMES = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat']
|
289
|
+
def value
|
290
|
+
SHORT_NAMES.index text_value[0..2].downcase
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
class MonthName < Treetop::Runtime::SyntaxNode
|
295
|
+
SHORT_NAMES = ['', 'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec']
|
296
|
+
def value
|
297
|
+
SHORT_NAMES.index text_value[0..2].downcase
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
class HourMinuteSecond < Treetop::Runtime::SyntaxNode
|
302
|
+
def value
|
303
|
+
if text_value.downcase == 'beginning'
|
304
|
+
'00:00:00'
|
305
|
+
elsif text_value.downcase == 'end'
|
306
|
+
'23:59:59'
|
307
|
+
else
|
308
|
+
"#{text_value}:00"[0..7]
|
309
|
+
end
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
class ZoneOffset < Treetop::Runtime::SyntaxNode
|
314
|
+
def value
|
315
|
+
::ZoneOffset.parse text_value
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
class Int < Treetop::Runtime::SyntaxNode
|
320
|
+
def value
|
321
|
+
text_value.to_i
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
end
|
326
|
+
end
|