chronic 0.2.0 → 0.2.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.
- data/History.txt +11 -4
- data/Rakefile +3 -25
- data/lib/chronic.rb +54 -1
- data/lib/chronic/chronic.rb +1 -1
- data/lib/chronic/handlers.rb +71 -16
- data/lib/chronic/repeater.rb +1 -0
- data/lib/chronic/repeaters/repeater_day.rb +6 -6
- data/lib/chronic/repeaters/repeater_day_name.rb +2 -2
- data/lib/chronic/repeaters/repeater_day_portion.rb +8 -8
- data/lib/chronic/repeaters/repeater_fortnight.rb +2 -2
- data/lib/chronic/repeaters/repeater_hour.rb +5 -5
- data/lib/chronic/repeaters/repeater_minute.rb +16 -6
- data/lib/chronic/repeaters/repeater_month.rb +14 -10
- data/lib/chronic/repeaters/repeater_month_name.rb +10 -10
- data/lib/chronic/repeaters/repeater_year.rb +12 -12
- data/test/test_Time.rb +50 -0
- data/test/test_parsing.rb +43 -1
- metadata +5 -4
data/History.txt
CHANGED
@@ -1,11 +1,18 @@
|
|
1
|
-
= 0.2.
|
1
|
+
= 0.2.1
|
2
2
|
|
3
3
|
* fixed time overflow issue
|
4
|
-
* implemented
|
4
|
+
* implemented "next" for minute repeater
|
5
|
+
* generalized time dealiasing to dealias regardless of day portion and time position
|
6
|
+
* added additional token match for cases like "friday evening at 7" and "tomorrow evening at 7"
|
7
|
+
* added support for Time#to_s output format: "Mon Apr 02 17:00:00 PDT 2007"
|
8
|
+
|
9
|
+
= 0.2.0 2007-03-20
|
10
|
+
|
11
|
+
* implemented numerizer, allowing the use of number words (e.g. five weeks ago) (by shalev)
|
5
12
|
|
6
13
|
= 0.1.6 2006-01-15
|
7
14
|
|
8
|
-
* added 'weekend' support (eventualbuddha)
|
15
|
+
* added 'weekend' support (by eventualbuddha)
|
9
16
|
|
10
17
|
= 0.1.5 2006-12-20
|
11
18
|
|
@@ -20,7 +27,7 @@
|
|
20
27
|
|
21
28
|
= 0.1.3
|
22
29
|
|
23
|
-
* improved regexes for word variations (Josh Goebel)
|
30
|
+
* improved regexes for word variations (by Josh Goebel)
|
24
31
|
* fixed a bug that caused "today at 3am" to return nil if current time is after 3am
|
25
32
|
|
26
33
|
= 0.1.2
|
data/Rakefile
CHANGED
@@ -7,6 +7,8 @@ require './lib/chronic.rb'
|
|
7
7
|
Hoe.new('chronic', Chronic::VERSION) do |p|
|
8
8
|
p.rubyforge_name = 'chronic'
|
9
9
|
p.summary = 'A natural language date parser'
|
10
|
+
p.author = 'Tom Preston-Werner'
|
11
|
+
p.email = 'tom@rubyisawesome.com'
|
10
12
|
p.description = p.paragraphs_of('README.txt', 2).join("\n\n")
|
11
13
|
p.url = p.paragraphs_of('README.txt', 0).first.split(/\n/)[1..-1]
|
12
14
|
p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
|
@@ -14,28 +16,4 @@ Hoe.new('chronic', Chronic::VERSION) do |p|
|
|
14
16
|
p.extra_deps = []
|
15
17
|
end
|
16
18
|
|
17
|
-
# vim: syntax=Ruby
|
18
|
-
|
19
|
-
__END__
|
20
|
-
|
21
|
-
require 'rubygems'
|
22
|
-
|
23
|
-
SPEC = Gem::Specification.new do |s|
|
24
|
-
s.name = 'chronic'
|
25
|
-
s.version = '0.1.5'
|
26
|
-
s.author = 'Tom Preston-Werner'
|
27
|
-
s.email = 'tom@rubyisawesome.com'
|
28
|
-
s.homepage = 'http://chronic.rubyforge.org'
|
29
|
-
s.platform = Gem::Platform::RUBY
|
30
|
-
s.summary = "A natural language date parser"
|
31
|
-
candidates = Dir["{lib,test}/**/*"]
|
32
|
-
s.files = candidates.delete_if do |item|
|
33
|
-
item.include?('.svn')
|
34
|
-
end
|
35
|
-
s.require_path = "lib"
|
36
|
-
s.autorequire = "chronic"
|
37
|
-
s.test_file = "test/suite.rb"
|
38
|
-
s.has_rdoc = true
|
39
|
-
s.extra_rdoc_files = ['README']
|
40
|
-
s.rdoc_options << '--main' << 'README'
|
41
|
-
end
|
19
|
+
# vim: syntax=Ruby
|
data/lib/chronic.rb
CHANGED
@@ -34,11 +34,12 @@ require 'chronic/pointer'
|
|
34
34
|
require 'chronic/scalar'
|
35
35
|
require 'chronic/ordinal'
|
36
36
|
require 'chronic/separator'
|
37
|
+
require 'chronic/time_zone'
|
37
38
|
|
38
39
|
require 'numerizer/numerizer'
|
39
40
|
|
40
41
|
module Chronic
|
41
|
-
VERSION = "0.2.
|
42
|
+
VERSION = "0.2.1"
|
42
43
|
|
43
44
|
def self.debug; false; end
|
44
45
|
end
|
@@ -50,6 +51,33 @@ def p(val)
|
|
50
51
|
puts
|
51
52
|
end
|
52
53
|
|
54
|
+
# class Time
|
55
|
+
# def self.construct(year, month = 1, day = 1, hour = 0, minute = 0, second = 0)
|
56
|
+
# # extra_seconds = second > 60 ? second - 60 : 0
|
57
|
+
# # extra_minutes = minute > 59 ? minute - 59 : 0
|
58
|
+
# # extra_hours = hour > 23 ? hour - 23 : 0
|
59
|
+
# # extra_days = day >
|
60
|
+
#
|
61
|
+
# if month > 12
|
62
|
+
# if month % 12 == 0
|
63
|
+
# year += (month - 12) / 12
|
64
|
+
# month = 12
|
65
|
+
# else
|
66
|
+
# year += month / 12
|
67
|
+
# month = month % 12
|
68
|
+
# end
|
69
|
+
# end
|
70
|
+
#
|
71
|
+
# base = Time.local(year, month)
|
72
|
+
# puts base
|
73
|
+
# offset = ((day - 1) * 24 * 60 * 60) + (hour * 60 * 60) + (minute * 60) + second
|
74
|
+
# puts offset.to_s
|
75
|
+
# date = base + offset
|
76
|
+
# puts date
|
77
|
+
# date
|
78
|
+
# end
|
79
|
+
# end
|
80
|
+
|
53
81
|
class Time
|
54
82
|
def self.construct(year, month = 1, day = 1, hour = 0, minute = 0, second = 0)
|
55
83
|
if second >= 60
|
@@ -67,6 +95,31 @@ class Time
|
|
67
95
|
hour = hour % 24
|
68
96
|
end
|
69
97
|
|
98
|
+
# determine if there is a day overflow. this is complicated by our crappy calendar
|
99
|
+
# system (non-constant number of days per month)
|
100
|
+
day <= 56 || raise("day must be no more than 56 (makes month resolution easier)")
|
101
|
+
if day > 28
|
102
|
+
# no month ever has fewer than 28 days, so only do this if necessary
|
103
|
+
leap_year = (year % 4 == 0) && !(year % 100 == 0)
|
104
|
+
leap_year_month_days = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
105
|
+
common_year_month_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
106
|
+
days_this_month = leap_year ? leap_year_month_days[month - 1] : common_year_month_days[month - 1]
|
107
|
+
if day > days_this_month
|
108
|
+
month += day / days_this_month
|
109
|
+
day = day % days_this_month
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
if month > 12
|
114
|
+
if month % 12 == 0
|
115
|
+
year += (month - 12) / 12
|
116
|
+
month = 12
|
117
|
+
else
|
118
|
+
year += month / 12
|
119
|
+
month = month % 12
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
70
123
|
Time.local(year, month, day, hour, minute, second)
|
71
124
|
end
|
72
125
|
end
|
data/lib/chronic/chronic.rb
CHANGED
@@ -66,7 +66,7 @@ module Chronic
|
|
66
66
|
@tokens = tokenizer.scan(@tokens, options)
|
67
67
|
end
|
68
68
|
|
69
|
-
[Grabber, Pointer, Scalar, Ordinal, Separator].each do |tokenizer|
|
69
|
+
[Grabber, Pointer, Scalar, Ordinal, Separator, TimeZone].each do |tokenizer|
|
70
70
|
@tokens = tokenizer.scan(@tokens)
|
71
71
|
end
|
72
72
|
|
data/lib/chronic/handlers.rb
CHANGED
@@ -6,7 +6,8 @@ module Chronic
|
|
6
6
|
@definitions ||=
|
7
7
|
{:time => [Handler.new([:repeater_time, :repeater_day_portion?], nil)],
|
8
8
|
|
9
|
-
:date => [Handler.new([:repeater_month_name, :scalar_day, :scalar_year], :
|
9
|
+
:date => [Handler.new([:repeater_day_name, :repeater_month_name, :scalar_day, :repeater_time, :time_zone, :scalar_year], :handle_rdn_rmn_sd_t_tz_sy),
|
10
|
+
Handler.new([:repeater_month_name, :scalar_day, :scalar_year], :handle_rmn_sd_sy),
|
10
11
|
Handler.new([:repeater_month_name, :scalar_day, :scalar_year, :separator_at?, 'time?'], :handle_rmn_sd_sy),
|
11
12
|
Handler.new([:repeater_month_name, :scalar_day, :separator_at?, 'time?'], :handle_rmn_sd),
|
12
13
|
Handler.new([:repeater_month_name, :ordinal_day, :separator_at?, 'time?'], :handle_rmn_od),
|
@@ -17,13 +18,17 @@ module Chronic
|
|
17
18
|
Handler.new([:scalar_year, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_day, :separator_at?, 'time?'], :handle_sy_sm_sd),
|
18
19
|
Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_year], :handle_sm_sy)],
|
19
20
|
|
21
|
+
# tonight at 7pm
|
20
22
|
:anchor => [Handler.new([:grabber?, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),
|
23
|
+
Handler.new([:grabber?, :repeater, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),
|
21
24
|
Handler.new([:repeater, :grabber, :repeater], :handle_r_g_r)],
|
22
25
|
|
26
|
+
# 3 weeks from now, in 2 months
|
23
27
|
:arrow => [Handler.new([:scalar, :repeater, :pointer], :handle_s_r_p),
|
24
28
|
Handler.new([:pointer, :scalar, :repeater], :handle_p_s_r),
|
25
29
|
Handler.new([:scalar, :repeater, :pointer, 'anchor'], :handle_s_r_p_a)],
|
26
30
|
|
31
|
+
# 3rd week in march
|
27
32
|
:narrow => [Handler.new([:ordinal, :repeater, :separator_in, :repeater], :handle_o_r_s_r),
|
28
33
|
Handler.new([:ordinal, :repeater, :grabber, :repeater], :handle_o_r_g_r)]
|
29
34
|
}
|
@@ -34,6 +39,7 @@ module Chronic
|
|
34
39
|
|
35
40
|
self.definitions[:date].each do |handler|
|
36
41
|
if handler.match(tokens, self.definitions)
|
42
|
+
puts "-date" if Chronic.debug
|
37
43
|
good_tokens = tokens.select { |o| !o.get_tag Separator }
|
38
44
|
return self.send(handler.handler_method, good_tokens, options)
|
39
45
|
end
|
@@ -43,6 +49,7 @@ module Chronic
|
|
43
49
|
|
44
50
|
self.definitions[:anchor].each do |handler|
|
45
51
|
if handler.match(tokens, self.definitions)
|
52
|
+
puts "-anchor" if Chronic.debug
|
46
53
|
good_tokens = tokens.select { |o| !o.get_tag Separator }
|
47
54
|
return self.send(handler.handler_method, good_tokens, options)
|
48
55
|
end
|
@@ -52,21 +59,24 @@ module Chronic
|
|
52
59
|
|
53
60
|
self.definitions[:arrow].each do |handler|
|
54
61
|
if handler.match(tokens, self.definitions)
|
62
|
+
puts "-arrow" if Chronic.debug
|
55
63
|
good_tokens = tokens.reject { |o| o.get_tag(SeparatorAt) || o.get_tag(SeparatorSlashOrDash) || o.get_tag(SeparatorComma) }
|
56
64
|
return self.send(handler.handler_method, good_tokens, options)
|
57
65
|
end
|
58
66
|
end
|
59
67
|
|
60
|
-
# not an arrow, let's hope it's
|
68
|
+
# not an arrow, let's hope it's a narrow
|
61
69
|
|
62
70
|
self.definitions[:narrow].each do |handler|
|
63
71
|
if handler.match(tokens, self.definitions)
|
72
|
+
puts "-narrow" if Chronic.debug
|
64
73
|
#good_tokens = tokens.select { |o| !o.get_tag Separator }
|
65
74
|
return self.send(handler.handler_method, tokens, options)
|
66
75
|
end
|
67
76
|
end
|
68
77
|
|
69
78
|
# I guess you're out of luck!
|
79
|
+
puts "-none" if Chronic.debug
|
70
80
|
return nil
|
71
81
|
end
|
72
82
|
|
@@ -122,6 +132,19 @@ module Chronic
|
|
122
132
|
end
|
123
133
|
end
|
124
134
|
|
135
|
+
def handle_rdn_rmn_sd_t_tz_sy(tokens, options) #:nodoc:
|
136
|
+
month = tokens[1].get_tag(RepeaterMonthName).index
|
137
|
+
day = tokens[2].get_tag(ScalarDay).type
|
138
|
+
year = tokens[5].get_tag(ScalarYear).type
|
139
|
+
|
140
|
+
begin
|
141
|
+
day_start = Time.local(year, month, day)
|
142
|
+
day_or_time(day_start, [tokens[3]], options)
|
143
|
+
rescue ArgumentError
|
144
|
+
nil
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
125
148
|
def handle_rmn_sd_sy(tokens, options) #:nodoc:
|
126
149
|
month = tokens[0].get_tag(RepeaterMonthName).index
|
127
150
|
day = tokens[1].get_tag(ScalarDay).type
|
@@ -333,22 +356,54 @@ module Chronic
|
|
333
356
|
|
334
357
|
def dealias_and_disambiguate_times(tokens, options) #:nodoc:
|
335
358
|
# handle aliases of am/pm
|
336
|
-
# 5:00 in the morning
|
337
|
-
# 7:00 in the evening
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
if
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
359
|
+
# 5:00 in the morning -> 5:00 am
|
360
|
+
# 7:00 in the evening -> 7:00 pm
|
361
|
+
|
362
|
+
day_portion_index = nil
|
363
|
+
tokens.each_with_index do |t, i|
|
364
|
+
if t.get_tag(RepeaterDayPortion)
|
365
|
+
day_portion_index = i
|
366
|
+
break
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
time_index = nil
|
371
|
+
tokens.each_with_index do |t, i|
|
372
|
+
if t.get_tag(RepeaterTime)
|
373
|
+
time_index = i
|
374
|
+
break
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
if (day_portion_index && time_index)
|
379
|
+
t1 = tokens[day_portion_index]
|
380
|
+
t1tag = t1.get_tag(RepeaterDayPortion)
|
381
|
+
|
382
|
+
if [:morning].include?(t1tag.type)
|
383
|
+
puts '--morning->am' if Chronic.debug
|
384
|
+
t1.untag(RepeaterDayPortion)
|
385
|
+
t1.tag(RepeaterDayPortion.new(:am))
|
386
|
+
elsif [:afternoon, :evening, :night].include?(t1tag.type)
|
387
|
+
puts "--#{t1tag.type}->pm" if Chronic.debug
|
388
|
+
t1.untag(RepeaterDayPortion)
|
389
|
+
t1.tag(RepeaterDayPortion.new(:pm))
|
349
390
|
end
|
350
391
|
end
|
351
|
-
|
392
|
+
|
393
|
+
# tokens.each_with_index do |t0, i|
|
394
|
+
# t1 = tokens[i + 1]
|
395
|
+
# if t1 && (t1tag = t1.get_tag(RepeaterDayPortion)) && t0.get_tag(RepeaterTime)
|
396
|
+
# if [:morning].include?(t1tag.type)
|
397
|
+
# puts '--morning->am' if Chronic.debug
|
398
|
+
# t1.untag(RepeaterDayPortion)
|
399
|
+
# t1.tag(RepeaterDayPortion.new(:am))
|
400
|
+
# elsif [:afternoon, :evening, :night].include?(t1tag.type)
|
401
|
+
# puts "--#{t1tag.type}->pm" if Chronic.debug
|
402
|
+
# t1.untag(RepeaterDayPortion)
|
403
|
+
# t1.tag(RepeaterDayPortion.new(:pm))
|
404
|
+
# end
|
405
|
+
# end
|
406
|
+
# end
|
352
407
|
|
353
408
|
# handle ambiguous times if :ambiguous_time_range is specified
|
354
409
|
if options[:ambiguous_time_range] != :none
|
data/lib/chronic/repeater.rb
CHANGED
@@ -33,6 +33,7 @@ class Chronic::Repeater < Chronic::Tag #:nodoc:
|
|
33
33
|
def self.scan_for_day_names(token)
|
34
34
|
scanner = {/^m[ou]n(day)?$/ => :monday,
|
35
35
|
/^t(ue|eu|oo|u|)s(day)?$/ => :tuesday,
|
36
|
+
/^tue$/ => :tuesday,
|
36
37
|
/^we(dnes|nds|nns)day$/ => :wednesday,
|
37
38
|
/^wed$/ => :wednesday,
|
38
39
|
/^th(urs|ers)day$/ => :thursday,
|
@@ -19,14 +19,14 @@ class Chronic::RepeaterDay < Chronic::Repeater #:nodoc:
|
|
19
19
|
|
20
20
|
case pointer
|
21
21
|
when :future
|
22
|
-
day_begin = Time.
|
23
|
-
day_end = Time.
|
22
|
+
day_begin = Time.construct(@now.year, @now.month, @now.day, @now.hour + 1)
|
23
|
+
day_end = Time.construct(@now.year, @now.month, @now.day) + DAY_SECONDS
|
24
24
|
when :past
|
25
|
-
day_begin = Time.
|
26
|
-
day_end = Time.
|
25
|
+
day_begin = Time.construct(@now.year, @now.month, @now.day)
|
26
|
+
day_end = Time.construct(@now.year, @now.month, @now.day, @now.hour)
|
27
27
|
when :none
|
28
|
-
day_begin = Time.
|
29
|
-
day_end = Time.
|
28
|
+
day_begin = Time.construct(@now.year, @now.month, @now.day)
|
29
|
+
day_end = Time.construct(@now.year, @now.month, @now.day) + DAY_SECONDS
|
30
30
|
end
|
31
31
|
|
32
32
|
Chronic::Span.new(day_begin, day_end)
|
@@ -7,7 +7,7 @@ class Chronic::RepeaterDayName < Chronic::Repeater #:nodoc:
|
|
7
7
|
direction = pointer == :future ? 1 : -1
|
8
8
|
|
9
9
|
if !@current_day_start
|
10
|
-
@current_day_start = Time.
|
10
|
+
@current_day_start = Time.construct(@now.year, @now.month, @now.day)
|
11
11
|
@current_day_start += direction * DAY_SECONDS
|
12
12
|
|
13
13
|
day_num = symbol_to_number(@type)
|
@@ -34,7 +34,7 @@ class Chronic::RepeaterDayName < Chronic::Repeater #:nodoc:
|
|
34
34
|
end
|
35
35
|
|
36
36
|
def to_s
|
37
|
-
super << '-
|
37
|
+
super << '-dayname-' << @type.to_s
|
38
38
|
end
|
39
39
|
|
40
40
|
private
|
@@ -28,27 +28,27 @@ class Chronic::RepeaterDayPortion < Chronic::Repeater #:nodoc:
|
|
28
28
|
full_day = 60 * 60 * 24
|
29
29
|
|
30
30
|
if !@current_span
|
31
|
-
now_seconds = @now - Time.
|
31
|
+
now_seconds = @now - Time.construct(@now.year, @now.month, @now.day)
|
32
32
|
if now_seconds < @range.begin
|
33
33
|
case pointer
|
34
34
|
when :future
|
35
|
-
range_start = Time.
|
35
|
+
range_start = Time.construct(@now.year, @now.month, @now.day) + @range.begin
|
36
36
|
when :past
|
37
|
-
range_start = Time.
|
37
|
+
range_start = Time.construct(@now.year, @now.month, @now.day) - full_day + @range.begin
|
38
38
|
end
|
39
39
|
elsif now_seconds > @range.end
|
40
40
|
case pointer
|
41
41
|
when :future
|
42
|
-
range_start = Time.
|
42
|
+
range_start = Time.construct(@now.year, @now.month, @now.day) + full_day + @range.begin
|
43
43
|
when :past
|
44
|
-
range_start = Time.
|
44
|
+
range_start = Time.construct(@now.year, @now.month, @now.day) + @range.begin
|
45
45
|
end
|
46
46
|
else
|
47
47
|
case pointer
|
48
48
|
when :future
|
49
|
-
range_start = Time.
|
49
|
+
range_start = Time.construct(@now.year, @now.month, @now.day) + full_day + @range.begin
|
50
50
|
when :past
|
51
|
-
range_start = Time.
|
51
|
+
range_start = Time.construct(@now.year, @now.month, @now.day) - full_day + @range.begin
|
52
52
|
end
|
53
53
|
end
|
54
54
|
|
@@ -66,7 +66,7 @@ class Chronic::RepeaterDayPortion < Chronic::Repeater #:nodoc:
|
|
66
66
|
def this(context = :future)
|
67
67
|
super
|
68
68
|
|
69
|
-
range_start = Time.
|
69
|
+
range_start = Time.construct(@now.year, @now.month, @now.day) + @range.begin
|
70
70
|
@current_span = Chronic::Span.new(range_start, range_start + (@range.end - @range.begin))
|
71
71
|
end
|
72
72
|
|
@@ -33,7 +33,7 @@ class Chronic::RepeaterFortnight < Chronic::Repeater #:nodoc:
|
|
33
33
|
|
34
34
|
case pointer
|
35
35
|
when :future
|
36
|
-
this_fortnight_start = Time.
|
36
|
+
this_fortnight_start = Time.construct(@now.year, @now.month, @now.day, @now.hour) + Chronic::RepeaterHour::HOUR_SECONDS
|
37
37
|
sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
|
38
38
|
sunday_repeater.start = @now
|
39
39
|
sunday_repeater.this(:future)
|
@@ -41,7 +41,7 @@ class Chronic::RepeaterFortnight < Chronic::Repeater #:nodoc:
|
|
41
41
|
this_fortnight_end = this_sunday_span.begin
|
42
42
|
Chronic::Span.new(this_fortnight_start, this_fortnight_end)
|
43
43
|
when :past
|
44
|
-
this_fortnight_end = Time.
|
44
|
+
this_fortnight_end = Time.construct(@now.year, @now.month, @now.day, @now.hour)
|
45
45
|
sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
|
46
46
|
sunday_repeater.start = @now
|
47
47
|
last_sunday_span = sunday_repeater.next(:past)
|
@@ -7,9 +7,9 @@ class Chronic::RepeaterHour < Chronic::Repeater #:nodoc:
|
|
7
7
|
if !@current_hour_start
|
8
8
|
case pointer
|
9
9
|
when :future
|
10
|
-
@current_hour_start = Time.
|
10
|
+
@current_hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour + 1)
|
11
11
|
when :past
|
12
|
-
@current_hour_start = Time.
|
12
|
+
@current_hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour - 1)
|
13
13
|
end
|
14
14
|
else
|
15
15
|
direction = pointer == :future ? 1 : -1
|
@@ -27,10 +27,10 @@ class Chronic::RepeaterHour < Chronic::Repeater #:nodoc:
|
|
27
27
|
hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min + 1)
|
28
28
|
hour_end = Time.construct(@now.year, @now.month, @now.day, @now.hour + 1)
|
29
29
|
when :past
|
30
|
-
hour_start = Time.
|
31
|
-
hour_end = Time.
|
30
|
+
hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour)
|
31
|
+
hour_end = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min)
|
32
32
|
when :none
|
33
|
-
hour_start = Time.
|
33
|
+
hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour)
|
34
34
|
hour_end = hour_begin + HOUR_SECONDS
|
35
35
|
end
|
36
36
|
|
@@ -4,9 +4,19 @@ class Chronic::RepeaterMinute < Chronic::Repeater #:nodoc:
|
|
4
4
|
def next(pointer = :future)
|
5
5
|
super
|
6
6
|
|
7
|
-
|
7
|
+
if !@current_minute_start
|
8
|
+
case pointer
|
9
|
+
when :future
|
10
|
+
@current_minute_start = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min + 1)
|
11
|
+
when :past
|
12
|
+
@current_minute_start = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min - 1)
|
13
|
+
end
|
14
|
+
else
|
15
|
+
direction = pointer == :future ? 1 : -1
|
16
|
+
@current_minute_start += direction * MINUTE_SECONDS
|
17
|
+
end
|
8
18
|
|
9
|
-
|
19
|
+
Chronic::Span.new(@current_minute_start, @current_minute_start + MINUTE_SECONDS)
|
10
20
|
end
|
11
21
|
|
12
22
|
def this(pointer = :future)
|
@@ -15,13 +25,13 @@ class Chronic::RepeaterMinute < Chronic::Repeater #:nodoc:
|
|
15
25
|
case pointer
|
16
26
|
when :future
|
17
27
|
minute_begin = @now
|
18
|
-
minute_end = Time.
|
28
|
+
minute_end = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min)
|
19
29
|
when :past
|
20
|
-
minute_begin = Time.
|
30
|
+
minute_begin = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min)
|
21
31
|
minute_end = @now
|
22
32
|
when :none
|
23
|
-
minute_begin = Time.
|
24
|
-
minute_end = Time.
|
33
|
+
minute_begin = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min)
|
34
|
+
minute_end = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min) + MINUTE_SECONDS
|
25
35
|
end
|
26
36
|
|
27
37
|
Chronic::Span.new(minute_begin, minute_end)
|
@@ -6,12 +6,12 @@ class Chronic::RepeaterMonth < Chronic::Repeater #:nodoc:
|
|
6
6
|
super
|
7
7
|
|
8
8
|
if !@current_month_start
|
9
|
-
@current_month_start = offset_by(Time.
|
9
|
+
@current_month_start = offset_by(Time.construct(@now.year, @now.month), 1, pointer)
|
10
10
|
else
|
11
|
-
@current_month_start = offset_by(Time.
|
11
|
+
@current_month_start = offset_by(Time.construct(@current_month_start.year, @current_month_start.month), 1, pointer)
|
12
12
|
end
|
13
13
|
|
14
|
-
Chronic::Span.new(@current_month_start, Time.
|
14
|
+
Chronic::Span.new(@current_month_start, Time.construct(@current_month_start.year, @current_month_start.month + 1))
|
15
15
|
end
|
16
16
|
|
17
17
|
def this(pointer = :future)
|
@@ -19,14 +19,14 @@ class Chronic::RepeaterMonth < Chronic::Repeater #:nodoc:
|
|
19
19
|
|
20
20
|
case pointer
|
21
21
|
when :future
|
22
|
-
month_start = Time.
|
23
|
-
month_end = self.offset_by(Time.
|
22
|
+
month_start = Time.construct(@now.year, @now.month, @now.day + 1)
|
23
|
+
month_end = self.offset_by(Time.construct(@now.year, @now.month), 1, :future)
|
24
24
|
when :past
|
25
|
-
month_start = Time.
|
26
|
-
month_end = Time.
|
25
|
+
month_start = Time.construct(@now.year, @now.month)
|
26
|
+
month_end = Time.construct(@now.year, @now.month, @now.day)
|
27
27
|
when :none
|
28
|
-
month_start = Time.
|
29
|
-
month_end = self.offset_by(Time.
|
28
|
+
month_start = Time.construct(@now.year, @now.month)
|
29
|
+
month_end = self.offset_by(Time.construct(@now.year, @now.month), 1, :future)
|
30
30
|
end
|
31
31
|
|
32
32
|
Chronic::Span.new(month_start, month_end)
|
@@ -48,10 +48,14 @@ class Chronic::RepeaterMonth < Chronic::Repeater #:nodoc:
|
|
48
48
|
new_year += 1
|
49
49
|
new_month -= YEAR_MONTHS
|
50
50
|
end
|
51
|
-
Time.
|
51
|
+
Time.construct(new_year, new_month, time.day, time.hour, time.min, time.sec)
|
52
52
|
end
|
53
53
|
|
54
54
|
def width
|
55
55
|
MONTH_SECONDS
|
56
56
|
end
|
57
|
+
|
58
|
+
def to_s
|
59
|
+
super << '-month'
|
60
|
+
end
|
57
61
|
end
|
@@ -9,30 +9,30 @@ class Chronic::RepeaterMonthName < Chronic::Repeater #:nodoc:
|
|
9
9
|
case pointer
|
10
10
|
when :future
|
11
11
|
if @now.month < target_month
|
12
|
-
@current_month_begin = Time.
|
12
|
+
@current_month_begin = Time.construct(@now.year, target_month)
|
13
13
|
else @now.month > target_month
|
14
|
-
@current_month_begin = Time.
|
14
|
+
@current_month_begin = Time.construct(@now.year + 1, target_month)
|
15
15
|
end
|
16
16
|
when :none
|
17
17
|
if @now.month <= target_month
|
18
|
-
@current_month_begin = Time.
|
18
|
+
@current_month_begin = Time.construct(@now.year, target_month)
|
19
19
|
else @now.month > target_month
|
20
|
-
@current_month_begin = Time.
|
20
|
+
@current_month_begin = Time.construct(@now.year + 1, target_month)
|
21
21
|
end
|
22
22
|
when :past
|
23
23
|
if @now.month > target_month
|
24
|
-
@current_month_begin = Time.
|
24
|
+
@current_month_begin = Time.construct(@now.year, target_month)
|
25
25
|
else @now.month < target_month
|
26
|
-
@current_month_begin = Time.
|
26
|
+
@current_month_begin = Time.construct(@now.year - 1, target_month)
|
27
27
|
end
|
28
28
|
end
|
29
29
|
@current_month_begin || raise("Current month should be set by now")
|
30
30
|
else
|
31
31
|
case pointer
|
32
32
|
when :future
|
33
|
-
@current_month_begin = Time.
|
33
|
+
@current_month_begin = Time.construct(@current_month_begin.year + 1, @current_month_begin.month)
|
34
34
|
when :past
|
35
|
-
@current_month_begin = Time.
|
35
|
+
@current_month_begin = Time.construct(@current_month_begin.year - 1, @current_month_begin.month)
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
@@ -47,7 +47,7 @@ class Chronic::RepeaterMonthName < Chronic::Repeater #:nodoc:
|
|
47
47
|
next_month_month = cur_month_month + 1
|
48
48
|
end
|
49
49
|
|
50
|
-
Chronic::Span.new(@current_month_begin, Time.
|
50
|
+
Chronic::Span.new(@current_month_begin, Time.construct(next_month_year, next_month_month))
|
51
51
|
end
|
52
52
|
|
53
53
|
def this(pointer = :future)
|
@@ -70,7 +70,7 @@ class Chronic::RepeaterMonthName < Chronic::Repeater #:nodoc:
|
|
70
70
|
end
|
71
71
|
|
72
72
|
def to_s
|
73
|
-
super << '-
|
73
|
+
super << '-monthname-' << @type.to_s
|
74
74
|
end
|
75
75
|
|
76
76
|
private
|
@@ -6,16 +6,16 @@ class Chronic::RepeaterYear < Chronic::Repeater #:nodoc:
|
|
6
6
|
if !@current_year_start
|
7
7
|
case pointer
|
8
8
|
when :future
|
9
|
-
@current_year_start = Time.
|
9
|
+
@current_year_start = Time.construct(@now.year + 1)
|
10
10
|
when :past
|
11
|
-
@current_year_start = Time.
|
11
|
+
@current_year_start = Time.construct(@now.year - 1)
|
12
12
|
end
|
13
13
|
else
|
14
14
|
diff = pointer == :future ? 1 : -1
|
15
|
-
@current_year_start = Time.
|
15
|
+
@current_year_start = Time.construct(@current_year_start.year + diff)
|
16
16
|
end
|
17
17
|
|
18
|
-
Chronic::Span.new(@current_year_start, Time.
|
18
|
+
Chronic::Span.new(@current_year_start, Time.construct(@current_year_start.year + 1))
|
19
19
|
end
|
20
20
|
|
21
21
|
def this(pointer = :future)
|
@@ -23,14 +23,14 @@ class Chronic::RepeaterYear < Chronic::Repeater #:nodoc:
|
|
23
23
|
|
24
24
|
case pointer
|
25
25
|
when :future
|
26
|
-
this_year_start = Time.
|
27
|
-
this_year_end = Time.
|
26
|
+
this_year_start = Time.construct(@now.year, @now.month, @now.day) + Chronic::RepeaterDay::DAY_SECONDS
|
27
|
+
this_year_end = Time.construct(@now.year + 1, 1, 1)
|
28
28
|
when :past
|
29
|
-
this_year_start = Time.
|
30
|
-
this_year_end = Time.
|
29
|
+
this_year_start = Time.construct(@now.year, 1, 1)
|
30
|
+
this_year_end = Time.construct(@now.year, @now.month, @now.day)
|
31
31
|
when :none
|
32
|
-
this_year_start = Time.
|
33
|
-
this_year_end = Time.
|
32
|
+
this_year_start = Time.construct(@now.year, 1, 1)
|
33
|
+
this_year_end = Time.construct(@now.year + 1, 1, 1)
|
34
34
|
end
|
35
35
|
|
36
36
|
Chronic::Span.new(this_year_start, this_year_end)
|
@@ -40,10 +40,10 @@ class Chronic::RepeaterYear < Chronic::Repeater #:nodoc:
|
|
40
40
|
direction = pointer == :future ? 1 : -1
|
41
41
|
|
42
42
|
sb = span.begin
|
43
|
-
new_begin = Time.
|
43
|
+
new_begin = Time.construct(sb.year + (amount * direction), sb.month, sb.day, sb.hour, sb.min, sb.sec)
|
44
44
|
|
45
45
|
se = span.end
|
46
|
-
new_end = Time.
|
46
|
+
new_end = Time.construct(se.year + (amount * direction), se.month, se.day, se.hour, se.min, se.sec)
|
47
47
|
|
48
48
|
Chronic::Span.new(new_begin, new_end)
|
49
49
|
end
|
data/test/test_Time.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'chronic'
|
2
|
+
require 'test/unit'
|
3
|
+
|
4
|
+
class TestTime < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def setup
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_normal
|
10
|
+
assert_equal Time.local(2006, 1, 2, 0, 0, 0), Time.construct(2006, 1, 2, 0, 0, 0)
|
11
|
+
assert_equal Time.local(2006, 1, 2, 3, 0, 0), Time.construct(2006, 1, 2, 3, 0, 0)
|
12
|
+
assert_equal Time.local(2006, 1, 2, 3, 4, 0), Time.construct(2006, 1, 2, 3, 4, 0)
|
13
|
+
assert_equal Time.local(2006, 1, 2, 3, 4, 5), Time.construct(2006, 1, 2, 3, 4, 5)
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_second_overflow
|
17
|
+
assert_equal Time.local(2006, 1, 1, 0, 1, 30), Time.construct(2006, 1, 1, 0, 0, 90)
|
18
|
+
assert_equal Time.local(2006, 1, 1, 0, 5, 0), Time.construct(2006, 1, 1, 0, 0, 300)
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_minute_overflow
|
22
|
+
assert_equal Time.local(2006, 1, 1, 1, 30), Time.construct(2006, 1, 1, 0, 90)
|
23
|
+
assert_equal Time.local(2006, 1, 1, 5), Time.construct(2006, 1, 1, 0, 300)
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_hour_overflow
|
27
|
+
assert_equal Time.local(2006, 1, 2, 12), Time.construct(2006, 1, 1, 36)
|
28
|
+
assert_equal Time.local(2006, 1, 7), Time.construct(2006, 1, 1, 144)
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_day_overflow
|
32
|
+
assert_equal Time.local(2006, 2, 1), Time.construct(2006, 1, 32)
|
33
|
+
assert_equal Time.local(2006, 3, 5), Time.construct(2006, 2, 33)
|
34
|
+
assert_equal Time.local(2004, 3, 4), Time.construct(2004, 2, 33)
|
35
|
+
assert_equal Time.local(2000, 3, 5), Time.construct(2000, 2, 33)
|
36
|
+
|
37
|
+
assert_nothing_raised do
|
38
|
+
Time.construct(2006, 1, 56)
|
39
|
+
end
|
40
|
+
|
41
|
+
assert_raise(RuntimeError) do
|
42
|
+
Time.construct(2006, 1, 57)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_month_overflow
|
47
|
+
assert_equal Time.local(2006, 1), Time.construct(2005, 13)
|
48
|
+
assert_equal Time.local(2005, 12), Time.construct(2000, 72)
|
49
|
+
end
|
50
|
+
end
|
data/test/test_parsing.rb
CHANGED
@@ -136,6 +136,15 @@ class TestParsing < Test::Unit::TestCase
|
|
136
136
|
time = parse_now("2006-08-20 15:30.30")
|
137
137
|
assert_equal Time.local(2006, 8, 20, 15, 30, 30), time
|
138
138
|
|
139
|
+
# rdn_rm_rd_rt_rtz_ry
|
140
|
+
|
141
|
+
time = parse_now("Mon Apr 02 17:00:00 PDT 2007")
|
142
|
+
assert_equal Time.local(2007, 4, 2, 17), time
|
143
|
+
|
144
|
+
now = Time.now
|
145
|
+
time = parse_now(now.to_s)
|
146
|
+
assert_equal now.to_s, time.to_s
|
147
|
+
|
139
148
|
# rm_sd_rt
|
140
149
|
|
141
150
|
#time = parse_now("jan 5 13:00")
|
@@ -152,11 +161,18 @@ class TestParsing < Test::Unit::TestCase
|
|
152
161
|
time = parse_now("1800-08-20")
|
153
162
|
assert_equal nil, time
|
154
163
|
end
|
164
|
+
|
165
|
+
def test_foo
|
166
|
+
Chronic.parse('two months ago this friday')
|
167
|
+
end
|
155
168
|
|
156
169
|
def test_parse_guess_r
|
157
170
|
time = parse_now("friday")
|
158
171
|
assert_equal Time.local(2006, 8, 18, 12), time
|
159
172
|
|
173
|
+
time = parse_now("tue")
|
174
|
+
assert_equal Time.local(2006, 8, 22, 12), time
|
175
|
+
|
160
176
|
time = parse_now("5")
|
161
177
|
assert_equal Time.local(2006, 8, 16, 17), time
|
162
178
|
|
@@ -214,6 +230,9 @@ class TestParsing < Test::Unit::TestCase
|
|
214
230
|
|
215
231
|
time = parse_now("sunday 6am")
|
216
232
|
assert_equal Time.local(2006, 8, 20, 6), time
|
233
|
+
|
234
|
+
time = parse_now("friday evening at 7")
|
235
|
+
assert_equal Time.local(2006, 8, 18, 19), time
|
217
236
|
end
|
218
237
|
|
219
238
|
def test_parse_guess_gr
|
@@ -233,6 +252,9 @@ class TestParsing < Test::Unit::TestCase
|
|
233
252
|
time = parse_now("this month", :context => :past)
|
234
253
|
assert_equal Time.local(2006, 8, 8, 12), time
|
235
254
|
|
255
|
+
time = Chronic.parse("next month", :now => Time.local(2006, 11, 15))
|
256
|
+
assert_equal Time.local(2006, 12, 16, 12), time
|
257
|
+
|
236
258
|
# month name
|
237
259
|
|
238
260
|
time = parse_now("last november")
|
@@ -310,6 +332,11 @@ class TestParsing < Test::Unit::TestCase
|
|
310
332
|
time = parse_now("tonight")
|
311
333
|
assert_equal Time.local(2006, 8, 16, 22), time
|
312
334
|
|
335
|
+
# minute
|
336
|
+
|
337
|
+
time = parse_now("next minute")
|
338
|
+
assert_equal Time.local(2006, 8, 16, 14, 1, 30), time
|
339
|
+
|
313
340
|
# second
|
314
341
|
|
315
342
|
time = parse_now("this second")
|
@@ -355,8 +382,17 @@ class TestParsing < Test::Unit::TestCase
|
|
355
382
|
|
356
383
|
time = parse_now("last week tuesday")
|
357
384
|
assert_equal Time.local(2006, 8, 8, 12), time
|
385
|
+
|
386
|
+
time = parse_now("tonight at 7")
|
387
|
+
assert_equal Time.local(2006, 8, 16, 19), time
|
388
|
+
|
389
|
+
time = parse_now("tonight 7")
|
390
|
+
assert_equal Time.local(2006, 8, 16, 19), time
|
391
|
+
|
392
|
+
time = parse_now("7 tonight")
|
393
|
+
assert_equal Time.local(2006, 8, 16, 19), time
|
358
394
|
end
|
359
|
-
|
395
|
+
|
360
396
|
def test_parse_guess_grrr
|
361
397
|
time = parse_now("today at 6:00pm")
|
362
398
|
assert_equal Time.local(2006, 8, 16, 18), time
|
@@ -369,6 +405,12 @@ class TestParsing < Test::Unit::TestCase
|
|
369
405
|
|
370
406
|
time = parse_now("yesterday at 4:00pm")
|
371
407
|
assert_equal Time.local(2006, 8, 15, 16), time
|
408
|
+
|
409
|
+
time = parse_now("tomorrow evening at 7")
|
410
|
+
assert_equal Time.local(2006, 8, 17, 19), time
|
411
|
+
|
412
|
+
time = parse_now("tomorrow morning at 5:30")
|
413
|
+
assert_equal Time.local(2006, 8, 17, 5, 30), time
|
372
414
|
end
|
373
415
|
|
374
416
|
def test_parse_guess_rgr
|
metadata
CHANGED
@@ -3,12 +3,12 @@ rubygems_version: 0.9.0
|
|
3
3
|
specification_version: 1
|
4
4
|
name: chronic
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.2.
|
7
|
-
date: 2007-
|
6
|
+
version: 0.2.1
|
7
|
+
date: 2007-04-21 00:00:00 -07:00
|
8
8
|
summary: A natural language date parser
|
9
9
|
require_paths:
|
10
10
|
- lib
|
11
|
-
email:
|
11
|
+
email: tom@rubyisawesome.com
|
12
12
|
homepage: "\thttp://chronic.rubyforge.org/"
|
13
13
|
rubyforge_project: chronic
|
14
14
|
description: Chronic is a natural language date/time parser written in pure Ruby. See below for the wide variety of formats Chronic will parse.
|
@@ -27,7 +27,7 @@ signing_key:
|
|
27
27
|
cert_chain:
|
28
28
|
post_install_message:
|
29
29
|
authors:
|
30
|
-
-
|
30
|
+
- Tom Preston-Werner
|
31
31
|
files:
|
32
32
|
- History.txt
|
33
33
|
- Manifest.txt
|
@@ -89,6 +89,7 @@ test_files:
|
|
89
89
|
- test/test_RepeaterWeekend.rb
|
90
90
|
- test/test_RepeaterYear.rb
|
91
91
|
- test/test_Span.rb
|
92
|
+
- test/test_Time.rb
|
92
93
|
- test/test_Token.rb
|
93
94
|
rdoc_options: []
|
94
95
|
|