timing 0.0.2 → 0.0.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.
@@ -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