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.
@@ -1,25 +1,23 @@
1
- module Chronic
2
-
3
- # Returns a Hash of default configuration options.
4
- DEFAULT_OPTIONS = {
5
- :context => :future,
6
- :now => nil,
7
- :guess => true,
8
- :ambiguous_time_range => 6,
9
- :endian_precedence => [:middle, :little],
10
- :ambiguous_year_future_bias => 50
11
- }
1
+ require 'chronic/handlers'
12
2
 
13
- class << self
3
+ module Chronic
4
+ class Parser
5
+ include Handlers
6
+
7
+ # Hash of default configuration options.
8
+ DEFAULT_OPTIONS = {
9
+ :context => :future,
10
+ :now => nil,
11
+ :guess => true,
12
+ :ambiguous_time_range => 6,
13
+ :endian_precedence => [:middle, :little],
14
+ :ambiguous_year_future_bias => 50
15
+ }
16
+
17
+ attr_accessor :now
18
+ attr_reader :options
14
19
 
15
- # Parses a string containing a natural language date or time.
16
- #
17
- # If the parser can find a date or time, either a Time or Chronic::Span
18
- # will be returned (depending on the value of `:guess`). If no
19
- # date or time can be found, `nil` will be returned.
20
- #
21
- # text - The String text to parse.
22
- # opts - An optional Hash of configuration options:
20
+ # options - An optional Hash of configuration options:
23
21
  # :context - If your string represents a birthday, you can set
24
22
  # this value to :past and if an ambiguous string is
25
23
  # given, it will assume it is in the past.
@@ -47,31 +45,18 @@ module Chronic
47
45
  # look x amount of years into the future and past. If the
48
46
  # two digit year is `now + x years` it's assumed to be the
49
47
  # future, `now - x years` is assumed to be the past.
50
- #
51
- # Returns a new Time object, or Chronic::Span if :guess option is false.
52
- def parse(text, opts={})
53
- options = DEFAULT_OPTIONS.merge opts
54
-
55
- # ensure the specified options are valid
56
- (opts.keys - DEFAULT_OPTIONS.keys).each do |key|
57
- raise ArgumentError, "#{key} is not a valid option key."
58
- end
59
-
60
- unless [:past, :future, :none].include?(options[:context])
61
- raise ArgumentError, "Invalid context, :past/:future only"
62
- end
63
-
64
- options[:text] = text
65
- Chronic.now = options[:now] || Chronic.time_class.now
48
+ def initialize(options = {})
49
+ @options = DEFAULT_OPTIONS.merge(options)
50
+ @now = options.delete(:now) || Chronic.time_class.now
51
+ end
66
52
 
67
- # tokenize words
53
+ # Parse "text" with the given options
54
+ # Returns either a Time or Chronic::Span, depending on the value of options[:guess]
55
+ def parse(text)
68
56
  tokens = tokenize(text, options)
57
+ span = tokens_to_span(tokens, options.merge(:text => text))
69
58
 
70
- if Chronic.debug
71
- puts "+#{'-' * 51}\n| #{tokens}\n+#{'-' * 51}"
72
- end
73
-
74
- span = tokens_to_span(tokens, options)
59
+ puts "+#{'-' * 51}\n| #{tokens}\n+#{'-' * 51}" if Chronic.debug
75
60
 
76
61
  if span
77
62
  options[:guess] ? guess(span) : span
@@ -101,13 +86,14 @@ module Chronic
101
86
  # Returns a new String ready for Chronic to parse.
102
87
  def pre_normalize(text)
103
88
  text = text.to_s.downcase
89
+ text.gsub!(/\b([ap])\.m\.?/, '\1m')
104
90
  text.gsub!(/\./, ':')
