chronic 0.3.0 → 0.4.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 +27 -0
- data/Manifest.txt +16 -5
- data/README.md +14 -8
- data/Rakefile +2 -8
- data/chronic.gemspec +8 -11
- data/lib/chronic.rb +21 -14
- data/lib/chronic/chronic.rb +38 -130
- data/lib/chronic/grabber.rb +11 -15
- data/lib/chronic/handlers.rb +63 -40
- data/lib/chronic/mini_date.rb +27 -0
- data/lib/chronic/numerizer.rb +120 -0
- data/lib/chronic/ordinal.rb +5 -10
- data/lib/chronic/pointer.rb +8 -10
- data/lib/chronic/repeater.rb +106 -109
- data/lib/chronic/repeaters/repeater_day.rb +43 -41
- data/lib/chronic/repeaters/repeater_day_name.rb +38 -36
- data/lib/chronic/repeaters/repeater_day_portion.rb +74 -73
- data/lib/chronic/repeaters/repeater_fortnight.rb +57 -55
- data/lib/chronic/repeaters/repeater_hour.rb +46 -44
- data/lib/chronic/repeaters/repeater_minute.rb +46 -44
- data/lib/chronic/repeaters/repeater_month.rb +52 -50
- data/lib/chronic/repeaters/repeater_month_name.rb +84 -80
- data/lib/chronic/repeaters/repeater_season.rb +97 -119
- data/lib/chronic/repeaters/repeater_season_name.rb +39 -39
- data/lib/chronic/repeaters/repeater_second.rb +32 -30
- data/lib/chronic/repeaters/repeater_time.rb +106 -101
- data/lib/chronic/repeaters/repeater_week.rb +60 -58
- data/lib/chronic/repeaters/repeater_weekday.rb +67 -58
- data/lib/chronic/repeaters/repeater_weekend.rb +54 -52
- data/lib/chronic/repeaters/repeater_year.rb +50 -48
- data/lib/chronic/scalar.rb +24 -16
- data/lib/chronic/separator.rb +15 -33
- data/lib/chronic/span.rb +31 -0
- data/lib/chronic/tag.rb +26 -0
- data/lib/chronic/time_zone.rb +7 -9
- data/lib/chronic/token.rb +35 -0
- data/test/helper.rb +5 -6
- data/test/test_Chronic.rb +5 -0
- data/test/test_Numerizer.rb +60 -39
- data/test/test_RepeaterHour.rb +4 -0
- data/test/test_parsing.rb +104 -13
- metadata +14 -20
- data/lib/chronic/numerizer/numerizer.rb +0 -97
data/lib/chronic/grabber.rb
CHANGED
@@ -1,26 +1,22 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
def self.scan(tokens)
|
1
|
+
module Chronic
|
2
|
+
class Grabber < Tag #:nodoc:
|
3
|
+
def self.scan(tokens, options)
|
5
4
|
tokens.each_index do |i|
|
6
|
-
if t =
|
5
|
+
if t = scan_for_all(tokens[i]) then tokens[i].tag(t); next end
|
7
6
|
end
|
8
|
-
tokens
|
9
7
|
end
|
10
8
|
|
11
9
|
def self.scan_for_all(token)
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
return nil
|
10
|
+
scan_for token, self,
|
11
|
+
{
|
12
|
+
/last/ => :last,
|
13
|
+
/this/ => :this,
|
14
|
+
/next/ => :next
|
15
|
+
}
|
19
16
|
end
|
20
17
|
|
21
18
|
def to_s
|
22
19
|
'grabber-' << @type.to_s
|
23
20
|
end
|
24
21
|
end
|
25
|
-
|
26
|
-
#end
|
22
|
+
end
|
data/lib/chronic/handlers.rb
CHANGED
@@ -3,8 +3,7 @@ module Chronic
|
|
3
3
|
class << self
|
4
4
|
|
5
5
|
def definitions(options={}) #:nodoc:
|
6
|
-
options[:endian_precedence]
|
7
|
-
|
6
|
+
options[:endian_precedence] ||= [:middle, :little]
|
8
7
|
# ensure the endian precedence is exactly two elements long
|
9
8
|
raise ChronicPain, "More than two elements specified for endian precedence array" unless options[:endian_precedence].length == 2
|
10
9
|
|
@@ -19,36 +18,48 @@ module Chronic
|
|
19
18
|
raise ChronicPain, "Unknown endian type: #{e.to_s}" unless instance_variable_defined?(endian_variable_name_for(e))
|
20
19
|
end
|
21
20
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
21
|
+
@definitions ||= {
|
22
|
+
:time => [
|
23
|
+
Handler.new([:repeater_time, :repeater_day_portion?], nil)
|
24
|
+
],
|
25
|
+
|
26
|
+
:date => [
|
27
|
+
Handler.new([:repeater_day_name, :repeater_month_name, :scalar_day, :repeater_time, :separator_slash_or_dash?, :time_zone, :scalar_year], :handle_rdn_rmn_sd_t_tz_sy),
|
28
|
+
Handler.new([:repeater_month_name, :scalar_day, :scalar_year], :handle_rmn_sd_sy),
|
29
|
+
Handler.new([:repeater_month_name, :ordinal_day, :scalar_year], :handle_rmn_od_sy),
|
30
|
+
Handler.new([:repeater_month_name, :scalar_day, :scalar_year, :separator_at?, 'time?'], :handle_rmn_sd_sy),
|
31
|
+
Handler.new([:repeater_month_name, :ordinal_day, :scalar_year, :separator_at?, 'time?'], :handle_rmn_od_sy),
|
32
|
+
Handler.new([:repeater_month_name, :scalar_day, :separator_at?, 'time?'], :handle_rmn_sd),
|
33
|
+
Handler.new([:repeater_time, :repeater_day_portion?, :separator_on?, :repeater_month_name, :scalar_day], :handle_rmn_sd_on),
|
34
|
+
Handler.new([:repeater_month_name, :ordinal_day, :separator_at?, 'time?'], :handle_rmn_od),
|
35
|
+
Handler.new([:repeater_time, :repeater_day_portion?, :separator_on?, :repeater_month_name, :ordinal_day], :handle_rmn_od_on),
|
36
|
+
Handler.new([:repeater_month_name, :scalar_year], :handle_rmn_sy),
|
37
|
+
Handler.new([:scalar_day, :repeater_month_name, :scalar_year, :separator_at?, 'time?'], :handle_sd_rmn_sy),
|
38
|
+
@middle_endian_handler,
|
39
|
+
@little_endian_handler,
|
40
|
+
Handler.new([:scalar_year, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_day, :separator_at?, 'time?'], :handle_sy_sm_sd),
|
41
|
+
Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_year], :handle_sm_sy)
|
42
|
+
],
|
43
|
+
|
44
|
+
# tonight at 7pm
|
45
|
+
:anchor => [
|
46
|
+
Handler.new([:grabber?, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),
|
47
|
+
Handler.new([:grabber?, :repeater, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),
|
48
|
+
Handler.new([:repeater, :grabber, :repeater], :handle_r_g_r)
|
49
|
+
],
|
50
|
+
|
51
|
+
# 3 weeks from now, in 2 months
|
52
|
+
:arrow => [
|
53
|
+
Handler.new([:scalar, :repeater, :pointer], :handle_s_r_p),
|
54
|
+
Handler.new([:pointer, :scalar, :repeater], :handle_p_s_r),
|
55
|
+
Handler.new([:scalar, :repeater, :pointer, 'anchor'], :handle_s_r_p_a)
|
56
|
+
],
|
57
|
+
|
58
|
+
# 3rd week in march
|
59
|
+
:narrow => [
|
60
|
+
Handler.new([:ordinal, :repeater, :separator_in, :repeater], :handle_o_r_s_r),
|
61
|
+
Handler.new([:ordinal, :repeater, :grabber, :repeater], :handle_o_r_g_r)
|
62
|
+
]
|
52
63
|
}
|
53
64
|
|
54
65
|
apply_endian_precedences(options[:endian_precedence])
|
@@ -130,10 +141,9 @@ module Chronic
|
|
130
141
|
|
131
142
|
if !time_tokens.empty?
|
132
143
|
@now = outer_span.begin
|
133
|
-
|
134
|
-
return time
|
144
|
+
get_anchor(dealias_and_disambiguate_times(time_tokens, options), options)
|
135
145
|
else
|
136
|
-
|
146
|
+
outer_span
|
137
147
|
end
|
138
148
|
end
|
139
149
|
|
@@ -211,6 +221,21 @@ module Chronic
|
|
211
221
|
end
|
212
222
|
end
|
213
223
|
|
224
|
+
def handle_rmn_od_sy(tokens, options) #:nodoc:
|
225
|
+
month = tokens[0].get_tag(RepeaterMonthName).index
|
226
|
+
day = tokens[1].get_tag(OrdinalDay).type
|
227
|
+
year = tokens[2].get_tag(ScalarYear).type
|
228
|
+
|
229
|
+
time_tokens = tokens.last(tokens.size - 3)
|
230
|
+
|
231
|
+
begin
|
232
|
+
day_start = Chronic.time_class.local(year, month, day)
|
233
|
+
day_or_time(day_start, time_tokens, options)
|
234
|
+
rescue ArgumentError
|
235
|
+
nil
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
214
239
|
def handle_sd_rmn_sy(tokens, options) #:nodoc:
|
215
240
|
new_tokens = [tokens[1], tokens[0], tokens[2]]
|
216
241
|
time_tokens = tokens.last(tokens.size - 3)
|
@@ -363,7 +388,7 @@ module Chronic
|
|
363
388
|
when :last
|
364
389
|
outer_span = head.next(:past)
|
365
390
|
when :this
|
366
|
-
if repeaters.size > 0
|
391
|
+
if options[:context] != :past and repeaters.size > 0
|
367
392
|
outer_span = head.this(:none)
|
368
393
|
else
|
369
394
|
outer_span = head.this(options[:context])
|
@@ -398,10 +423,8 @@ module Chronic
|
|
398
423
|
head.start = pointer == :future ? span.begin : span.end
|
399
424
|
h = head.this(:none)
|
400
425
|
|
401
|
-
if span.
|
402
|
-
|
403
|
-
else
|
404
|
-
return nil
|
426
|
+
if span.cover?(h.begin) || span.cover?(h.end)
|
427
|
+
find_within(rest, h, pointer)
|
405
428
|
end
|
406
429
|
end
|
407
430
|
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Chronic
|
2
|
+
class MiniDate
|
3
|
+
attr_accessor :month, :day
|
4
|
+
|
5
|
+
def initialize(month, day)
|
6
|
+
@month = month
|
7
|
+
@day = day
|
8
|
+
end
|
9
|
+
|
10
|
+
def is_between?(md_start, md_end)
|
11
|
+
return true if (@month == md_start.month and @day >= md_start.day) ||
|
12
|
+
(@month == md_end.month and @day <= md_end.day)
|
13
|
+
|
14
|
+
i = md_start.month + 1
|
15
|
+
until i == md_end.month
|
16
|
+
return true if @month == i
|
17
|
+
i = (i+1) % 12
|
18
|
+
end
|
19
|
+
|
20
|
+
return false
|
21
|
+
end
|
22
|
+
|
23
|
+
def equals?(other)
|
24
|
+
@month == other.month and day == other.day
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'strscan'
|
2
|
+
|
3
|
+
module Chronic
|
4
|
+
class Numerizer
|
5
|
+
|
6
|
+
DIRECT_NUMS = [
|
7
|
+
['eleven', '11'],
|
8
|
+
['twelve', '12'],
|
9
|
+
['thirteen', '13'],
|
10
|
+
['fourteen', '14'],
|
11
|
+
['fifteen', '15'],
|
12
|
+
['sixteen', '16'],
|
13
|
+
['seventeen', '17'],
|
14
|
+
['eighteen', '18'],
|
15
|
+
['nineteen', '19'],
|
16
|
+
['ninteen', '19'], # Common mis-spelling
|
17
|
+
['zero', '0'],
|
18
|
+
['one', '1'],
|
19
|
+
['two', '2'],
|
20
|
+
['three', '3'],
|
21
|
+
['four(\W|$)', '4\1'], # The weird regex is so that it matches four but not fourty
|
22
|
+
['five', '5'],
|
23
|
+
['six(\W|$)', '6\1'],
|
24
|
+
['seven(\W|$)', '7\1'],
|
25
|
+
['eight(\W|$)', '8\1'],
|
26
|
+
['nine(\W|$)', '9\1'],
|
27
|
+
['ten', '10'],
|
28
|
+
['\ba[\b^$]', '1'] # doesn't make sense for an 'a' at the end to be a 1
|
29
|
+
]
|
30
|
+
|
31
|
+
ORDINALS = [
|
32
|
+
['first', '1'],
|
33
|
+
['third', '3'],
|
34
|
+
['fourth', '4'],
|
35
|
+
['fifth', '5'],
|
36
|
+
['sixth', '6'],
|
37
|
+
['seventh', '7'],
|
38
|
+
['eighth', '8'],
|
39
|
+
['ninth', '9'],
|
40
|
+
['tenth', '10']
|
41
|
+
]
|
42
|
+
|
43
|
+
TEN_PREFIXES = [
|
44
|
+
['twenty', 20],
|
45
|
+
['thirty', 30],
|
46
|
+
['forty', 40],
|
47
|
+
['fourty', 40], # Common mis-spelling
|
48
|
+
['fifty', 50],
|
49
|
+
['sixty', 60],
|
50
|
+
['seventy', 70],
|
51
|
+
['eighty', 80],
|
52
|
+
['ninety', 90]
|
53
|
+
]
|
54
|
+
|
55
|
+
BIG_PREFIXES = [
|
56
|
+
['hundred', 100],
|
57
|
+
['thousand', 1000],
|
58
|
+
['million', 1_000_000],
|
59
|
+
['billion', 1_000_000_000],
|
60
|
+
['trillion', 1_000_000_000_000],
|
61
|
+
]
|
62
|
+
|
63
|
+
def self.numerize(string)
|
64
|
+
string = string.dup
|
65
|
+
|
66
|
+
# preprocess
|
67
|
+
string.gsub!(/ +|([^\d])-([^\d])/, '\1 \2') # will mutilate hyphenated-words but shouldn't matter for date extraction
|
68
|
+
string.gsub!(/a half/, 'haAlf') # take the 'a' out so it doesn't turn into a 1, save the half for the end
|
69
|
+
|
70
|
+
# easy/direct replacements
|
71
|
+
|
72
|
+
DIRECT_NUMS.each do |dn|
|
73
|
+
string.gsub!(/#{dn[0]}/i, '<num>' + dn[1])
|
74
|
+
end
|
75
|
+
|
76
|
+
ORDINALS.each do |on|
|
77
|
+
string.gsub!(/#{on[0]}/i, '<num>' + on[1] + on[0][-2, 2])
|
78
|
+
end
|
79
|
+
|
80
|
+
# ten, twenty, etc.
|
81
|
+
|
82
|
+
TEN_PREFIXES.each do |tp|
|
83
|
+
string.gsub!(/(?:#{tp[0]}) *<num>(\d(?=[^\d]|$))*/i) { '<num>' + (tp[1] + $1.to_i).to_s }
|
84
|
+
end
|
85
|
+
|
86
|
+
TEN_PREFIXES.each do |tp|
|
87
|
+
string.gsub!(/#{tp[0]}/i) { '<num>' + tp[1].to_s }
|
88
|
+
end
|
89
|
+
|
90
|
+
# hundreds, thousands, millions, etc.
|
91
|
+
|
92
|
+
BIG_PREFIXES.each do |bp|
|
93
|
+
string.gsub!(/(?:<num>)?(\d*) *#{bp[0]}/i) { '<num>' + (bp[1] * $1.to_i).to_s}
|
94
|
+
andition(string)
|
95
|
+
end
|
96
|
+
|
97
|
+
# fractional addition
|
98
|
+
# I'm not combining this with the previous block as using float addition complicates the strings
|
99
|
+
# (with extraneous .0's and such )
|
100
|
+
string.gsub!(/(\d+)(?: | and |-)*haAlf/i) { ($1.to_f + 0.5).to_s }
|
101
|
+
|
102
|
+
string.gsub(/<num>/, '')
|
103
|
+
end
|
104
|
+
|
105
|
+
class << self
|
106
|
+
private
|
107
|
+
|
108
|
+
def andition(string)
|
109
|
+
sc = StringScanner.new(string)
|
110
|
+
while(sc.scan_until(/<num>(\d+)( | and )<num>(\d+)(?=[^\w]|$)/i))
|
111
|
+
if sc[2] =~ /and/ || sc[1].size > sc[3].size
|
112
|
+
string[(sc.pos - sc.matched_size)..(sc.pos-1)] = '<num>' + (sc[1].to_i + sc[3].to_i).to_s
|
113
|
+
sc.reset
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
end
|
data/lib/chronic/ordinal.rb
CHANGED
@@ -1,29 +1,24 @@
|
|
1
1
|
module Chronic
|
2
2
|
|
3
3
|
class Ordinal < Tag #:nodoc:
|
4
|
-
def self.scan(tokens)
|
4
|
+
def self.scan(tokens, options)
|
5
5
|
# for each token
|
6
6
|
tokens.each_index do |i|
|
7
|
-
if t =
|
8
|
-
if t =
|
7
|
+
if t = scan_for_ordinals(tokens[i]) then tokens[i].tag(t) end
|
8
|
+
if t = scan_for_days(tokens[i]) then tokens[i].tag(t) end
|
9
9
|
end
|
10
|
-
tokens
|
11
10
|
end
|
12
11
|
|
13
12
|
def self.scan_for_ordinals(token)
|
14
|
-
if token.word =~ /^(\d*)(st|nd|rd|th)$/
|
15
|
-
return Ordinal.new($1.to_i)
|
16
|
-
end
|
17
|
-
return nil
|
13
|
+
Ordinal.new($1.to_i) if token.word =~ /^(\d*)(st|nd|rd|th)$/
|
18
14
|
end
|
19
15
|
|
20
16
|
def self.scan_for_days(token)
|
21
17
|
if token.word =~ /^(\d*)(st|nd|rd|th)$/
|
22
18
|
unless $1.to_i > 31 || $1.to_i < 1
|
23
|
-
|
19
|
+
OrdinalDay.new(token.word.to_i)
|
24
20
|
end
|
25
21
|
end
|
26
|
-
return nil
|
27
22
|
end
|
28
23
|
|
29
24
|
def to_s
|
data/lib/chronic/pointer.rb
CHANGED
@@ -1,22 +1,20 @@
|
|
1
1
|
module Chronic
|
2
2
|
|
3
3
|
class Pointer < Tag #:nodoc:
|
4
|
-
def self.scan(tokens)
|
4
|
+
def self.scan(tokens, options)
|
5
5
|
# for each token
|
6
6
|
tokens.each_index do |i|
|
7
|
-
if t =
|
7
|
+
if t = scan_for_all(tokens[i]) then tokens[i].tag(t) end
|
8
8
|
end
|
9
|
-
tokens
|
10
9
|
end
|
11
10
|
|
12
11
|
def self.scan_for_all(token)
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
return nil
|
12
|
+
scan_for token, self,
|
13
|
+
{
|
14
|
+
/\bpast\b/ => :past,
|
15
|
+
/\bfuture\b/ => :future,
|
16
|
+
/\bin\b/ => :future
|
17
|
+
}
|
20
18
|
end
|
21
19
|
|
22
20
|
def to_s
|
data/lib/chronic/repeater.rb
CHANGED
@@ -1,129 +1,126 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
1
|
+
module Chronic
|
2
|
+
class Repeater < Tag #:nodoc:
|
3
|
+
def self.scan(tokens, options)
|
4
|
+
# for each token
|
5
|
+
tokens.each_index do |i|
|
6
|
+
if t = scan_for_season_names(tokens[i]) then tokens[i].tag(t); next end
|
7
|
+
if t = scan_for_month_names(tokens[i]) then tokens[i].tag(t); next end
|
8
|
+
if t = scan_for_day_names(tokens[i]) then tokens[i].tag(t); next end
|
9
|
+
if t = scan_for_day_portions(tokens[i]) then tokens[i].tag(t); next end
|
10
|
+
if t = scan_for_times(tokens[i]) then tokens[i].tag(t); next end
|
11
|
+
if t = scan_for_units(tokens[i]) then tokens[i].tag(t); next end
|
12
|
+
end
|
11
13
|
end
|
12
|
-
tokens
|
13
|
-
end
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
15
|
+
def self.scan_for_season_names(token)
|
16
|
+
scan_for token, RepeaterSeasonName,
|
17
|
+
{
|
18
|
+
/^springs?$/ => :spring,
|
19
|
+
/^summers?$/ => :summer,
|
20
|
+
/^(autumn)|(fall)s?$/ => :autumn,
|
21
|
+
/^winters?$/ => :winter
|
22
|
+
}
|
22
23
|
end
|
23
24
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
scanner.keys.each do |scanner_item|
|
41
|
-
return Chronic::RepeaterMonthName.new(scanner[scanner_item]) if scanner_item =~ token.word
|
25
|
+
def self.scan_for_month_names(token)
|
26
|
+
scan_for token, RepeaterMonthName,
|
27
|
+
{
|
28
|
+
/^jan\.?(uary)?$/ => :january,
|
29
|
+
/^feb\.?(ruary)?$/ => :february,
|
30
|
+
/^mar\.?(ch)?$/ => :march,
|
31
|
+
/^apr\.?(il)?$/ => :april,
|
32
|
+
/^may$/ => :may,
|
33
|
+
/^jun\.?e?$/ => :june,
|
34
|
+
/^jul\.?y?$/ => :july,
|
35
|
+
/^aug\.?(ust)?$/ => :august,
|
36
|
+
/^sep\.?(t\.?|tember)?$/ => :september,
|
37
|
+
/^oct\.?(ober)?$/ => :october,
|
38
|
+
/^nov\.?(ember)?$/ => :november,
|
39
|
+
/^dec\.?(ember)?$/ => :december
|
40
|
+
}
|
42
41
|
end
|
43
|
-
return nil
|
44
|
-
end
|
45
42
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
43
|
+
def self.scan_for_day_names(token)
|
44
|
+
scan_for token, RepeaterDayName,
|
45
|
+
{
|
46
|
+
/^m[ou]n(day)?$/ => :monday,
|
47
|
+
/^t(ue|eu|oo|u|)s(day)?$/ => :tuesday,
|
48
|
+
/^tue$/ => :tuesday,
|
49
|
+
/^we(dnes|nds|nns)day$/ => :wednesday,
|
50
|
+
/^wed$/ => :wednesday,
|
51
|
+
/^th(urs|ers)day$/ => :thursday,
|
52
|
+
/^thu(rs)?$/ => :thursday,
|
53
|
+
/^fr[iy](day)?$/ => :friday,
|
54
|
+
/^sat(t?[ue]rday)?$/ => :saturday,
|
55
|
+
/^su[nm](day)?$/ => :sunday
|
56
|
+
}
|
59
57
|
end
|
60
|
-
return nil
|
61
|
-
end
|
62
58
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
59
|
+
def self.scan_for_day_portions(token)
|
60
|
+
scan_for token, RepeaterDayPortion,
|
61
|
+
{
|
62
|
+
/^ams?$/ => :am,
|
63
|
+
/^pms?$/ => :pm,
|
64
|
+
/^mornings?$/ => :morning,
|
65
|
+
/^afternoons?$/ => :afternoon,
|
66
|
+
/^evenings?$/ => :evening,
|
67
|
+
/^(night|nite)s?$/ => :night
|
68
|
+
}
|
72
69
|
end
|
73
|
-
return nil
|
74
|
-
end
|
75
70
|
|
76
|
-
|
77
|
-
|
78
|
-
|
71
|
+
def self.scan_for_times(token)
|
72
|
+
if token.word =~ /^\d{1,2}(:?\d{2})?([\.:]?\d{2})?$/
|
73
|
+
return Chronic::RepeaterTime.new(token.word)
|
74
|
+
end
|
75
|
+
return nil
|
79
76
|
end
|
80
|
-
return nil
|
81
|
-
end
|
82
77
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
78
|
+
def self.scan_for_units(token)
|
79
|
+
{
|
80
|
+
/^years?$/ => :year,
|
81
|
+
/^seasons?$/ => :season,
|
82
|
+
/^months?$/ => :month,
|
83
|
+
/^fortnights?$/ => :fortnight,
|
84
|
+
/^weeks?$/ => :week,
|
85
|
+
/^weekends?$/ => :weekend,
|
86
|
+
/^(week|business)days?$/ => :weekday,
|
87
|
+
/^days?$/ => :day,
|
88
|
+
/^hours?$/ => :hour,
|
89
|
+
/^minutes?$/ => :minute,
|
90
|
+
/^seconds?$/ => :second
|
91
|
+
}.each do |item, symbol|
|
92
|
+
if item =~ token.word
|
93
|
+
klass_name = 'Repeater' + symbol.to_s.capitalize
|
94
|
+
klass = Chronic.const_get(klass_name)
|
95
|
+
return klass.new(symbol)
|
96
|
+
end
|
100
97
|
end
|
98
|
+
return nil
|
101
99
|
end
|
102
|
-
return nil
|
103
|
-
end
|
104
100
|
|
105
|
-
|
106
|
-
|
107
|
-
|
101
|
+
def <=>(other)
|
102
|
+
width <=> other.width
|
103
|
+
end
|
108
104
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
105
|
+
# returns the width (in seconds or months) of this repeatable.
|
106
|
+
def width
|
107
|
+
raise("Repeatable#width must be overridden in subclasses")
|
108
|
+
end
|
113
109
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
110
|
+
# returns the next occurance of this repeatable.
|
111
|
+
def next(pointer)
|
112
|
+
!@now.nil? || raise("Start point must be set before calling #next")
|
113
|
+
[:future, :none, :past].include?(pointer) || raise("First argument 'pointer' must be one of :past or :future")
|
114
|
+
#raise("Repeatable#next must be overridden in subclasses")
|
115
|
+
end
|
120
116
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
117
|
+
def this(pointer)
|
118
|
+
!@now.nil? || raise("Start point must be set before calling #this")
|
119
|
+
[:future, :past, :none].include?(pointer) || raise("First argument 'pointer' must be one of :past, :future, :none")
|
120
|
+
end
|
125
121
|
|
126
|
-
|
127
|
-
|
122
|
+
def to_s
|
123
|
+
'repeater'
|
124
|
+
end
|
128
125
|
end
|
129
|
-
end
|
126
|
+
end
|