chronic 0.4.1 → 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 +36 -2
- data/Manifest.txt +6 -2
- data/README.md +8 -2
- data/Rakefile +9 -101
- data/chronic.gemspec +12 -78
- data/lib/chronic/chronic.rb +225 -91
- data/lib/chronic/grabber.rb +12 -3
- data/lib/chronic/handlers.rb +94 -198
- data/lib/chronic/mini_date.rb +6 -2
- data/lib/chronic/ordinal.rb +15 -4
- data/lib/chronic/pointer.rb +12 -3
- data/lib/chronic/repeater.rb +28 -9
- 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 +19 -8
- data/lib/chronic/scalar.rb +29 -1
- data/lib/chronic/season.rb +37 -0
- data/lib/chronic/separator.rb +24 -7
- data/lib/chronic/tag.rb +10 -1
- 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_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 +31 -29
- data/benchmark/benchmark.rb +0 -13
|
@@ -2,10 +2,11 @@ module Chronic
|
|
|
2
2
|
class RepeaterMonth < Repeater #:nodoc:
|
|
3
3
|
MONTH_SECONDS = 2_592_000 # 30 * 24 * 60 * 60
|
|
4
4
|
YEAR_MONTHS = 12
|
|
5
|
+
MONTH_DAYS = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
|
6
|
+
MONTH_DAYS_LEAP = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
|
5
7
|
|
|
6
8
|
def initialize(type)
|
|
7
9
|
super
|
|
8
|
-
@current_month_start = nil
|
|
9
10
|
end
|
|
10
11
|
|
|
11
12
|
def next(pointer)
|
|
@@ -54,7 +55,11 @@ module Chronic
|
|
|
54
55
|
new_year += 1
|
|
55
56
|
new_month -= YEAR_MONTHS
|
|
56
57
|
end
|
|
57
|
-
|
|
58
|
+
|
|
59
|
+
days = month_days(new_year, new_month)
|
|
60
|
+
new_day = time.day > days ? days : time.day
|
|
61
|
+
|
|
62
|
+
Time.construct(new_year, new_month, new_day, time.hour, time.min, time.sec)
|
|
58
63
|
end
|
|
59
64
|
|
|
60
65
|
def width
|
|
@@ -64,5 +69,11 @@ module Chronic
|
|
|
64
69
|
def to_s
|
|
65
70
|
super << '-month'
|
|
66
71
|
end
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
def month_days(year, month)
|
|
76
|
+
Date.leap?(year) ? MONTH_DAYS_LEAP[month - 1] : MONTH_DAYS[month - 1]
|
|
77
|
+
end
|
|
67
78
|
end
|
|
68
79
|
end
|
|
@@ -18,32 +18,30 @@ module Chronic
|
|
|
18
18
|
|
|
19
19
|
def initialize(type)
|
|
20
20
|
super
|
|
21
|
-
@current_month_begin = nil
|
|
22
21
|
end
|
|
23
22
|
|
|
24
23
|
def next(pointer)
|
|
25
24
|
super
|
|
26
25
|
|
|
27
26
|
if !@current_month_begin
|
|
28
|
-
target_month = symbol_to_number(@type)
|
|
29
27
|
case pointer
|
|
30
28
|
when :future
|
|
31
|
-
if @now.month <
|
|
32
|
-
@current_month_begin = Time.construct(@now.year,
|
|
33
|
-
elsif @now.month >
|
|
34
|
-
@current_month_begin = Time.construct(@now.year + 1,
|
|
29
|
+
if @now.month < index
|
|
30
|
+
@current_month_begin = Time.construct(@now.year, index)
|
|
31
|
+
elsif @now.month > index
|
|
32
|
+
@current_month_begin = Time.construct(@now.year + 1, index)
|
|
35
33
|
end
|
|
36
34
|
when :none
|
|
37
|
-
if @now.month <=
|
|
38
|
-
@current_month_begin = Time.construct(@now.year,
|
|
39
|
-
elsif @now.month >
|
|
40
|
-
@current_month_begin = Time.construct(@now.year + 1,
|
|
35
|
+
if @now.month <= index
|
|
36
|
+
@current_month_begin = Time.construct(@now.year, index)
|
|
37
|
+
elsif @now.month > index
|
|
38
|
+
@current_month_begin = Time.construct(@now.year + 1, index)
|
|
41
39
|
end
|
|
42
40
|
when :past
|
|
43
|
-
if @now.month >=
|
|
44
|
-
@current_month_begin = Time.construct(@now.year,
|
|
45
|
-
elsif @now.month <
|
|
46
|
-
@current_month_begin = Time.construct(@now.year - 1,
|
|
41
|
+
if @now.month >= index
|
|
42
|
+
@current_month_begin = Time.construct(@now.year, index)
|
|
43
|
+
elsif @now.month < index
|
|
44
|
+
@current_month_begin = Time.construct(@now.year - 1, index)
|
|
47
45
|
end
|
|
48
46
|
end
|
|
49
47
|
@current_month_begin || raise("Current month should be set by now")
|
|
@@ -86,17 +84,11 @@ module Chronic
|
|
|
86
84
|
end
|
|
87
85
|
|
|
88
86
|
def index
|
|
89
|
-
|
|
87
|
+
@index ||= MONTHS[@type]
|
|
90
88
|
end
|
|
91
89
|
|
|
92
90
|
def to_s
|
|
93
91
|
super << '-monthname-' << @type.to_s
|
|
94
92
|
end
|
|
95
|
-
|
|
96
|
-
private
|
|
97
|
-
|
|
98
|
-
def symbol_to_number(sym)
|
|
99
|
-
MONTHS[sym] || raise("Invalid symbol specified")
|
|
100
|
-
end
|
|
101
93
|
end
|
|
102
94
|
end
|
|
@@ -1,22 +1,4 @@
|
|
|
1
1
|
module Chronic
|
|
2
|
-
class Season
|
|
3
|
-
attr_reader :start, :end
|
|
4
|
-
|
|
5
|
-
def initialize(myStart, myEnd)
|
|
6
|
-
@start = myStart
|
|
7
|
-
@end = myEnd
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
def self.find_next_season(season, pointer)
|
|
11
|
-
lookup = {:spring => 0, :summer => 1, :autumn => 2, :winter => 3}
|
|
12
|
-
next_season_num = (lookup[season]+1*pointer) % 4
|
|
13
|
-
lookup.invert[next_season_num]
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def self.season_after(season); find_next_season(season, +1); end
|
|
17
|
-
def self.season_before(season); find_next_season(season, -1); end
|
|
18
|
-
end
|
|
19
|
-
|
|
20
2
|
class RepeaterSeason < Repeater #:nodoc:
|
|
21
3
|
SEASON_SECONDS = 7_862_400 # 91 * 24 * 60 * 60
|
|
22
4
|
SEASONS = {
|
|
@@ -28,7 +10,6 @@ module Chronic
|
|
|
28
10
|
|
|
29
11
|
def initialize(type)
|
|
30
12
|
super
|
|
31
|
-
@next_season_start = nil
|
|
32
13
|
end
|
|
33
14
|
|
|
34
15
|
def next(pointer)
|
|
@@ -27,7 +27,6 @@ module Chronic
|
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
def initialize(time)
|
|
30
|
-
@current_time = nil
|
|
31
30
|
t = time.gsub(/\:/, '')
|
|
32
31
|
|
|
33
32
|
@type =
|
|
@@ -37,7 +36,7 @@ module Chronic
|
|
|
37
36
|
hours == 12 ? Tick.new(0 * 60 * 60, true) : Tick.new(hours * 60 * 60, true)
|
|
38
37
|
when 3
|
|
39
38
|
hours = t[0..0].to_i
|
|
40
|
-
ambiguous = hours >
|
|
39
|
+
ambiguous = hours > 0
|
|
41
40
|
Tick.new((hours * 60 * 60) + (t[1..2].to_i * 60), ambiguous)
|
|
42
41
|
when 4
|
|
43
42
|
ambiguous = time =~ /:/ && t[0..0].to_i != 0 && t[0..1].to_i <= 12
|
|
@@ -4,7 +4,6 @@ module Chronic
|
|
|
4
4
|
|
|
5
5
|
def initialize(type)
|
|
6
6
|
super
|
|
7
|
-
@current_year_start = nil
|
|
8
7
|
end
|
|
9
8
|
|
|
10
9
|
def next(pointer)
|
|
@@ -45,13 +44,8 @@ module Chronic
|
|
|
45
44
|
|
|
46
45
|
def offset(span, amount, pointer)
|
|
47
46
|
direction = pointer == :future ? 1 : -1
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
new_begin = Time.construct(sb.year + (amount * direction), sb.month, sb.day, sb.hour, sb.min, sb.sec)
|
|
51
|
-
|
|
52
|
-
se = span.end
|
|
53
|
-
new_end = Time.construct(se.year + (amount * direction), se.month, se.day, se.hour, se.min, se.sec)
|
|
54
|
-
|
|
47
|
+
new_begin = build_offset_time(span.begin, amount, direction)
|
|
48
|
+
new_end = build_offset_time(span.end, amount, direction)
|
|
55
49
|
Span.new(new_begin, new_end)
|
|
56
50
|
end
|
|
57
51
|
|
|
@@ -62,5 +56,22 @@ module Chronic
|
|
|
62
56
|
def to_s
|
|
63
57
|
super << '-year'
|
|
64
58
|
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def build_offset_time(time, amount, direction)
|
|
63
|
+
year = time.year + (amount * direction)
|
|
64
|
+
days = month_days(year, time.month)
|
|
65
|
+
day = time.day > days ? days : time.day
|
|
66
|
+
Time.construct(year, time.month, day, time.hour, time.min, time.sec)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def month_days(year, month)
|
|
70
|
+
if Date.leap?(year)
|
|
71
|
+
RepeaterMonth::MONTH_DAYS_LEAP[month - 1]
|
|
72
|
+
else
|
|
73
|
+
RepeaterMonth::MONTH_DAYS[month - 1]
|
|
74
|
+
end
|
|
75
|
+
end
|
|
65
76
|
end
|
|
66
77
|
end
|
data/lib/chronic/scalar.rb
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
module Chronic
|
|
2
|
-
class Scalar < Tag
|
|
2
|
+
class Scalar < Tag
|
|
3
3
|
DAY_PORTIONS = %w( am pm morning afternoon evening night )
|
|
4
4
|
|
|
5
|
+
# Scan an Array of {Token}s and apply any necessary Scalar tags to
|
|
6
|
+
# each token
|
|
7
|
+
#
|
|
8
|
+
# @param [Array<Token>] tokens Array of tokens to scan
|
|
9
|
+
# @param [Hash] options Options specified in {Chronic.parse}
|
|
10
|
+
# @return [Array] list of tokens
|
|
5
11
|
def self.scan(tokens, options)
|
|
6
12
|
tokens.each_index do |i|
|
|
7
13
|
if t = scan_for_scalars(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
|
|
@@ -11,6 +17,9 @@ module Chronic
|
|
|
11
17
|
end
|
|
12
18
|
end
|
|
13
19
|
|
|
20
|
+
# @param [Token] token
|
|
21
|
+
# @param [Token] post_token
|
|
22
|
+
# @return [Scalar, nil]
|
|
14
23
|
def self.scan_for_scalars(token, post_token)
|
|
15
24
|
if token.word =~ /^\d*$/
|
|
16
25
|
unless post_token && DAY_PORTIONS.include?(post_token.word)
|
|
@@ -19,6 +28,9 @@ module Chronic
|
|
|
19
28
|
end
|
|
20
29
|
end
|
|
21
30
|
|
|
31
|
+
# @param [Token] token
|
|
32
|
+
# @param [Token] post_token
|
|
33
|
+
# @return [ScalarDay, nil]
|
|
22
34
|
def self.scan_for_days(token, post_token)
|
|
23
35
|
if token.word =~ /^\d\d?$/
|
|
24
36
|
toi = token.word.to_i
|
|
@@ -28,6 +40,9 @@ module Chronic
|
|
|
28
40
|
end
|
|
29
41
|
end
|
|
30
42
|
|
|
43
|
+
# @param [Token] token
|
|
44
|
+
# @param [Token] post_token
|
|
45
|
+
# @return [ScalarMonth, nil]
|
|
31
46
|
def self.scan_for_months(token, post_token)
|
|
32
47
|
if token.word =~ /^\d\d?$/
|
|
33
48
|
toi = token.word.to_i
|
|
@@ -37,6 +52,10 @@ module Chronic
|
|
|
37
52
|
end
|
|
38
53
|
end
|
|
39
54
|
|
|
55
|
+
# @param [Token] token
|
|
56
|
+
# @param [Token] post_token
|
|
57
|
+
# @param [Hash] options Options specified in {Chronic.parse}
|
|
58
|
+
# @return [ScalarYear, nil]
|
|
40
59
|
def self.scan_for_years(token, post_token, options)
|
|
41
60
|
if token.word =~ /^([1-9]\d)?\d\d?$/
|
|
42
61
|
unless post_token && DAY_PORTIONS.include?(post_token.word)
|
|
@@ -47,6 +66,15 @@ module Chronic
|
|
|
47
66
|
end
|
|
48
67
|
|
|
49
68
|
# Build a year from a 2 digit suffix
|
|
69
|
+
#
|
|
70
|
+
# @example
|
|
71
|
+
# make_year(96, 50) #=> 1996
|
|
72
|
+
# make_year(79, 20) #=> 2079
|
|
73
|
+
# make_year(00, 50) #=> 2000
|
|
74
|
+
#
|
|
75
|
+
# @param [Integer] year The two digit year to build from
|
|
76
|
+
# @param [Integer] bias The amount of future years to bias
|
|
77
|
+
# @return [Integer] The 4 digit year
|
|
50
78
|
def self.make_year(year, bias)
|
|
51
79
|
return year if year.to_s.size > 2
|
|
52
80
|
start_year = Chronic.time_class.now.year - bias
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module Chronic
|
|
2
|
+
class Season
|
|
3
|
+
# @return [MiniDate]
|
|
4
|
+
attr_reader :start
|
|
5
|
+
|
|
6
|
+
# @return [MiniDate]
|
|
7
|
+
attr_reader :end
|
|
8
|
+
|
|
9
|
+
# @param [MiniDate] start_date
|
|
10
|
+
# @param [MiniDate] end_date
|
|
11
|
+
def initialize(start_date, end_date)
|
|
12
|
+
@start = start_date
|
|
13
|
+
@end = end_date
|
|
14
|
+
end
|
|
15
|
+
|
|
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
|
+
def self.find_next_season(season, pointer)
|
|
20
|
+
lookup = {:spring => 0, :summer => 1, :autumn => 2, :winter => 3}
|
|
21
|
+
next_season_num = (lookup[season] + 1 * pointer) % 4
|
|
22
|
+
lookup.invert[next_season_num]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# @param [Symbol] season The season name
|
|
26
|
+
# @return [Symbol] The new season name
|
|
27
|
+
def self.season_after(season)
|
|
28
|
+
find_next_season(season, +1)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @param [Symbol] season The season name
|
|
32
|
+
# @return [Symbol] The new season name
|
|
33
|
+
def self.season_before(season)
|
|
34
|
+
find_next_season(season, -1)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
data/lib/chronic/separator.rb
CHANGED
|
@@ -1,19 +1,30 @@
|
|
|
1
1
|
module Chronic
|
|
2
|
-
class Separator < Tag
|
|
2
|
+
class Separator < Tag
|
|
3
|
+
|
|
4
|
+
# Scan an Array of {Token}s and apply any necessary Separator tags to
|
|
5
|
+
# each token
|
|
6
|
+
#
|
|
7
|
+
# @param [Array<Token>] tokens Array of tokens to scan
|
|
8
|
+
# @param [Hash] options Options specified in {Chronic.parse}
|
|
9
|
+
# @return [Array] list of tokens
|
|
3
10
|
def self.scan(tokens, options)
|
|
4
|
-
tokens.
|
|
5
|
-
if t = scan_for_commas(
|
|
6
|
-
if t = scan_for_slash_or_dash(
|
|
7
|
-
if t = scan_for_at(
|
|
8
|
-
if t = scan_for_in(
|
|
9
|
-
if t = scan_for_on(
|
|
11
|
+
tokens.each do |token|
|
|
12
|
+
if t = scan_for_commas(token) then token.tag(t); next end
|
|
13
|
+
if t = scan_for_slash_or_dash(token) then token.tag(t); next end
|
|
14
|
+
if t = scan_for_at(token) then token.tag(t); next end
|
|
15
|
+
if t = scan_for_in(token) then token.tag(t); next end
|
|
16
|
+
if t = scan_for_on(token) then token.tag(t); next end
|
|
10
17
|
end
|
|
11
18
|
end
|
|
12
19
|
|
|
20
|
+
# @param [Token] token
|
|
21
|
+
# @return [SeparatorComma, nil]
|
|
13
22
|
def self.scan_for_commas(token)
|
|
14
23
|
scan_for token, SeparatorComma, { /^,$/ => :comma }
|
|
15
24
|
end
|
|
16
25
|
|
|
26
|
+
# @param [Token] token
|
|
27
|
+
# @return [SeparatorSlashOrDash, nil]
|
|
17
28
|
def self.scan_for_slash_or_dash(token)
|
|
18
29
|
scan_for token, SeparatorSlashOrDash,
|
|
19
30
|
{
|
|
@@ -22,14 +33,20 @@ module Chronic
|
|
|
22
33
|
}
|
|
23
34
|
end
|
|
24
35
|
|
|
36
|
+
# @param [Token] token
|
|
37
|
+
# @return [SeparatorAt, nil]
|
|
25
38
|
def self.scan_for_at(token)
|
|
26
39
|
scan_for token, SeparatorAt, { /^(at|@)$/ => :at }
|
|
27
40
|
end
|
|
28
41
|
|
|
42
|
+
# @param [Token] token
|
|
43
|
+
# @return [SeparatorIn, nil]
|
|
29
44
|
def self.scan_for_in(token)
|
|
30
45
|
scan_for token, SeparatorIn, { /^in$/ => :in }
|
|
31
46
|
end
|
|
32
47
|
|
|
48
|
+
# @param [Token] token
|
|
49
|
+
# @return [SeparatorOn, nil]
|
|
33
50
|
def self.scan_for_on(token)
|
|
34
51
|
scan_for token, SeparatorOn, { /^on$/ => :on }
|
|
35
52
|
end
|
data/lib/chronic/tag.rb
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
module Chronic
|
|
2
2
|
# Tokens are tagged with subclassed instances of this class when
|
|
3
3
|
# they match specific criteria
|
|
4
|
-
class Tag
|
|
4
|
+
class Tag
|
|
5
|
+
|
|
6
|
+
# @return [Symbol]
|
|
5
7
|
attr_accessor :type
|
|
6
8
|
|
|
9
|
+
# @param [Symbol] type
|
|
7
10
|
def initialize(type)
|
|
8
11
|
@type = type
|
|
9
12
|
end
|
|
10
13
|
|
|
14
|
+
# @param [Time] s Set the start timestamp for this Tag
|
|
11
15
|
def start=(s)
|
|
12
16
|
@now = s
|
|
13
17
|
end
|
|
@@ -15,6 +19,11 @@ module Chronic
|
|
|
15
19
|
class << self
|
|
16
20
|
private
|
|
17
21
|
|
|
22
|
+
# @param [Token] token
|
|
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
|
|
18
27
|
def scan_for(token, klass, items={})
|
|
19
28
|
case items
|
|
20
29
|
when Regexp
|
data/lib/chronic/time_zone.rb
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
module Chronic
|
|
2
|
-
class TimeZone < Tag
|
|
2
|
+
class TimeZone < Tag
|
|
3
|
+
|
|
4
|
+
# Scan an Array of {Token}s and apply any necessary TimeZone tags to
|
|
5
|
+
# each token
|
|
6
|
+
#
|
|
7
|
+
# @param [Array<Token>] tokens Array of tokens to scan
|
|
8
|
+
# @param [Hash] options Options specified in {Chronic.parse}
|
|
9
|
+
# @return [Array] list of tokens
|
|
3
10
|
def self.scan(tokens, options)
|
|
4
|
-
tokens.
|
|
5
|
-
if t = scan_for_all(
|
|
11
|
+
tokens.each do |token|
|
|
12
|
+
if t = scan_for_all(token) then token.tag(t); next end
|
|
6
13
|
end
|
|
7
14
|
end
|
|
8
15
|
|
|
16
|
+
# @param [Token] token
|
|
17
|
+
# @return [TimeZone, nil]
|
|
9
18
|
def self.scan_for_all(token)
|
|
10
19
|
scan_for token, self,
|
|
11
20
|
{
|
data/lib/chronic/token.rb
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
module Chronic
|
|
2
|
-
class Token
|
|
3
|
-
|
|
2
|
+
class Token
|
|
3
|
+
|
|
4
|
+
# @return [String] The word this Token represents
|
|
5
|
+
attr_accessor :word
|
|
6
|
+
|
|
7
|
+
# @return [Array] A list of tag associated with this Token
|
|
8
|
+
attr_accessor :tags
|
|
4
9
|
|
|
5
10
|
def initialize(word)
|
|
6
11
|
@word = word
|
|
@@ -8,21 +13,26 @@ module Chronic
|
|
|
8
13
|
end
|
|
9
14
|
|
|
10
15
|
# Tag this token with the specified tag
|
|
16
|
+
#
|
|
17
|
+
# @param [Tag] new_tag An instance of {Tag} or one of its subclasses
|
|
11
18
|
def tag(new_tag)
|
|
12
19
|
@tags << new_tag
|
|
13
20
|
end
|
|
14
21
|
|
|
15
22
|
# Remove all tags of the given class
|
|
23
|
+
#
|
|
24
|
+
# @param [Class] The tag class to remove
|
|
16
25
|
def untag(tag_class)
|
|
17
26
|
@tags.delete_if { |m| m.kind_of? tag_class }
|
|
18
27
|
end
|
|
19
28
|
|
|
20
|
-
#
|
|
29
|
+
# @return [Boolean] true if this token has any tags
|
|
21
30
|
def tagged?
|
|
22
31
|
@tags.size > 0
|
|
23
32
|
end
|
|
24
33
|
|
|
25
|
-
#
|
|
34
|
+
# @param [Class] tag_class The tag class to search for
|
|
35
|
+
# @return [Tag] The first Tag that matches the given class
|
|
26
36
|
def get_tag(tag_class)
|
|
27
37
|
@tags.find { |m| m.kind_of? tag_class }
|
|
28
38
|
end
|
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
|