chronic 0.9.1 → 0.10.2

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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -4
  3. data/HISTORY.md +21 -0
  4. data/README.md +8 -0
  5. data/Rakefile +28 -8
  6. data/chronic.gemspec +8 -5
  7. data/lib/chronic/date.rb +82 -0
  8. data/lib/chronic/handler.rb +34 -25
  9. data/lib/chronic/handlers.rb +22 -3
  10. data/lib/chronic/ordinal.rb +22 -20
  11. data/lib/chronic/parser.rb +31 -26
  12. data/lib/chronic/repeater.rb +18 -18
  13. data/lib/chronic/repeaters/repeater_day.rb +4 -3
  14. data/lib/chronic/repeaters/repeater_day_name.rb +5 -4
  15. data/lib/chronic/repeaters/repeater_day_portion.rb +4 -3
  16. data/lib/chronic/repeaters/repeater_fortnight.rb +4 -3
  17. data/lib/chronic/repeaters/repeater_hour.rb +4 -3
  18. data/lib/chronic/repeaters/repeater_minute.rb +4 -3
  19. data/lib/chronic/repeaters/repeater_month.rb +5 -4
  20. data/lib/chronic/repeaters/repeater_month_name.rb +4 -3
  21. data/lib/chronic/repeaters/repeater_season.rb +5 -3
  22. data/lib/chronic/repeaters/repeater_second.rb +4 -3
  23. data/lib/chronic/repeaters/repeater_time.rb +35 -25
  24. data/lib/chronic/repeaters/repeater_week.rb +4 -3
  25. data/lib/chronic/repeaters/repeater_weekday.rb +4 -3
  26. data/lib/chronic/repeaters/repeater_weekend.rb +4 -3
  27. data/lib/chronic/repeaters/repeater_year.rb +5 -4
  28. data/lib/chronic/scalar.rb +34 -69
  29. data/lib/chronic/separator.rb +107 -8
  30. data/lib/chronic/sign.rb +49 -0
  31. data/lib/chronic/tag.rb +5 -4
  32. data/lib/chronic/time.rb +40 -0
  33. data/lib/chronic.rb +16 -10
  34. data/test/helper.rb +2 -2
  35. data/test/test_chronic.rb +55 -4
  36. data/test/test_handler.rb +20 -0
  37. data/test/test_parsing.rb +75 -3
  38. data/test/test_repeater_time.rb +18 -0
  39. metadata +37 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7033fa51e89c8552e935aab7ee0bb5673338d063
4
- data.tar.gz: 21a09efad9e8da2dcd3ed32a5c8605707f36abc7
3
+ metadata.gz: d0bcdd2460eb86f8898212a9eca5e325c9aa5e61
4
+ data.tar.gz: d8abd19da58e28473139b8f137991cb0ee150c49
5
5
  SHA512:
6
- metadata.gz: f3d63e888e712dad2b0d4c70d11688607e56a3d1b4ac11632e14b3f23a57f21a2fd32403f50cf13002a9859607a89e6521b5c39f4ec003edd5631791cf7cf409
7
- data.tar.gz: 9e2d80dcf087643ce865ede613dbd2d006942b1986e46c02f75b012711ee79bb74f0b576ba812d8b54ab599411213548effc036cc888c97b82c70fdf21627365
6
+ metadata.gz: 04bfd4f32ce8f8ef6585042425ea64cc95d07998287fb16fa45b8d0a06d5dd48f8b85e6fecbf00e69101e560f21527ef6539a8994a9457357d25c2c832c7b87f
7
+ data.tar.gz: 87db3c22292ed9d5a22ecc84e6b14dafb2ed1c7a4c2f829873593b86b37707999adfaf43a5e0d801daffaaec9c7b599beade2d366d108c60bf2e8989998ed4b4
data/.gitignore CHANGED
@@ -1,6 +1,6 @@
1
- pkg
2
1
  *.rbc
3
- rdoc
4
2
  .yardoc
