chronic 0.4.0 → 0.5.0
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/.gemtest +0 -0
- data/.gitignore +5 -0
- data/.yardopts +3 -0
- data/HISTORY.md +39 -1
- data/Manifest.txt +7 -2
- data/README.md +8 -2
- data/Rakefile +9 -101
- data/chronic.gemspec +12 -77
- data/lib/chronic/chronic.rb +225 -91
- data/lib/chronic/grabber.rb +12 -3
- data/lib/chronic/handlers.rb +95 -199
- data/lib/chronic/mini_date.rb +8 -3
- data/lib/chronic/ordinal.rb +14 -5
- data/lib/chronic/pointer.rb +11 -5
- data/lib/chronic/repeater.rb +31 -19
- data/lib/chronic/repeaters/repeater_day.rb +0 -1
- data/lib/chronic/repeaters/repeater_day_name.rb +0 -1
- data/lib/chronic/repeaters/repeater_day_portion.rb +0 -1
- data/lib/chronic/repeaters/repeater_fortnight.rb +0 -1
- data/lib/chronic/repeaters/repeater_hour.rb +0 -1
- data/lib/chronic/repeaters/repeater_minute.rb +0 -1
- data/lib/chronic/repeaters/repeater_month.rb +13 -2
- data/lib/chronic/repeaters/repeater_month_name.rb +13 -21
- data/lib/chronic/repeaters/repeater_season.rb +0 -19
- data/lib/chronic/repeaters/repeater_season_name.rb +0 -2
- data/lib/chronic/repeaters/repeater_second.rb +0 -1
- data/lib/chronic/repeaters/repeater_time.rb +1 -2
- data/lib/chronic/repeaters/repeater_week.rb +0 -1
- data/lib/chronic/repeaters/repeater_weekday.rb +0 -1
- data/lib/chronic/repeaters/repeater_weekend.rb +0 -1
- data/lib/chronic/repeaters/repeater_year.rb +20 -9
- data/lib/chronic/scalar.rb +30 -5
- data/lib/chronic/season.rb +37 -0
- data/lib/chronic/separator.rb +24 -9
- data/lib/chronic/tag.rb +17 -3
- data/lib/chronic/time_zone.rb +12 -3
- data/lib/chronic/token.rb +14 -4
- data/lib/chronic.rb +62 -49
- data/test/test_Chronic.rb +52 -2
- data/test/test_MiniDate.rb +32 -0
- data/test/test_RepeaterMonth.rb +4 -0
- data/test/test_RepeaterSeason.rb +40 -0
- data/test/test_RepeaterYear.rb +7 -0
- data/test/test_Time.rb +1 -1
- data/test/test_parsing.rb +159 -113
- metadata +33 -29
- data/benchmark/benchmark.rb +0 -13
data/lib/chronic.rb
CHANGED
|
@@ -1,33 +1,83 @@
|
|
|
1
|
-
|
|
1
|
+
require 'time'
|
|
2
|
+
require 'date'
|
|
3
|
+
|
|
4
|
+
# Parse natural language dates and times into Time or {Chronic::Span} objects
|
|
2
5
|
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
# Purpose: Parse natural language dates and times into Time or
|
|
6
|
-
# Chronic::Span objects
|
|
6
|
+
# @example
|
|
7
|
+
# require 'chronic'
|
|
7
8
|
#
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
# Time.now #=> Sun Aug 27 23:18:25 PDT 2006
|
|
10
|
+
#
|
|
11
|
+
# Chronic.parse('tomorrow')
|
|
12
|
+
# #=> Mon Aug 28 12:00:00 PDT 2006
|
|
13
|
+
#
|
|
14
|
+
# Chronic.parse('monday', :context => :past)
|
|
15
|
+
# #=> Mon Aug 21 12:00:00 PDT 2006
|
|
16
|
+
#
|
|
17
|
+
# Chronic.parse('this tuesday 5:00')
|
|
18
|
+
# #=> Tue Aug 29 17:00:00 PDT 2006
|
|
19
|
+
#
|
|
20
|
+
# Chronic.parse('this tuesday 5:00', :ambiguous_time_range => :none)
|
|
21
|
+
# #=> Tue Aug 29 05:00:00 PDT 2006
|
|
22
|
+
#
|
|
23
|
+
# Chronic.parse('may 27th', :now => Time.local(2000, 1, 1))
|
|
24
|
+
# #=> Sat May 27 12:00:00 PDT 2000
|
|
25
|
+
#
|
|
26
|
+
# Chronic.parse('may 27th', :guess => false)
|
|
27
|
+
# #=> Sun May 27 00:00:00 PDT 2007..Mon May 28 00:00:00 PDT 2007
|
|
28
|
+
#
|
|
29
|
+
# @author Tom Preston-Werner, Lee Jarvis
|
|
10
30
|
module Chronic
|
|
11
|
-
VERSION = "0.
|
|
31
|
+
VERSION = "0.5.0"
|
|
12
32
|
|
|
13
33
|
class << self
|
|
34
|
+
|
|
35
|
+
# @return [Boolean] true when debug mode is enabled
|
|
14
36
|
attr_accessor :debug
|
|
37
|
+
|
|
38
|
+
# @example
|
|
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
|
+
# @return [Time] The time class Chronic uses internally
|
|
15
48
|
attr_accessor :time_class
|
|
49
|
+
|
|
50
|
+
# The current Time Chronic is using to base from
|
|
51
|
+
#
|
|
52
|
+
# @example
|
|
53
|
+
# Time.now #=> 2011-06-06 14:13:43 +0100
|
|
54
|
+
# Chronic.parse('yesterday') #=> 2011-06-05 12:00:00 +0100
|
|
55
|
+
#
|
|
56
|
+
# now = Time.local(2025, 12, 24)
|
|
57
|
+
# Chronic.parse('tomorrow', :now => now) #=> 2025-12-25 12:00:00 +0000
|
|
58
|
+
#
|
|
59
|
+
# @return [Time, nil]
|
|
60
|
+
attr_accessor :now
|
|
16
61
|
end
|
|
17
62
|
|
|
18
63
|
self.debug = false
|
|
19
64
|
self.time_class = Time
|
|
20
65
|
end
|
|
21
66
|
|
|
22
|
-
require 'time'
|
|
23
|
-
require 'date'
|
|
24
|
-
|
|
25
67
|
require 'chronic/chronic'
|
|
26
68
|
require 'chronic/handlers'
|
|
27
69
|
require 'chronic/mini_date'
|
|
28
70
|
require 'chronic/tag'
|
|
29
71
|
require 'chronic/span'
|
|
30
72
|
require 'chronic/token'
|
|
73
|
+
require 'chronic/grabber'
|
|
74
|
+
require 'chronic/pointer'
|
|
75
|
+
require 'chronic/scalar'
|
|
76
|
+
require 'chronic/ordinal'
|
|
77
|
+
require 'chronic/separator'
|
|
78
|
+
require 'chronic/time_zone'
|
|
79
|
+
require 'chronic/numerizer'
|
|
80
|
+
require 'chronic/season'
|
|
31
81
|
|
|
32
82
|
require 'chronic/repeater'
|
|
33
83
|
require 'chronic/repeaters/repeater_year'
|
|
@@ -47,42 +97,6 @@ require 'chronic/repeaters/repeater_minute'
|
|
|
47
97
|
require 'chronic/repeaters/repeater_second'
|
|
48
98
|
require 'chronic/repeaters/repeater_time'
|
|
49
99
|
|
|
50
|
-
require 'chronic/grabber'
|
|
51
|
-
require 'chronic/pointer'
|
|
52
|
-
require 'chronic/scalar'
|
|
53
|
-
require 'chronic/ordinal'
|
|
54
|
-
require 'chronic/separator'
|
|
55
|
-
require 'chronic/time_zone'
|
|
56
|
-
|
|
57
|
-
require 'chronic/numerizer'
|
|
58
|
-
|
|
59
|
-
# class Time
|
|
60
|
-
# def self.construct(year, month = 1, day = 1, hour = 0, minute = 0, second = 0)
|
|
61
|
-
# # extra_seconds = second > 60 ? second - 60 : 0
|
|
62
|
-
# # extra_minutes = minute > 59 ? minute - 59 : 0
|
|
63
|
-
# # extra_hours = hour > 23 ? hour - 23 : 0
|
|
64
|
-
# # extra_days = day >
|
|
65
|
-
#
|
|
66
|
-
# if month > 12
|
|
67
|
-
# if month % 12 == 0
|
|
68
|
-
# year += (month - 12) / 12
|
|
69
|
-
# month = 12
|
|
70
|
-
# else
|
|
71
|
-
# year += month / 12
|
|
72
|
-
# month = month % 12
|
|
73
|
-
# end
|
|
74
|
-
# end
|
|
75
|
-
#
|
|
76
|
-
# base = Time.local(year, month)
|
|
77
|
-
# puts base
|
|
78
|
-
# offset = ((day - 1) * 24 * 60 * 60) + (hour * 60 * 60) + (minute * 60) + second
|
|
79
|
-
# puts offset.to_s
|
|
80
|
-
# date = base + offset
|
|
81
|
-
# puts date
|
|
82
|
-
# date
|
|
83
|
-
# end
|
|
84
|
-
# end
|
|
85
|
-
|
|
86
100
|
class Time
|
|
87
101
|
def self.construct(year, month = 1, day = 1, hour = 0, minute = 0, second = 0)
|
|
88
102
|
if second >= 60
|
|
@@ -105,10 +119,9 @@ class Time
|
|
|
105
119
|
day <= 56 || raise("day must be no more than 56 (makes month resolution easier)")
|
|
106
120
|
if day > 28
|
|
107
121
|
# no month ever has fewer than 28 days, so only do this if necessary
|
|
108
|
-
leap_year = (year % 4 == 0) && !(year % 100 == 0)
|
|
109
122
|
leap_year_month_days = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
|
110
123
|
common_year_month_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
|
111
|
-
days_this_month =
|
|
124
|
+
days_this_month = Date.leap?(year) ? leap_year_month_days[month - 1] : common_year_month_days[month - 1]
|
|
112
125
|
if day > days_this_month
|
|
113
126
|
month += day / days_this_month
|
|
114
127
|
day = day % days_this_month
|
data/test/test_Chronic.rb
CHANGED
|
@@ -21,7 +21,7 @@ class TestChronic < Test::Unit::TestCase
|
|
|
21
21
|
|
|
22
22
|
assert_equal :morning, tokens[1].tags[0].type
|
|
23
23
|
|
|
24
|
-
tokens = Chronic.dealias_and_disambiguate_times(tokens, {})
|
|
24
|
+
tokens = Chronic::Handlers.dealias_and_disambiguate_times(tokens, {})
|
|
25
25
|
|
|
26
26
|
assert_equal :am, tokens[1].tags[0].type
|
|
27
27
|
assert_equal 2, tokens.size
|
|
@@ -34,7 +34,7 @@ class TestChronic < Test::Unit::TestCase
|
|
|
34
34
|
|
|
35
35
|
assert_equal :morning, tokens[1].tags[0].type
|
|
36
36
|
|
|
37
|
-
tokens = Chronic.dealias_and_disambiguate_times(tokens, {})
|
|
37
|
+
tokens = Chronic::Handlers.dealias_and_disambiguate_times(tokens, {})
|
|
38
38
|
|
|
39
39
|
assert_equal :morning, tokens[1].tags[0].type
|
|
40
40
|
assert_equal 2, tokens.size
|
|
@@ -51,4 +51,54 @@ class TestChronic < Test::Unit::TestCase
|
|
|
51
51
|
assert_equal Time.local(2006, 11, 16), Chronic.guess(span)
|
|
52
52
|
end
|
|
53
53
|
|
|
54
|
+
def test_now
|
|
55
|
+
Chronic.parse('now', :now => Time.local(2006, 01))
|
|
56
|
+
assert_equal Time.local(2006, 01), Chronic.now
|
|
57
|
+
|
|
58
|
+
Chronic.parse('now', :now => Time.local(2007, 01))
|
|
59
|
+
assert_equal Time.local(2007, 01), Chronic.now
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def test_endian_definitions
|
|
63
|
+
# middle, little
|
|
64
|
+
endians = [
|
|
65
|
+
Chronic::Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_day, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sm_sd_sy),
|
|
66
|
+
Chronic::Handler.new([:scalar_day, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sd_sm_sy)
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
assert_equal endians, Chronic.definitions[:endian]
|
|
70
|
+
|
|
71
|
+
defs = Chronic.definitions(:endian_precedence => :little)
|
|
72
|
+
assert_equal endians.reverse, defs[:endian]
|
|
73
|
+
|
|
74
|
+
defs = Chronic.definitions(:endian_precedence => [:little, :middle])
|
|
75
|
+
assert_equal endians.reverse, defs[:endian]
|
|
76
|
+
|
|
77
|
+
assert_raises(Chronic::InvalidArgumentException) do
|
|
78
|
+
Chronic.definitions(:endian_precedence => :invalid)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def test_passing_options
|
|
83
|
+
assert_raises(Chronic::InvalidArgumentException) do
|
|
84
|
+
Chronic.parse('now', :invalid => :option)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
assert_raises(Chronic::InvalidArgumentException) do
|
|
88
|
+
Chronic.parse('now', :context => :invalid_context)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def test_debug
|
|
93
|
+
require 'stringio'
|
|
94
|
+
$stdout = StringIO.new
|
|
95
|
+
Chronic.debug = true
|
|
96
|
+
|
|
97
|
+
Chronic.parse 'now'
|
|
98
|
+
assert $stdout.string.include?('this(grabber-this)')
|
|
99
|
+
ensure
|
|
100
|
+
$stdout = STDOUT
|
|
101
|
+
Chronic.debug = false
|
|
102
|
+
end
|
|
103
|
+
|
|
54
104
|
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
require File.join(File.dirname(__FILE__), *%w[helper])
|
|
2
|
+
|
|
3
|
+
class TestMiniDate < Test::Unit::TestCase
|
|
4
|
+
def test_valid_month
|
|
5
|
+
assert_raise(Chronic::InvalidArgumentException){ Chronic::MiniDate.new(0,12) }
|
|
6
|
+
assert_raise(Chronic::InvalidArgumentException){ Chronic::MiniDate.new(13,1) }
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def test_is_between
|
|
10
|
+
m=Chronic::MiniDate.new(3,2)
|
|
11
|
+
assert m.is_between?(Chronic::MiniDate.new(2,4), Chronic::MiniDate.new(4,7))
|
|
12
|
+
assert !m.is_between?(Chronic::MiniDate.new(1,5), Chronic::MiniDate.new(2,7))
|
|
13
|
+
|
|
14
|
+
#There was a hang if date tested is in december and outside the testing range
|
|
15
|
+
m=Chronic::MiniDate.new(12,24)
|
|
16
|
+
assert !m.is_between?(Chronic::MiniDate.new(10,1), Chronic::MiniDate.new(12,21))
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def test_is_between_short_range
|
|
20
|
+
m=Chronic::MiniDate.new(5,10)
|
|
21
|
+
assert m.is_between?(Chronic::MiniDate.new(5,3), Chronic::MiniDate.new(5,12))
|
|
22
|
+
assert !m.is_between?(Chronic::MiniDate.new(5,11), Chronic::MiniDate.new(5,15))
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def test_is_between_wrapping_range
|
|
26
|
+
m=Chronic::MiniDate.new(1,1)
|
|
27
|
+
assert m.is_between?(Chronic::MiniDate.new(11,11), Chronic::MiniDate.new(2,2))
|
|
28
|
+
m=Chronic::MiniDate.new(12,12)
|
|
29
|
+
assert m.is_between?(Chronic::MiniDate.new(11,11), Chronic::MiniDate.new(1,5))
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
end
|
data/test/test_RepeaterMonth.rb
CHANGED
|
@@ -23,6 +23,10 @@ class TestRepeaterMonth < Test::Unit::TestCase
|
|
|
23
23
|
|
|
24
24
|
time = Chronic::RepeaterMonth.new(:month).offset_by(@now, 10, :past)
|
|
25
25
|
assert_equal Time.local(2005, 10, 16, 14), time
|
|
26
|
+
|
|
27
|
+
time = Chronic::RepeaterMonth.new(:month).offset_by(Time.local(2010, 3, 29), 1, :past)
|
|
28
|
+
assert_equal 2, time.month
|
|
29
|
+
assert_equal 28, time.day
|
|
26
30
|
end
|
|
27
31
|
|
|
28
32
|
def test_offset
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
require File.join(File.dirname(__FILE__), *%w[helper])
|
|
2
|
+
|
|
3
|
+
class TestRepeaterSeason < Test::Unit::TestCase
|
|
4
|
+
|
|
5
|
+
def setup
|
|
6
|
+
@now = Time.local(2006, 8, 16, 14, 0, 0, 0)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def test_next_future
|
|
10
|
+
seasons = Chronic::RepeaterSeason.new(:season)
|
|
11
|
+
seasons.start = @now
|
|
12
|
+
|
|
13
|
+
next_season = seasons.next(:future)
|
|
14
|
+
assert_equal Time.local(2006, 9, 23), next_season.begin
|
|
15
|
+
assert_equal Time.local(2006, 12, 21), next_season.end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def test_next_past
|
|
19
|
+
seasons = Chronic::RepeaterSeason.new(:season)
|
|
20
|
+
seasons.start = @now
|
|
21
|
+
|
|
22
|
+
last_season = seasons.next(:past)
|
|
23
|
+
assert_equal Time.local(2006, 3, 20), last_season.begin
|
|
24
|
+
assert_equal Time.local(2006, 6, 20), last_season.end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def test_this
|
|
28
|
+
seasons = Chronic::RepeaterSeason.new(:season)
|
|
29
|
+
seasons.start = @now
|
|
30
|
+
|
|
31
|
+
this_season = seasons.this(:future)
|
|
32
|
+
assert_equal Time.local(2006, 8, 17), this_season.begin
|
|
33
|
+
assert_equal Time.local(2006, 9, 22), this_season.end
|
|
34
|
+
|
|
35
|
+
this_season = seasons.this(:past)
|
|
36
|
+
assert_equal Time.local(2006, 6, 21), this_season.begin
|
|
37
|
+
assert_equal Time.local(2006, 8, 16), this_season.end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
end
|
data/test/test_RepeaterYear.rb
CHANGED
|
@@ -57,6 +57,13 @@ class TestRepeaterYear < Test::Unit::TestCase
|
|
|
57
57
|
|
|
58
58
|
assert_equal Time.local(1996, 8, 16, 14), offset_span.begin
|
|
59
59
|
assert_equal Time.local(1996, 8, 16, 14, 0, 1), offset_span.end
|
|
60
|
+
|
|
61
|
+
now = Time.local(2008, 2, 29)
|
|
62
|
+
span = Chronic::Span.new(now, now + 1)
|
|
63
|
+
offset_span = Chronic::RepeaterYear.new(:year).offset(span, 1, :past)
|
|
64
|
+
|
|
65
|
+
assert_equal Time.local(2007, 2, 28), offset_span.begin
|
|
66
|
+
assert_equal Time.local(2007, 2, 28, 0, 0, 1), offset_span.end
|
|
60
67
|
end
|
|
61
68
|
|
|
62
69
|
end
|
data/test/test_Time.rb
CHANGED
|
@@ -31,7 +31,7 @@ class TestTime < Test::Unit::TestCase
|
|
|
31
31
|
assert_equal Time.local(2006, 2, 1), Time.construct(2006, 1, 32)
|
|
32
32
|
assert_equal Time.local(2006, 3, 5), Time.construct(2006, 2, 33)
|
|
33
33
|
assert_equal Time.local(2004, 3, 4), Time.construct(2004, 2, 33)
|
|
34
|
-
assert_equal Time.local(2000, 3,
|
|
34
|
+
assert_equal Time.local(2000, 3, 4), Time.construct(2000, 2, 33)
|
|
35
35
|
|
|
36
36
|
assert_nothing_raised do
|
|
37
37
|
Time.construct(2006, 1, 56)
|