chronic 0.6.7 → 0.7.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/HISTORY.md +10 -0
- data/README.md +5 -0
- data/chronic.gemspec +3 -0
- data/lib/chronic.rb +49 -43
- data/lib/chronic/chronic.rb +72 -62
- data/lib/chronic/grabber.rb +9 -7
- data/lib/chronic/handler.rb +10 -12
- data/lib/chronic/handlers.rb +33 -1
- data/lib/chronic/ordinal.rb +12 -9
- data/lib/chronic/pointer.rb +9 -7
- data/lib/chronic/repeater.rb +28 -18
- data/lib/chronic/repeaters/repeater_day_portion.rb +25 -11
- data/lib/chronic/scalar.rb +30 -23
- data/lib/chronic/season.rb +1 -12
- data/lib/chronic/separator.rb +21 -15
- data/lib/chronic/tag.rb +5 -11
- data/lib/chronic/time_zone.rb +9 -7
- data/lib/chronic/token.rb +12 -10
- data/test/helper.rb +7 -1
- data/test/{test_Chronic.rb → test_chronic.rb} +6 -6
- data/test/{test_DaylightSavings.rb → test_daylight_savings.rb} +1 -1
- data/test/{test_Handler.rb → test_handler.rb} +1 -1
- data/test/{test_MiniDate.rb → test_mini_date.rb} +9 -9
- data/test/{test_Numerizer.rb → test_numerizer.rb} +1 -1
- data/test/test_parsing.rb +68 -10
- 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} +1 -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} +1 -1
- data/test/{test_Token.rb → test_token.rb} +1 -1
- metadata +76 -44
- data/.gemtest +0 -0
- data/.yardopts +0 -3
data/lib/chronic/handlers.rb
CHANGED
@@ -224,6 +224,27 @@ module Chronic
|
|
224
224
|
handle_sm_sd_sy(new_tokens + time_tokens, options)
|
225
225
|
end
|
226
226
|
|
227
|
+
# Handle scalar-day/scalar-month AND scalar-month/scalar-day
|
228
|
+
def handle_sm_sd(tokens, options)
|
229
|
+
month = tokens[0].get_tag(ScalarMonth).type
|
230
|
+
day = tokens[1].get_tag(ScalarDay).type
|
231
|
+
year = Chronic.now.year
|
232
|
+
|
233
|
+
if Array(options[:endian_precedence]).first == :little
|
234
|
+
day, month = month, day
|
235
|
+
end
|
236
|
+
|
237
|
+
return if month_overflow?(year, month, day)
|
238
|
+
|
239
|
+
begin
|
240
|
+
start_time = Chronic.time_class.local(year, month, day)
|
241
|
+
end_time = Chronic.time_class.local(year, month, day + 1)
|
242
|
+
Span.new(start_time, end_time)
|
243
|
+
rescue ArgumentError
|
244
|
+
nil
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
227
248
|
# Handle scalar-month/scalar-year
|
228
249
|
def handle_sm_sy(tokens, options)
|
229
250
|
month = tokens[0].get_tag(ScalarMonth).type
|
@@ -296,6 +317,15 @@ module Chronic
|
|
296
317
|
end
|
297
318
|
end
|
298
319
|
|
320
|
+
def handle_sm_rmn_sy(tokens, options)
|
321
|
+
day = tokens[0].get_tag(ScalarDay).type
|
322
|
+
month = tokens[1].get_tag(RepeaterMonthName).index
|
323
|
+
year = tokens[2].get_tag(ScalarYear).type
|
324
|
+
time = Chronic.time_class.local(year, month, day)
|
325
|
+
end_time = Chronic.time_class.local(year, month, day + 1)
|
326
|
+
Span.new(time, end_time)
|
327
|
+
end
|
328
|
+
|
299
329
|
# anchors
|
300
330
|
|
301
331
|
# Handle repeaters
|
@@ -318,7 +348,7 @@ module Chronic
|
|
318
348
|
repeater = tokens[1].get_tag(Repeater)
|
319
349
|
pointer = tokens[2].get_tag(Pointer).type
|
320
350
|
|
321
|
-
repeater.offset(span, distance, pointer)
|
351
|
+
repeater.offset(span, distance, pointer) if repeater.respond_to?(:offset)
|
322
352
|
end
|
323
353
|
|
324
354
|
# Handle scalar/repeater/pointer
|
@@ -434,6 +464,8 @@ module Chronic
|
|
434
464
|
else
|
435
465
|
day > RepeaterMonth::MONTH_DAYS[month - 1]
|
436
466
|
end
|
467
|
+
rescue ArgumentError
|
468
|
+
false
|
437
469
|
end
|
438
470
|
|
439
471
|
# Recursively finds repeaters within other repeaters.
|
data/lib/chronic/ordinal.rb
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
module Chronic
|
2
2
|
class Ordinal < Tag
|
3
3
|
|
4
|
-
# Scan an Array of
|
5
|
-
# each token
|
4
|
+
# Scan an Array of Token objects and apply any necessary Ordinal
|
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_ordinals(token) then token.tag(t) end
|
@@ -14,14 +15,16 @@ module Chronic
|
|
14
15
|
end
|
15
16
|
end
|
16
17
|
|
17
|
-
#
|
18
|
-
#
|
18
|
+
# token - The Token object we want to scan.
|
19
|
+
#
|
20
|
+
# Returns a new Ordinal object.
|
19
21
|
def self.scan_for_ordinals(token)
|
20
22
|
Ordinal.new($1.to_i) if token.word =~ /^(\d*)(st|nd|rd|th)$/
|
21
23
|
end
|
22
24
|
|
23
|
-
#
|
24
|
-
#
|
25
|
+
# token - The Token object we want to scan.
|
26
|
+
#
|
27
|
+
# Returns a new Ordinal object.
|
25
28
|
def self.scan_for_days(token)
|
26
29
|
if token.word =~ /^(\d*)(st|nd|rd|th)$/
|
27
30
|
unless $1.to_i > 31 || $1.to_i < 1
|
data/lib/chronic/pointer.rb
CHANGED
@@ -1,20 +1,22 @@
|
|
1
1
|
module Chronic
|
2
2
|
class Pointer < Tag
|
3
3
|
|
4
|
-
# Scan an Array of
|
5
|
-
# each token
|
4
|
+
# Scan an Array of Token objects and apply any necessary Pointer
|
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) 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/repeater.rb
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
module Chronic
|
2
2
|
class Repeater < Tag
|
3
3
|
|
4
|
-
# Scan an Array of
|
5
|
-
# each token
|
4
|
+
# Scan an Array of Token objects and apply any necessary Repeater
|
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_season_names(token) then token.tag(t); next end
|
@@ -18,8 +19,9 @@ module Chronic
|
|
18
19
|
end
|
19
20
|
end
|
20
21
|
|
21
|
-
#
|
22
|
-
#
|
22
|
+
# token - The Token object we want to scan.
|
23
|
+
#
|
24
|
+
# Returns a new Repeater object.
|
23
25
|
def self.scan_for_season_names(token)
|
24
26
|
scan_for token, RepeaterSeasonName,
|
25
27
|
{
|
@@ -30,8 +32,9 @@ module Chronic
|
|
30
32
|
}
|
31
33
|
end
|
32
34
|
|
33
|
-
#
|
34
|
-
#
|
35
|
+
# token - The Token object we want to scan.
|
36
|
+
#
|
37
|
+
# Returns a new Repeater object.
|
35
38
|
def self.scan_for_month_names(token)
|
36
39
|
scan_for token, RepeaterMonthName,
|
37
40
|
{
|
@@ -50,8 +53,9 @@ module Chronic
|
|
50
53
|
}
|
51
54
|
end
|
52
55
|
|
53
|
-
#
|
54
|
-
#
|
56
|
+
# token - The Token object we want to scan.
|
57
|
+
#
|
58
|
+
# Returns a new Repeater object.
|
55
59
|
def self.scan_for_day_names(token)
|
56
60
|
scan_for token, RepeaterDayName,
|
57
61
|
{
|
@@ -65,8 +69,9 @@ module Chronic
|
|
65
69
|
}
|
66
70
|
end
|
67
71
|
|
68
|
-
#
|
69
|
-
#
|
72
|
+
# token - The Token object we want to scan.
|
73
|
+
#
|
74
|
+
# Returns a new Repeater object.
|
70
75
|
def self.scan_for_day_portions(token)
|
71
76
|
scan_for token, RepeaterDayPortion,
|
72
77
|
{
|
@@ -79,14 +84,16 @@ module Chronic
|
|
79
84
|
}
|
80
85
|
end
|
81
86
|
|
82
|
-
#
|
83
|
-
#
|
87
|
+
# token - The Token object we want to scan.
|
88
|
+
#
|
89
|
+
# Returns a new Repeater object.
|
84
90
|
def self.scan_for_times(token)
|
85
91
|
scan_for token, RepeaterTime, /^\d{1,2}(:?\d{2})?([\.:]?\d{2})?$/
|
86
92
|
end
|
87
93
|
|
88
|
-
#
|
89
|
-
#
|
94
|
+
# token - The Token object we want to scan.
|
95
|
+
#
|
96
|
+
# Returns a new Repeater object.
|
90
97
|
def self.scan_for_units(token)
|
91
98
|
{
|
92
99
|
/^years?$/ => :year,
|
@@ -97,8 +104,11 @@ module Chronic
|
|
97
104
|
/^weekends?$/ => :weekend,
|
98
105
|
/^(week|business)days?$/ => :weekday,
|
99
106
|
/^days?$/ => :day,
|
107
|
+
/^hrs?$/ => :hour,
|
100
108
|
/^hours?$/ => :hour,
|
109
|
+
/^mins?$/ => :minute,
|
101
110
|
/^minutes?$/ => :minute,
|
111
|
+
/^secs?$/ => :second,
|
102
112
|
/^seconds?$/ => :second
|
103
113
|
}.each do |item, symbol|
|
104
114
|
if item =~ token.word
|
@@ -132,4 +142,4 @@ module Chronic
|
|
132
142
|
'repeater'
|
133
143
|
end
|
134
144
|
end
|
135
|
-
end
|
145
|
+
end
|
@@ -25,8 +25,6 @@ module Chronic
|
|
25
25
|
def next(pointer)
|
26
26
|
super
|
27
27
|
|
28
|
-
full_day = 60 * 60 * 24
|
29
|
-
|
30
28
|
if !@current_span
|
31
29
|
now_seconds = @now - Chronic.construct(@now.year, @now.month, @now.day)
|
32
30
|
if now_seconds < @range.begin
|
@@ -34,32 +32,38 @@ module Chronic
|
|
34
32
|
when :future
|
35
33
|
range_start = Chronic.construct(@now.year, @now.month, @now.day) + @range.begin
|
36
34
|
when :past
|
37
|
-
range_start = Chronic.construct(@now.year, @now.month, @now.day
|
35
|
+
range_start = Chronic.construct(@now.year, @now.month, @now.day - 1) + @range.begin
|
38
36
|
end
|
39
37
|
elsif now_seconds > @range.end
|
40
38
|
case pointer
|
41
39
|
when :future
|
42
|
-
range_start = Chronic.construct(@now.year, @now.month, @now.day
|
40
|
+
range_start = Chronic.construct(@now.year, @now.month, @now.day + 1) + @range.begin
|
43
41
|
when :past
|
44
42
|
range_start = Chronic.construct(@now.year, @now.month, @now.day) + @range.begin
|
45
43
|
end
|
46
44
|
else
|
47
45
|
case pointer
|
48
46
|
when :future
|
49
|
-
range_start = Chronic.construct(@now.year, @now.month, @now.day
|
47
|
+
range_start = Chronic.construct(@now.year, @now.month, @now.day + 1) + @range.begin
|
50
48
|
when :past
|
51
|
-
range_start = Chronic.construct(@now.year, @now.month, @now.day
|
49
|
+
range_start = Chronic.construct(@now.year, @now.month, @now.day - 1) + @range.begin
|
52
50
|
end
|
53
51
|
end
|
54
|
-
|
55
|
-
|
52
|
+
offset = (@range.end - @range.begin)
|
53
|
+
range_end = construct_date_from_reference_and_offset(range_start, offset)
|
54
|
+
@current_span = Span.new(range_start, range_end)
|
56
55
|
else
|
56
|
+
days_to_shift_window =
|
57
57
|
case pointer
|
58
58
|
when :future
|
59
|
-
|
59
|
+
1
|
60
60
|
when :past
|
61
|
-
|
61
|
+
-1
|
62
62
|
end
|
63
|
+
|
64
|
+
new_begin = Chronic.construct(@current_span.begin.year, @current_span.begin.month, @current_span.begin.day + days_to_shift_window, @current_span.begin.hour, @current_span.begin.min, @current_span.begin.sec)
|
65
|
+
new_end = Chronic.construct(@current_span.end.year, @current_span.end.month, @current_span.end.day + days_to_shift_window, @current_span.end.hour, @current_span.end.min, @current_span.end.sec)
|
66
|
+
@current_span = Span.new(new_begin, new_end)
|
63
67
|
end
|
64
68
|
end
|
65
69
|
|
@@ -67,7 +71,8 @@ module Chronic
|
|
67
71
|
super
|
68
72
|
|
69
73
|
range_start = Chronic.construct(@now.year, @now.month, @now.day) + @range.begin
|
70
|
-
|
74
|
+
range_end = construct_date_from_reference_and_offset(range_start)
|
75
|
+
@current_span = Span.new(range_start, range_end)
|
71
76
|
end
|
72
77
|
|
73
78
|
def offset(span, amount, pointer)
|
@@ -90,5 +95,14 @@ module Chronic
|
|
90
95
|
def to_s
|
91
96
|
super << '-dayportion-' << @type.to_s
|
92
97
|
end
|
98
|
+
|
99
|
+
private
|
100
|
+
def construct_date_from_reference_and_offset(reference, offset = nil)
|
101
|
+
elapsed_seconds_for_range = offset || (@range.end - @range.begin)
|
102
|
+
second_hand = ((elapsed_seconds_for_range - (12 * 60))) % 60
|
103
|
+
minute_hand = (elapsed_seconds_for_range - second_hand) / (60) % 60
|
104
|
+
hour_hand = (elapsed_seconds_for_range - minute_hand - second_hand) / (60 * 60) + reference.hour % 24
|
105
|
+
Chronic.construct(reference.year, reference.month, reference.day, hour_hand, minute_hand, second_hand)
|
106
|
+
end
|
93
107
|
end
|
94
108
|
end
|
data/lib/chronic/scalar.rb
CHANGED
@@ -2,12 +2,13 @@ module Chronic
|
|
2
2
|
class Scalar < Tag
|
3
3
|
DAY_PORTIONS = %w( am pm morning afternoon evening night )
|
4
4
|
|
5
|
-
# Scan an Array of
|
6
|
-
# each token
|
5
|
+
# Scan an Array of Token objects and apply any necessary Scalar
|
6
|
+
# tags to each token.
|
7
7
|
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
8
|
+
# tokens - An Array of tokens to scan.
|
9
|
+
# options - The Hash of options specified in Chronic::parse.
|
10
|
+
#
|
11
|
+
# Returns an Array of tokens.
|
11
12
|
def self.scan(tokens, options)
|
12
13
|
tokens.each_index do |i|
|
13
14
|
if t = scan_for_scalars(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
|
@@ -17,9 +18,10 @@ module Chronic
|
|
17
18
|
end
|
18
19
|
end
|
19
20
|
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
21
|
+
# token - The Token object we want to scan.
|
22
|
+
# post_token - The next Token object.
|
23
|
+
#
|
24
|
+
# Returns a new Scalar object.
|
23
25
|
def self.scan_for_scalars(token, post_token)
|
24
26
|
if token.word =~ /^\d*$/
|
25
27
|
unless post_token && DAY_PORTIONS.include?(post_token.word)
|
@@ -28,9 +30,10 @@ module Chronic
|
|
28
30
|
end
|
29
31
|
end
|
30
32
|
|
31
|
-
#
|
32
|
-
#
|
33
|
-
#
|
33
|
+
# token - The Token object we want to scan.
|
34
|
+
# post_token - The next Token object.
|
35
|
+
#
|
36
|
+
# Returns a new Scalar object.
|
34
37
|
def self.scan_for_days(token, post_token)
|
35
38
|
if token.word =~ /^\d\d?$/
|
36
39
|
toi = token.word.to_i
|
@@ -40,9 +43,10 @@ module Chronic
|
|
40
43
|
end
|
41
44
|
end
|
42
45
|
|
43
|
-
#
|
44
|
-
#
|
45
|
-
#
|
46
|
+
# token - The Token object we want to scan.
|
47
|
+
# post_token - The next Token object.
|
48
|
+
#
|
49
|
+
# Returns a new Scalar object.
|
46
50
|
def self.scan_for_months(token, post_token)
|
47
51
|
if token.word =~ /^\d\d?$/
|
48
52
|
toi = token.word.to_i
|
@@ -52,10 +56,11 @@ module Chronic
|
|
52
56
|
end
|
53
57
|
end
|
54
58
|
|
55
|
-
#
|
56
|
-
#
|
57
|
-
#
|
58
|
-
#
|
59
|
+
# token - The Token object we want to scan.
|
60
|
+
# post_token - The next Token object.
|
61
|
+
# options - The Hash of options specified in Chronic::parse.
|
62
|
+
#
|
63
|
+
# Returns a new Scalar object.
|
59
64
|
def self.scan_for_years(token, post_token, options)
|
60
65
|
if token.word =~ /^([1-9]\d)?\d\d?$/
|
61
66
|
unless post_token && DAY_PORTIONS.include?(post_token.word)
|
@@ -65,16 +70,18 @@ module Chronic
|
|
65
70
|
end
|
66
71
|
end
|
67
72
|
|
68
|
-
# Build a year from a 2 digit suffix
|
73
|
+
# Build a year from a 2 digit suffix.
|
74
|
+
#
|
75
|
+
# year - The two digit Integer year to build from.
|
76
|
+
# bias - The Integer amount of future years to bias.
|
77
|
+
#
|
78
|
+
# Examples:
|
69
79
|
#
|
70
|
-
# @example
|
71
80
|
# make_year(96, 50) #=> 1996
|
72
81
|
# make_year(79, 20) #=> 2079
|
73
82
|
# make_year(00, 50) #=> 2000
|
74
83
|
#
|
75
|
-
#
|
76
|
-
# @param [Integer] bias The amount of future years to bias
|
77
|
-
# @return [Integer] The 4 digit year
|
84
|
+
# Returns The Integer 4 digit year.
|
78
85
|
def self.make_year(year, bias)
|
79
86
|
return year if year.to_s.size > 2
|
80
87
|
start_year = Chronic.time_class.now.year - bias
|
data/lib/chronic/season.rb
CHANGED
@@ -1,35 +1,24 @@
|
|
1
1
|
module Chronic
|
2
2
|
class Season
|
3
|
-
# @return [MiniDate]
|
4
|
-
attr_reader :start
|
5
3
|
|
6
|
-
|
4
|
+
attr_reader :start
|
7
5
|
attr_reader :end
|
8
6
|
|
9
|
-
# @param [MiniDate] start_date
|
10
|
-
# @param [MiniDate] end_date
|
11
7
|
def initialize(start_date, end_date)
|
12
8
|
@start = start_date
|
13
9
|
@end = end_date
|
14
10
|
end
|
15
11
|
|
16
|
-
# @param [Symbol] season The season name
|
17
|
-
# @param [Integer] pointer The direction (-1 for past, 1 for future)
|
18
|
-
# @return [Symbol] The new season name
|
19
12
|
def self.find_next_season(season, pointer)
|
20
13
|
lookup = [:spring, :summer, :autumn, :winter]
|
21
14
|
next_season_num = (lookup.index(season) + 1 * pointer) % 4
|
22
15
|
lookup[next_season_num]
|
23
16
|
end
|
24
17
|
|
25
|
-
# @param [Symbol] season The season name
|
26
|
-
# @return [Symbol] The new season name
|
27
18
|
def self.season_after(season)
|
28
19
|
find_next_season(season, +1)
|
29
20
|
end
|
30
21
|
|
31
|
-
# @param [Symbol] season The season name
|
32
|
-
# @return [Symbol] The new season name
|
33
22
|
def self.season_before(season)
|
34
23
|
find_next_season(season, -1)
|
35
24
|
end
|