Hokkaido 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. data/Gemfile +2 -0
  2. data/Gemfile.lock +2 -2
  3. data/ansiterm-color/.gitignore +3 -0
  4. data/ansiterm-color/CHANGES +28 -0
  5. data/ansiterm-color/COPYING +340 -0
  6. data/ansiterm-color/README +137 -0
  7. data/ansiterm-color/Rakefile +88 -0
  8. data/ansiterm-color/VERSION +1 -0
  9. data/ansiterm-color/bin/cdiff +19 -0
  10. data/ansiterm-color/bin/decolor +12 -0
  11. data/ansiterm-color/doc-main.txt +119 -0
  12. data/ansiterm-color/examples/example.rb +90 -0
  13. data/ansiterm-color/install.rb +15 -0
  14. data/ansiterm-color/lib/term/ansicolor/.keep +0 -0
  15. data/ansiterm-color/lib/term/ansicolor/version.rb +10 -0
  16. data/ansiterm-color/lib/term/ansicolor.rb +102 -0
  17. data/ansiterm-color/make_doc.rb +4 -0
  18. data/ansiterm-color/tests/ansicolor_test.rb +66 -0
  19. data/chronic/.gitignore +6 -0
  20. data/chronic/HISTORY.md +205 -0
  21. data/chronic/LICENSE +21 -0
  22. data/chronic/README.md +181 -0
  23. data/chronic/Rakefile +46 -0
  24. data/chronic/chronic.gemspec +20 -0
  25. data/chronic/lib/chronic/grabber.rb +33 -0
  26. data/chronic/lib/chronic/handler.rb +88 -0
  27. data/chronic/lib/chronic/handlers.rb +572 -0
  28. data/chronic/lib/chronic/mini_date.rb +38 -0
  29. data/chronic/lib/chronic/numerizer.rb +121 -0
  30. data/chronic/lib/chronic/ordinal.rb +47 -0
  31. data/chronic/lib/chronic/pointer.rb +32 -0
  32. data/chronic/lib/chronic/repeater.rb +145 -0
  33. data/chronic/lib/chronic/repeaters/repeater_day.rb +53 -0
  34. data/chronic/lib/chronic/repeaters/repeater_day_name.rb +52 -0
  35. data/chronic/lib/chronic/repeaters/repeater_day_portion.rb +108 -0
  36. data/chronic/lib/chronic/repeaters/repeater_fortnight.rb +71 -0
  37. data/chronic/lib/chronic/repeaters/repeater_hour.rb +58 -0
  38. data/chronic/lib/chronic/repeaters/repeater_minute.rb +58 -0
  39. data/chronic/lib/chronic/repeaters/repeater_month.rb +79 -0
  40. data/chronic/lib/chronic/repeaters/repeater_month_name.rb +94 -0
  41. data/chronic/lib/chronic/repeaters/repeater_season.rb +109 -0
  42. data/chronic/lib/chronic/repeaters/repeater_season_name.rb +43 -0
  43. data/chronic/lib/chronic/repeaters/repeater_second.rb +42 -0
  44. data/chronic/lib/chronic/repeaters/repeater_time.rb +128 -0
  45. data/chronic/lib/chronic/repeaters/repeater_week.rb +74 -0
  46. data/chronic/lib/chronic/repeaters/repeater_weekday.rb +85 -0
  47. data/chronic/lib/chronic/repeaters/repeater_weekend.rb +66 -0
  48. data/chronic/lib/chronic/repeaters/repeater_year.rb +77 -0
  49. data/chronic/lib/chronic/scalar.rb +116 -0
  50. data/chronic/lib/chronic/season.rb +26 -0
  51. data/chronic/lib/chronic/separator.rb +94 -0
  52. data/chronic/lib/chronic/span.rb +31 -0
  53. data/chronic/lib/chronic/tag.rb +36 -0
  54. data/chronic/lib/chronic/time_zone.rb +32 -0
  55. data/chronic/lib/chronic/token.rb +47 -0
  56. data/chronic/lib/chronic.rb +442 -0
  57. data/chronic/test/helper.rb +12 -0
  58. data/chronic/test/test_chronic.rb +150 -0
  59. data/chronic/test/test_daylight_savings.rb +118 -0
  60. data/chronic/test/test_handler.rb +104 -0
  61. data/chronic/test/test_mini_date.rb +32 -0
  62. data/chronic/test/test_numerizer.rb +72 -0
  63. data/chronic/test/test_parsing.rb +1061 -0
  64. data/chronic/test/test_repeater_day_name.rb +51 -0
  65. data/chronic/test/test_repeater_day_portion.rb +254 -0
  66. data/chronic/test/test_repeater_fortnight.rb +62 -0
  67. data/chronic/test/test_repeater_hour.rb +68 -0
  68. data/chronic/test/test_repeater_minute.rb +34 -0
  69. data/chronic/test/test_repeater_month.rb +50 -0
  70. data/chronic/test/test_repeater_month_name.rb +56 -0
  71. data/chronic/test/test_repeater_season.rb +40 -0
  72. data/chronic/test/test_repeater_time.rb +70 -0
  73. data/chronic/test/test_repeater_week.rb +62 -0
  74. data/chronic/test/test_repeater_weekday.rb +55 -0
  75. data/chronic/test/test_repeater_weekend.rb +74 -0
  76. data/chronic/test/test_repeater_year.rb +69 -0
  77. data/chronic/test/test_span.rb +23 -0
  78. data/chronic/test/test_token.rb +25 -0
  79. data/lib/Hokkaido/version.rb +1 -1
  80. data/lib/Hokkaido.rb +13 -9
  81. data/lib/gem_modifier.rb +23 -3
  82. data/lib/term/ansicolor.rb +4 -1
  83. data/spec/Hokkaido/port_spec.rb +15 -7
  84. metadata +78 -2