105
91
  text.gsub!(/['"]/, '')
106
92
  text.gsub!(/,/, ' ')
107
93
  text.gsub!(/^second /, '2nd ')
108
94
  text.gsub!(/\bsecond (of|day|month|hour|minute|second)\b/, '2nd \1')
109
95
  text = Numerizer.numerize(text)
110
- text.gsub!(/ \-(\d{4})\b/, ' tzminus\1')
96
+ text.gsub!(/\-(\d{2}:?\d{2})\b/, 'tzminus\1')
111
97
  text.gsub!(/([\/\-\,\@])/) { ' ' + $1 + ' ' }
112
98
  text.gsub!(/(?:^|\s)0(\d+:\d+\s*pm?\b)/, ' \1')
113
99
  text.gsub!(/\btoday\b/, 'this day')
@@ -116,29 +102,25 @@ module Chronic
116
102
  text.gsub!(/\bnoon\b/, '12:00pm')
117
103
  text.gsub!(/\bmidnight\b/, '24:00')
118
104
  text.gsub!(/\bnow\b/, 'this second')
105
+ text.gsub!('quarter', '15')
106
+ text.gsub!('half', '30')
107
+ text.gsub!(/(\d{1,2}) (to|till|prior to|before)\b/, '\1 minutes past')
108
+ text.gsub!(/(\d{1,2}) (after|past)\b/, '\1 minutes future')
119
109
  text.gsub!(/\b(?:ago|before(?: now)?)\b/, 'past')
120
110
  text.gsub!(/\bthis (?:last|past)\b/, 'last')
121
111
  text.gsub!(/\b(?:in|during) the (morning)\b/, '\1')
122
112
  text.gsub!(/\b(?:in the|during the|at) (afternoon|evening|night)\b/, '\1')
123
113
  text.gsub!(/\btonight\b/, 'this night')
124
114
  text.gsub!(/\b\d+:?\d*[ap]\b/,'\0m')
115
+ text.gsub!(/\b(\d{2})(\d{2})(am|pm)\b/, '\1:\2\3')
125
116
  text.gsub!(/(\d)([ap]m|oclock)\b/, '\1 \2')
126
117
  text.gsub!(/\b(hence|after|from)\b/, 'future')
127
118
  text.gsub!(/^\s?an? /i, '1 ')
128
119
  text.gsub!(/\b(\d{4}):(\d{2}):(\d{2})\b/, '\1 / \2 / \3') # DTOriginal
120
+ text.gsub!(/\b0(\d+):(\d{2}):(\d{2}) ([ap]m)\b/, '\1:\2:\3 \4')
129
121
  text
130
122
  end
131
123
 
132
- # Convert number words to numbers (three => 3, fourth => 4th).
133
- #
134
- # text - The String to convert.
135
- #
136
- # Returns a new String with words converted to numbers.
137
- def numericize_numbers(text)
138
- warn "Chronic.numericize_numbers will be deprecated in version 0.7.0. Please use Chronic::Numerizer.numerize instead"
139
- Numerizer.numerize(text)
140
- end
141
-
142
124
  # Guess a specific time within the given span.
143
125
  #
144
126
  # span - The Chronic::Span object to calcuate a guess from.
@@ -152,17 +134,16 @@ module Chronic
152
134
  end
153
135
  end
154
136
 
155
- # List of Handler definitions. See #parse for a list of options this
137
+ # List of Handler definitions. See Chronic.parse for a list of options this
156
138
  # method accepts.
157
139
  #
158
- # options - An optional Hash of configuration options:
159
- # :endian_precedence -
140
+ # options - An optional Hash of configuration options.
160
141
  #
161
- # Returns A Hash of Handler definitions.
162
- def definitions(options={})
142
+ # Returns a Hash of Handler definitions.
143
+ def definitions(options = {})
163
144
  options[:endian_precedence] ||= [:middle, :little]
164
145
 
165
- @definitions ||= {
146
+ @@definitions ||= {
166
147
  :time => [
167
148
  Handler.new([:repeater_time, :repeater_day_portion?], nil)
168
149
  ],
@@ -172,12 +153,15 @@ module Chronic
172
153
  Handler.new([:repeater_day_name, :repeater_month_name, :scalar_day], :handle_rdn_rmn_sd),
173
154
  Handler.new([:repeater_day_name, :repeater_month_name, :scalar_day, :scalar_year], :handle_rdn_rmn_sd_sy),
174
155
  Handler.new([:repeater_day_name, :repeater_month_name, :ordinal_day], :handle_rdn_rmn_od),
156
+ Handler.new([:repeater_day_name, :repeater_month_name, :scalar_day, :separator_at?, 'time?'], :handle_rdn_rmn_sd),
157
+ Handler.new([:repeater_day_name, :repeater_month_name, :ordinal_day, :separator_at?, 'time?'], :handle_rdn_rmn_od),
158
+ Handler.new([:repeater_day_name, :ordinal_day, :separator_at?, 'time?'], :handle_rdn_od),
175
159
  Handler.new([:scalar_year, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_day, :repeater_time, :time_zone], :handle_generic),
176
160
  Handler.new([:repeater_month_name, :scalar_day, :scalar_year], :handle_rmn_sd_sy),
177
161
  Handler.new([:repeater_month_name, :ordinal_day, :scalar_year], :handle_rmn_od_sy),
178
162
  Handler.new([:repeater_month_name, :scalar_day, :scalar_year, :separator_at?, 'time?'], :handle_rmn_sd_sy),
179
163
  Handler.new([:repeater_month_name, :ordinal_day, :scalar_year, :separator_at?, 'time?'], :handle_rmn_od_sy),
180
- Handler.new([:repeater_month_name, :scalar_day, :separator_at?, 'time?'], :handle_rmn_sd),
164
+ Handler.new([:repeater_month_name, :separator_slash_or_dash?, :scalar_day, :separator_at?, 'time?'], :handle_rmn_sd),
181
165
  Handler.new([:repeater_time, :repeater_day_portion?, :separator_on?, :repeater_month_name, :scalar_day], :handle_rmn_sd_on),
182
166
  Handler.new([:repeater_month_name, :ordinal_day, :separator_at?, 'time?'], :handle_rmn_od),
183
167
  Handler.new([:ordinal_day, :repeater_month_name, :scalar_year, :separator_at?, 'time?'], :handle_od_rmn_sy),
@@ -187,29 +171,27 @@ module Chronic
187
171
  Handler.new([:repeater_time, :repeater_day_portion?, :separator_on?, :repeater_month_name, :ordinal_day], :handle_rmn_od_on),
188
172
  Handler.new([:repeater_month_name, :scalar_year], :handle_rmn_sy),
189
173
  Handler.new([:scalar_day, :repeater_month_name, :scalar_year, :separator_at?, 'time?'], :handle_sd_rmn_sy),
190
- Handler.new([:scalar_day, :repeater_month_name, :separator_at?, 'time?'], :handle_sd_rmn),
174
+ Handler.new([:scalar_day, :separator_slash_or_dash?, :repeater_month_name, :separator_at?, 'time?'], :handle_sd_rmn),
191
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),
192
177
  Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_year], :handle_sm_sy),
193
178
  Handler.new([:scalar_day, :separator_slash_or_dash, :repeater_month_name, :separator_slash_or_dash, :scalar_year, :repeater_time?], :handle_sm_rmn_sy),
194
- Handler.new([:scalar_year, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :time_zone], :handle_generic)
195
-
179
+ Handler.new([:scalar_year, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar?, :time_zone], :handle_generic),
196
180
  ],
