gitlab-chronic 0.10.4 → 0.10.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -1
  3. data/.travis.yml +4 -6
  4. data/HISTORY.md +4 -0
  5. data/README.md +7 -10
  6. data/chronic.gemspec +1 -1
  7. data/lib/chronic/date.rb +7 -6
  8. data/lib/chronic/{tags/grabber.rb → grabber.rb} +10 -6
  9. data/lib/chronic/handlers.rb +2 -27
  10. data/lib/chronic/mini_date.rb +2 -2
  11. data/lib/chronic/numerizer.rb +130 -0
  12. data/lib/chronic/{tags/ordinal.rb → ordinal.rb} +8 -11
  13. data/lib/chronic/parser.rb +80 -34
  14. data/lib/chronic/{tags/pointer.rb → pointer.rb} +9 -5
  15. data/lib/chronic/{tags/repeater.rb → repeater.rb} +11 -26
  16. data/lib/chronic/repeaters/repeater_day.rb +1 -1
  17. data/lib/chronic/repeaters/repeater_day_name.rb +2 -2
  18. data/lib/chronic/repeaters/repeater_day_portion.rb +3 -3
  19. data/lib/chronic/repeaters/repeater_fortnight.rb +1 -1
  20. data/lib/chronic/repeaters/repeater_hour.rb +1 -1
  21. data/lib/chronic/repeaters/repeater_minute.rb +1 -1
  22. data/lib/chronic/repeaters/repeater_month.rb +1 -1
  23. data/lib/chronic/repeaters/repeater_month_name.rb +2 -2
  24. data/lib/chronic/repeaters/repeater_season.rb +1 -1
  25. data/lib/chronic/repeaters/repeater_second.rb +1 -1
  26. data/lib/chronic/repeaters/repeater_time.rb +2 -2
  27. data/lib/chronic/repeaters/repeater_week.rb +22 -23
  28. data/lib/chronic/repeaters/repeater_weekday.rb +2 -2
  29. data/lib/chronic/repeaters/repeater_weekend.rb +1 -1
  30. data/lib/chronic/repeaters/repeater_year.rb +1 -1
  31. data/lib/chronic/{tags/scalar.rb → scalar.rb} +10 -18
  32. data/lib/chronic/separator.rb +207 -0
  33. data/lib/chronic/{tags/sign.rb → sign.rb} +16 -2
  34. data/lib/chronic/tag.rb +7 -59
  35. data/lib/chronic/time.rb +8 -8
  36. data/lib/chronic/{tags/time_zone.rb → time_zone.rb} +1 -1
  37. data/lib/chronic/token.rb +3 -13
  38. data/lib/chronic/version.rb +1 -1
  39. data/lib/gitlab-chronic.rb +14 -18
  40. data/test/test_chronic.rb +6 -26
  41. data/test/test_handler.rb +1 -1
  42. data/test/test_numerizer.rb +86 -0
  43. data/test/test_parsing.rb +8 -306
  44. data/test/test_repeater_week.rb +0 -53
  45. data/test/test_token.rb +0 -6
  46. metadata +13 -19
  47. data/lib/chronic/definition.rb +0 -128
  48. data/lib/chronic/dictionary.rb +0 -36
  49. data/lib/chronic/repeaters/repeater_quarter.rb +0 -59
  50. data/lib/chronic/repeaters/repeater_quarter_name.rb +0 -40
  51. data/lib/chronic/tags/separator.rb +0 -123
  52. data/lib/chronic/tokenizer.rb +0 -38
  53. data/test/test_repeater_quarter.rb +0 -70
  54. data/test/test_repeater_quarter_name.rb +0 -198
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f25bcc92f81d5633ffe7fa39e82b9997b7f0a88e4edfdaf1ad6a64b590c620f1
4
- data.tar.gz: 960db7245d7a8a44dcf7512d66a120c5f0ce4c2cbe57b04a7b2f56e54a91b525
3
+ metadata.gz: 2b5a569ed8fd73d35d0726a7cb2f19f1ca3507543032bff4ca3f1aa73e1ba94f
4
+ data.tar.gz: 15f9949677fc421bf44def4fbf0da85928847e4918d4375657cf43c64f3f92fe
5
5
  SHA512:
6
- metadata.gz: ae2b083127378b81b41e61322f4f61eddf41c3896ce064e3587c6df836cfc045395ba41ada4892b2c60ff12ca6ace925ee664820c9ac389db96da61a9e3b3dba
7
- data.tar.gz: c591716552c0215495d942059fc3d8fa43eb4346b7ff1d5bc1da2c80ea07fecd63d8edc8093252bc536a2e85f3dea2bdaa7452c6d2ea9f82672bf39a9ea7b197
6
+ metadata.gz: d8ae6e9b8292042fce47b1125f2fd11aab058e45c5ab00fe01fdee2e8b8dddcc062fd505f1de80bc7a05fcc04c329e3229746bcbfaaa07b5d3d82ad7161a1a09
7
+ data.tar.gz: 727012bcb6c52dcd35e3f87ef099d50e27d7ab7b7c719a8f21d25e4ab802c06b3aa70a1acb790fa376efb2851d2c2d8e03c33539e3c03e51f33b90f04893044f
data/.gitignore CHANGED
@@ -1,6 +1,5 @@
1
1
  *.rbc
2
2
  .yardoc
3
- /.bundle/
4
3
  /pkg/
5
4
  /coverage/
6
5
  /doc/
data/.travis.yml CHANGED
@@ -1,10 +1,8 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.4.1
4
- - 2.3
5
- - 2.2
6
- - 2.1
7
- - 2.0.0
8
3
  - 1.9.3
4
+ - 1.9.2
5
+ - 1.8.7
9
6
  - jruby-19mode
10
- - rbx-2
7
+ - jruby-18mode
8
+ - rbx-19mode
data/HISTORY.md CHANGED
@@ -1,5 +1,9 @@
1
1
  ## --- Begin gitlab-chronic changes ---
2
2
 
