Hokkaido 0.0.3 → 0.0.4

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 (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