197
181
 
198
- # tonight at 7pm
199
182
  :anchor => [
200
183
  Handler.new([:separator_on?, :grabber?, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),
201
184
  Handler.new([:grabber?, :repeater, :repeater, :separator?, :repeater?, :repeater?], :handle_r),
202
185
  Handler.new([:repeater, :grabber, :repeater], :handle_r_g_r)
203
186
  ],
204
187
 
205
- # 3 weeks from now, in 2 months
206
188
  :arrow => [
207
189
  Handler.new([:scalar, :repeater, :pointer], :handle_s_r_p),
190
+ Handler.new([:scalar, :repeater, :separator_and?, :scalar, :repeater, :pointer, :separator_at?, 'anchor'], :handle_s_r_a_s_r_p_a),
208
191
  Handler.new([:pointer, :scalar, :repeater], :handle_p_s_r),
209
192
  Handler.new([:scalar, :repeater, :pointer, :separator_at?, 'anchor'], :handle_s_r_p_a)
210
193
  ],
211
194
 
212
- # 3rd week in march
213
195
  :narrow => [
214
196
  Handler.new([:ordinal, :repeater, :separator_in, :repeater], :handle_o_r_s_r),
215
197
  Handler.new([:ordinal, :repeater, :grabber, :repeater], :handle_o_r_g_r)
@@ -225,68 +207,12 @@ module Chronic
225
207
 
226
208
  case endian = Array(options[:endian_precedence]).first
227
209
  when :little
228
- @definitions[:endian] = endians.reverse
210
+ @@definitions.merge(:endian => endians.reverse)
229
211
  when :middle
230
- @definitions[:endian] = endians
212
+ @@definitions.merge(:endian => endians)
231
213
  else
232
214
  raise ArgumentError, "Unknown endian option '#{endian}'"
233
215
  end
234
-
235
- @definitions
236
- end
237
-
238
- # Construct a new time object determining possible month overflows
239
- # and leap years.
240
- #
241
- # year - Integer year.
242
- # month - Integer month.
243
- # day - Integer day.
244
- # hour - Integer hour.
245
- # minute - Integer minute.
246
- # second - Integer second.
247
- #
248
- # Returns a new Time object constructed from these params.
249
- def construct(year, month = 1, day = 1, hour = 0, minute = 0, second = 0)
250
- if second >= 60
251
- minute += second / 60
252
- second = second % 60
253
- end
254
-
255
- if minute >= 60
256
- hour += minute / 60
257
- minute = minute % 60
258
- end
259
-
260
- if hour >= 24
261
- day += hour / 24
262
- hour = hour % 24
263
- end
264
-
265
- # determine if there is a day overflow. this is complicated by our crappy calendar
266
- # system (non-constant number of days per month)
267
- day <= 56 || raise("day must be no more than 56 (makes month resolution easier)")
268
- if day > 28
269
- # no month ever has fewer than 28 days, so only do this if necessary
270
- leap_year_month_days = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
271
- common_year_month_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
272
- days_this_month = Date.leap?(year) ? leap_year_month_days[month - 1] : common_year_month_days[month - 1]
273
- if day > days_this_month
274
- month += day / days_this_month
275
- day = day % days_this_month
276
- end
277
- end
278
-
279
- if month > 12
280
- if month % 12 == 0
281
- year += (month - 12) / 12
282
- month = 12
283
- else
284
- year += month / 12
285
- month = month % 12
286
- end
287
- end
288
-
289
- Chronic.time_class.local(year, month, day, hour, minute, second)
290
216
  end
291
217
 
292
218
  private
@@ -306,37 +232,32 @@ module Chronic
306
232
  (definitions[:endian] + definitions[:date]).each do |handler|
307
233
  if handler.match(tokens, definitions)
308
234
  good_tokens = tokens.select { |o| !o.get_tag Separator }
309
- return handler.invoke(:date, good_tokens, options)
235
+ return handler.invoke(:date, good_tokens, self, options)
310
236
  end
311
237
  end
312
238
 
313
239
  definitions[:anchor].each do |handler|
314
240
  if handler.match(tokens, definitions)
315
241
  good_tokens = tokens.select { |o| !o.get_tag Separator }
316
- return handler.invoke(:anchor, good_tokens, options)
242
+ return handler.invoke(:anchor, good_tokens, self, options)
317
243
  end
318
244
  end
319
245
 
320
246
  definitions[:arrow].each do |handler|
321
247
  if handler.match(tokens, definitions)
322
- good_tokens = tokens.reject { |o| o.get_tag(SeparatorAt) || o.get_tag(SeparatorSlashOrDash) || o.get_tag(SeparatorComma) }
323
- return handler.invoke(:arrow, good_tokens, options)
248
+ good_tokens = tokens.reject { |o| o.get_tag(SeparatorAt) || o.get_tag(SeparatorSlashOrDash) || o.get_tag(SeparatorComma) || o.get_tag(SeparatorAnd) }
249
+ return handler.invoke(:arrow, good_tokens, self, options)
324
250
  end
325
251
  end
326
252
 
327
253
  definitions[:narrow].each do |handler|
328
254
  if handler.match(tokens, definitions)
329
- return handler.invoke(:narrow, tokens, options)
255
+ return handler.invoke(:narrow, tokens, self, options)
330
256
  end
331
257
  end
332
258
 
333
259
  puts "-none" if Chronic.debug
334
260
  return nil
335
261
  end
336
-
337
- end
338
-
339
- # Internal exception
340
- class ChronicPain < Exception
341
262
  end
342
263
  end
@@ -38,18 +38,18 @@ module Chronic
38
38
  def self.scan_for_month_names(token)
39
39
  scan_for token, RepeaterMonthName,
40
40
  {
41
- /^jan\.?(uary)?$/ => :january,
42
- /^feb\.?(ruary)?$/ => :february,
43
- /^mar\.?(ch)?$/ => :march,
44
- /^apr\.?(il)?$/ => :april,
41
+ /^jan[:\.]?(uary)?$/ => :january,
42
+ /^feb[:\.]?(ruary)?$/ => :february,
43
+ /^mar[:\.]?(ch)?$/ => :march,
44
+ /^apr[:\.]?(il)?$/ => :april,
45
45
  /^may$/ => :may,
46
- /^jun\.?e?$/ => :june,
47
- /^jul\.?y?$/ => :july,
48
- /^aug\.?(ust)?$/ => :august,
49
- /^sep\.?(t\.?|tember)?$/ => :september,
50
- /^oct\.?(ober)?$/ => :october,
51
- /^nov\.?(ember)?$/ => :november,
52
- /^dec\.?(ember)?$/ => :december
46
+ /^jun[:\.]?e?$/ => :june,
47
+ /^jul[:\.]?y?$/ => :july,
48
+ /^aug[:\.]?(ust)?$/ => :august,
49
+ /^sep[:\.]?(t[:\.]?|tember)?$/ => :september,
50
+ /^oct[:\.]?(ober)?$/ => :october,
51
+ /^nov[:\.]?(ember)?$/ => :november,
52
+ /^dec[:\.]?(ember)?$/ => :december
53
53
  }
54
54
  end
55
55
 
@@ -15,6 +15,7 @@ module Chronic
15
15
  if t = scan_for_at(token) then token.tag(t); next end
16
16
  if t = scan_for_in(token) then token.tag(t); next end
17
17
  if t = scan_for_on(token) then token.tag(t); next end
18
+ if t = scan_for_and(token) then token.tag(t); next end
18
19
  end
19
20
  end
20
21
 
@@ -57,6 +58,13 @@ module Chronic
57
58
  scan_for token, SeparatorOn, { /^on$/ => :on }
58
59
  end
59
60
 
61
+ # token - The Token object we want to scan.
62
+ #
63
+ # Returns a new SeperatorAnd Object object.
64
+ def self.scan_for_and(token)
65
+ scan_for token, SeparatorAnd, { /^and$/ => :and }
66
+ end
67
+
60
68
  def to_s
61
69
  'separator'
62
70
  end
@@ -91,4 +99,10 @@ module Chronic
91
99
  super << '-on'
92
100
  end
93
101
  end
94
- end
102
+
103
+ class SeparatorAnd < Separator #:nodoc:
104
+ def to_s
105
+ super << '-and'
106
+ end
107
+ end
108
+ end
data/lib/chronic/span.rb CHANGED
@@ -25,7 +25,7 @@ module Chronic
25
25
  '(' << self.begin.to_s << '..' << self.end.to_s << ')'
26
26
  end
27
27
 
28
- alias :cover? :include? unless RUBY_VERSION =~ /^1.9/
28
+ alias :cover? :include? if RUBY_VERSION =~ /^1.8/
29
29
 
30
30
  end
31
- end
31
+ end
data/lib/chronic/token.rb CHANGED
@@ -43,5 +43,9 @@ module Chronic
43
43
  def to_s
44
44
  @word << '(' << @tags.join(', ') << ') '
45
45
  end
46
+
47
+ def inspect
48
+ to_s
49
+ end
46
50
  end
47
51
  end
data/lib/chronic.rb CHANGED
@@ -1,6 +1,41 @@
1
1
  require 'time'
2
2
  require 'date'
3
3
 
4
+ require 'chronic/parser'
5
+
6
+ require 'chronic/handler'
7
+ require 'chronic/handlers'
8
+ require 'chronic/mini_date'
9
+ require 'chronic/tag'
10
+ require 'chronic/span'
11
+ require 'chronic/token'
12
+ require 'chronic/grabber'
13
+ require 'chronic/pointer'
14
+ require 'chronic/scalar'
15
+ require 'chronic/ordinal'
16
+ require 'chronic/separator'
17
+ require 'chronic/time_zone'
18
+ require 'chronic/numerizer'
19
+ require 'chronic/season'
20
+
21
+ require 'chronic/repeater'
22
+ require 'chronic/repeaters/repeater_year'
23
+ require 'chronic/repeaters/repeater_season'
24
+ require 'chronic/repeaters/repeater_season_name'
25
+ require 'chronic/repeaters/repeater_month'
26
+ require 'chronic/repeaters/repeater_month_name'
27
+ require 'chronic/repeaters/repeater_fortnight'
28
+ require 'chronic/repeaters/repeater_week'
29
+ require 'chronic/repeaters/repeater_weekend'
30
+ require 'chronic/repeaters/repeater_weekday'
31
+ require 'chronic/repeaters/repeater_day'
32
+ require 'chronic/repeaters/repeater_day_name'
33
+ require 'chronic/repeaters/repeater_day_portion'
34
+ require 'chronic/repeaters/repeater_hour'
35
+ require 'chronic/repeaters/repeater_minute'
36
+ require 'chronic/repeaters/repeater_second'
37
+ require 'chronic/repeaters/repeater_time'
38
+
4
39
  # Parse natural language dates and times into Time or Chronic::Span objects.
5
40
  #
6
41
  # Examples:
@@ -14,20 +49,8 @@ require 'date'
14
49
  #
15
50
  # Chronic.parse('monday', :context => :past)
16
51
  # #=> Mon Aug 21 12:00:00 PDT 2006
17
- #
18
- # Chronic.parse('this tuesday 5:00')
19
- # #=> Tue Aug 29 17:00:00 PDT 2006
20
- #
21
- # Chronic.parse('this tuesday 5:00', :ambiguous_time_range => :none)
22
- # #=> Tue Aug 29 05:00:00 PDT 2006
23
- #
24
- # Chronic.parse('may 27th', :now => Time.local(2000, 1, 1))
25
- # #=> Sat May 27 12:00:00 PDT 2000
26
- #
27
- # Chronic.parse('may 27th', :guess => false)
28
- # #=> Sun May 27 00:00:00 PDT 2007..Mon May 28 00:00:00 PDT 2007
29
52
  module Chronic
30
- VERSION = "0.8.0"
53
+ VERSION = "0.9.1"
31
54
 
32
55
  class << self
33
56
 
@@ -46,72 +69,76 @@ module Chronic
46
69
  #
47
70
  # Returns The Time class Chronic uses internally.
48
71
  attr_accessor :time_class
49
-
50
- # The current Time Chronic is using to base from.
51
- #
52
- # Examples:
53
- #
54
- # Time.now #=> 2011-06-06 14:13:43 +0100
55
- # Chronic.parse('yesterday') #=> 2011-06-05 12:00:00 +0100
56
- #
57
- # now = Time.local(2025, 12, 24)
58
- # Chronic.parse('tomorrow', :now => now) #=> 2025-12-25 12:00:00 +0000
59
- #
60
- # Returns a Time object.
61
- attr_accessor :now
62
72
  end
63
73
 
64
74
  self.debug = false
65
75
  self.time_class = Time
66
76
 
67
- autoload :Handler, 'chronic/handler'
68
- autoload :Handlers, 'chronic/handlers'
69
- autoload :MiniDate, 'chronic/mini_date'
70
- autoload :Tag, 'chronic/tag'
71
- autoload :Span, 'chronic/span'
72
- autoload :Token, 'chronic/token'
73
- autoload :Grabber, 'chronic/grabber'
74
- autoload :Pointer, 'chronic/pointer'
75
- autoload :Scalar, 'chronic/scalar'
76
- autoload :Ordinal, 'chronic/ordinal'
77
- autoload :OrdinalDay, 'chronic/ordinal'
78
- autoload :Separator, 'chronic/separator'
79
- autoload :TimeZone, 'chronic/time_zone'
80
- autoload :Numerizer, 'chronic/numerizer'
81
- autoload :Season, 'chronic/season'
82
-
83
- autoload :Repeater, 'chronic/repeater'
84
- autoload :RepeaterYear, 'chronic/repeaters/repeater_year'
85
- autoload :RepeaterSeason, 'chronic/repeaters/repeater_season'
86
- autoload :RepeaterSeasonName, 'chronic/repeaters/repeater_season_name'
87
- autoload :RepeaterMonth, 'chronic/repeaters/repeater_month'
88
- autoload :RepeaterMonthName, 'chronic/repeaters/repeater_month_name'
89
- autoload :RepeaterFortnight, 'chronic/repeaters/repeater_fortnight'
90
- autoload :RepeaterWeek, 'chronic/repeaters/repeater_week'
91
- autoload :RepeaterWeekend, 'chronic/repeaters/repeater_weekend'
92
- autoload :RepeaterWeekday, 'chronic/repeaters/repeater_weekday'
93
- autoload :RepeaterDay, 'chronic/repeaters/repeater_day'
94
- autoload :RepeaterDayName, 'chronic/repeaters/repeater_day_name'
95
- autoload :RepeaterDayPortion, 'chronic/repeaters/repeater_day_portion'
96
- autoload :RepeaterHour, 'chronic/repeaters/repeater_hour'
97
- autoload :RepeaterMinute, 'chronic/repeaters/repeater_minute'
98
- autoload :RepeaterSecond, 'chronic/repeaters/repeater_second'
99
- autoload :RepeaterTime, 'chronic/repeaters/repeater_time'
100
77
 
101
- end
78
+ # Parses a string containing a natural language date or time.
79
+ #
80
+ # If the parser can find a date or time, either a Time or Chronic::Span
81
+ # will be returned (depending on the value of `:guess`). If no
82
+ # date or time can be found, `nil` will be returned.
83
+ #
84
+ # text - The String text to parse.
85
+ # opts - An optional Hash of configuration options passed to Parser::new.
86
+ def self.parse(text, options = {})
87
+ Parser.new(options).parse(text)
88
+ end
102
89
 
103
- require 'chronic/chronic'
90
+ # Construct a new time object determining possible month overflows
91
+ # and leap years.
92
+ #
93
+ # year - Integer year.
94
+ # month - Integer month.
95
+ # day - Integer day.
96
+ # hour - Integer hour.
97
+ # minute - Integer minute.
98
+ # second - Integer second.
99
+ #
100
+ # Returns a new Time object constructed from these params.
101
+ def self.construct(year, month = 1, day = 1, hour = 0, minute = 0, second = 0)
102
+ if second >= 60
103
+ minute += second / 60
104
+ second = second % 60
105
+ end
104
106
 
105
- class Time
107
+ if minute >= 60
108
+ hour += minute / 60
109
+ minute = minute % 60
110
+ end
106
111
 
107
- def self.construct(year, month = 1, day = 1, hour = 0, minute = 0, second = 0)
108
- warn "Time.construct will be deprecated in version 0.7.0. Please use Chronic.construct instead"
109
- Chronic.construct(year, month, day, hour, minute, second)
110
- end
112
+ if hour >= 24
113
+ day += hour / 24
114
+ hour = hour % 24
115
+ end
116
+
117
+ # determine if there is a day overflow. this is complicated by our crappy calendar
118
+ # system (non-constant number of days per month)
119
+ day <= 56 || raise("day must be no more than 56 (makes month resolution easier)")
120
+ if day > 28
121
+ # no month ever has fewer than 28 days, so only do this if necessary
122
+ leap_year_month_days = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
123
+ common_year_month_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
124
+ days_this_month = Date.leap?(year) ? leap_year_month_days[month - 1] : common_year_month_days[month - 1]
125
+ if day > days_this_month
126
+ month += day / days_this_month
127
+ day = day % days_this_month
128
+ end
129
+ end
130
+
131
+ if month > 12
132
+ if month % 12 == 0
133
+ year += (month - 12) / 12
134
+ month = 12
135
+ else
136
+ year += month / 12
137
+ month = month % 12
138
+ end
139
+ end
111
140
 
112
- def to_minidate
113
- warn "Time.to_minidate will be deprecated in version 0.7.0. Please use Chronic::MiniDate.from_time(time) instead"
114
- Chronic::MiniDate.from_time(self)
141
+ Chronic.time_class.local(year, month, day, hour, minute, second)
115
142
  end
116
143
 
117
144
  end
data/test/test_chronic.rb CHANGED
@@ -8,12 +8,12 @@ class TestChronic < TestCase
8
8
  end
9
9
 
10
10
  def test_pre_normalize
11
- assert_equal Chronic.pre_normalize('12:55 pm'), Chronic.pre_normalize('12.55 pm')
11
+ assert_equal Chronic::Parser.new.pre_normalize('12:55 pm'), Chronic::Parser.new.pre_normalize('12.55 pm')
12
12
  end
13
13
 
14
14
  def test_pre_normalize_numerized_string
15
15
  string = 'two and a half years'
16
- assert_equal Chronic::Numerizer.numerize(string), Chronic.pre_normalize(string)
16
+ assert_equal Chronic::Numerizer.numerize(string), Chronic::Parser.new.pre_normalize(string)
17
17
  end
18
18
 
19
19
  def test_post_normalize_am_pm_aliases
@@ -46,21 +46,13 @@ class TestChronic < TestCase
46
46
 
47
47
  def test_guess
48
48
  span = Chronic::Span.new(Time.local(2006, 8, 16, 0), Time.local(2006, 8, 17, 0))
49
- assert_equal Time.local(2006, 8, 16, 12), Chronic.guess(span)
49
+ assert_equal Time.local(2006, 8, 16, 12), Chronic::Parser.new.guess(span)
50
50
 
51
51
  span = Chronic::Span.new(Time.local(2006, 8, 16, 0), Time.local(2006, 8, 17, 0, 0, 1))
52
- assert_equal Time.local(2006, 8, 16, 12), Chronic.guess(span)
52
+ assert_equal Time.local(2006, 8, 16, 12), Chronic::Parser.new.guess(span)
53
53
 
54
54
  span = Chronic::Span.new(Time.local(2006, 11), Time.local(2006, 12))
55
- assert_equal Time.local(2006, 11, 16), Chronic.guess(span)
56
- end
57
-
58
- def test_now
59
- Chronic.parse('now', :now => Time.local(2006, 01))
60
- assert_equal Time.local(2006, 01), Chronic.now
61
-
62
- Chronic.parse('now', :now => Time.local(2007, 01))
63
- assert_equal Time.local(2007, 01), Chronic.now
55
+ assert_equal Time.local(2006, 11, 16), Chronic::Parser.new.guess(span)
64
56
  end
65
57
 
66
58
  def test_endian_definitions
@@ -72,26 +64,16 @@ class TestChronic < TestCase
72
64
  Chronic::Handler.new([:scalar_day, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sd_sm_sy)
73
65
  ]
74
66
 
75
- assert_equal endians, Chronic.definitions[:endian]
67
+ assert_equal endians, Chronic::Parser.new.definitions[:endian]
76
68
 
77
- defs = Chronic.definitions(:endian_precedence => :little)
69
+ defs = Chronic::Parser.new.definitions(:endian_precedence => :little)
78
70
  assert_equal endians.reverse, defs[:endian]
79
71
 
80
- defs = Chronic.definitions(:endian_precedence => [:little, :middle])
72
+ defs = Chronic::Parser.new.definitions(:endian_precedence => [:little, :middle])
81
73
  assert_equal endians.reverse, defs[:endian]
82
74
 
83
75
  assert_raises(ArgumentError) do
84
- Chronic.definitions(:endian_precedence => :invalid)
85
- end
86
- end
87
-
88
- def test_passing_options
89
- assert_raises(ArgumentError) do
90
- Chronic.parse('now', :invalid => :option)
91
- end
92
-
93
- assert_raises(ArgumentError) do
94
- Chronic.parse('now', :context => :invalid_context)
76
+ Chronic::Parser.new.definitions(:endian_precedence => :invalid)
95
77
  end
96
78
  end
97
79