@@ -0,0 +1,442 @@
1
+ require 'time'
2
+ require 'date'
3
+
4
+ # Parse natural language dates and times into Time or Chronic::Span objects.
5
+ #
6
+ # Examples:
7
+ #
8
+ # require 'chronic'
9
+ #
10
+ # Time.now #=> Sun Aug 27 23:18:25 PDT 2006
11
+ #
12
+ # Chronic.parse('tomorrow')
13
+ # #=> Mon Aug 28 12:00:00 PDT 2006
14
+ #
15
+ # Chronic.parse('monday', :context => :past)
16
+ # #=> 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
+ module Chronic
30
+ VERSION = "0.8.0"
31
+
32
+ class << self
33
+
34
+ # Returns true when debug mode is enabled.
35
+ attr_accessor :debug
36
+
37
+ # Examples:
38
+ #
39
+ # require 'chronic'
40
+ # require 'active_support/time'
41
+ #
42
+ # Time.zone = 'UTC'
43
+ # Chronic.time_class = Time.zone
44
+ # Chronic.parse('June 15 2006 at 5:54 AM')
45
+ # # => Thu, 15 Jun 2006 05:45:00 UTC +00:00
46
+ #
47
+ # Returns The Time class Chronic uses internally.
48
+ 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
+ end
63
+
64
+ self.debug = false
65
+ self.time_class = Time
66
+
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
+
101
+ # Returns a Hash of default configuration options.
102
+ DEFAULT_OPTIONS = {
103
+ :context => :future,
104
+ :now => nil,
105
+ :guess => true,
106
+ :ambiguous_time_range => 6,
107
+ :endian_precedence => [:middle, :little],
108
+ :ambiguous_year_future_bias => 50
109
+ }
110
+
111
+ class << self
112
+
113
+ # Parses a string containing a natural language date or time.
114
+ #
115
+ # If the parser can find a date or time, either a Time or Chronic::Span
116
+ # will be returned (depending on the value of `:guess`). If no
117
+ # date or time can be found, `nil` will be returned.
118
+ #
119
+ # text - The String text to parse.
120
+ # opts - An optional Hash of configuration options:
121
+ # :context - If your string represents a birthday, you can set
122
+ # this value to :past and if an ambiguous string is
123
+ # given, it will assume it is in the past.
124
+ # :now - Time, all computations will be based off of time
125
+ # instead of Time.now.
126
+ # :guess - By default the parser will guess a single point in time
127
+ # for the given date or time. If you'd rather have the
128
+ # entire time span returned, set this to false
129
+ # and a Chronic::Span will be returned.
130
+ # :ambiguous_time_range - If an Integer is given, ambiguous times
131
+ # (like 5:00) will be assumed to be within the range of
132
+ # that time in the AM to that time in the PM. For
133
+ # example, if you set it to `7`, then the parser will
134
+ # look for the time between 7am and 7pm. In the case of
135
+ # 5:00, it would assume that means 5:00pm. If `:none`
136
+ # is given, no assumption will be made, and the first
137
+ # matching instance of that time will be used.
138
+ # :endian_precedence - By default, Chronic will parse "03/04/2011"
139
+ # as the fourth day of the third month. Alternatively you
140
+ # can tell Chronic to parse this as the third day of the
141
+ # fourth month by setting this to [:little, :middle].
142
+ # :ambiguous_year_future_bias - When parsing two digit years
143
+ # (ie 79) unlike Rubys Time class, Chronic will attempt
144
+ # to assume the full year using this figure. Chronic will
145
+ # look x amount of years into the future and past. If the
146
+ # two digit year is `now + x years` it's assumed to be the
147
+ # future, `now - x years` is assumed to be the past.
148
+ #
149
+ # Returns a new Time object, or Chronic::Span if :guess option is false.
150
+ def parse(text, opts={})
151
+ options = DEFAULT_OPTIONS.merge opts
152
+
153
+ # ensure the specified options are valid
154
+ (opts.keys - DEFAULT_OPTIONS.keys).each do |key|
155
+ raise ArgumentError, "#{key} is not a valid option key."
156
+ end
157
+
158
+ unless [:past, :future, :none].include?(options[:context])
159
+ raise ArgumentError, "Invalid context, :past/:future only"
160
+ end
161
+
162
+ options[:text] = text
163
+ Chronic.now = options[:now] || Chronic.time_class.now
164
+
165
+ # tokenize words
166
+ tokens = tokenize(text, options)
167
+
168
+ if Chronic.debug
169
+ puts "+#{'-' * 51}\n| #{tokens}\n+#{'-' * 51}"
170
+ end
171
+
172
+ span = tokens_to_span(tokens, options)
173
+
174
+ if span
175
+ options[:guess] ? guess(span) : span
176
+ end
177
+ end
178
+
179
+ # Clean up the specified text ready for parsing.
180
+ #
181
+ # Clean up the string by stripping unwanted characters, converting
182
+ # idioms to their canonical form, converting number words to numbers
183
+ # (three => 3), and converting ordinal words to numeric
184
+ # ordinals (third => 3rd)
185
+ #
186
+ # text - The String text to normalize.
187
+ #
188
+ # Examples:
189
+ #
190
+ # Chronic.pre_normalize('first day in May')
191
+ # #=> "1st day in may"
192
+ #
193
+ # Chronic.pre_normalize('tomorrow after noon')
194
+ # #=> "next day future 12:00"
195
+ #
196
+ # Chronic.pre_normalize('one hundred and thirty six days from now')
197
+ # #=> "136 days future this second"
198
+ #
199
+ # Returns a new String ready for Chronic to parse.
200
+ def pre_normalize(text)
201
+ text = text.to_s.downcase
202
+ text.gsub!(/\./, ':')
203
+ text.gsub!(/['"]/, '')
204
+ text.gsub!(/,/, ' ')
205
+ text.gsub!(/^second /, '2nd ')
206
+ text.gsub!(/\bsecond (of|day|month|hour|minute|second)\b/, '2nd \1')
207
+ text = Numerizer.numerize(text)
208
+ text.gsub!(/\-(\d{2}:?\d{2})\b/, 'tzminus\1')
209
+ text.gsub!(/([\/\-\,\@])/) { ' ' + $1 + ' ' }
210
+ text.gsub!(/(?:^|\s)0(\d+:\d+\s*pm?\b)/, ' \1')
211
+ text.gsub!(/\btoday\b/, 'this day')
212
+ text.gsub!(/\btomm?orr?ow\b/, 'next day')
213
+ text.gsub!(/\byesterday\b/, 'last day')
214
+ text.gsub!(/\bnoon\b/, '12:00pm')
215
+ text.gsub!(/\bmidnight\b/, '24:00')
216
+ text.gsub!(/\bnow\b/, 'this second')
217
+ text.gsub!(/\b(?:ago|before(?: now)?)\b/, 'past')
218
+ text.gsub!(/\bthis (?:last|past)\b/, 'last')
219
+ text.gsub!(/\b(?:in|during) the (morning)\b/, '\1')
220
+ text.gsub!(/\b(?:in the|during the|at) (afternoon|evening|night)\b/, '\1')
221
+ text.gsub!(/\btonight\b/, 'this night')
222
+ text.gsub!(/\b\d+:?\d*[ap]\b/,'\0m')
223
+ text.gsub!(/(\d)([ap]m|oclock)\b/, '\1 \2')
224
+ text.gsub!(/\b(hence|after|from)\b/, 'future')
225
+ text.gsub!(/^\s?an? /i, '1 ')
226
+ text.gsub!(/\b(\d{4}):(\d{2}):(\d{2})\b/, '\1 / \2 / \3') # DTOriginal
227
+ text
228
+ end
229
+
230
+ # Convert number words to numbers (three => 3, fourth => 4th).
231
+ #
232
+ # text - The String to convert.
233
+ #
234
+ # Returns a new String with words converted to numbers.
235
+ def numericize_numbers(text)
236
+ warn "Chronic.numericize_numbers will be deprecated in version 0.7.0. Please use Chronic::Numerizer.numerize instead"
237
+ Numerizer.numerize(text)
238
+ end
239
+
240
+ # Guess a specific time within the given span.
241
+ #
242
+ # span - The Chronic::Span object to calcuate a guess from.
243
+ #
244
+ # Returns a new Time object.
245
+ def guess(span)
246
+ if span.width > 1
247
+ span.begin + (span.width / 2)
248
+ else
249
+ span.begin
250
+ end
251
+ end
252
+
253
+ # List of Handler definitions. See #parse for a list of options this
254
+ # method accepts.
255
+ #
256
+ # options - An optional Hash of configuration options:
257
+ # :endian_precedence -
258
+ #
259
+ # Returns A Hash of Handler definitions.
260
+ def definitions(options={})
261
+ options[:endian_precedence] ||= [:middle, :little]
262
+
263
+ @definitions ||= {
264
+ :time => [
265
+ Handler.new([:repeater_time, :repeater_day_portion?], nil)
266
+ ],
267
+
268
+ :date => [
269
+ Handler.new([:repeater_day_name, :repeater_month_name, :scalar_day, :repeater_time, :separator_slash_or_dash?, :time_zone, :scalar_year], :handle_generic),
270
+ Handler.new([:repeater_day_name, :repeater_month_name, :scalar_day], :handle_rdn_rmn_sd),
271
+ Handler.new([:repeater_day_name, :repeater_month_name, :scalar_day, :scalar_year], :handle_rdn_rmn_sd_sy),
272
+ Handler.new([:repeater_day_name, :repeater_month_name, :ordinal_day], :handle_rdn_rmn_od),
273
+ Handler.new([:scalar_year, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_day, :repeater_time, :time_zone], :handle_generic),
274
+ Handler.new([:repeater_month_name, :scalar_day, :scalar_year], :handle_rmn_sd_sy),
275
+ Handler.new([:repeater_month_name, :ordinal_day, :scalar_year], :handle_rmn_od_sy),
276
+ Handler.new([:repeater_month_name, :scalar_day, :scalar_year, :separator_at?, 'time?'], :handle_rmn_sd_sy),
277
+ Handler.new([:repeater_month_name, :ordinal_day, :scalar_year, :separator_at?, 'time?'], :handle_rmn_od_sy),
278
+ Handler.new([:repeater_month_name, :scalar_day, :separator_at?, 'time?'], :handle_rmn_sd),
279
+ Handler.new([:repeater_time, :repeater_day_portion?, :separator_on?, :repeater_month_name, :scalar_day], :handle_rmn_sd_on),
280
+ Handler.new([:repeater_month_name, :ordinal_day, :separator_at?, 'time?'], :handle_rmn_od),
281
+ Handler.new([:ordinal_day, :repeater_month_name, :scalar_year, :separator_at?, 'time?'], :handle_od_rmn_sy),
282
+ Handler.new([:ordinal_day, :repeater_month_name, :separator_at?, 'time?'], :handle_od_rmn),
283
+ Handler.new([:ordinal_day, :grabber?, :repeater_month, :separator_at?, 'time?'], :handle_od_rm),
284
+ Handler.new([:scalar_year, :repeater_month_name, :ordinal_day], :handle_sy_rmn_od),
285
+ Handler.new([:repeater_time, :repeater_day_portion?, :separator_on?, :repeater_month_name, :ordinal_day], :handle_rmn_od_on),
286
+ Handler.new([:repeater_month_name, :scalar_year], :handle_rmn_sy),
287
+ Handler.new([:scalar_day, :repeater_month_name, :scalar_year, :separator_at?, 'time?'], :handle_sd_rmn_sy),
288
+ Handler.new([:scalar_day, :repeater_month_name, :separator_at?, 'time?'], :handle_sd_rmn),
289
+ Handler.new([:scalar_year, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_day, :separator_at?, 'time?'], :handle_sy_sm_sd),
290
+ Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_year], :handle_sm_sy),
291
+ Handler.new([:scalar_day, :separator_slash_or_dash, :repeater_month_name, :separator_slash_or_dash, :scalar_year, :repeater_time?], :handle_sm_rmn_sy),
292
+ Handler.new([:scalar_year, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar?, :time_zone], :handle_generic),
293
+ # Handler.new([:scalar_year, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :time_zone], :handle_generic)
294
+
295
+ ],
296
+
297
+ # tonight at 7pm
298
+ :anchor => [
299
+ Handler.new([:separator_on?, :grabber?, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),
300
+ Handler.new([:grabber?, :repeater, :repeater, :separator?, :repeater?, :repeater?], :handle_r),
301
+ Handler.new([:repeater, :grabber, :repeater], :handle_r_g_r)
302
+ ],
303
+
304
+ # 3 weeks from now, in 2 months
305
+ :arrow => [
306
+ Handler.new([:scalar, :repeater, :pointer], :handle_s_r_p),
307
+ Handler.new([:pointer, :scalar, :repeater], :handle_p_s_r),
308
+ Handler.new([:scalar, :repeater, :pointer, :separator_at?, 'anchor'], :handle_s_r_p_a)
309
+ ],
310
+
311
+ # 3rd week in march
312
+ :narrow => [
313
+ Handler.new([:ordinal, :repeater, :separator_in, :repeater], :handle_o_r_s_r),
314
+ Handler.new([:ordinal, :repeater, :grabber, :repeater], :handle_o_r_g_r)
315
+ ]
316
+ }
317
+
318
+ endians = [
319
+ Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_day, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sm_sd_sy),
320
+ Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_day, :separator_at?, 'time?'], :handle_sm_sd),
321
+ Handler.new([:scalar_day, :separator_slash_or_dash, :scalar_month, :separator_at?, 'time?'], :handle_sd_sm),
322
+ Handler.new([:scalar_day, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sd_sm_sy)
323
+ ]
324
+
325
+ case endian = Array(options[:endian_precedence]).first
326
+ when :little
327
+ @definitions[:endian] = endians.reverse
328
+ when :middle
329
+ @definitions[:endian] = endians
330
+ else
331
+ raise ArgumentError, "Unknown endian option '#{endian}'"
332
+ end
333
+
334
+ @definitions
335
+ end
336
+
337
+ # Construct a new time object determining possible month overflows
338
+ # and leap years.
339
+ #
340
+ # year - Integer year.
341
+ # month - Integer month.
342
+ # day - Integer day.
343
+ # hour - Integer hour.
344
+ # minute - Integer minute.
345
+ # second - Integer second.
346
+ #
347
+ # Returns a new Time object constructed from these params.
348
+ def construct(year, month = 1, day = 1, hour = 0, minute = 0, second = 0)
349
+ if second >= 60
350
+ minute += second / 60
351
+ second = second % 60
352
+ end
353
+
354
+ if minute >= 60
355
+ hour += minute / 60
356
+ minute = minute % 60
357
+ end
358
+
359
+ if hour >= 24
360
+ day += hour / 24
361
+ hour = hour % 24
362
+ end
363
+
364
+ # determine if there is a day overflow. this is complicated by our crappy calendar
365
+ # system (non-constant number of days per month)
366
+ day <= 56 || raise("day must be no more than 56 (makes month resolution easier)")
367
+ if day > 28
368
+ # no month ever has fewer than 28 days, so only do this if necessary
369
+ leap_year_month_days = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
370
+ common_year_month_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
371
+ days_this_month = Date.leap?(year) ? leap_year_month_days[month - 1] : common_year_month_days[month - 1]
372
+ if day > days_this_month
373
+ month += day / days_this_month
374
+ day = day % days_this_month
375
+ end
376
+ end
377
+
378
+ if month > 12
379
+ if month % 12 == 0
380
+ year += (month - 12) / 12
381
+ month = 12
382
+ else
383
+ year += month / 12
384
+ month = month % 12
385
+ end
386
+ end
387
+
388
+ Chronic.time_class.local(year, month, day, hour, minute, second)
389
+ end
390
+
391
+ private
392
+
393
+ def tokenize(text, options)
394
+ text = pre_normalize(text)
395
+ tokens = text.split(' ').map { |word| Token.new(word) }
396
+ [Repeater, Grabber, Pointer, Scalar, Ordinal, Separator, TimeZone].each do |tok|
397
+ tok.scan(tokens, options)
398
+ end
399
+ tokens.select { |token| token.tagged? }
400
+ end
401
+
402
+ def tokens_to_span(tokens, options)
403
+ definitions = definitions(options)
404
+
405
+ (definitions[:endian] + definitions[:date]).each do |handler|
406
+ if handler.match(tokens, definitions)
407
+ good_tokens = tokens.select { |o| !o.get_tag Separator }
408
+ return handler.invoke(:date, good_tokens, options)
409
+ end
410
+ end
411
+
412
+ definitions[:anchor].each do |handler|
413
+ if handler.match(tokens, definitions)
414
+ good_tokens = tokens.select { |o| !o.get_tag Separator }
415
+ return handler.invoke(:anchor, good_tokens, options)
416
+ end
417
+ end
418
+
419
+ definitions[:arrow].each do |handler|
420
+ if handler.match(tokens, definitions)
421
+ good_tokens = tokens.reject { |o| o.get_tag(SeparatorAt) || o.get_tag(SeparatorSlashOrDash) || o.get_tag(SeparatorComma) }
422
+ return handler.invoke(:arrow, good_tokens, options)
423
+ end
424
+ end
425
+
426
+ definitions[:narrow].each do |handler|
427
+ if handler.match(tokens, definitions)
428
+ return handler.invoke(:narrow, tokens, options)
429
+ end
430
+ end
431
+
432
+ puts "-none" if Chronic.debug
433
+ return nil
434
+ end
435
+
436
+ end
437
+
438
+ # Internal exception
439
+ class ChronicPain < Exception
440
+ end
441
+
442
+ end
@@ -0,0 +1,12 @@
1
+ unless defined? Chronic
2
+ $:.unshift File.expand_path('../../lib', __FILE__)
3
+ require 'chronic'
4
+ end
5
+
6
+ require 'minitest/autorun'
7
+
8
+ class TestCase < MiniTest::Unit::TestCase
9
+ def self.test(name, &block)
10
+ define_method("test_#{name.gsub(/\W/, '_')}", &block) if block
11
+ end
12
+ end
@@ -0,0 +1,150 @@
1
+ require 'helper'
2
+
3
+ class TestChronic < TestCase
4
+
5
+ def setup
6
+ # Wed Aug 16 14:00:00 UTC 2006
7
+ @now = Time.local(2006, 8, 16, 14, 0, 0, 0)
8
+ end
9
+
10
+ def test_pre_normalize
11
+ assert_equal Chronic.pre_normalize('12:55 pm'), Chronic.pre_normalize('12.55 pm')
12
+ end
13
+
14
+ def test_pre_normalize_numerized_string
15
+ string = 'two and a half years'
16
+ assert_equal Chronic::Numerizer.numerize(string), Chronic.pre_normalize(string)
17
+ end
18
+
19
+ def test_post_normalize_am_pm_aliases
20
+ # affect wanted patterns
21
+
22
+ tokens = [Chronic::Token.new("5:00"), Chronic::Token.new("morning")]
23
+ tokens[0].tag(Chronic::RepeaterTime.new("5:00"))
24
+ tokens[1].tag(Chronic::RepeaterDayPortion.new(:morning))
25
+
26
+ assert_equal :morning, tokens[1].tags[0].type
27
+
28
+ tokens = Chronic::Handlers.dealias_and_disambiguate_times(tokens, {})
29
+
30
+ assert_equal :am, tokens[1].tags[0].type
31
+ assert_equal 2, tokens.size
32
+
33
+ # don't affect unwanted patterns
34
+
35
+ tokens = [Chronic::Token.new("friday"), Chronic::Token.new("morning")]
36
+ tokens[0].tag(Chronic::RepeaterDayName.new(:friday))
37
+ tokens[1].tag(Chronic::RepeaterDayPortion.new(:morning))
38
+
39
+ assert_equal :morning, tokens[1].tags[0].type
40
+
41
+ tokens = Chronic::Handlers.dealias_and_disambiguate_times(tokens, {})
42
+
43
+ assert_equal :morning, tokens[1].tags[0].type
44
+ assert_equal 2, tokens.size
45
+ end
46
+
47
+ def test_guess
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)
50
+
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)
53
+
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
64
+ end
65
+
66
+ def test_endian_definitions
67
+ # middle, little
68
+ endians = [
69
+ Chronic::Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_day, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sm_sd_sy),
70
+ Chronic::Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_day, :separator_at?, 'time?'], :handle_sm_sd),
71
+ Chronic::Handler.new([:scalar_day, :separator_slash_or_dash, :scalar_month, :separator_at?, 'time?'], :handle_sd_sm),
72
+ 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
+ ]
74
+
75
+ assert_equal endians, Chronic.definitions[:endian]
76
+
77
+ defs = Chronic.definitions(:endian_precedence => :little)
78
+ assert_equal endians.reverse, defs[:endian]
79
+
80
+ defs = Chronic.definitions(:endian_precedence => [:little, :middle])
81
+ assert_equal endians.reverse, defs[:endian]
82
+
83
+ 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)
95
+ end
96
+ end
97
+
98
+ def test_debug
99
+ require 'stringio'
100
+ $stdout = StringIO.new
101
+ Chronic.debug = true
102
+
103
+ Chronic.parse 'now'
104
+ assert $stdout.string.include?('this(grabber-this)')
105
+ ensure
106
+ $stdout = STDOUT
107
+ Chronic.debug = false
108
+ end
109
+
110
+ # Chronic.construct
111
+
112
+ def test_normal
113
+ assert_equal Time.local(2006, 1, 2, 0, 0, 0), Chronic.construct(2006, 1, 2, 0, 0, 0)
114
+ assert_equal Time.local(2006, 1, 2, 3, 0, 0), Chronic.construct(2006, 1, 2, 3, 0, 0)
115
+ assert_equal Time.local(2006, 1, 2, 3, 4, 0), Chronic.construct(2006, 1, 2, 3, 4, 0)
116
+ assert_equal Time.local(2006, 1, 2, 3, 4, 5), Chronic.construct(2006, 1, 2, 3, 4, 5)
117
+ end
118
+
119
+ def test_second_overflow
120
+ assert_equal Time.local(2006, 1, 1, 0, 1, 30), Chronic.construct(2006, 1, 1, 0, 0, 90)
121
+ assert_equal Time.local(2006, 1, 1, 0, 5, 0), Chronic.construct(2006, 1, 1, 0, 0, 300)
122
+ end
123
+
124
+ def test_minute_overflow
125
+ assert_equal Time.local(2006, 1, 1, 1, 30), Chronic.construct(2006, 1, 1, 0, 90)
126
+ assert_equal Time.local(2006, 1, 1, 5), Chronic.construct(2006, 1, 1, 0, 300)
127
+ end
128
+
129
+ def test_hour_overflow
130
+ assert_equal Time.local(2006, 1, 2, 12), Chronic.construct(2006, 1, 1, 36)
131
+ assert_equal Time.local(2006, 1, 7), Chronic.construct(2006, 1, 1, 144)
132
+ end
133
+
134
+ def test_day_overflow
135
+ assert_equal Time.local(2006, 2, 1), Chronic.construct(2006, 1, 32)
136
+ assert_equal Time.local(2006, 3, 5), Chronic.construct(2006, 2, 33)
137
+ assert_equal Time.local(2004, 3, 4), Chronic.construct(2004, 2, 33)
138
+ assert_equal Time.local(2000, 3, 4), Chronic.construct(2000, 2, 33)
139
+
140
+ assert_raises(RuntimeError) do
141
+ Chronic.construct(2006, 1, 57)
142
+ end
143
+ end
144
+
145
+ def test_month_overflow
146
+ assert_equal Time.local(2006, 1), Chronic.construct(2005, 13)
147
+ assert_equal Time.local(2005, 12), Chronic.construct(2000, 72)
148
+ end
149
+
150
+ end