5
- doc
6
- tags
3
+ /pkg/
4
+ /coverage/
5
+ /doc/
6
+ /tags/
data/HISTORY.md CHANGED
@@ -1,3 +1,24 @@
1
+ # 0.10.2 / 2013-09-09
2
+
3
+ * Fix 1.8.7 support (due to be dropped in 0.11.0)
4
+ * Bugfix for times with negative zones
5
+
6
+ # 0.10.1 / 2013-08-27
7
+
8
+ * Support `ActiveSupport::TimeZone` (#209, #208)
9
+
10
+ # 0.10.0 / 2013-08-25
11
+
12
+ * Chronic will parse subseconds correctly
13
+ for all supported date/time formats (#195, #198 and #200)
14
+ * Support for date format: dd.mm.yyyy (#197)
15
+ * Option `:hours24` to parse as 24 hour clock (#201 and #202)
16
+ * `:guess` option allows to specify which part of Span to return.
17
+ (accepted values `false`,`true`,`:begin`, `:middle`, `:end`)
18
+ * Replace `rcov` with `SimpleCov` for coverage generation
19
+ * Add more tests
20
+ * Various changes in codebase (#202 and #206)
21
+
1
22
  # 0.9.1 / 2013-02-25
2
23
 
3
24
  * Ensure Chronic strips periods from day portions (#173)
data/README.md CHANGED
@@ -37,8 +37,15 @@ Chronic.parse('may 27th', :guess => false)
37
37
 
38
38
  Chronic.parse('6/4/2012', :endian_precedence => :little)
39
39
  #=> Fri Apr 06 00:00:00 PDT 2012
40
+
41
+ Chronic.parse('INVALID DATE')
42
+ #=> nil
40
43
  ```
41
44
 
45
+ If the parser can find a date or time, either a Time or Chronic::Span
46
+ will be returned (depending on the value of `:guess`). If no
47
+ date or time can be found, `nil` will be returned.
48
+
42
49
  See `Chronic.parse` for detailed usage instructions.
43
50
 
44
51
  ## Examples
@@ -128,6 +135,7 @@ Specific Times (many of the above with an added time)
128
135
  * January 5 at 7pm
129
136
  * 22nd of june at 8am
130
137
  * 1979-05-27 05:00:00
138
+ * 03/01/2012 07:25:09.234567
131
139
  * etc
132
140
 
133
141
 
data/Rakefile CHANGED
@@ -5,17 +5,37 @@ def version
5
5
  contents[/VERSION = "([^"]+)"/, 1]
6
6
  end
7
7
 
8
- task :test do
8
+ def do_test
9
9
  $:.unshift './test'
10
10
  Dir.glob('test/test_*.rb').each { |t| require File.basename(t) }
11
11
  end
12
12
 
13
- desc "Generate RCov test coverage and open in your browser"
13
+ def open_command
14
+ case RUBY_PLATFORM
15
+ when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
16
+ 'start'
17
+ when /darwin|mac os/
18
+ 'open'
19
+ else
20
+ 'xdg-open'
21
+ end
22
+ end
23
+
24
+ task :test do
25
+ do_test
26
+ end
27
+
28
+ desc "Generate SimpleCov test coverage and open in your browser"
14
29
  task :coverage do
15
- require 'rcov'
16
- sh "rm -fr coverage"
17
- sh "rcov test/test_*.rb"
18
- sh "open coverage/index.html"
30
+ require 'simplecov'
31
+ FileUtils.rm_rf("./coverage")
32
+ SimpleCov.command_name 'Unit Tests'
33
+ SimpleCov.at_exit do
34
+ SimpleCov.result.format!
35
+ sh "#{open_command} #{SimpleCov.coverage_path}/index.html"
36
+ end
37
+ SimpleCov.start
38
+ do_test
19
39
  end
20
40
 
21
41
  desc "Open an irb session preloaded with this library"
@@ -38,9 +58,9 @@ end
38
58
 
39
59
  desc "Build a gem from the gemspec"
40
60
  task :build do
41
- sh "mkdir -p pkg"
61
+ FileUtils.mkdir_p "pkg"
42
62
  sh "gem build chronic.gemspec"
43
- sh "mv chronic-#{version}.gem pkg"
63
+ FileUtils.mv("./chronic-#{version}.gem", "pkg")
44
64
  end
45
65
 
46
66
  task :default => :test
data/chronic.gemspec CHANGED
@@ -8,13 +8,16 @@ Gem::Specification.new do |s|
8
8
  s.summary = 'Natural language date/time parsing.'
9
9
  s.description = 'Chronic is a natural language date/time parser written in pure Ruby.'
10
10
  s.authors = ['Tom Preston-Werner', 'Lee Jarvis']
11
- s.email = ['tom@mojombo.com', 'lee@jarvis.co']
11
+ s.email = ['tom@mojombo.com', 'ljjarvis@gmail.com']
12
12
  s.homepage = 'http://github.com/mojombo/chronic'
13
+ s.license = 'MIT'
13
14
  s.rdoc_options = ['--charset=UTF-8']
14
15
  s.extra_rdoc_files = %w[README.md HISTORY.md LICENSE]
15
- s.files = `git ls-files`.split("\n")
16
- s.test_files = `git ls-files -- test`.split("\n")
16
+ s.files = `git ls-files`.split($/)
17
+ s.test_files = `git ls-files -- test`.split($/)
17
18
 
18
19
  s.add_development_dependency 'rake'
19
- s.add_development_dependency 'minitest'
20
- end
20
+ s.add_development_dependency 'simplecov'
21
+ s.add_development_dependency 'minitest', '~> 5.0'
22
+ s.add_development_dependency 'activesupport'
23
+ end
@@ -0,0 +1,82 @@
1
+ module Chronic
2
+ class Date
3
+ YEAR_MONTHS = 12
4
+ MONTH_DAYS = [nil, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
5
+ MONTH_DAYS_LEAP = [nil, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
6
+ YEAR_SECONDS = 31_536_000 # 365 * 24 * 60 * 60
7
+ SEASON_SECONDS = 7_862_400 # 91 * 24 * 60 * 60
8
+ MONTH_SECONDS = 2_592_000 # 30 * 24 * 60 * 60
9
+ FORTNIGHT_SECONDS = 1_209_600 # 14 * 24 * 60 * 60
10
+ WEEK_SECONDS = 604_800 # 7 * 24 * 60 * 60
11
+ WEEKEND_SECONDS = 172_800 # 2 * 24 * 60 * 60
12
+ DAY_SECONDS = 86_400 # 24 * 60 * 60
13
+ MONTHS = {
14
+ :january => 1,
15
+ :february => 2,
16
+ :march => 3,
17
+ :april => 4,
18
+ :may => 5,
19
+ :june => 6,
20
+ :july => 7,
21
+ :august => 8,
22
+ :september => 9,
23
+ :october => 10,
24
+ :november => 11,
25
+ :december => 12
26
+ }
27
+ DAYS = {
28
+ :sunday => 0,
29
+ :monday => 1,
30
+ :tuesday => 2,
31
+ :wednesday => 3,
32
+ :thursday => 4,
33
+ :friday => 5,
34
+ :saturday => 6
35
+ }
36
+
37
+ # Checks if given number could be day
38
+ def self.could_be_day?(day)
39
+ day >= 1 && day <= 31
40
+ end
41
+
42
+ # Checks if given number could be month
43
+ def self.could_be_month?(month)
44
+ month >= 1 && month <= 12
45
+ end
46
+
47
+ # Checks if given number could be year
48
+ def self.could_be_year?(year)
49
+ year >= 1 && year <= 9999
50
+ end
51
+
52
+ # Build a year from a 2 digit suffix.
53
+ #
54
+ # year - The two digit Integer year to build from.
55
+ # bias - The Integer amount of future years to bias.
56
+ #
57
+ # Examples:
58
+ #
59
+ # make_year(96, 50) #=> 1996
60
+ # make_year(79, 20) #=> 2079
61
+ # make_year(00, 50) #=> 2000
62
+ #
63
+ # Returns The Integer 4 digit year.
64
+ def self.make_year(year, bias)
65
+ return year if year.to_s.size > 2
66
+ start_year = Chronic.time_class.now.year - bias
67
+ century = (start_year / 100) * 100
68
+ full_year = century + year
69
+ full_year += 100 if full_year < start_year
70
+ full_year
71
+ end
72
+
73
+ def self.month_overflow?(year, month, day)
74
+ if ::Date.leap?(year)
75
+ day > Date::MONTH_DAYS_LEAP[month]
76
+ else
77
+ day > Date::MONTH_DAYS[month]
78
+ end
79
+ end
80
+
81
+ end
82
+ end
@@ -19,39 +19,48 @@ module Chronic
19
19
  # Returns true if a match is found.
20
20
  def match(tokens, definitions)
21
21
  token_index = 0
22
+ @pattern.each do |elements|
23
+ was_optional = false
24
+ elements = [elements] unless elements.is_a?(Array)
25
+
26
+ elements.each_index do |i|
27
+ name = elements[i].to_s
28
+ optional = name[-1, 1] == '?'
29
+ name = name.chop if optional
30
+
31
+ case elements[i]
32
+ when Symbol
33
+ if tags_match?(name, tokens, token_index)
34
+ token_index += 1
35
+ break
36
+ else
37
+ if optional
38
+ was_optional = true
39
+ next
40
+ elsif i + 1 < elements.count
41
+ next
42
+ else
43
+ return false unless was_optional
44
+ end
45
+ end
22
46
 
23
- @pattern.each do |element|
24
- name = element.to_s
25
- optional = name[-1, 1] == '?'
26
- name = name.chop if optional
47
+ when String
48
+ return true if optional && token_index == tokens.size
27
49
 
28
- case element
29
- when Symbol
30
- if tags_match?(name, tokens, token_index)
31
- token_index += 1
32
- next
33
- else
34
- if optional
35
- next
50
+ if definitions.key?(name.to_sym)
51
+ sub_handlers = definitions[name.to_sym]
36
52
  else
37
- return false
53
+ raise "Invalid subset #{name} specified"
38
54
  end
39
- end
40
- when String
41
- return true if optional && token_index == tokens.size
42
55
 
43
- if definitions.key?(name.to_sym)
44
- sub_handlers = definitions[name.to_sym]
56
+ sub_handlers.each do |sub_handler|
57
+ return true if sub_handler.match(tokens[token_index..tokens.size], definitions)
58
+ end
45
59
  else
46
- raise "Invalid subset #{name} specified"
47
- end
48
-
49
- sub_handlers.each do |sub_handler|
50
- return true if sub_handler.match(tokens[token_index..tokens.size], definitions)
60
+ raise "Invalid match type: #{elements[i].class}"
51
61
  end
52
- else
53
- raise "Invalid match type: #{element.class}"
54
62
  end
63
+
55
64
  end
56
65
 
57
66
  return false if token_index != tokens.size
@@ -134,6 +134,8 @@ module Chronic
134
134
  def handle_generic(tokens, options)
135
135
  t = Chronic.time_class.parse(options[:text])
136
136
  Span.new(t, t + 1)
137
+ rescue ArgumentError => e
138
+ raise e unless e.message =~ /out of range/
137
139
  end
138
140
 
139
141
  # Handle repeater-month-name/scalar-day/scalar-year
@@ -304,6 +306,23 @@ module Chronic
304
306
  end
305
307
  end
306
308
 
309
+ # Handle RepeaterDayName RepeaterMonthName OrdinalDay ScalarYear
310
+ def handle_rdn_rmn_od_sy(tokens, options)
311
+ month = tokens[1].get_tag(RepeaterMonthName)
312
+ day = tokens[2].get_tag(OrdinalDay).type
313
+ year = tokens[3].get_tag(ScalarYear).type
314
+
315
+ return if month_overflow?(year, month.index, day)
316
+
317
+ begin
318
+ start_time = Chronic.time_class.local(year, month.index, day)
319
+ end_time = time_with_rollover(year, month.index, day + 1)
320
+ Span.new(start_time, end_time)
321
+ rescue ArgumentError
322
+ nil
323
+ end
324
+ end
325
+
307
326
  # Handle RepeaterDayName OrdinalDay
308
327
  def handle_rdn_od(tokens, options)
309
328
  day = tokens[1].get_tag(OrdinalDay).type
@@ -476,9 +495,9 @@ module Chronic
476
495
  def day_or_time(day_start, time_tokens, options)
477
496
  outer_span = Span.new(day_start, day_start + (24 * 60 * 60))
478
497
 
479
- if !time_tokens.empty?
498
+ unless time_tokens.empty?
480
499
  self.now = outer_span.begin
481
- get_anchor(dealias_and_disambiguate_times(time_tokens, options), options)
500
+ get_anchor(dealias_and_disambiguate_times(time_tokens, options), options.merge(:context => :future))
482
501
  else
483
502
  outer_span
484
503
  end
@@ -525,7 +544,7 @@ module Chronic
525
544
  end
526
545
 
527
546
  def month_overflow?(year, month, day)
528
- if Date.leap?(year)
547
+ if ::Date.leap?(year)
529
548
  day > RepeaterMonth::MONTH_DAYS_LEAP[month - 1]
530
549
  else
531
550
  day > RepeaterMonth::MONTH_DAYS[month - 1]
@@ -9,26 +9,16 @@ module Chronic
9
9
  #
10
10
  # Returns an Array of tokens.
11
11
  def self.scan(tokens, options)
12
- tokens.each do |token|
13
- if t = scan_for_ordinals(token) then token.tag(t) end
14
- if t = scan_for_days(token) then token.tag(t) end
15
- end
16
- end
17
-
18
- # token - The Token object we want to scan.
19
- #
20
- # Returns a new Ordinal object.
21
- def self.scan_for_ordinals(token)
22
- Ordinal.new($1.to_i) if token.word =~ /^(\d*)(st|nd|rd|th)$/
23
- end
24
-
25
- # token - The Token object we want to scan.
26
- #
27
- # Returns a new Ordinal object.
28
- def self.scan_for_days(token)
29
- if token.word =~ /^(\d*)(st|nd|rd|th)$/
30
- unless $1.to_i > 31 || $1.to_i < 1
31
- OrdinalDay.new(token.word.to_i)
12
+ tokens.each_index do |i|
13
+ if tokens[i].word =~ /^(\d+)(st|nd|rd|th|\.)$/
14
+ ordinal = $1.to_i
15
+ tokens[i].tag(Ordinal.new(ordinal))
16
+ tokens[i].tag(OrdinalDay.new(ordinal)) if Chronic::Date::could_be_day?(ordinal)
17
+ tokens[i].tag(OrdinalMonth.new(ordinal)) if Chronic::Date::could_be_month?(ordinal)
18
+ if Chronic::Date::could_be_year?(ordinal)
19
+ year = Chronic::Date::make_year(ordinal, options[:ambiguous_year_future_bias])
20
+ tokens[i].tag(OrdinalYear.new(year.to_i))
21
+ end
32
22
  end
33
23
  end
34
24
  end
@@ -44,4 +34,16 @@ module Chronic
44
34
  end
45
35
  end
46
36
 
37
+ class OrdinalMonth < Ordinal #:nodoc:
38
+ def to_s
39
+ super << '-month-' << @type.to_s
40
+ end
41
+ end
42
+
43
+ class OrdinalYear < Ordinal #:nodoc:
44
+ def to_s
45
+ super << '-year-' << @type.to_s
46
+ end
47
+ end
48
+
47
49
  end
@@ -8,6 +8,7 @@ module Chronic
8
8
  DEFAULT_OPTIONS = {
9
9
  :context => :future,
10
10
  :now => nil,
11
+ :hours24 => nil,
11
12
  :guess => true,
12
13
  :ambiguous_time_range => 6,
13
14
  :endian_precedence => [:middle, :little],
@@ -23,10 +24,13 @@ module Chronic
23
24
  # given, it will assume it is in the past.
24
25
  # :now - Time, all computations will be based off of time
25
26
  # instead of Time.now.
27
+ # :hours24 - Time will be parsed as it would be 24 hour clock.
26
28
  # :guess - By default the parser will guess a single point in time
27
29
  # for the given date or time. If you'd rather have the
28
30
  # entire time span returned, set this to false
29
- # and a Chronic::Span will be returned.
31
+ # and a Chronic::Span will be returned. Setting :guess to :end
32
+ # will return last time from Span, to :middle for middle (same as just true)
33
+ # and :begin for first time from span.
30
34
  # :ambiguous_time_range - If an Integer is given, ambiguous times
31
35
  # (like 5:00) will be assumed to be within the range of
32
36
  # that time in the AM to that time in the PM. For
@@ -58,9 +62,7 @@ module Chronic
58
62
 
59
63
  puts "+#{'-' * 51}\n| #{tokens}\n+#{'-' * 51}" if Chronic.debug
60
64
 
61
- if span
62
- options[:guess] ? guess(span) : span
63
- end
65
+ guess(span, options[:guess]) if span
64
66
  end
65
67
 
66
68
  # Clean up the specified text ready for parsing.
@@ -86,14 +88,16 @@ module Chronic
86
88
  # Returns a new String ready for Chronic to parse.
87
89
  def pre_normalize(text)
88
90
  text = text.to_s.downcase
91
+ text.gsub!(/\b(\d{2})\.(\d{2})\.(\d{4})\b/, '\3 / \2 / \1')
89
92
  text.gsub!(/\b([ap])\.m\.?/, '\1m')
93
+ text.gsub!(/(\s+|:\d{2}|:\d{2}\.\d{3})\-(\d{2}:?\d{2})\b/, '\1tzminus\2')
90
94
  text.gsub!(/\./, ':')
95
+ text.gsub!(/([ap]):m:?/, '\1m')
91
96
  text.gsub!(/['"]/, '')
92
97
  text.gsub!(/,/, ' ')
93
98
  text.gsub!(/^second /, '2nd ')
94
99
  text.gsub!(/\bsecond (of|day|month|hour|minute|second)\b/, '2nd \1')
95
100
  text = Numerizer.numerize(text)
96
- text.gsub!(/\-(\d{2}:?\d{2})\b/, 'tzminus\1')
97
101
  text.gsub!(/([\/\-\,\@])/) { ' ' + $1 + ' ' }
98
102
  text.gsub!(/(?:^|\s)0(\d+:\d+\s*pm?\b)/, ' \1')
99
103
  text.gsub!(/\btoday\b/, 'this day')
@@ -126,12 +130,11 @@ module Chronic
126
130
  # span - The Chronic::Span object to calcuate a guess from.
127
131
  #
128
132
  # Returns a new Time object.
129
- def guess(span)
130
- if span.width > 1
131
- span.begin + (span.width / 2)
132
- else
133
- span.begin
134
- end
133
+ def guess(span, mode = :middle)
134
+ return span if not mode
135
+ return span.begin + span.width / 2 if span.width > 1 and (mode == true or mode == :middle)
136
+ return span.end if mode == :end
137
+ span.begin
135
138
  end
136
139
 
137
140
  # List of Handler definitions. See Chronic.parse for a list of options this
@@ -149,19 +152,21 @@ module Chronic
149
152
  ],
150
153
 
151
154
  :date => [
152
- Handler.new([:repeater_day_name, :repeater_month_name, :scalar_day, :repeater_time, :separator_slash_or_dash?, :time_zone, :scalar_year], :handle_generic),
155
+ Handler.new([:repeater_day_name, :repeater_month_name, :scalar_day, :repeater_time, [:separator_slash?, :separator_dash?], :time_zone, :scalar_year], :handle_generic),
153
156
  Handler.new([:repeater_day_name, :repeater_month_name, :scalar_day], :handle_rdn_rmn_sd),
154
157
  Handler.new([:repeater_day_name, :repeater_month_name, :scalar_day, :scalar_year], :handle_rdn_rmn_sd_sy),
155
158
  Handler.new([:repeater_day_name, :repeater_month_name, :ordinal_day], :handle_rdn_rmn_od),
159
+ Handler.new([:repeater_day_name, :repeater_month_name, :ordinal_day, :scalar_year], :handle_rdn_rmn_od_sy),
156
160
  Handler.new([:repeater_day_name, :repeater_month_name, :scalar_day, :separator_at?, 'time?'], :handle_rdn_rmn_sd),
157
161
  Handler.new([:repeater_day_name, :repeater_month_name, :ordinal_day, :separator_at?, 'time?'], :handle_rdn_rmn_od),
158
162
  Handler.new([:repeater_day_name, :ordinal_day, :separator_at?, 'time?'], :handle_rdn_od),
159
- Handler.new([:scalar_year, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_day, :repeater_time, :time_zone], :handle_generic),
163
+ Handler.new([:scalar_year, [:separator_slash, :separator_dash], :scalar_month, [:separator_slash, :separator_dash], :scalar_day, :repeater_time, :time_zone], :handle_generic),
164
+ Handler.new([:ordinal_day], :handle_generic),
160
165
  Handler.new([:repeater_month_name, :scalar_day, :scalar_year], :handle_rmn_sd_sy),
161
166
  Handler.new([:repeater_month_name, :ordinal_day, :scalar_year], :handle_rmn_od_sy),
162
167
  Handler.new([:repeater_month_name, :scalar_day, :scalar_year, :separator_at?, 'time?'], :handle_rmn_sd_sy),
163
168
  Handler.new([:repeater_month_name, :ordinal_day, :scalar_year, :separator_at?, 'time?'], :handle_rmn_od_sy),
164
- Handler.new([:repeater_month_name, :separator_slash_or_dash?, :scalar_day, :separator_at?, 'time?'], :handle_rmn_sd),
169
+ Handler.new([:repeater_month_name, [:separator_slash?, :separator_dash?], :scalar_day, :separator_at?, 'time?'], :handle_rmn_sd),
165
170
  Handler.new([:repeater_time, :repeater_day_portion?, :separator_on?, :repeater_month_name, :scalar_day], :handle_rmn_sd_on),
166
171
  Handler.new([:repeater_month_name, :ordinal_day, :separator_at?, 'time?'], :handle_rmn_od),
167
172
  Handler.new([:ordinal_day, :repeater_month_name, :scalar_year, :separator_at?, 'time?'], :handle_od_rmn_sy),
@@ -171,12 +176,12 @@ module Chronic
171
176
  Handler.new([:repeater_time, :repeater_day_portion?, :separator_on?, :repeater_month_name, :ordinal_day], :handle_rmn_od_on),
172
177
  Handler.new([:repeater_month_name, :scalar_year], :handle_rmn_sy),
173
178
  Handler.new([:scalar_day, :repeater_month_name, :scalar_year, :separator_at?, 'time?'], :handle_sd_rmn_sy),
174
- Handler.new([:scalar_day, :separator_slash_or_dash?, :repeater_month_name, :separator_at?, 'time?'], :handle_sd_rmn),
175
- Handler.new([:scalar_year, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_day, :separator_at?, 'time?'], :handle_sy_sm_sd),
176
- Handler.new([:scalar_year, :separator_slash_or_dash, :scalar_month], :handle_sy_sm),
177
- Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_year], :handle_sm_sy),
178
- Handler.new([:scalar_day, :separator_slash_or_dash, :repeater_month_name, :separator_slash_or_dash, :scalar_year, :repeater_time?], :handle_sm_rmn_sy),
179
- Handler.new([:scalar_year, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar?, :time_zone], :handle_generic),
179
+ Handler.new([:scalar_day, [:separator_slash?, :separator_dash?], :repeater_month_name, :separator_at?, 'time?'], :handle_sd_rmn),
180
+ Handler.new([:scalar_year, [:separator_slash, :separator_dash], :scalar_month, [:separator_slash, :separator_dash], :scalar_day, :separator_at?, 'time?'], :handle_sy_sm_sd),
181
+ Handler.new([:scalar_year, [:separator_slash, :separator_dash], :scalar_month], :handle_sy_sm),
182
+ Handler.new([:scalar_month, [:separator_slash, :separator_dash], :scalar_year], :handle_sm_sy),
183
+ Handler.new([:scalar_day, [:separator_slash, :separator_dash], :repeater_month_name, [:separator_slash, :separator_dash], :scalar_year, :repeater_time?], :handle_sm_rmn_sy),
184
+ Handler.new([:scalar_year, [:separator_slash, :separator_dash], :scalar_month, [:separator_slash, :separator_dash], :scalar?, :time_zone], :handle_generic),
180
185
  ],
181
186
 
182
187
  :anchor => [
@@ -199,10 +204,10 @@ module Chronic
199
204
  }
200
205
 
201
206
  endians = [
202
- Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_day, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sm_sd_sy),
203
- Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_day, :separator_at?, 'time?'], :handle_sm_sd),
204
- Handler.new([:scalar_day, :separator_slash_or_dash, :scalar_month, :separator_at?, 'time?'], :handle_sd_sm),
205
- Handler.new([:scalar_day, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sd_sm_sy)
207
+ Handler.new([:scalar_month, [:separator_slash, :separator_dash], :scalar_day, [:separator_slash, :separator_dash], :scalar_year, :separator_at?, 'time?'], :handle_sm_sd_sy),
208
+ Handler.new([:scalar_month, [:separator_slash, :separator_dash], :scalar_day, :separator_at?, 'time?'], :handle_sm_sd),
209
+ Handler.new([:scalar_day, [:separator_slash, :separator_dash], :scalar_month, :separator_at?, 'time?'], :handle_sd_sm),
210
+ Handler.new([:scalar_day, [:separator_slash, :separator_dash], :scalar_month, [:separator_slash, :separator_dash], :scalar_year, :separator_at?, 'time?'], :handle_sd_sm_sy)
206
211
  ]
207
212
 
208
213
  case endian = Array(options[:endian_precedence]).first
@@ -220,7 +225,7 @@ module Chronic
220
225
  def tokenize(text, options)
221
226
  text = pre_normalize(text)
222
227
  tokens = text.split(' ').map { |word| Token.new(word) }
223
- [Repeater, Grabber, Pointer, Scalar, Ordinal, Separator, TimeZone].each do |tok|
228
+ [Repeater, Grabber, Pointer, Scalar, Ordinal, Separator, Sign, TimeZone].each do |tok|
224
229
  tok.scan(tokens, options)
225
230
  end
226
231
  tokens.select { |token| token.tagged? }
@@ -245,7 +250,7 @@ module Chronic
245
250
 
246
251
  definitions[:arrow].each do |handler|
247
252
  if handler.match(tokens, definitions)
248
- good_tokens = tokens.reject { |o| o.get_tag(SeparatorAt) || o.get_tag(SeparatorSlashOrDash) || o.get_tag(SeparatorComma) || o.get_tag(SeparatorAnd) }
253
+ good_tokens = tokens.reject { |o| o.get_tag(SeparatorAt) || o.get_tag(SeparatorSlash) || o.get_tag(SeparatorDash) || o.get_tag(SeparatorComma) || o.get_tag(SeparatorAnd) }
249
254
  return handler.invoke(:arrow, good_tokens, self, options)
250
255
  end
251
256
  end
@@ -10,32 +10,32 @@ module Chronic
10
10
  # Returns an Array of tokens.
11
11
  def self.scan(tokens, options)
12
12
  tokens.each do |token|
13
- if t = scan_for_season_names(token) then token.tag(t); next end
14
- if t = scan_for_month_names(token) then token.tag(t); next end
15
- if t = scan_for_day_names(token) then token.tag(t); next end
16
- if t = scan_for_day_portions(token) then token.tag(t); next end
17
- if t = scan_for_times(token) then token.tag(t); next end
18
- if t = scan_for_units(token) then token.tag(t); next end
13
+ if t = scan_for_season_names(token, options) then token.tag(t); next end
14
+ if t = scan_for_month_names(token, options) then token.tag(t); next end
15
+ if t = scan_for_day_names(token, options) then token.tag(t); next end
16
+ if t = scan_for_day_portions(token, options) then token.tag(t); next end
17
+ if t = scan_for_times(token, options) then token.tag(t); next end
18
+ if t = scan_for_units(token, options) then token.tag(t); next end
19
19
  end
20
20
  end
21
21
 
22
22
  # token - The Token object we want to scan.
23
23
  #
24
24
  # Returns a new Repeater object.
25
- def self.scan_for_season_names(token)
25
+ def self.scan_for_season_names(token, options = {})
26
26
  scan_for token, RepeaterSeasonName,
27
27
  {
28
28
  /^springs?$/ => :spring,
29
29
  /^summers?$/ => :summer,
30
30
  /^(autumn)|(fall)s?$/ => :autumn,
31
31
  /^winters?$/ => :winter
32
- }
32
+ }, options
33
33
  end
34
34
 
35
35
  # token - The Token object we want to scan.
36
36
  #
37
37
  # Returns a new Repeater object.
38
- def self.scan_for_month_names(token)
38
+ def self.scan_for_month_names(token, options = {})
39
39
  scan_for token, RepeaterMonthName,
40
40
  {
41
41
  /^jan[:\.]?(uary)?$/ => :january,
@@ -50,13 +50,13 @@ module Chronic
50
50
  /^oct[:\.]?(ober)?$/ => :october,
51
51
  /^nov[:\.]?(ember)?$/ => :november,
52
52
  /^dec[:\.]?(ember)?$/ => :december
53
- }
53
+ }, options
54
54
  end
55
55
 
56
56
  # token - The Token object we want to scan.
57
57
  #
58
58
  # Returns a new Repeater object.
59
- def self.scan_for_day_names(token)
59
+ def self.scan_for_day_names(token, options = {})
60
60
  scan_for token, RepeaterDayName,
61
61
  {
62
62
  /^m[ou]n(day)?$/ => :monday,
@@ -66,13 +66,13 @@ module Chronic
66
66
  /^fr[iy](day)?$/ => :friday,
67
67
  /^sat(t?[ue]rday)?$/ => :saturday,
68
68
  /^su[nm](day)?$/ => :sunday
69
- }
69
+ }, options
70
70
  end
71
71
 
72
72
  # token - The Token object we want to scan.
73
73
  #
74
74
  # Returns a new Repeater object.
75
- def self.scan_for_day_portions(token)
75
+ def self.scan_for_day_portions(token, options = {})
76
76
  scan_for token, RepeaterDayPortion,
77
77
  {
78
78
  /^ams?$/ => :am,
@@ -81,20 +81,20 @@ module Chronic
81
81
  /^afternoons?$/ => :afternoon,
82
82
  /^evenings?$/ => :evening,
83
83
  /^(night|nite)s?$/ => :night
84
- }
84
+ }, options
85
85
  end
86
86
 
87
87
  # token - The Token object we want to scan.
88
88
  #
89
89
  # Returns a new Repeater object.
90
- def self.scan_for_times(token)
91
- scan_for token, RepeaterTime, /^\d{1,2}(:?\d{1,2})?([\.:]?\d{1,2})?$/
90
+ def self.scan_for_times(token, options = {})
91
+ scan_for token, RepeaterTime, /^\d{1,2}(:?\d{1,2})?([\.:]?\d{1,2}([\.:]\d{1,6})?)?$/, options
92
92
  end
93
93
 
94
94
  # token - The Token object we want to scan.
95
95
  #
96
96
  # Returns a new Repeater object.
97
- def self.scan_for_units(token)
97
+ def self.scan_for_units(token, options = {})
98
98
  {
99
99
  /^years?$/ => :year,
100
100
  /^seasons?$/ => :season,
@@ -114,7 +114,7 @@ module Chronic
114
114
  if item =~ token.word
115
115
  klass_name = 'Repeater' + symbol.to_s.capitalize
116
116
  klass = Chronic.const_get(klass_name)
117
- return klass.new(symbol)
117
+ return klass.new(symbol, options)
118
118
  end
119
119
  end
120
120
  return nil