3
+ # 0.10.5 / 2019-11-25
4
+
5
+ * Revert changes made between v0.10.2..master ((https://gitlab.com/gitlab-org/gitlab-chronic/merge_requests/7)
6
+
3
7
  # 0.10.4 / 2019-11-24
4
8
 
5
9
  * Rename chronic -> gitlab-chronic (https://gitlab.com/gitlab-org/gitlab-chronic/merge_requests/5)
data/README.md CHANGED
@@ -57,7 +57,7 @@ Chronic can parse a huge variety of date and time formats. Following is a
57
57
  small sample of strings that will be properly parsed. Parsing is case
58
58
  insensitive and will handle common abbreviations and misspellings.
59
59
 
60
- #### Simple
60
+ Simple
61
61
 
62
62
  * thursday
63
63
  * november
@@ -89,7 +89,7 @@ insensitive and will handle common abbreviations and misspellings.
89
89
  * afternoon yesterday
90
90
  * thursday last week
91
91
 
92
- #### Complex
92
+ Complex
93
93
 
94
94
  * 3 years ago
95
95
  * a year ago
@@ -108,7 +108,7 @@ insensitive and will handle common abbreviations and misspellings.
108
108
  * fourteenth of june 2010 at eleven o'clock in the evening
109
109
  * may seventh '97 at three in the morning
110
110
 
111
- #### Specific Dates
111
+ Specific Dates
112
112
 
113
113
  * January 5
114
114
  * 22nd of june
@@ -133,14 +133,12 @@ insensitive and will handle common abbreviations and misspellings.
133
133
  * 17:00
134
134
  * 0800
135
135
 
136
- #### Specific Times (many of the above with an added time)
136
+ Specific Times (many of the above with an added time)
137
137
 
138
138
  * January 5 at 7pm
139
139
  * 22nd of june at 8am
140
140
  * 1979-05-27 05:00:00
141
141
  * 03/01/2012 07:25:09.234567
142
- * 2013-08-01T19:30:00.345-07:00
143
- * 2013-08-01T19:30:00.34-07:00
144
142
  * etc
145
143
 
146
144
 
@@ -163,7 +161,7 @@ class to get full time zone support.
163
161
 
164
162
  Chronic uses Ruby's built in Time class for all time storage and computation.
165
163
  Because of this, only times that the Time class can handle will be properly
166
- parsed. Parsing for times outside of this range will simply return `nil`.
164
+ parsed. Parsing for times outside of this range will simply return nil.
167
165
  Support for a wider range of times is planned for a future release.
168
166
 
169
167
 
@@ -177,10 +175,9 @@ The best way to get your changes merged back into core is as follows:
177
175
 
178
176
  1. Clone down your fork
179
177
  1. Create a thoughtfully named topic branch to contain your change
180
- 1. Install the development dependencies by running `bundle install`
181
178
  1. Hack away
182
- 1. Add tests and make sure everything still passes by running `bundle exec rake`
183
- 1. Ensure your tests pass in multiple timezones. ie `TZ=utc bundle exec rake` `TZ=BST bundle exec rake`
179
+ 1. Add tests and make sure everything still passes by running `rake`
180
+ 1. Ensure your tests pass in multiple timezones. ie `TZ=utc rake` `TZ=BST rake`
184
181
  1. If you are adding new functionality, document it in the README
185
182
  1. Do not change the version number, we will do that on our end
186
183
  1. If necessary, rebase your commits into logical chunks, without errors
data/chronic.gemspec CHANGED
@@ -1,5 +1,5 @@
1
1
  $:.unshift File.expand_path('../lib', __FILE__)
2
- require 'chronic/version'
2
+ require 'gitlab-chronic'
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = 'gitlab-chronic'
data/lib/chronic/date.rb CHANGED
@@ -35,19 +35,20 @@ module Chronic
35
35
  }
36
36
 
37
37
  # Checks if given number could be day
38
- def self.could_be_day?(day, width = nil)
39
- day >= 1 && day <= 31 && (width.nil? || width <= 2)
38
+ def self.could_be_day?(day)
39
+ day >= 1 && day <= 31
40
40
  end
41
41
 
42
42
  # Checks if given number could be month
43
- def self.could_be_month?(month, width = nil)
44
- month >= 1 && month <= 12 && (width.nil? || width <= 2)
43
+ def self.could_be_month?(month)
44
+ month >= 1 && month <= 12
45
45
  end
46
46
 
47
47
  # Checks if given number could be year
48
- def self.could_be_year?(year, width = nil)
49
- year >= 0 && year <= 9999 && (width.nil? || width == 2 || width == 4)
48
+ def self.could_be_year?(year)
49
+ year >= 1 && year <= 9999
50
50
  end
51
+
51
52
  # Build a year from a 2 digit suffix.
52
53
  #
53
54
  # year - The two digit Integer year to build from.
@@ -10,15 +10,19 @@ module Chronic
10
10
  # Returns an Array of Token objects.
11
11
  def self.scan(tokens, options)
12
12
  tokens.each do |token|
13
- token.tag scan_for(token, self, patterns, options)
13
+ if t = scan_for_all(token) then token.tag(t); next end
14
14
  end
15
15
  end
16
16
 
17
- def self.patterns
18
- @@patterns ||= {
19
- 'last' => :last,
20
- 'this' => :this,
21
- 'next' => :next
17
+ # token - The Token object to scan.
18
+ #
19
+ # Returns a new Grabber object.
20
+ def self.scan_for_all(token)
21
+ scan_for token, self,
22
+ {
23
+ /last/ => :last,
24
+ /this/ => :this,
25
+ /next/ => :next
22
26
  }
23
27
  end
24
28
 
@@ -8,7 +8,6 @@ module Chronic
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)
11
- day_start = Chronic.time_class.local(year + 1, month, day) if options[:context] == :future && day_start < now
12
11
 
13
12
  day_or_time(day_start, time_tokens, options)
14
13
  end
@@ -110,19 +109,6 @@ module Chronic
110
109
  handle_m_d(month, day, tokens[token_range], options)
111
110
  end
112
111
 
113
- # Handle scalar-year/repeater-quarter-name
114
- def handle_sy_rqn(tokens, options)
115
- handle_rqn_sy(tokens[0..1].reverse, options)
116
- end
117
-
118
- # Handle repeater-quarter-name/scalar-year
119
- def handle_rqn_sy(tokens, options)
120
- year = tokens[1].get_tag(ScalarYear).type
121
- quarter_tag = tokens[0].get_tag(RepeaterQuarterName)
122
- quarter_tag.start = Chronic.construct(year)
123
- quarter_tag.this(:none)
124
- end
125
-
126
112
  # Handle repeater-month-name/scalar-year
127
113
  def handle_rmn_sy(tokens, options)
128
114
  month = tokens[0].get_tag(RepeaterMonthName).index
@@ -252,13 +238,7 @@ module Chronic
252
238
 
253
239
  begin
254
240
  day_start = Chronic.time_class.local(year, month, day)
255
-
256
- if options[:context] == :future && day_start < now
257
- day_start = Chronic.time_class.local(year + 1, month, day)
258
- elsif options[:context] == :past && day_start > now
259
- day_start = Chronic.time_class.local(year - 1, month, day)
260
- end
261
-
241
+ day_start = Chronic.time_class.local(year + 1, month, day) if options[:context] == :future && day_start < now
262
242
  day_or_time(day_start, time_tokens, options)
263
243
  rescue ArgumentError
264
244
  nil
@@ -470,11 +450,6 @@ module Chronic
470
450
  handle_srp(tokens, anchor_span, options)
471
451
  end
472
452
 
473
- # Handle repeater/scalar/repeater/pointer
474
- def handle_rmn_s_r_p(tokens, options)
475
- handle_s_r_p_a(tokens[1..3] + tokens[0..0], options)
476
- end
477
-
478
453
  def handle_s_r_a_s_r_p_a(tokens, options)
479
454
  anchor_span = get_anchor(tokens[4..tokens.size - 1], options)
480
455
 
@@ -553,7 +528,7 @@ module Chronic
553
528
  when :next
554
529
  outer_span = head.next(:future)
555
530
  else
556
- raise 'Invalid grabber'
531
+ raise "Invalid grabber"
557
532
  end
558
533
 
559
534
  if Chronic.debug
@@ -8,7 +8,7 @@ module Chronic
8
8
 
9
9
  def initialize(month, day)
10
10
  unless (1..12).include?(month)
11
- raise ArgumentError, '1..12 are valid months'
11
+ raise ArgumentError, "1..12 are valid months"
12
12
  end
13
13
 
14
14
  @month = month
@@ -35,4 +35,4 @@ module Chronic
35
35
  @month == other.month and @day == other.day
36
36
  end
37
37
  end
38
- end
38
+ end
@@ -0,0 +1,130 @@
1
+ require 'strscan'
2
+
3
+ module Chronic
4
+ class Numerizer
5
+
6
+ DIRECT_NUMS = [
7
+ ['eleven', '11'],
8
+ ['twelve', '12'],
9
+ ['thirteen', '13'],
10
+ ['fourteen', '14'],
11
+ ['fifteen', '15'],
12
+ ['sixteen', '16'],
13
+ ['seventeen', '17'],
14
+ ['eighteen', '18'],
15
+ ['nineteen', '19'],
16
+ ['ninteen', '19'], # Common mis-spelling
17
+ ['zero', '0'],
18
+ ['one', '1'],
19
+ ['two', '2'],
20
+ ['three', '3'],
21
+ ['four(\W|$)', '4\1'], # The weird regex is so that it matches four but not fourty
22
+ ['five', '5'],
23
+ ['six(\W|$)', '6\1'],
24
+ ['seven(\W|$)', '7\1'],
25
+ ['eight(\W|$)', '8\1'],
26
+ ['nine(\W|$)', '9\1'],
27
+ ['ten', '10'],
28
+ ['\ba[\b^$]', '1'] # doesn't make sense for an 'a' at the end to be a 1
29
+ ]
30
+
31
+ ORDINALS = [
32
+ ['first', '1'],
33
+ ['third', '3'],
34
+ ['fourth', '4'],
35
+ ['fifth', '5'],
36
+ ['sixth', '6'],
37
+ ['seventh', '7'],
38
+ ['eighth', '8'],
39
+ ['ninth', '9'],
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']
50
+ ]
51
+
52
+ TEN_PREFIXES = [
53
+ ['twenty', 20],
54
+ ['thirty', 30],
55
+ ['forty', 40],
56
+ ['fourty', 40], # Common mis-spelling
57
+ ['fifty', 50],
58
+ ['sixty', 60],
59
+ ['seventy', 70],
60
+ ['eighty', 80],
61
+ ['ninety', 90]
62
+ ]
63
+
64
+ BIG_PREFIXES = [
65
+ ['hundred', 100],
66
+ ['thousand', 1000],
67
+ ['million', 1_000_000],
68
+ ['billion', 1_000_000_000],
69
+ ['trillion', 1_000_000_000_000],
70
+ ]
71
+
72
+ def self.numerize(string)
73
+ string = string.dup
74
+
75
+ # preprocess
76
+ string.gsub!(/ +|([^\d])-([^\d])/, '\1 \2') # will mutilate hyphenated-words but shouldn't matter for date extraction
77
+ string.gsub!(/a half/, 'haAlf') # take the 'a' out so it doesn't turn into a 1, save the half for the end
78
+
79
+ # easy/direct replacements
80
+
81
+ DIRECT_NUMS.each do |dn|
82
+ string.gsub!(/#{dn[0]}/i, '<num>' + dn[1])
83
+ end
84
+
85
+ ORDINALS.each do |on|
86
+ string.gsub!(/#{on[0]}/i, '<num>' + on[1] + on[0][-2, 2])
87
+ end
88
+
89
+ # ten, twenty, etc.
90
+
91
+ TEN_PREFIXES.each do |tp|
92
+ string.gsub!(/(?:#{tp[0]}) *<num>(\d(?=[^\d]|$))*/i) { '<num>' + (tp[1] + $1.to_i).to_s }
93
+ end
94
+
95
+ TEN_PREFIXES.each do |tp|
96
+ string.gsub!(/#{tp[0]}/i) { '<num>' + tp[1].to_s }
97
+ end
98
+
99
+ # hundreds, thousands, millions, etc.
100
+
101
+ BIG_PREFIXES.each do |bp|
102
+ string.gsub!(/(?:<num>)?(\d*) *#{bp[0]}/i) { $1.empty? ? bp[1] : '<num>' + (bp[1] * $1.to_i).to_s}
103
+ andition(string)
104
+ end
105
+
106
+ # fractional addition
107
+ # I'm not combining this with the previous block as using float addition complicates the strings
108
+ # (with extraneous .0's and such )
109
+ string.gsub!(/(\d+)(?: | and |-)*haAlf/i) { ($1.to_f + 0.5).to_s }
110
+
111
+ string.gsub(/<num>/, '')
112
+ end
113
+
114
+ class << self
115
+ private
116
+
117
+ def andition(string)
118
+ sc = StringScanner.new(string)
119
+
120
+ while sc.scan_until(/<num>(\d+)( | and )<num>(\d+)(?=[^\w]|$)/i)
121
+ if sc[2] =~ /and/ || sc[1].size > sc[3].size
122
+ string[(sc.pos - sc.matched_size)..(sc.pos-1)] = '<num>' + (sc[1].to_i + sc[3].to_i).to_s
123
+ sc.reset
124
+ end
125
+ end
126
+ end
127
+
128
+ end
129
+ end
130
+ end
@@ -11,17 +11,14 @@ module Chronic
11
11
  def self.scan(tokens, options)
12
12
  tokens.each_index do |i|
13
13
  if tokens[i].word =~ /^(\d+)(st|nd|rd|th|\.)$/
14
- width = $1.length
15
- ordinal = $1.to_i
16
- tokens[i].tag(Ordinal.new(ordinal, width))
17
- tokens[i].tag(OrdinalDay.new(ordinal, width)) if Chronic::Date::could_be_day?(ordinal, width)
18
- tokens[i].tag(OrdinalMonth.new(ordinal, width)) if Chronic::Date::could_be_month?(ordinal, width)
19
- if Chronic::Date::could_be_year?(ordinal, width)
20
- year = Chronic::Date::make_year(ordinal, options[:ambiguous_year_future_bias])
21
- tokens[i].tag(OrdinalYear.new(year.to_i, width))
22
- end
23
- elsif tokens[i].word =~ /^second$/
24
- tokens[i].tag(Ordinal.new(2, 1))
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
25
22
  end
26
23
  end
27
24
  end
@@ -1,4 +1,3 @@
1
- require 'chronic/dictionary'
2
1
  require 'chronic/handlers'
3
2
 
4
3
  module Chronic
@@ -10,7 +9,6 @@ module Chronic
10
9
  :context => :future,
11
10
  :now => nil,
12
11
  :hours24 => nil,
13
- :week_start => :sunday,
14
12
  :guess => true,
15
13
  :ambiguous_time_range => 6,
16
14
  :endian_precedence => [:middle, :little],
@@ -27,9 +25,6 @@ module Chronic
27
25
  # :now - Time, all computations will be based off of time
28
26
  # instead of Time.now.
29
27
  # :hours24 - Time will be parsed as it would be 24 hour clock.
30
- # :week_start - By default, the parser assesses weeks start on
31
- # sunday but you can change this value to :monday if
32
- # needed.
33
28
  # :guess - By default the parser will guess a single point in time
34
29
  # for the given date or time. If you'd rather have the
35
30
  # entire time span returned, set this to false
@@ -55,9 +50,8 @@ module Chronic
55
50
  # two digit year is `now + x years` it's assumed to be the
56
51
  # future, `now - x years` is assumed to be the past.
57
52
  def initialize(options = {})
58
- validate_options!(options)
59
53
  @options = DEFAULT_OPTIONS.merge(options)
60
- @now = options[:now] || Chronic.time_class.now
54
+ @now = options.delete(:now) || Chronic.time_class.now
61
55
  end
62
56
 
63
57
  # Parse "text" with the given options
@@ -94,35 +88,22 @@ module Chronic
94
88
  # Returns a new String ready for Chronic to parse.
95
89
  def pre_normalize(text)
96
90
  text = text.to_s.downcase
97
- text.gsub!(/\b(\d{1,2})\.(\d{1,2})\.(\d{4})\b/, '\3 / \2 / \1')
91
+ text.gsub!(/\b(\d{2})\.(\d{2})\.(\d{4})\b/, '\3 / \2 / \1')
98
92
  text.gsub!(/\b([ap])\.m\.?/, '\1m')
99
- text.gsub!(/(\s+|:\d{2}|:\d{2}\.\d+)\-(\d{2}:?\d{2})\b/, '\1tzminus\2')
93
+ text.gsub!(/(\s+|:\d{2}|:\d{2}\.\d{3})\-(\d{2}:?\d{2})\b/, '\1tzminus\2')
100
94
  text.gsub!(/\./, ':')
101
95
  text.gsub!(/([ap]):m:?/, '\1m')
102
- text.gsub!(/'(\d{2})\b/) do
103
- number = $1.to_i
104
-
105
- if Chronic::Date::could_be_year?(number)
106
- Chronic::Date::make_year(number, options[:ambiguous_year_future_bias])
107
- else
108
- number
109
- end
110
- end
111
96
  text.gsub!(/['"]/, '')
112
97
  text.gsub!(/,/, ' ')
113
98
  text.gsub!(/^second /, '2nd ')
114
- text.gsub!(/\bsecond (of|day|month|hour|minute|second|quarter)\b/, '2nd \1')
115
- text.gsub!(/\bthird quarter\b/, '3rd q')
116
- text.gsub!(/\bfourth quarter\b/, '4th q')
117
- text.gsub!(/quarters?(\s+|$)(?!to|till|past|after|before)/, 'q\1')
99
+ text.gsub!(/\bsecond (of|day|month|hour|minute|second)\b/, '2nd \1')
118
100
  text = Numerizer.numerize(text)
119
- text.gsub!(/\b(\d)(?:st|nd|rd|th)\s+q\b/, 'q\1')
120
101
  text.gsub!(/([\/\-\,\@])/) { ' ' + $1 + ' ' }
121
102
  text.gsub!(/(?:^|\s)0(\d+:\d+\s*pm?\b)/, ' \1')
122
103
  text.gsub!(/\btoday\b/, 'this day')
123
104
  text.gsub!(/\btomm?orr?ow\b/, 'next day')
124
105
  text.gsub!(/\byesterday\b/, 'last day')
125
- text.gsub!(/\bnoon|midday\b/, '12:00pm')
106
+ text.gsub!(/\bnoon\b/, '12:00pm')
126
107
  text.gsub!(/\bmidnight\b/, '24:00')
127
108
  text.gsub!(/\bnow\b/, 'this second')
128
109
  text.gsub!('quarter', '15')
@@ -163,22 +144,87 @@ module Chronic
163
144
  #
164
145
  # Returns a Hash of Handler definitions.
165
146
  def definitions(options = {})
166
- SpanDictionary.new(options).definitions
167
- end
147
+ options[:endian_precedence] ||= [:middle, :little]
168
148
 
169
- private
149
+ @@definitions ||= {
150
+ :time => [
151
+ Handler.new([:repeater_time, :repeater_day_portion?], nil)
152
+ ],
153
+
154
+ :date => [
155
+ Handler.new([:repeater_day_name, :repeater_month_name, :scalar_day, :repeater_time, [:separator_slash?, :separator_dash?], :time_zone, :scalar_year], :handle_generic),
156
+ Handler.new([:repeater_day_name, :repeater_month_name, :scalar_day], :handle_rdn_rmn_sd),
157
+ Handler.new([:repeater_day_name, :repeater_month_name, :scalar_day, :scalar_year], :handle_rdn_rmn_sd_sy),
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),
160
+ Handler.new([:repeater_day_name, :repeater_month_name, :scalar_day, :separator_at?, 'time?'], :handle_rdn_rmn_sd),
161
+ Handler.new([:repeater_day_name, :repeater_month_name, :ordinal_day, :separator_at?, 'time?'], :handle_rdn_rmn_od),
162
+ Handler.new([:repeater_day_name, :ordinal_day, :separator_at?, 'time?'], :handle_rdn_od),
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),
165
+ Handler.new([:repeater_month_name, :scalar_day, :scalar_year], :handle_rmn_sd_sy),
166
+ Handler.new([:repeater_month_name, :ordinal_day, :scalar_year], :handle_rmn_od_sy),
167
+ Handler.new([:repeater_month_name, :scalar_day, :scalar_year, :separator_at?, 'time?'], :handle_rmn_sd_sy),
168
+ Handler.new([:repeater_month_name, :ordinal_day, :scalar_year, :separator_at?, 'time?'], :handle_rmn_od_sy),
169
+ Handler.new([:repeater_month_name, [:separator_slash?, :separator_dash?], :scalar_day, :separator_at?, 'time?'], :handle_rmn_sd),
170
+ Handler.new([:repeater_time, :repeater_day_portion?, :separator_on?, :repeater_month_name, :scalar_day], :handle_rmn_sd_on),
171
+ Handler.new([:repeater_month_name, :ordinal_day, :separator_at?, 'time?'], :handle_rmn_od),
172
+ Handler.new([:ordinal_day, :repeater_month_name, :scalar_year, :separator_at?, 'time?'], :handle_od_rmn_sy),
173
+ Handler.new([:ordinal_day, :repeater_month_name, :separator_at?, 'time?'], :handle_od_rmn),
174
+ Handler.new([:ordinal_day, :grabber?, :repeater_month, :separator_at?, 'time?'], :handle_od_rm),
175
+ Handler.new([:scalar_year, :repeater_month_name, :ordinal_day], :handle_sy_rmn_od),
176
+ Handler.new([:repeater_time, :repeater_day_portion?, :separator_on?, :repeater_month_name, :ordinal_day], :handle_rmn_od_on),
177
+ Handler.new([:repeater_month_name, :scalar_year], :handle_rmn_sy),
178
+ Handler.new([:scalar_day, :repeater_month_name, :scalar_year, :separator_at?, 'time?'], :handle_sd_rmn_sy),
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),
185
+ ],
170
186
 
187
+ :anchor => [
188
+ Handler.new([:separator_on?, :grabber?, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),
189
+ Handler.new([:grabber?, :repeater, :repeater, :separator?, :repeater?, :repeater?], :handle_r),
190
+ Handler.new([:repeater, :grabber, :repeater], :handle_r_g_r)
191
+ ],
171
192
 
172
- def validate_options!(options)
173
- given = options.keys.map(&:to_s).sort
174
- allowed = DEFAULT_OPTIONS.keys.map(&:to_s).sort
175
- non_permitted = given - allowed
176
- raise ArgumentError, "Unsupported option(s): #{non_permitted.join(', ')}" if non_permitted.any?
193
+ :arrow => [
194
+ Handler.new([:scalar, :repeater, :pointer], :handle_s_r_p),
195
+ Handler.new([:scalar, :repeater, :separator_and?, :scalar, :repeater, :pointer, :separator_at?, 'anchor'], :handle_s_r_a_s_r_p_a),
196
+ Handler.new([:pointer, :scalar, :repeater], :handle_p_s_r),
197
+ Handler.new([:scalar, :repeater, :pointer, :separator_at?, 'anchor'], :handle_s_r_p_a)
198
+ ],
199
+
200
+ :narrow => [
201
+ Handler.new([:ordinal, :repeater, :separator_in, :repeater], :handle_o_r_s_r),
202
+ Handler.new([:ordinal, :repeater, :grabber, :repeater], :handle_o_r_g_r)
203
+ ]
204
+ }
205
+
206
+ endians = [
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)
211
+ ]
212
+
213
+ case endian = Array(options[:endian_precedence]).first
214
+ when :little
215
+ @@definitions.merge(:endian => endians.reverse)
216
+ when :middle
217
+ @@definitions.merge(:endian => endians)
218
+ else
219
+ raise ArgumentError, "Unknown endian option '#{endian}'"
220
+ end
177
221
  end
178
222
 
223
+ private
224
+
179
225
  def tokenize(text, options)
180
226
  text = pre_normalize(text)
181
- tokens = Tokenizer::tokenize(text)
227
+ tokens = text.split(' ').map { |word| Token.new(word) }
182
228
  [Repeater, Grabber, Pointer, Scalar, Ordinal, Separator, Sign, TimeZone].each do |tok|
183
229
  tok.scan(tokens, options)
184
230
  end
@@ -215,7 +261,7 @@ module Chronic
215
261
  end
216
262
  end
217
263
 
218
- puts '-none' if Chronic.debug
264
+ puts "-none" if Chronic.debug
219
265
  return nil
220
266
  end
221
267
  end