chronic 0.6.3 → 0.10.2
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.
- checksums.yaml +7 -0
- data/.gitignore +4 -3
- data/.travis.yml +8 -0
- data/HISTORY.md +76 -0
- data/README.md +47 -42
- data/Rakefile +30 -12
- data/chronic.gemspec +10 -4
- data/lib/chronic/date.rb +82 -0
- data/lib/chronic/grabber.rb +9 -7
- data/lib/chronic/handler.rb +47 -40
- data/lib/chronic/handlers.rb +240 -28
- data/lib/chronic/numerizer.rb +11 -2
- data/lib/chronic/ordinal.rb +28 -23
- data/lib/chronic/parser.rb +268 -0
- data/lib/chronic/pointer.rb +9 -7
- data/lib/chronic/repeater.rb +58 -48
- data/lib/chronic/repeaters/repeater_day.rb +4 -3
- data/lib/chronic/repeaters/repeater_day_name.rb +5 -4
- data/lib/chronic/repeaters/repeater_day_portion.rb +33 -18
- data/lib/chronic/repeaters/repeater_fortnight.rb +4 -3
- data/lib/chronic/repeaters/repeater_hour.rb +4 -3
- data/lib/chronic/repeaters/repeater_minute.rb +4 -3
- data/lib/chronic/repeaters/repeater_month.rb +5 -4
- data/lib/chronic/repeaters/repeater_month_name.rb +4 -3
- data/lib/chronic/repeaters/repeater_season.rb +5 -3
- data/lib/chronic/repeaters/repeater_second.rb +4 -3
- data/lib/chronic/repeaters/repeater_time.rb +35 -25
- data/lib/chronic/repeaters/repeater_week.rb +4 -3
- data/lib/chronic/repeaters/repeater_weekday.rb +4 -3
- data/lib/chronic/repeaters/repeater_weekend.rb +4 -3
- data/lib/chronic/repeaters/repeater_year.rb +6 -5
- data/lib/chronic/scalar.rb +40 -68
- data/lib/chronic/season.rb +1 -12
- data/lib/chronic/separator.rb +142 -23
- data/lib/chronic/sign.rb +49 -0
- data/lib/chronic/span.rb +2 -2
- data/lib/chronic/tag.rb +10 -15
- data/lib/chronic/time.rb +40 -0
- data/lib/chronic/time_zone.rb +9 -7
- data/lib/chronic/token.rb +16 -10
- data/lib/chronic.rb +109 -70
- data/test/helper.rb +8 -2
- data/test/{test_Chronic.rb → test_chronic.rb} +69 -34
- data/test/{test_DaylightSavings.rb → test_daylight_savings.rb} +1 -1
- data/test/{test_Handler.rb → test_handler.rb} +38 -14
- data/test/{test_MiniDate.rb → test_mini_date.rb} +9 -9
- data/test/{test_Numerizer.rb → test_numerizer.rb} +16 -2
- data/test/test_parsing.rb +392 -22
- data/test/{test_RepeaterDayName.rb → test_repeater_day_name.rb} +1 -1
- data/test/test_repeater_day_portion.rb +254 -0
- data/test/{test_RepeaterFortnight.rb → test_repeater_fortnight.rb} +1 -1
- data/test/{test_RepeaterHour.rb → test_repeater_hour.rb} +1 -1
- data/test/{test_RepeaterMinute.rb → test_repeater_minute.rb} +1 -1
- data/test/{test_RepeaterMonth.rb → test_repeater_month.rb} +1 -1
- data/test/{test_RepeaterMonthName.rb → test_repeater_month_name.rb} +1 -1
- data/test/{test_RepeaterSeason.rb → test_repeater_season.rb} +1 -1
- data/test/{test_RepeaterTime.rb → test_repeater_time.rb} +19 -1
- data/test/{test_RepeaterWeek.rb → test_repeater_week.rb} +1 -1
- data/test/{test_RepeaterWeekday.rb → test_repeater_weekday.rb} +1 -1
- data/test/{test_RepeaterWeekend.rb → test_repeater_weekend.rb} +1 -1
- data/test/{test_RepeaterYear.rb → test_repeater_year.rb} +1 -1
- data/test/{test_Span.rb → test_span.rb} +2 -2
- data/test/{test_Token.rb → test_token.rb} +1 -1
- metadata +129 -87
- data/.gemtest +0 -0
- data/.yardopts +0 -3
- data/lib/chronic/chronic.rb +0 -323
data/lib/chronic/separator.rb
CHANGED
|
@@ -1,56 +1,126 @@
|
|
|
1
1
|
module Chronic
|
|
2
2
|
class Separator < Tag
|
|
3
3
|
|
|
4
|
-
# Scan an Array of
|
|
5
|
-
# each token
|
|
4
|
+
# Scan an Array of Token objects and apply any necessary Separator
|
|
5
|
+
# tags to each token.
|
|
6
6
|
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
7
|
+
# tokens - An Array of tokens to scan.
|
|
8
|
+
# options - The Hash of options specified in Chronic::parse.
|
|
9
|
+
#
|
|
10
|
+
# Returns an Array of tokens.
|
|
10
11
|
def self.scan(tokens, options)
|
|
11
12
|
tokens.each do |token|
|
|
12
13
|
if t = scan_for_commas(token) then token.tag(t); next end
|
|
13
|
-
if t =
|
|
14
|
+
if t = scan_for_dots(token) then token.tag(t); next end
|
|
15
|
+
if t = scan_for_colon(token) then token.tag(t); next end
|
|
16
|
+
if t = scan_for_space(token) then token.tag(t); next end
|
|
17
|
+
if t = scan_for_slash(token) then token.tag(t); next end
|
|
18
|
+
if t = scan_for_dash(token) then token.tag(t); next end
|
|
19
|
+
if t = scan_for_quote(token) then token.tag(t); next end
|
|
14
20
|
if t = scan_for_at(token) then token.tag(t); next end
|
|
15
21
|
if t = scan_for_in(token) then token.tag(t); next end
|
|
16
22
|
if t = scan_for_on(token) then token.tag(t); next end
|
|
23
|
+
if t = scan_for_and(token) then token.tag(t); next end
|
|
24
|
+
if t = scan_for_t(token) then token.tag(t); next end
|
|
25
|
+
if t = scan_for_w(token) then token.tag(t); next end
|
|
17
26
|
end
|
|
18
27
|
end
|
|
19
28
|
|
|
20
|
-
#
|
|
21
|
-
#
|
|
29
|
+
# token - The Token object we want to scan.
|
|
30
|
+
#
|
|
31
|
+
# Returns a new SeparatorComma object.
|
|
22
32
|
def self.scan_for_commas(token)
|
|
23
33
|
scan_for token, SeparatorComma, { /^,$/ => :comma }
|
|
24
34
|
end
|
|
25
35
|
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
|
|
29
|
-
|
|
36
|
+
# token - The Token object we want to scan.
|
|
37
|
+
#
|
|
38
|
+
# Returns a new SeparatorDot object.
|
|
39
|
+
def self.scan_for_dots(token)
|
|
40
|
+
scan_for token, SeparatorDot, { /^\.$/ => :dot }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# token - The Token object we want to scan.
|
|
44
|
+
#
|
|
45
|
+
# Returns a new SeparatorColon object.
|
|
46
|
+
def self.scan_for_colon(token)
|
|
47
|
+
scan_for token, SeparatorColon, { /^:$/ => :colon }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# token - The Token object we want to scan.
|
|
51
|
+
#
|
|
52
|
+
# Returns a new SeparatorSpace object.
|
|
53
|
+
def self.scan_for_space(token)
|
|
54
|
+
scan_for token, SeparatorSpace, { /^ $/ => :space }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# token - The Token object we want to scan.
|
|
58
|
+
#
|
|
59
|
+
# Returns a new SeparatorSlash object.
|
|
60
|
+
def self.scan_for_slash(token)
|
|
61
|
+
scan_for token, SeparatorSlash, { /^\/$/ => :slash }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# token - The Token object we want to scan.
|
|
65
|
+
#
|
|
66
|
+
# Returns a new SeparatorDash object.
|
|
67
|
+
def self.scan_for_dash(token)
|
|
68
|
+
scan_for token, SeparatorDash, { /^-$/ => :dash }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# token - The Token object we want to scan.
|
|
72
|
+
#
|
|
73
|
+
# Returns a new SeparatorQuote object.
|
|
74
|
+
def self.scan_for_quote(token)
|
|
75
|
+
scan_for token, SeparatorQuote,
|
|
30
76
|
{
|
|
31
|
-
|
|
32
|
-
|
|
77
|
+
/^'$/ => :single_quote,
|
|
78
|
+
/^"$/ => :double_quote
|
|
33
79
|
}
|
|
34
80
|
end
|
|
35
81
|
|
|
36
|
-
#
|
|
37
|
-
#
|
|
82
|
+
# token - The Token object we want to scan.
|
|
83
|
+
#
|
|
84
|
+
# Returns a new SeparatorAt object.
|
|
38
85
|
def self.scan_for_at(token)
|
|
39
86
|
scan_for token, SeparatorAt, { /^(at|@)$/ => :at }
|
|
40
87
|
end
|
|
41
88
|
|
|
42
|
-
#
|
|
43
|
-
#
|
|
89
|
+
# token - The Token object we want to scan.
|
|
90
|
+
#
|
|
91
|
+
# Returns a new SeparatorIn object.
|
|
44
92
|
def self.scan_for_in(token)
|
|
45
93
|
scan_for token, SeparatorIn, { /^in$/ => :in }
|
|
46
94
|
end
|
|
47
95
|
|
|
48
|
-
#
|
|
49
|
-
#
|
|
96
|
+
# token - The Token object we want to scan.
|
|
97
|
+
#
|
|
98
|
+
# Returns a new SeparatorOn object.
|
|
50
99
|
def self.scan_for_on(token)
|
|
51
100
|
scan_for token, SeparatorOn, { /^on$/ => :on }
|
|
52
101
|
end
|
|
53
102
|
|
|
103
|
+
# token - The Token object we want to scan.
|
|
104
|
+
#
|
|
105
|
+
# Returns a new SeperatorAnd Object object.
|
|
106
|
+
def self.scan_for_and(token)
|
|
107
|
+
scan_for token, SeparatorAnd, { /^and$/ => :and }
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# token - The Token object we want to scan.
|
|
111
|
+
#
|
|
112
|
+
# Returns a new SeperatorT Object object.
|
|
113
|
+
def self.scan_for_t(token)
|
|
114
|
+
scan_for token, SeparatorT, { /^t$/ => :T }
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# token - The Token object we want to scan.
|
|
118
|
+
#
|
|
119
|
+
# Returns a new SeperatorW Object object.
|
|
120
|
+
def self.scan_for_w(token)
|
|
121
|
+
scan_for token, SeparatorW, { /^w$/ => :W }
|
|
122
|
+
end
|
|
123
|
+
|
|
54
124
|
def to_s
|
|
55
125
|
'separator'
|
|
56
126
|
end
|
|
@@ -62,9 +132,39 @@ module Chronic
|
|
|
62
132
|
end
|
|
63
133
|
end
|
|
64
134
|
|
|
65
|
-
class
|
|
135
|
+
class SeparatorDot < Separator #:nodoc:
|
|
66
136
|
def to_s
|
|
67
|
-
super << '-
|
|
137
|
+
super << '-dot'
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
class SeparatorColon < Separator #:nodoc:
|
|
142
|
+
def to_s
|
|
143
|
+
super << '-colon'
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
class SeparatorSpace < Separator #:nodoc:
|
|
148
|
+
def to_s
|
|
149
|
+
super << '-space'
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
class SeparatorSlash < Separator #:nodoc:
|
|
154
|
+
def to_s
|
|
155
|
+
super << '-slash'
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
class SeparatorDash < Separator #:nodoc:
|
|
160
|
+
def to_s
|
|
161
|
+
super << '-dash'
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
class SeparatorQuote < Separator #:nodoc:
|
|
166
|
+
def to_s
|
|
167
|
+
super << '-quote-' << @type.to_s
|
|
68
168
|
end
|
|
69
169
|
end
|
|
70
170
|
|
|
@@ -85,4 +185,23 @@ module Chronic
|
|
|
85
185
|
super << '-on'
|
|
86
186
|
end
|
|
87
187
|
end
|
|
88
|
-
|
|
188
|
+
|
|
189
|
+
class SeparatorAnd < Separator #:nodoc:
|
|
190
|
+
def to_s
|
|
191
|
+
super << '-and'
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
class SeparatorT < Separator #:nodoc:
|
|
196
|
+
def to_s
|
|
197
|
+
super << '-T'
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
class SeparatorW < Separator #:nodoc:
|
|
202
|
+
def to_s
|
|
203
|
+
super << '-W'
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
end
|
data/lib/chronic/sign.rb
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
module Chronic
|
|
2
|
+
class Sign < Tag
|
|
3
|
+
|
|
4
|
+
# Scan an Array of Token objects and apply any necessary Sign
|
|
5
|
+
# tags to each token.
|
|
6
|
+
#
|
|
7
|
+
# tokens - An Array of tokens to scan.
|
|
8
|
+
# options - The Hash of options specified in Chronic::parse.
|
|
9
|
+
#
|
|
10
|
+
# Returns an Array of tokens.
|
|
11
|
+
def self.scan(tokens, options)
|
|
12
|
+
tokens.each do |token|
|
|
13
|
+
if t = scan_for_plus(token) then token.tag(t); next end
|
|
14
|
+
if t = scan_for_minus(token) then token.tag(t); next end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# token - The Token object we want to scan.
|
|
19
|
+
#
|
|
20
|
+
# Returns a new SignPlus object.
|
|
21
|
+
def self.scan_for_plus(token)
|
|
22
|
+
scan_for token, SignPlus, { /^\+$/ => :plus }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# token - The Token object we want to scan.
|
|
26
|
+
#
|
|
27
|
+
# Returns a new SignMinus object.
|
|
28
|
+
def self.scan_for_minus(token)
|
|
29
|
+
scan_for token, SignMinus, { /^-$/ => :minus }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def to_s
|
|
33
|
+
'sign'
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
class SignPlus < Sign #:nodoc:
|
|
38
|
+
def to_s
|
|
39
|
+
super << '-plus'
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
class SignMinus < Sign #:nodoc:
|
|
44
|
+
def to_s
|
|
45
|
+
super << '-minus'
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
end
|
data/lib/chronic/span.rb
CHANGED
data/lib/chronic/tag.rb
CHANGED
|
@@ -1,36 +1,31 @@
|
|
|
1
1
|
module Chronic
|
|
2
2
|
# Tokens are tagged with subclassed instances of this class when
|
|
3
|
-
# they match specific criteria
|
|
3
|
+
# they match specific criteria.
|
|
4
4
|
class Tag
|
|
5
5
|
|
|
6
|
-
# @return [Symbol]
|
|
7
6
|
attr_accessor :type
|
|
8
7
|
|
|
9
|
-
#
|
|
10
|
-
def initialize(type)
|
|
8
|
+
# type - The Symbol type of this tag.
|
|
9
|
+
def initialize(type, options = {})
|
|
11
10
|
@type = type
|
|
11
|
+
@options = options
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
#
|
|
15
|
-
def start=(
|
|
16
|
-
@now =
|
|
14
|
+
# time - Set the start Time for this Tag.
|
|
15
|
+
def start=(time)
|
|
16
|
+
@now = time
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
class << self
|
|
20
20
|
private
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
# @param [Class] klass The class instance to create
|
|
24
|
-
# @param [Regexp, Hash] items
|
|
25
|
-
# @return [Object, nil] either a new instance of `klass` or `nil` if
|
|
26
|
-
# no match is found
|
|
27
|
-
def scan_for(token, klass, items={})
|
|
22
|
+
def scan_for(token, klass, items={}, options = {})
|
|
28
23
|
case items
|
|
29
24
|
when Regexp
|
|
30
|
-
return klass.new(token.word) if items =~ token.word
|
|
25
|
+
return klass.new(token.word, options) if items =~ token.word
|
|
31
26
|
when Hash
|
|
32
27
|
items.each do |item, symbol|
|
|
33
|
-
return klass.new(symbol) if item =~ token.word
|
|
28
|
+
return klass.new(symbol, options) if item =~ token.word
|
|
34
29
|
end
|
|
35
30
|
end
|
|
36
31
|
nil
|
data/lib/chronic/time.rb
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module Chronic
|
|
2
|
+
class Time
|
|
3
|
+
HOUR_SECONDS = 3600 # 60 * 60
|
|
4
|
+
MINUTE_SECONDS = 60
|
|
5
|
+
SECOND_SECONDS = 1 # haha, awesome
|
|
6
|
+
SUBSECOND_SECONDS = 0.001
|
|
7
|
+
|
|
8
|
+
# Checks if given number could be hour
|
|
9
|
+
def self.could_be_hour?(hour)
|
|
10
|
+
hour >= 0 && hour <= 24
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Checks if given number could be minute
|
|
14
|
+
def self.could_be_minute?(minute)
|
|
15
|
+
minute >= 0 && minute <= 60
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Checks if given number could be second
|
|
19
|
+
def self.could_be_second?(second)
|
|
20
|
+
second >= 0 && second <= 60
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Checks if given number could be subsecond
|
|
24
|
+
def self.could_be_subsecond?(subsecond)
|
|
25
|
+
subsecond >= 0 && subsecond <= 999999
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# normalize offset in seconds to offset as string +mm:ss or -mm:ss
|
|
29
|
+
def self.normalize_offset(offset)
|
|
30
|
+
return offset if offset.is_a?(String)
|
|
31
|
+
offset = Chronic.time_class.now.to_time.utc_offset unless offset # get current system's UTC offset if offset is nil
|
|
32
|
+
sign = '+'
|
|
33
|
+
sign = '-' if offset < 0
|
|
34
|
+
hours = (offset.abs / 3600).to_i.to_s.rjust(2,'0')
|
|
35
|
+
minutes = (offset.abs % 3600).to_s.rjust(2,'0')
|
|
36
|
+
sign + hours + minutes
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
end
|
|
40
|
+
end
|
data/lib/chronic/time_zone.rb
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
module Chronic
|
|
2
2
|
class TimeZone < Tag
|
|
3
3
|
|
|
4
|
-
# Scan an Array of
|
|
5
|
-
# each token
|
|
4
|
+
# Scan an Array of Token objects and apply any necessary TimeZone
|
|
5
|
+
# tags to each token.
|
|
6
6
|
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
7
|
+
# tokens - An Array of tokens to scan.
|
|
8
|
+
# options - The Hash of options specified in Chronic::parse.
|
|
9
|
+
#
|
|
10
|
+
# Returns an Array of tokens.
|
|
10
11
|
def self.scan(tokens, options)
|
|
11
12
|
tokens.each do |token|
|
|
12
13
|
if t = scan_for_all(token) then token.tag(t); next end
|
|
13
14
|
end
|
|
14
15
|
end
|
|
15
16
|
|
|
16
|
-
#
|
|
17
|
-
#
|
|
17
|
+
# token - The Token object we want to scan.
|
|
18
|
+
#
|
|
19
|
+
# Returns a new Pointer object.
|
|
18
20
|
def self.scan_for_all(token)
|
|
19
21
|
scan_for token, self,
|
|
20
22
|
{
|
data/lib/chronic/token.rb
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
module Chronic
|
|
2
2
|
class Token
|
|
3
3
|
|
|
4
|
-
# @return [String] The word this Token represents
|
|
5
4
|
attr_accessor :word
|
|
6
|
-
|
|
7
|
-
# @return [Array] A list of tag associated with this Token
|
|
8
5
|
attr_accessor :tags
|
|
9
6
|
|
|
10
7
|
def initialize(word)
|
|
@@ -12,27 +9,32 @@ module Chronic
|
|
|
12
9
|
@tags = []
|
|
13
10
|
end
|
|
14
11
|
|
|
15
|
-
# Tag this token with the specified tag
|
|
12
|
+
# Tag this token with the specified tag.
|
|
13
|
+
#
|
|
14
|
+
# new_tag - The new Tag object.
|
|
16
15
|
#
|
|
17
|
-
#
|
|
16
|
+
# Returns nothing.
|
|
18
17
|
def tag(new_tag)
|
|
19
18
|
@tags << new_tag
|
|
20
19
|
end
|
|
21
20
|
|
|
22
|
-
# Remove all tags of the given class
|
|
21
|
+
# Remove all tags of the given class.
|
|
22
|
+
#
|
|
23
|
+
# tag_class - The tag Class to remove.
|
|
23
24
|
#
|
|
24
|
-
#
|
|
25
|
+
# Returns nothing.
|
|
25
26
|
def untag(tag_class)
|
|
26
27
|
@tags.delete_if { |m| m.kind_of? tag_class }
|
|
27
28
|
end
|
|
28
29
|
|
|
29
|
-
#
|
|
30
|
+
# Returns true if this token has any tags.
|
|
30
31
|
def tagged?
|
|
31
32
|
@tags.size > 0
|
|
32
33
|
end
|
|
33
34
|
|
|
34
|
-
#
|
|
35
|
-
#
|
|
35
|
+
# tag_class - The tag Class to search for.
|
|
36
|
+
#
|
|
37
|
+
# Returns The first Tag that matches the given class.
|
|
36
38
|
def get_tag(tag_class)
|
|
37
39
|
@tags.find { |m| m.kind_of? tag_class }
|
|
38
40
|
end
|
|
@@ -41,5 +43,9 @@ module Chronic
|
|
|
41
43
|
def to_s
|
|
42
44
|
@word << '(' << @tags.join(', ') << ') '
|
|
43
45
|
end
|
|
46
|
+
|
|
47
|
+
def inspect
|
|
48
|
+
to_s
|
|
49
|
+
end
|
|
44
50
|
end
|
|
45
51
|
end
|
data/lib/chronic.rb
CHANGED
|
@@ -1,70 +1,10 @@
|
|
|
1
1
|
require 'time'
|
|
2
2
|
require 'date'
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
# require 'chronic'
|
|
8
|
-
#
|
|
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
|
|
30
|
-
module Chronic
|
|
31
|
-
VERSION = "0.6.3"
|
|
32
|
-
|
|
33
|
-
class << self
|
|
34
|
-
|
|
35
|
-
# @return [Boolean] true when debug mode is enabled
|
|
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
|
|
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
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
self.debug = false
|
|
64
|
-
self.time_class = Time
|
|
65
|
-
end
|
|
4
|
+
require 'chronic/parser'
|
|
5
|
+
require 'chronic/date'
|
|
6
|
+
require 'chronic/time'
|
|
66
7
|
|
|
67
|
-
require 'chronic/chronic'
|
|
68
8
|
require 'chronic/handler'
|
|
69
9
|
require 'chronic/handlers'
|
|
70
10
|
require 'chronic/mini_date'
|
|
@@ -76,6 +16,7 @@ require 'chronic/pointer'
|
|
|
76
16
|
require 'chronic/scalar'
|
|
77
17
|
require 'chronic/ordinal'
|
|
78
18
|
require 'chronic/separator'
|
|
19
|
+
require 'chronic/sign'
|
|
79
20
|
require 'chronic/time_zone'
|
|
80
21
|
require 'chronic/numerizer'
|
|
81
22
|
require 'chronic/season'
|
|
@@ -98,14 +39,112 @@ require 'chronic/repeaters/repeater_minute'
|
|
|
98
39
|
require 'chronic/repeaters/repeater_second'
|
|
99
40
|
require 'chronic/repeaters/repeater_time'
|
|
100
41
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
42
|
+
# Parse natural language dates and times into Time or Chronic::Span objects.
|
|
43
|
+
#
|
|
44
|
+
# Examples:
|
|
45
|
+
#
|
|
46
|
+
# require 'chronic'
|
|
47
|
+
#
|
|
48
|
+
# Time.now #=> Sun Aug 27 23:18:25 PDT 2006
|
|
49
|
+
#
|
|
50
|
+
# Chronic.parse('tomorrow')
|
|
51
|
+
# #=> Mon Aug 28 12:00:00 PDT 2006
|
|
52
|
+
#
|
|
53
|
+
# Chronic.parse('monday', :context => :past)
|
|
54
|
+
# #=> Mon Aug 21 12:00:00 PDT 2006
|
|
55
|
+
module Chronic
|
|
56
|
+
VERSION = "0.10.2"
|
|
57
|
+
|
|
58
|
+
class << self
|
|
59
|
+
|
|
60
|
+
# Returns true when debug mode is enabled.
|
|
61
|
+
attr_accessor :debug
|
|
62
|
+
|
|
63
|
+
# Examples:
|
|
64
|
+
#
|
|
65
|
+
# require 'chronic'
|
|
66
|
+
# require 'active_support/time'
|
|
67
|
+
#
|
|
68
|
+
# Time.zone = 'UTC'
|
|
69
|
+
# Chronic.time_class = Time.zone
|
|
70
|
+
# Chronic.parse('June 15 2006 at 5:54 AM')
|
|
71
|
+
# # => Thu, 15 Jun 2006 05:45:00 UTC +00:00
|
|
72
|
+
#
|
|
73
|
+
# Returns The Time class Chronic uses internally.
|
|
74
|
+
attr_accessor :time_class
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
self.debug = false
|
|
78
|
+
self.time_class = ::Time
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# Parses a string containing a natural language date or time.
|
|
82
|
+
#
|
|
83
|
+
# If the parser can find a date or time, either a Time or Chronic::Span
|
|
84
|
+
# will be returned (depending on the value of `:guess`). If no
|
|
85
|
+
# date or time can be found, `nil` will be returned.
|
|
86
|
+
#
|
|
87
|
+
# text - The String text to parse.
|
|
88
|
+
# opts - An optional Hash of configuration options passed to Parser::new.
|
|
89
|
+
def self.parse(text, options = {})
|
|
90
|
+
Parser.new(options).parse(text)
|
|
105
91
|
end
|
|
106
92
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
93
|
+
# Construct a new time object determining possible month overflows
|
|
94
|
+
# and leap years.
|
|
95
|
+
#
|
|
96
|
+
# year - Integer year.
|
|
97
|
+
# month - Integer month.
|
|
98
|
+
# day - Integer day.
|
|
99
|
+
# hour - Integer hour.
|
|
100
|
+
# minute - Integer minute.
|
|
101
|
+
# second - Integer second.
|
|
102
|
+
#
|
|
103
|
+
# Returns a new Time object constructed from these params.
|
|
104
|
+
def self.construct(year, month = 1, day = 1, hour = 0, minute = 0, second = 0, offset = nil)
|
|
105
|
+
if second >= 60
|
|
106
|
+
minute += second / 60
|
|
107
|
+
second = second % 60
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
if minute >= 60
|
|
111
|
+
hour += minute / 60
|
|
112
|
+
minute = minute % 60
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
if hour >= 24
|
|
116
|
+
day += hour / 24
|
|
117
|
+
hour = hour % 24
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# determine if there is a day overflow. this is complicated by our crappy calendar
|
|
121
|
+
# system (non-constant number of days per month)
|
|
122
|
+
day <= 56 || raise("day must be no more than 56 (makes month resolution easier)")
|
|
123
|
+
if day > 28 # no month ever has fewer than 28 days, so only do this if necessary
|
|
124
|
+
days_this_month = ::Date.leap?(year) ? Date::MONTH_DAYS_LEAP[month] : Date::MONTH_DAYS[month]
|
|
125
|
+
if day > days_this_month
|
|
126
|
+
month += day / days_this_month
|
|
127
|
+
day = day % days_this_month
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
if month > 12
|
|
132
|
+
if month % 12 == 0
|
|
133
|
+
year += (month - 12) / 12
|
|
134
|
+
month = 12
|
|
135
|
+
else
|
|
136
|
+
year += month / 12
|
|
137
|
+
month = month % 12
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
if Chronic.time_class.name == "Date"
|
|
141
|
+
Chronic.time_class.new(year, month, day)
|
|
142
|
+
elsif not Chronic.time_class.respond_to?(:new) or (RUBY_VERSION.to_f < 1.9 and Chronic.time_class.name == "Time")
|
|
143
|
+
Chronic.time_class.local(year, month, day, hour, minute, second)
|
|
144
|
+
else
|
|
145
|
+
offset = Time::normalize_offset(offset) if Chronic.time_class.name == "DateTime"
|
|
146
|
+
Chronic.time_class.new(year, month, day, hour, minute, second, offset)
|
|
147
|
+
end
|
|
110
148
|
end
|
|
149
|
+
|
|
111
150
|
end
|
data/test/helper.rb
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
unless defined? Chronic
|
|
2
|
-
$:.unshift File.expand_path('../../lib')
|
|
2
|
+
$:.unshift File.expand_path('../../lib', __FILE__)
|
|
3
3
|
require 'chronic'
|
|
4
4
|
end
|
|
5
5
|
|
|
6
|
-
require '
|
|
6
|
+
require 'minitest/autorun'
|
|
7
|
+
|
|
8
|
+
class TestCase < MiniTest::Test
|
|
9
|
+
def self.test(name, &block)
|
|
10
|
+
define_method("test_#{name.gsub(/\W/, '_')}", &block) if block
|
|
11
|
+
end
|
|
12
|
+
end
|