timing 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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