chronic_2011 0.1.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/.gitignore +6 -0
- data/HISTORY.md +4 -0
- data/LICENSE +21 -0
- data/README.md +180 -0
- data/Rakefile +46 -0
- data/chronic.gemspec +18 -0
- data/lib/chronic.rb +117 -0
- data/lib/chronic/chronic.rb +346 -0
- data/lib/chronic/grabber.rb +33 -0
- data/lib/chronic/handler.rb +88 -0
- data/lib/chronic/handlers.rb +553 -0
- data/lib/chronic/mini_date.rb +38 -0
- data/lib/chronic/numerizer.rb +121 -0
- data/lib/chronic/ordinal.rb +47 -0
- data/lib/chronic/pointer.rb +32 -0
- data/lib/chronic/repeater.rb +142 -0
- data/lib/chronic/repeaters/repeater_day.rb +53 -0
- data/lib/chronic/repeaters/repeater_day_name.rb +52 -0
- data/lib/chronic/repeaters/repeater_day_portion.rb +108 -0
- data/lib/chronic/repeaters/repeater_fortnight.rb +71 -0
- data/lib/chronic/repeaters/repeater_hour.rb +58 -0
- data/lib/chronic/repeaters/repeater_minute.rb +58 -0
- data/lib/chronic/repeaters/repeater_month.rb +79 -0
- data/lib/chronic/repeaters/repeater_month_name.rb +94 -0
- data/lib/chronic/repeaters/repeater_season.rb +109 -0
- data/lib/chronic/repeaters/repeater_season_name.rb +43 -0
- data/lib/chronic/repeaters/repeater_second.rb +42 -0
- data/lib/chronic/repeaters/repeater_time.rb +128 -0
- data/lib/chronic/repeaters/repeater_week.rb +74 -0
- data/lib/chronic/repeaters/repeater_weekday.rb +85 -0
- data/lib/chronic/repeaters/repeater_weekend.rb +66 -0
- data/lib/chronic/repeaters/repeater_year.rb +77 -0
- data/lib/chronic/scalar.rb +116 -0
- data/lib/chronic/season.rb +26 -0
- data/lib/chronic/separator.rb +94 -0
- data/lib/chronic/span.rb +31 -0
- data/lib/chronic/tag.rb +36 -0
- data/lib/chronic/time_zone.rb +32 -0
- data/lib/chronic/token.rb +47 -0
- data/test/helper.rb +12 -0
- data/test/test_chronic.rb +148 -0
- data/test/test_daylight_savings.rb +118 -0
- data/test/test_handler.rb +104 -0
- data/test/test_mini_date.rb +32 -0
- data/test/test_numerizer.rb +72 -0
- data/test/test_parsing.rb +977 -0
- data/test/test_repeater_day_name.rb +51 -0
- data/test/test_repeater_day_portion.rb +254 -0
- data/test/test_repeater_fortnight.rb +62 -0
- data/test/test_repeater_hour.rb +68 -0
- data/test/test_repeater_minute.rb +34 -0
- data/test/test_repeater_month.rb +50 -0
- data/test/test_repeater_month_name.rb +56 -0
- data/test/test_repeater_season.rb +40 -0
- data/test/test_repeater_time.rb +70 -0
- data/test/test_repeater_week.rb +62 -0
- data/test/test_repeater_weekday.rb +55 -0
- data/test/test_repeater_weekend.rb +74 -0
- data/test/test_repeater_year.rb +69 -0
- data/test/test_span.rb +23 -0
- data/test/test_token.rb +25 -0
- metadata +156 -0
@@ -0,0 +1,66 @@
|
|
1
|
+
module Chronic
|
2
|
+
class RepeaterWeekend < Repeater #:nodoc:
|
3
|
+
WEEKEND_SECONDS = 172_800 # (2 * 24 * 60 * 60)
|
4
|
+
|
5
|
+
def initialize(type)
|
6
|
+
super
|
7
|
+
end
|
8
|
+
|
9
|
+
def next(pointer)
|
10
|
+
super
|
11
|
+
|
12
|
+
if !@current_week_start
|
13
|
+
case pointer
|
14
|
+
when :future
|
15
|
+
saturday_repeater = RepeaterDayName.new(:saturday)
|
16
|
+
saturday_repeater.start = @now
|
17
|
+
next_saturday_span = saturday_repeater.next(:future)
|
18
|
+
@current_week_start = next_saturday_span.begin
|
19
|
+
when :past
|
20
|
+
saturday_repeater = RepeaterDayName.new(:saturday)
|
21
|
+
saturday_repeater.start = (@now + RepeaterDay::DAY_SECONDS)
|
22
|
+
last_saturday_span = saturday_repeater.next(:past)
|
23
|
+
@current_week_start = last_saturday_span.begin
|
24
|
+
end
|
25
|
+
else
|
26
|
+
direction = pointer == :future ? 1 : -1
|
27
|
+
@current_week_start += direction * RepeaterWeek::WEEK_SECONDS
|
28
|
+
end
|
29
|
+
|
30
|
+
Span.new(@current_week_start, @current_week_start + WEEKEND_SECONDS)
|
31
|
+
end
|
32
|
+
|
33
|
+
def this(pointer = :future)
|
34
|
+
super
|
35
|
+
|
36
|
+
case pointer
|
37
|
+
when :future, :none
|
38
|
+
saturday_repeater = RepeaterDayName.new(:saturday)
|
39
|
+
saturday_repeater.start = @now
|
40
|
+
this_saturday_span = saturday_repeater.this(:future)
|
41
|
+
Span.new(this_saturday_span.begin, this_saturday_span.begin + WEEKEND_SECONDS)
|
42
|
+
when :past
|
43
|
+
saturday_repeater = RepeaterDayName.new(:saturday)
|
44
|
+
saturday_repeater.start = @now
|
45
|
+
last_saturday_span = saturday_repeater.this(:past)
|
46
|
+
Span.new(last_saturday_span.begin, last_saturday_span.begin + WEEKEND_SECONDS)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def offset(span, amount, pointer)
|
51
|
+
direction = pointer == :future ? 1 : -1
|
52
|
+
weekend = RepeaterWeekend.new(:weekend)
|
53
|
+
weekend.start = span.begin
|
54
|
+
start = weekend.next(pointer).begin + (amount - 1) * direction * RepeaterWeek::WEEK_SECONDS
|
55
|
+
Span.new(start, start + (span.end - span.begin))
|
56
|
+
end
|
57
|
+
|
58
|
+
def width
|
59
|
+
WEEKEND_SECONDS
|
60
|
+
end
|
61
|
+
|
62
|
+
def to_s
|
63
|
+
super << '-weekend'
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module Chronic
|
2
|
+
class RepeaterYear < Repeater #:nodoc:
|
3
|
+
YEAR_SECONDS = 31536000 # 365 * 24 * 60 * 60
|
4
|
+
|
5
|
+
def initialize(type)
|
6
|
+
super
|
7
|
+
end
|
8
|
+
|
9
|
+
def next(pointer)
|
10
|
+
super
|
11
|
+
|
12
|
+
if !@current_year_start
|
13
|
+
case pointer
|
14
|
+
when :future
|
15
|
+
@current_year_start = Chronic.construct(@now.year + 1)
|
16
|
+
when :past
|
17
|
+
@current_year_start = Chronic.construct(@now.year - 1)
|
18
|
+
end
|
19
|
+
else
|
20
|
+
diff = pointer == :future ? 1 : -1
|
21
|
+
@current_year_start = Chronic.construct(@current_year_start.year + diff)
|
22
|
+
end
|
23
|
+
|
24
|
+
Span.new(@current_year_start, Chronic.construct(@current_year_start.year + 1))
|
25
|
+
end
|
26
|
+
|
27
|
+
def this(pointer = :future)
|
28
|
+
super
|
29
|
+
|
30
|
+
case pointer
|
31
|
+
when :future
|
32
|
+
this_year_start = Chronic.construct(@now.year, @now.month, @now.day + 1)
|
33
|
+
this_year_end = Chronic.construct(@now.year + 1, 1, 1)
|
34
|
+
when :past
|
35
|
+
this_year_start = Chronic.construct(@now.year, 1, 1)
|
36
|
+
this_year_end = Chronic.construct(@now.year, @now.month, @now.day)
|
37
|
+
when :none
|
38
|
+
this_year_start = Chronic.construct(@now.year, 1, 1)
|
39
|
+
this_year_end = Chronic.construct(@now.year + 1, 1, 1)
|
40
|
+
end
|
41
|
+
|
42
|
+
Span.new(this_year_start, this_year_end)
|
43
|
+
end
|
44
|
+
|
45
|
+
def offset(span, amount, pointer)
|
46
|
+
direction = pointer == :future ? 1 : -1
|
47
|
+
new_begin = build_offset_time(span.begin, amount, direction)
|
48
|
+
new_end = build_offset_time(span.end, amount, direction)
|
49
|
+
Span.new(new_begin, new_end)
|
50
|
+
end
|
51
|
+
|
52
|
+
def width
|
53
|
+
YEAR_SECONDS
|
54
|
+
end
|
55
|
+
|
56
|
+
def to_s
|
57
|
+
super << '-year'
|
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
|
+
Chronic.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
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
module Chronic
|
2
|
+
class Scalar < Tag
|
3
|
+
DAY_PORTIONS = %w( am pm morning afternoon evening night )
|
4
|
+
|
5
|
+
# Scan an Array of Token objects and apply any necessary Scalar
|
6
|
+
# tags to each token.
|
7
|
+
#
|
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.
|
12
|
+
def self.scan(tokens, options)
|
13
|
+
tokens.each_index do |i|
|
14
|
+
if t = scan_for_scalars(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
|
15
|
+
if t = scan_for_days(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
|
16
|
+
if t = scan_for_months(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
|
17
|
+
if t = scan_for_years(tokens[i], tokens[i + 1], options) then tokens[i].tag(t) end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# token - The Token object we want to scan.
|
22
|
+
# post_token - The next Token object.
|
23
|
+
#
|
24
|
+
# Returns a new Scalar object.
|
25
|
+
def self.scan_for_scalars(token, post_token)
|
26
|
+
if token.word =~ /^\d*$/
|
27
|
+
unless post_token && DAY_PORTIONS.include?(post_token.word)
|
28
|
+
return Scalar.new(token.word.to_i)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# token - The Token object we want to scan.
|
34
|
+
# post_token - The next Token object.
|
35
|
+
#
|
36
|
+
# Returns a new Scalar object.
|
37
|
+
def self.scan_for_days(token, post_token)
|
38
|
+
if token.word =~ /^\d\d?$/
|
39
|
+
toi = token.word.to_i
|
40
|
+
unless toi > 31 || toi < 1 || (post_token && DAY_PORTIONS.include?(post_token.word))
|
41
|
+
return ScalarDay.new(toi)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# token - The Token object we want to scan.
|
47
|
+
# post_token - The next Token object.
|
48
|
+
#
|
49
|
+
# Returns a new Scalar object.
|
50
|
+
def self.scan_for_months(token, post_token)
|
51
|
+
if token.word =~ /^\d\d?$/
|
52
|
+
toi = token.word.to_i
|
53
|
+
unless toi > 12 || toi < 1 || (post_token && DAY_PORTIONS.include?(post_token.word))
|
54
|
+
return ScalarMonth.new(toi)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
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.
|
64
|
+
def self.scan_for_years(token, post_token, options)
|
65
|
+
if token.word =~ /^([1-9]\d)?\d\d?$/
|
66
|
+
unless post_token && DAY_PORTIONS.include?(post_token.word)
|
67
|
+
year = make_year(token.word.to_i, options[:ambiguous_year_future_bias])
|
68
|
+
return ScalarYear.new(year.to_i)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
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:
|
79
|
+
#
|
80
|
+
# make_year(96, 50) #=> 1996
|
81
|
+
# make_year(79, 20) #=> 2079
|
82
|
+
# make_year(00, 50) #=> 2000
|
83
|
+
#
|
84
|
+
# Returns The Integer 4 digit year.
|
85
|
+
def self.make_year(year, bias)
|
86
|
+
return year if year.to_s.size > 2
|
87
|
+
start_year = Chronic.time_class.now.year - bias
|
88
|
+
century = (start_year / 100) * 100
|
89
|
+
full_year = century + year
|
90
|
+
full_year += 100 if full_year < start_year
|
91
|
+
full_year
|
92
|
+
end
|
93
|
+
|
94
|
+
def to_s
|
95
|
+
'scalar'
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
class ScalarDay < Scalar #:nodoc:
|
100
|
+
def to_s
|
101
|
+
super << '-day-' << @type.to_s
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
class ScalarMonth < Scalar #:nodoc:
|
106
|
+
def to_s
|
107
|
+
super << '-month-' << @type.to_s
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
class ScalarYear < Scalar #:nodoc:
|
112
|
+
def to_s
|
113
|
+
super << '-year-' << @type.to_s
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Chronic
|
2
|
+
class Season
|
3
|
+
|
4
|
+
attr_reader :start
|
5
|
+
attr_reader :end
|
6
|
+
|
7
|
+
def initialize(start_date, end_date)
|
8
|
+
@start = start_date
|
9
|
+
@end = end_date
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.find_next_season(season, pointer)
|
13
|
+
lookup = [:spring, :summer, :autumn, :winter]
|
14
|
+
next_season_num = (lookup.index(season) + 1 * pointer) % 4
|
15
|
+
lookup[next_season_num]
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.season_after(season)
|
19
|
+
find_next_season(season, +1)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.season_before(season)
|
23
|
+
find_next_season(season, -1)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module Chronic
|
2
|
+
class Separator < Tag
|
3
|
+
|
4
|
+
# Scan an Array of Token objects and apply any necessary Separator
|
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_commas(token) then token.tag(t); next end
|
14
|
+
if t = scan_for_slash_or_dash(token) then token.tag(t); next end
|
15
|
+
if t = scan_for_at(token) then token.tag(t); next end
|
16
|
+
if t = scan_for_in(token) then token.tag(t); next end
|
17
|
+
if t = scan_for_on(token) then token.tag(t); next end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# token - The Token object we want to scan.
|
22
|
+
#
|
23
|
+
# Returns a new SeparatorComma object.
|
24
|
+
def self.scan_for_commas(token)
|
25
|
+
scan_for token, SeparatorComma, { /^,$/ => :comma }
|
26
|
+
end
|
27
|
+
|
28
|
+
# token - The Token object we want to scan.
|
29
|
+
#
|
30
|
+
# Returns a new SeparatorSlashOrDash object.
|
31
|
+
def self.scan_for_slash_or_dash(token)
|
32
|
+
scan_for token, SeparatorSlashOrDash,
|
33
|
+
{
|
34
|
+
/^-$/ => :dash,
|
35
|
+
/^\/$/ => :slash
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
# token - The Token object we want to scan.
|
40
|
+
#
|
41
|
+
# Returns a new SeparatorAt object.
|
42
|
+
def self.scan_for_at(token)
|
43
|
+
scan_for token, SeparatorAt, { /^(at|@)$/ => :at }
|
44
|
+
end
|
45
|
+
|
46
|
+
# token - The Token object we want to scan.
|
47
|
+
#
|
48
|
+
# Returns a new SeparatorIn object.
|
49
|
+
def self.scan_for_in(token)
|
50
|
+
scan_for token, SeparatorIn, { /^in$/ => :in }
|
51
|
+
end
|
52
|
+
|
53
|
+
# token - The Token object we want to scan.
|
54
|
+
#
|
55
|
+
# Returns a new SeparatorOn object.
|
56
|
+
def self.scan_for_on(token)
|
57
|
+
scan_for token, SeparatorOn, { /^on$/ => :on }
|
58
|
+
end
|
59
|
+
|
60
|
+
def to_s
|
61
|
+
'separator'
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class SeparatorComma < Separator #:nodoc:
|
66
|
+
def to_s
|
67
|
+
super << '-comma'
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class SeparatorSlashOrDash < Separator #:nodoc:
|
72
|
+
def to_s
|
73
|
+
super << '-slashordash-' << @type.to_s
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class SeparatorAt < Separator #:nodoc:
|
78
|
+
def to_s
|
79
|
+
super << '-at'
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
class SeparatorIn < Separator #:nodoc:
|
84
|
+
def to_s
|
85
|
+
super << '-in'
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
class SeparatorOn < Separator #:nodoc:
|
90
|
+
def to_s
|
91
|
+
super << '-on'
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
data/lib/chronic/span.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
module Chronic
|
2
|
+
# A Span represents a range of time. Since this class extends
|
3
|
+
# Range, you can use #begin and #end to get the beginning and
|
4
|
+
# ending times of the span (they will be of class Time)
|
5
|
+
class Span < Range
|
6
|
+
# Returns the width of this span in seconds
|
7
|
+
def width
|
8
|
+
(self.end - self.begin).to_i
|
9
|
+
end
|
10
|
+
|
11
|
+
# Add a number of seconds to this span, returning the
|
12
|
+
# resulting Span
|
13
|
+
def +(seconds)
|
14
|
+
Span.new(self.begin + seconds, self.end + seconds)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Subtract a number of seconds to this span, returning the
|
18
|
+
# resulting Span
|
19
|
+
def -(seconds)
|
20
|
+
self + -seconds
|
21
|
+
end
|
22
|
+
|
23
|
+
# Prints this span in a nice fashion
|
24
|
+
def to_s
|
25
|
+
'(' << self.begin.to_s << '..' << self.end.to_s << ')'
|
26
|
+
end
|
27
|
+
|
28
|
+
alias :cover? :include? unless RUBY_VERSION =~ /^1.9/
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
data/lib/chronic/tag.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
module Chronic
|
2
|
+
# Tokens are tagged with subclassed instances of this class when
|
3
|
+
# they match specific criteria.
|
4
|
+
class Tag
|
5
|
+
|
6
|
+
attr_accessor :type
|
7
|
+
|
8
|
+
# type - The Symbol type of this tag.
|
9
|
+
def initialize(type)
|
10
|
+
@type = type
|
11
|
+
end
|
12
|
+
|
13
|
+
# time - Set the start Time for this Tag.
|
14
|
+
def start=(time)
|
15
|
+
@now = time
|
16
|
+
end
|
17
|
+
|
18
|
+
class << self
|
19
|
+
private
|
20
|
+
|
21
|
+
def scan_for(token, klass, items={})
|
22
|
+
case items
|
23
|
+
when Regexp
|
24
|
+
return klass.new(token.word) if items =~ token.word
|
25
|
+
when Hash
|
26
|
+
items.each do |item, symbol|
|
27
|
+
return klass.new(symbol) if item =~ token.word
|
28
|
+
end
|
29
|
+
end
|
30
|
+
nil
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|