chronic 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|