chronic 0.8.0 → 0.9.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7033fa51e89c8552e935aab7ee0bb5673338d063
4
+ data.tar.gz: 21a09efad9e8da2dcd3ed32a5c8605707f36abc7
5
+ SHA512:
6
+ metadata.gz: f3d63e888e712dad2b0d4c70d11688607e56a3d1b4ac11632e14b3f23a57f21a2fd32403f50cf13002a9859607a89e6521b5c39f4ec003edd5631791cf7cf409
7
+ data.tar.gz: 9e2d80dcf087643ce865ede613dbd2d006942b1986e46c02f75b012711ee79bb74f0b576ba812d8b54ab599411213548effc036cc888c97b82c70fdf21627365
data/.travis.yml ADDED
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 1.9.2
5
+ - 1.8.7
6
+ - jruby-19mode
7
+ - jruby-18mode
8
+ - rbx-19mode
data/HISTORY.md CHANGED
@@ -1,3 +1,20 @@
1
+ # 0.9.1 / 2013-02-25
2
+
3
+ * Ensure Chronic strips periods from day portions (#173)
4
+ * Properly numerize "twelfth", "twentieth" etc. (#172, James McKinney)
5
+ * Ensure Chronic is compatible with Ruby 2.0.0 (#165, Ravil Bayramgalin)
6
+
7
+ # 0.9.0 / 2012-12-21
8
+
9
+ * Implement Chronic::Parser class and create an instance of this class
10
+ instead of leaving all data in the class level of Chronic
11
+ * Various bug fixes
12
+ * Add support for excel date formats (#149, @jmondo)
13
+ * Added support for time expressions such as '10 till' or 'half
14
+ past two' (#146, @chicagogrooves)
15
+ * Add support for RepeaterDayName, RepeaterMonthName,
16
+ Ordinal/ScalarDay and Time (#153, @kareemk)
17
+
1
18
  # 0.8.0 / 2012-09-16
2
19
 
3
20
  * Support parsing "<ordinal> of this month" (#109)
data/README.md CHANGED
@@ -1,60 +1,47 @@
1
1
  Chronic
2
2
  =======
3
3
 
4
- ## DESCRIPTION
5
-
6
4
  Chronic is a natural language date/time parser written in pure Ruby. See below
7
5
  for the wide variety of formats Chronic will parse.
8
6
 
7
+ ## Installation
9
8
 
10
- ## INSTALLATION
11
-
12
- ### RubyGems
13
-
14
- $ [sudo] gem install chronic
15
-
16
- ### GitHub
9
+ ```
10
+ $ gem install chronic
11
+ ```
17
12
 
18
- $ git clone git://github.com/mojombo/chronic.git
19
- $ cd chronic && gem build chronic.gemspec
20
- $ gem install chronic-<version>.gem
13
+ ## Usage
21
14
 
15
+ ```ruby
16
+ require 'chronic'
22
17
 
23
- ## USAGE
18
+ Time.now #=> Sun Aug 27 23:18:25 PDT 2006
24
19
 
25
- You can parse strings containing a natural language date using the
26
- `Chronic.parse` method.
20
+ Chronic.parse('tomorrow')
21
+ #=> Mon Aug 28 12:00:00 PDT 2006
27
22
 
28
- require 'chronic'
23
+ Chronic.parse('monday', :context => :past)
24
+ #=> Mon Aug 21 12:00:00 PDT 2006
29
25
 
30
- Time.now #=> Sun Aug 27 23:18:25 PDT 2006
26
+ Chronic.parse('this tuesday 5:00')
27
+ #=> Tue Aug 29 17:00:00 PDT 2006
31
28
 
32
- Chronic.parse('tomorrow')
33
- #=> Mon Aug 28 12:00:00 PDT 2006
29
+ Chronic.parse('this tuesday 5:00', :ambiguous_time_range => :none)
30
+ #=> Tue Aug 29 05:00:00 PDT 2006
34
31
 
35
- Chronic.parse('monday', :context => :past)
36
- #=> Mon Aug 21 12:00:00 PDT 2006
32
+ Chronic.parse('may 27th', :now => Time.local(2000, 1, 1))
33
+ #=> Sat May 27 12:00:00 PDT 2000
37
34
 
38
- Chronic.parse('this tuesday 5:00')
39
- #=> Tue Aug 29 17:00:00 PDT 2006
35
+ Chronic.parse('may 27th', :guess => false)
36
+ #=> Sun May 27 00:00:00 PDT 2007..Mon May 28 00:00:00 PDT 2007
40
37
 
41
- Chronic.parse('this tuesday 5:00', :ambiguous_time_range => :none)
42
- #=> Tue Aug 29 05:00:00 PDT 2006
43
-
44
- Chronic.parse('may 27th', :now => Time.local(2000, 1, 1))
45
- #=> Sat May 27 12:00:00 PDT 2000
46
-
47
- Chronic.parse('may 27th', :guess => false)
48
- #=> Sun May 27 00:00:00 PDT 2007..Mon May 28 00:00:00 PDT 2007
49
-
50
- Chronic.parse('6/4/2012', :endian_precedence => :little)
51
- #=> Fri Apr 06 00:00:00 PDT 2012
52
-
38
+ Chronic.parse('6/4/2012', :endian_precedence => :little)
39
+ #=> Fri Apr 06 00:00:00 PDT 2012
40
+ ```
53
41
 
54
42
  See `Chronic.parse` for detailed usage instructions.
55
43
 
56
-
57
- ## EXAMPLES
44
+ ## Examples
58
45
 
59
46
  Chronic can parse a huge variety of date and time formats. Following is a
60
47
  small sample of strings that will be properly parsed. Parsing is case
@@ -68,12 +55,17 @@ Simple
68
55
  * friday 13:00
69
56
  * mon 2:35
70
57
  * 4pm
58
+ * 10 to 8
59
+ * 10 past 2
60
+ * half past 2
71
61
  * 6 in the morning
72
62
  * friday 1pm
73
63
  * sat 7 in the evening
74
64
  * yesterday
75
65
  * today
76
66
  * tomorrow
67
+ * last week
68
+ * next week
77
69
  * this tuesday
78
70
  * next month
79
71
  * last winter
@@ -139,7 +131,7 @@ Specific Times (many of the above with an added time)
139
131
  * etc
140
132
 
141
133
 
142
- ## TIME ZONES
134
+ ## Time Zones
143
135
 
144
136
  Chronic allows you to set which Time class to use when constructing times. By
145
137
  default, the built in Ruby time class creates times in your system's local
@@ -147,13 +139,14 @@ time zone. You can set this to something like ActiveSupport's
147
139
  [TimeZone](http://api.rubyonrails.org/classes/ActiveSupport/TimeZone.html)
148
140
  class to get full time zone support.
149
141
 
150
- >> Time.zone = "UTC"
151
- >> Chronic.time_class = Time.zone
152
- >> Chronic.parse("June 15 2006 at 5:45 AM")
153
- => Thu, 15 Jun 2006 05:45:00 UTC +00:00
154
-
142
+ ```
143
+ >> Time.zone = "UTC"
144
+ >> Chronic.time_class = Time.zone
145
+ >> Chronic.parse("June 15 2006 at 5:45 AM")
146
+ => Thu, 15 Jun 2006 05:45:00 UTC +00:00
147
+ ```
155
148
 
156
- ## LIMITATIONS
149
+ ## Limitations
157
150
 
158
151
  Chronic uses Ruby's built in Time class for all time storage and computation.
159
152
  Because of this, only times that the Time class can handle will be properly
@@ -161,7 +154,7 @@ parsed. Parsing for times outside of this range will simply return nil.
161
154
  Support for a wider range of times is planned for a future release.
162
155
 
163
156
 
164
- ## CONTRIBUTE
157
+ ## Contribute
165
158
 
166
159
  If you'd like to hack on Chronic, start by forking the repo on GitHub:
167
160
 
@@ -6,8 +6,8 @@ module Chronic
6
6
  attr_reader :handler_method
7
7
 
8
8
  # pattern - An Array of patterns to match tokens against.
9
- # handler_method - A Symbole representing the method to be invoked
10
- # when patterns are matched.
9
+ # handler_method - A Symbol representing the method to be invoked
10
+ # when a pattern matches.
11
11
  def initialize(pattern, handler_method)
12
12
  @pattern = pattern
13
13
  @handler_method = handler_method
@@ -43,14 +43,14 @@ module Chronic
43
43
  if definitions.key?(name.to_sym)
44
44
  sub_handlers = definitions[name.to_sym]
45
45
  else
46
- raise ChronicPain, "Invalid subset #{name} specified"
46
+ raise "Invalid subset #{name} specified"
47
47
  end
48
48
 
49
49
  sub_handlers.each do |sub_handler|
50
50
  return true if sub_handler.match(tokens[token_index..tokens.size], definitions)
51
51
  end
52
52
  else
53
- raise ChronicPain, "Invalid match type: #{element.class}"
53
+ raise "Invalid match type: #{element.class}"
54
54
  end
55
55
  end
56
56
 
@@ -58,13 +58,13 @@ module Chronic
58
58
  return true
59
59
  end
60
60
 
61
- def invoke(type, tokens, options)
61
+ def invoke(type, tokens, parser, options)
62
62
  if Chronic.debug
63
63
  puts "-#{type}"
64
64
  puts "Handler: #{@handler_method}"
65
65
  end
66
66
 
67
- Handlers.send(@handler_method, tokens, options)
67
+ parser.send(@handler_method, tokens, options)
68
68
  end
69
69
 
70
70
  # other - The other Handler object to compare.
@@ -85,4 +85,4 @@ module Chronic
85
85
  end
86
86
 
87
87
  end
88
- end
88
+ end
@@ -4,7 +4,7 @@ module Chronic
4
4
 
5
5
  # Handle month/day
6
6
  def handle_m_d(month, day, time_tokens, options)
7
- month.start = Chronic.now
7
+ month.start = self.now
8
8
  span = month.this(options[:context])
9
9
  year, month = span.begin.year, span.begin.month
10
10
  day_start = Chronic.time_class.local(year, month, day)
@@ -17,7 +17,7 @@ module Chronic
17
17
  month = tokens[0].get_tag(RepeaterMonthName)
18
18
  day = tokens[1].get_tag(ScalarDay).type
19
19
 
20
- return if month_overflow?(Chronic.now.year, month.index, day)
20
+ return if month_overflow?(self.now.year, month.index, day)
21
21
 
22
22
  handle_m_d(month, day, tokens[2..tokens.size], options)
23
23
  end
@@ -34,7 +34,7 @@ module Chronic
34
34
  token_range = 0..0
35
35
  end
36
36
 
37
- return if month_overflow?(Chronic.now.year, month.index, day)
37
+ return if month_overflow?(self.now.year, month.index, day)
38
38
 
39
39
  handle_m_d(month, day, tokens[token_range], options)
40
40
  end
@@ -44,7 +44,7 @@ module Chronic
44
44
  month = tokens[0].get_tag(RepeaterMonthName)
45
45
  day = tokens[1].get_tag(OrdinalDay).type
46
46
 
47
- return if month_overflow?(Chronic.now.year, month.index, day)
47
+ return if month_overflow?(self.now.year, month.index, day)
48
48
 
49
49
  handle_m_d(month, day, tokens[2..tokens.size], options)
50
50
  end
@@ -61,7 +61,7 @@ module Chronic
61
61
  month = tokens[1].get_tag(RepeaterMonthName)
62
62
  day = tokens[0].get_tag(OrdinalDay).type
63
63
 
64
- return if month_overflow?(Chronic.now.year, month.index, day)
64
+ return if month_overflow?(self.now.year, month.index, day)
65
65
 
66
66
  handle_m_d(month, day, tokens[2..tokens.size], options)
67
67
  end
@@ -87,7 +87,7 @@ module Chronic
87
87
  month = tokens[1].get_tag(RepeaterMonthName)
88
88
  day = tokens[0].get_tag(ScalarDay).type
89
89
 
90
- return if month_overflow?(Chronic.now.year, month.index, day)
90
+ return if month_overflow?(self.now.year, month.index, day)
91
91
 
92
92
  handle_m_d(month, day, tokens[2..tokens.size], options)
93
93
  end
@@ -104,7 +104,7 @@ module Chronic
104
104
  token_range = 0..0
105
105
  end
106
106
 
107
- return if month_overflow?(Chronic.now.year, month.index, day)
107
+ return if month_overflow?(self.now.year, month.index, day)
108
108
 
109
109
  handle_m_d(month, day, tokens[token_range], options)
110
110
  end
@@ -229,13 +229,14 @@ module Chronic
229
229
  def handle_sm_sd(tokens, options)
230
230
  month = tokens[0].get_tag(ScalarMonth).type
231
231
  day = tokens[1].get_tag(ScalarDay).type
232
- year = Chronic.now.year
232
+ year = self.now.year
233
233
  time_tokens = tokens.last(tokens.size - 2)
234
234
 
235
235
  return if month_overflow?(year, month, day)
236
236
 
237
237
  begin
238
238
  day_start = Chronic.time_class.local(year, month, day)
239
+ day_start = Chronic.time_class.local(year + 1, month, day) if options[:context] == :future && day_start < now
239
240
  day_or_time(day_start, time_tokens, options)
240
241
  rescue ArgumentError
241
242
  nil
@@ -249,11 +250,7 @@ module Chronic
249
250
  handle_sm_sd(new_tokens + time_tokens, options)
250
251
  end
251
252
 
252
- # Handle scalar-month/scalar-year
253
- def handle_sm_sy(tokens, options)
254
- month = tokens[0].get_tag(ScalarMonth).type
255
- year = tokens[1].get_tag(ScalarYear).type
256
-
253
+ def handle_year_and_month(year, month)
257
254
  if month == 12
258
255
  next_month_year = year + 1
259
256
  next_month_month = 1
@@ -270,18 +267,64 @@ module Chronic
270
267
  end
271
268
  end
272
269
 
270
+ # Handle scalar-month/scalar-year
271
+ def handle_sm_sy(tokens, options)
272
+ month = tokens[0].get_tag(ScalarMonth).type
273
+ year = tokens[1].get_tag(ScalarYear).type
274
+ handle_year_and_month(year, month)
275
+ end
276
+
277
+ # Handle scalar-year/scalar-month
278
+ def handle_sy_sm(tokens, options)
279
+ year = tokens[0].get_tag(ScalarYear).type
280
+ month = tokens[1].get_tag(ScalarMonth).type
281
+ handle_year_and_month(year, month)
282
+ end
283
+
273
284
  # Handle RepeaterDayName RepeaterMonthName OrdinalDay
274
285
  def handle_rdn_rmn_od(tokens, options)
275
286
  month = tokens[1].get_tag(RepeaterMonthName)
276
287
  day = tokens[2].get_tag(OrdinalDay).type
277
- year = Chronic.now.year
288
+ time_tokens = tokens.last(tokens.size - 3)
289
+ year = self.now.year
278
290
 
279
291
  return if month_overflow?(year, month.index, day)
280
292
 
281
293
  begin
282
- start_time = Chronic.time_class.local(year, month.index, day)
283
- end_time = time_with_rollover(year, month.index, day + 1)
284
- Span.new(start_time, end_time)
294
+ if time_tokens.empty?
295
+ start_time = Chronic.time_class.local(year, month.index, day)
296
+ end_time = time_with_rollover(year, month.index, day + 1)
297
+ Span.new(start_time, end_time)
298
+ else
299
+ day_start = Chronic.time_class.local(year, month.index, day)
300
+ day_or_time(day_start, time_tokens, options)
301
+ end
302
+ rescue ArgumentError
303
+ nil
304
+ end
305
+ end
306
+
307
+ # Handle RepeaterDayName OrdinalDay
308
+ def handle_rdn_od(tokens, options)
309
+ day = tokens[1].get_tag(OrdinalDay).type
310
+ time_tokens = tokens.last(tokens.size - 2)
311
+ year = self.now.year
312
+ month = self.now.month
313
+ if options[:context] == :future
314
+ self.now.day > day ? month += 1 : month
315
+ end
316
+
317
+ return if month_overflow?(year, month, day)
318
+
319
+ begin
320
+ if time_tokens.empty?
321
+ start_time = Chronic.time_class.local(year, month, day)
322
+ end_time = time_with_rollover(year, month, day + 1)
323
+ Span.new(start_time, end_time)
324
+ else
325
+ day_start = Chronic.time_class.local(year, month, day)
326
+ day_or_time(day_start, time_tokens, options)
327
+ end
285
328
  rescue ArgumentError
286
329
  nil
287
330
  end
@@ -291,14 +334,20 @@ module Chronic
291
334
  def handle_rdn_rmn_sd(tokens, options)
292
335
  month = tokens[1].get_tag(RepeaterMonthName)
293
336
  day = tokens[2].get_tag(ScalarDay).type
294
- year = Chronic.now.year
337
+ time_tokens = tokens.last(tokens.size - 3)
338
+ year = self.now.year
295
339
 
296
340
  return if month_overflow?(year, month.index, day)
297
341
 
298
342
  begin
299
- start_time = Chronic.time_class.local(year, month.index, day)
300
- end_time = time_with_rollover(year, month.index, day + 1)
301
- Span.new(start_time, end_time)
343
+ if time_tokens.empty?
344
+ start_time = Chronic.time_class.local(year, month.index, day)
345
+ end_time = time_with_rollover(year, month.index, day + 1)
346
+ Span.new(start_time, end_time)
347
+ else
348
+ day_start = Chronic.time_class.local(year, month.index, day)
349
+ day_or_time(day_start, time_tokens, options)
350
+ end
302
351
  rescue ArgumentError
303
352
  nil
304
353
  end
@@ -332,7 +381,8 @@ module Chronic
332
381
  end_time = Chronic.time_class.local(year, month, day + 1, h, m, s)
333
382
  else
334
383
  time = Chronic.time_class.local(year, month, day)
335
- end_time = Chronic.time_class.local(year, month, day + 1)
384
+ day += 1 unless day >= 31
385
+ end_time = Chronic.time_class.local(year, month, day)
336
386
  end
337
387
  Span.new(time, end_time)
338
388
  end
@@ -364,8 +414,7 @@ module Chronic
364
414
 
365
415
  # Handle scalar/repeater/pointer
366
416
  def handle_s_r_p(tokens, options)
367
- repeater = tokens[1].get_tag(Repeater)
368
- span = Span.new(Chronic.now, Chronic.now + 1)
417
+ span = Span.new(self.now, self.now + 1)
369
418
 
370
419
  handle_srp(tokens, span, options)
371
420
  end
@@ -382,6 +431,13 @@ module Chronic
382
431
  handle_srp(tokens, anchor_span, options)
383
432
  end
384
433
 
434
+ def handle_s_r_a_s_r_p_a(tokens, options)
435
+ anchor_span = get_anchor(tokens[4..tokens.size - 1], options)
436
+
437
+ span = handle_srp(tokens[0..1]+tokens[4..6], anchor_span, options)
438
+ handle_srp(tokens[2..3]+tokens[4..6], span, options)
439
+ end
440
+
385
441
  # narrows
386
442
 
387
443
  # Handle oridinal repeaters
@@ -421,7 +477,7 @@ module Chronic
421
477
  outer_span = Span.new(day_start, day_start + (24 * 60 * 60))
422
478
 
423
479
  if !time_tokens.empty?
424
- Chronic.now = outer_span.begin
480
+ self.now = outer_span.begin
425
481
  get_anchor(dealias_and_disambiguate_times(time_tokens, options), options)
426
482
  else
427
483
  outer_span
@@ -439,7 +495,7 @@ module Chronic
439
495
  end
440
496
 
441
497
  head = repeaters.shift
442
- head.start = Chronic.now
498
+ head.start = self.now
443
499
 
444
500
  case grabber.type
445
501
  when :last
@@ -453,7 +509,7 @@ module Chronic
453
509
  when :next
454
510
  outer_span = head.next(:future)
455
511
  else
456
- raise ChronicPain, "Invalid grabber"
512
+ raise "Invalid grabber"
457
513
  end
458
514
 
459
515
  if Chronic.debug
@@ -37,7 +37,16 @@ module Chronic
37
37
  ['seventh', '7'],
38
38
  ['eighth', '8'],
39
39
  ['ninth', '9'],
40
- ['tenth', '10']
40
+ ['tenth', '10'],
41
+ ['twelfth', '12'],
42
+ ['twentieth', '20'],
43
+ ['thirtieth', '30'],
44
+ ['fourtieth', '40'],
45
+ ['fiftieth', '50'],
46
+ ['sixtieth', '60'],
47
+ ['seventieth', '70'],
48
+ ['eightieth', '80'],
49
+ ['ninetieth', '90']
41
50
  ]
42
51
 
43
52
  TEN_PREFIXES = [
@@ -90,7 +99,7 @@ module Chronic
90
99
  # hundreds, thousands, millions, etc.
91
100
 
92
101
  BIG_PREFIXES.each do |bp|
93
- string.gsub!(/(?:<num>)?(\d*) *#{bp[0]}/i) { '<num>' + (bp[1] * $1.to_i).to_s}
102
+ string.gsub!(/(?:<num>)?(\d*) *#{bp[0]}/i) { $1.empty? ? bp[1] : '<num>' + (bp[1] * $1.to_i).to_s}
94
103
  andition(string)
95
104
  end
96
105