chronic 0.2.3 → 0.3.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 +76 -0
- data/LICENSE +21 -0
- data/README.md +165 -0
- data/Rakefile +145 -18
- data/benchmark/benchmark.rb +13 -0
- data/chronic.gemspec +85 -0
- data/lib/chronic.rb +21 -19
- data/lib/chronic/chronic.rb +59 -49
- data/lib/chronic/grabber.rb +2 -2
- data/lib/chronic/handlers.rb +167 -112
- data/lib/{numerizer → chronic/numerizer}/numerizer.rb +17 -23
- data/lib/chronic/ordinal.rb +6 -6
- data/lib/chronic/pointer.rb +3 -3
- data/lib/chronic/repeater.rb +26 -12
- data/lib/chronic/repeaters/repeater_day.rb +17 -12
- data/lib/chronic/repeaters/repeater_day_name.rb +17 -12
- data/lib/chronic/repeaters/repeater_day_portion.rb +13 -12
- data/lib/chronic/repeaters/repeater_fortnight.rb +14 -9
- data/lib/chronic/repeaters/repeater_hour.rb +15 -10
- data/lib/chronic/repeaters/repeater_minute.rb +15 -10
- data/lib/chronic/repeaters/repeater_month.rb +20 -15
- data/lib/chronic/repeaters/repeater_month_name.rb +21 -16
- data/lib/chronic/repeaters/repeater_season.rb +136 -9
- data/lib/chronic/repeaters/repeater_season_name.rb +38 -17
- data/lib/chronic/repeaters/repeater_second.rb +15 -10
- data/lib/chronic/repeaters/repeater_time.rb +49 -42
- data/lib/chronic/repeaters/repeater_week.rb +16 -11
- data/lib/chronic/repeaters/repeater_weekday.rb +77 -0
- data/lib/chronic/repeaters/repeater_weekend.rb +14 -9
- data/lib/chronic/repeaters/repeater_year.rb +19 -13
- data/lib/chronic/scalar.rb +16 -14
- data/lib/chronic/separator.rb +25 -10
- data/lib/chronic/time_zone.rb +4 -3
- data/test/helper.rb +7 -0
- data/test/test_Chronic.rb +17 -18
- data/test/test_DaylightSavings.rb +118 -0
- data/test/test_Handler.rb +37 -38
- data/test/test_Numerizer.rb +8 -5
- data/test/test_RepeaterDayName.rb +15 -16
- data/test/test_RepeaterFortnight.rb +16 -17
- data/test/test_RepeaterHour.rb +18 -19
- data/test/test_RepeaterMinute.rb +34 -0
- data/test/test_RepeaterMonth.rb +16 -17
- data/test/test_RepeaterMonthName.rb +17 -18
- data/test/test_RepeaterTime.rb +20 -22
- data/test/test_RepeaterWeek.rb +16 -17
- data/test/test_RepeaterWeekday.rb +55 -0
- data/test/test_RepeaterWeekend.rb +21 -22
- data/test/test_RepeaterYear.rb +17 -18
- data/test/test_Span.rb +5 -6
- data/test/test_Time.rb +11 -12
- data/test/test_Token.rb +5 -6
- data/test/test_parsing.rb +300 -204
- metadata +74 -52
- data/History.txt +0 -53
- data/README.txt +0 -149
- data/test/suite.rb +0 -9
@@ -44,32 +44,34 @@ class Numerizer
|
|
44
44
|
['trillion', 1_000_000_000_000],
|
45
45
|
]
|
46
46
|
|
47
|
-
|
48
|
-
def numerize(string)
|
47
|
+
def self.numerize(string)
|
49
48
|
string = string.dup
|
50
|
-
|
49
|
+
|
51
50
|
# preprocess
|
52
|
-
string.gsub!(/ +|([^\d])-([
|
51
|
+
string.gsub!(/ +|([^\d])-([^\d])/, '\1 \2') # will mutilate hyphenated-words but shouldn't matter for date extraction
|
53
52
|
string.gsub!(/a half/, 'haAlf') # take the 'a' out so it doesn't turn into a 1, save the half for the end
|
54
53
|
|
55
54
|
# easy/direct replacements
|
56
|
-
|
55
|
+
|
57
56
|
DIRECT_NUMS.each do |dn|
|
58
|
-
string.gsub!(/#{dn[0]}/i, dn[1])
|
57
|
+
string.gsub!(/#{dn[0]}/i, '<num>' + dn[1])
|
59
58
|
end
|
60
59
|
|
61
60
|
# ten, twenty, etc.
|
62
61
|
|
63
62
|
TEN_PREFIXES.each do |tp|
|
64
|
-
string.gsub!(/(?:#{tp[0]})(
|
63
|
+
string.gsub!(/(?:#{tp[0]}) *<num>(\d(?=[^\d]|$))*/i) { '<num>' + (tp[1] + $1.to_i).to_s }
|
64
|
+
end
|
65
|
+
|
66
|
+
TEN_PREFIXES.each do |tp|
|
67
|
+
string.gsub!(/#{tp[0]}/i) { '<num>' + tp[1].to_s }
|
65
68
|
end
|
66
69
|
|
67
70
|
# hundreds, thousands, millions, etc.
|
68
71
|
|
69
72
|
BIG_PREFIXES.each do |bp|
|
70
|
-
string.gsub!(/(\d*) *#{bp[0]}/i) { (bp[1] * $1.to_i).to_s}
|
73
|
+
string.gsub!(/(?:<num>)?(\d*) *#{bp[0]}/i) { '<num>' + (bp[1] * $1.to_i).to_s}
|
71
74
|
andition(string)
|
72
|
-
#combine_numbers(string) # Should to be more efficient way to do this
|
73
75
|
end
|
74
76
|
|
75
77
|
# fractional addition
|
@@ -77,27 +79,19 @@ class << self
|
|
77
79
|
# (with extraneous .0's and such )
|
78
80
|
string.gsub!(/(\d+)(?: | and |-)*haAlf/i) { ($1.to_f + 0.5).to_s }
|
79
81
|
|
80
|
-
string
|
82
|
+
string.gsub(/<num>/, '')
|
81
83
|
end
|
82
84
|
|
83
|
-
private
|
84
|
-
|
85
|
+
private
|
86
|
+
|
87
|
+
def self.andition(string)
|
85
88
|
sc = StringScanner.new(string)
|
86
|
-
while(sc.scan_until(
|
89
|
+
while(sc.scan_until(/<num>(\d+)( | and )<num>(\d+)(?=[^\w]|$)/i))
|
87
90
|
if sc[2] =~ /and/ || sc[1].size > sc[3].size
|
88
|
-
string[(sc.pos - sc.matched_size)..(sc.pos-1)] = (sc[1].to_i + sc[3].to_i).to_s
|
91
|
+
string[(sc.pos - sc.matched_size)..(sc.pos-1)] = '<num>' + (sc[1].to_i + sc[3].to_i).to_s
|
89
92
|
sc.reset
|
90
93
|
end
|
91
94
|
end
|
92
95
|
end
|
93
96
|
|
94
|
-
# def combine_numbers(string)
|
95
|
-
# sc = StringScanner.new(string)
|
96
|
-
# while(sc.scan_until(/(\d+)(?: | and |-)(\d+)(?=[^\w]|$)/i))
|
97
|
-
# string[(sc.pos - sc.matched_size)..(sc.pos-1)] = (sc[1].to_i + sc[2].to_i).to_s
|
98
|
-
# sc.reset
|
99
|
-
# end
|
100
|
-
# end
|
101
|
-
|
102
97
|
end
|
103
|
-
end
|
data/lib/chronic/ordinal.rb
CHANGED
@@ -9,32 +9,32 @@ module Chronic
|
|
9
9
|
end
|
10
10
|
tokens
|
11
11
|
end
|
12
|
-
|
12
|
+
|
13
13
|
def self.scan_for_ordinals(token)
|
14
14
|
if token.word =~ /^(\d*)(st|nd|rd|th)$/
|
15
15
|
return Ordinal.new($1.to_i)
|
16
16
|
end
|
17
17
|
return nil
|
18
18
|
end
|
19
|
-
|
19
|
+
|
20
20
|
def self.scan_for_days(token)
|
21
21
|
if token.word =~ /^(\d*)(st|nd|rd|th)$/
|
22
|
-
unless $1.to_i > 31
|
22
|
+
unless $1.to_i > 31 || $1.to_i < 1
|
23
23
|
return OrdinalDay.new(token.word.to_i)
|
24
24
|
end
|
25
25
|
end
|
26
26
|
return nil
|
27
27
|
end
|
28
|
-
|
28
|
+
|
29
29
|
def to_s
|
30
30
|
'ordinal'
|
31
31
|
end
|
32
32
|
end
|
33
|
-
|
33
|
+
|
34
34
|
class OrdinalDay < Ordinal #:nodoc:
|
35
35
|
def to_s
|
36
36
|
super << '-day-' << @type.to_s
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
40
|
-
end
|
40
|
+
end
|
data/lib/chronic/pointer.rb
CHANGED
@@ -8,7 +8,7 @@ module Chronic
|
|
8
8
|
end
|
9
9
|
tokens
|
10
10
|
end
|
11
|
-
|
11
|
+
|
12
12
|
def self.scan_for_all(token)
|
13
13
|
scanner = {/\bpast\b/ => :past,
|
14
14
|
/\bfuture\b/ => :future,
|
@@ -18,10 +18,10 @@ module Chronic
|
|
18
18
|
end
|
19
19
|
return nil
|
20
20
|
end
|
21
|
-
|
21
|
+
|
22
22
|
def to_s
|
23
23
|
'pointer-' << @type.to_s
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
-
end
|
27
|
+
end
|
data/lib/chronic/repeater.rb
CHANGED
@@ -2,6 +2,7 @@ class Chronic::Repeater < Chronic::Tag #:nodoc:
|
|
2
2
|
def self.scan(tokens, options)
|
3
3
|
# for each token
|
4
4
|
tokens.each_index do |i|
|
5
|
+
if t = self.scan_for_season_names(tokens[i]) then tokens[i].tag(t); next end
|
5
6
|
if t = self.scan_for_month_names(tokens[i]) then tokens[i].tag(t); next end
|
6
7
|
if t = self.scan_for_day_names(tokens[i]) then tokens[i].tag(t); next end
|
7
8
|
if t = self.scan_for_day_portions(tokens[i]) then tokens[i].tag(t); next end
|
@@ -10,7 +11,19 @@ class Chronic::Repeater < Chronic::Tag #:nodoc:
|
|
10
11
|
end
|
11
12
|
tokens
|
12
13
|
end
|
13
|
-
|
14
|
+
|
15
|
+
def self.scan_for_season_names(token)
|
16
|
+
scanner = {/^springs?$/ => :spring,
|
17
|
+
/^summers?$/ => :summer,
|
18
|
+
/^(autumn)|(fall)s?$/ => :autumn,
|
19
|
+
/^winters?$/ => :winter}
|
20
|
+
scanner.keys.each do |scanner_item|
|
21
|
+
return Chronic::RepeaterSeasonName.new(scanner[scanner_item]) if scanner_item =~ token.word
|
22
|
+
end
|
23
|
+
|
24
|
+
return nil
|
25
|
+
end
|
26
|
+
|
14
27
|
def self.scan_for_month_names(token)
|
15
28
|
scanner = {/^jan\.?(uary)?$/ => :january,
|
16
29
|
/^feb\.?(ruary)?$/ => :february,
|
@@ -29,7 +42,7 @@ class Chronic::Repeater < Chronic::Tag #:nodoc:
|
|
29
42
|
end
|
30
43
|
return nil
|
31
44
|
end
|
32
|
-
|
45
|
+
|
33
46
|
def self.scan_for_day_names(token)
|
34
47
|
scanner = {/^m[ou]n(day)?$/ => :monday,
|
35
48
|
/^t(ue|eu|oo|u|)s(day)?$/ => :tuesday,
|
@@ -46,7 +59,7 @@ class Chronic::Repeater < Chronic::Tag #:nodoc:
|
|
46
59
|
end
|
47
60
|
return nil
|
48
61
|
end
|
49
|
-
|
62
|
+
|
50
63
|
def self.scan_for_day_portions(token)
|
51
64
|
scanner = {/^ams?$/ => :am,
|
52
65
|
/^pms?$/ => :pm,
|
@@ -59,14 +72,14 @@ class Chronic::Repeater < Chronic::Tag #:nodoc:
|
|
59
72
|
end
|
60
73
|
return nil
|
61
74
|
end
|
62
|
-
|
75
|
+
|
63
76
|
def self.scan_for_times(token, options)
|
64
77
|
if token.word =~ /^\d{1,2}(:?\d{2})?([\.:]?\d{2})?$/
|
65
78
|
return Chronic::RepeaterTime.new(token.word, options)
|
66
79
|
end
|
67
80
|
return nil
|
68
81
|
end
|
69
|
-
|
82
|
+
|
70
83
|
def self.scan_for_units(token)
|
71
84
|
scanner = {/^years?$/ => :year,
|
72
85
|
/^seasons?$/ => :season,
|
@@ -74,6 +87,7 @@ class Chronic::Repeater < Chronic::Tag #:nodoc:
|
|
74
87
|
/^fortnights?$/ => :fortnight,
|
75
88
|
/^weeks?$/ => :week,
|
76
89
|
/^weekends?$/ => :weekend,
|
90
|
+
/^(week|business)days?$/ => :weekday,
|
77
91
|
/^days?$/ => :day,
|
78
92
|
/^hours?$/ => :hour,
|
79
93
|
/^minutes?$/ => :minute,
|
@@ -82,34 +96,34 @@ class Chronic::Repeater < Chronic::Tag #:nodoc:
|
|
82
96
|
if scanner_item =~ token.word
|
83
97
|
klass_name = 'Chronic::Repeater' + scanner[scanner_item].to_s.capitalize
|
84
98
|
klass = eval(klass_name)
|
85
|
-
return klass.new(scanner[scanner_item])
|
99
|
+
return klass.new(scanner[scanner_item])
|
86
100
|
end
|
87
101
|
end
|
88
102
|
return nil
|
89
103
|
end
|
90
|
-
|
104
|
+
|
91
105
|
def <=>(other)
|
92
106
|
width <=> other.width
|
93
107
|
end
|
94
|
-
|
108
|
+
|
95
109
|
# returns the width (in seconds or months) of this repeatable.
|
96
110
|
def width
|
97
111
|
raise("Repeatable#width must be overridden in subclasses")
|
98
112
|
end
|
99
|
-
|
113
|
+
|
100
114
|
# returns the next occurance of this repeatable.
|
101
115
|
def next(pointer)
|
102
116
|
!@now.nil? || raise("Start point must be set before calling #next")
|
103
117
|
[:future, :none, :past].include?(pointer) || raise("First argument 'pointer' must be one of :past or :future")
|
104
118
|
#raise("Repeatable#next must be overridden in subclasses")
|
105
119
|
end
|
106
|
-
|
120
|
+
|
107
121
|
def this(pointer)
|
108
122
|
!@now.nil? || raise("Start point must be set before calling #this")
|
109
123
|
[:future, :past, :none].include?(pointer) || raise("First argument 'pointer' must be one of :past, :future, :none")
|
110
124
|
end
|
111
|
-
|
125
|
+
|
112
126
|
def to_s
|
113
127
|
'repeater'
|
114
128
|
end
|
115
|
-
end
|
129
|
+
end
|
@@ -1,22 +1,27 @@
|
|
1
1
|
class Chronic::RepeaterDay < Chronic::Repeater #:nodoc:
|
2
2
|
DAY_SECONDS = 86_400 # (24 * 60 * 60)
|
3
|
-
|
3
|
+
|
4
|
+
def initialize(type)
|
5
|
+
super
|
6
|
+
@current_day_start = nil
|
7
|
+
end
|
8
|
+
|
4
9
|
def next(pointer)
|
5
10
|
super
|
6
|
-
|
11
|
+
|
7
12
|
if !@current_day_start
|
8
|
-
@current_day_start =
|
13
|
+
@current_day_start = Chronic.time_class.local(@now.year, @now.month, @now.day)
|
9
14
|
end
|
10
|
-
|
15
|
+
|
11
16
|
direction = pointer == :future ? 1 : -1
|
12
17
|
@current_day_start += direction * DAY_SECONDS
|
13
|
-
|
18
|
+
|
14
19
|
Chronic::Span.new(@current_day_start, @current_day_start + DAY_SECONDS)
|
15
20
|
end
|
16
|
-
|
21
|
+
|
17
22
|
def this(pointer = :future)
|
18
23
|
super
|
19
|
-
|
24
|
+
|
20
25
|
case pointer
|
21
26
|
when :future
|
22
27
|
day_begin = Time.construct(@now.year, @now.month, @now.day, @now.hour + 1)
|
@@ -28,20 +33,20 @@ class Chronic::RepeaterDay < Chronic::Repeater #:nodoc:
|
|
28
33
|
day_begin = Time.construct(@now.year, @now.month, @now.day)
|
29
34
|
day_end = Time.construct(@now.year, @now.month, @now.day) + DAY_SECONDS
|
30
35
|
end
|
31
|
-
|
36
|
+
|
32
37
|
Chronic::Span.new(day_begin, day_end)
|
33
38
|
end
|
34
|
-
|
39
|
+
|
35
40
|
def offset(span, amount, pointer)
|
36
41
|
direction = pointer == :future ? 1 : -1
|
37
42
|
span + direction * amount * DAY_SECONDS
|
38
43
|
end
|
39
|
-
|
44
|
+
|
40
45
|
def width
|
41
46
|
DAY_SECONDS
|
42
47
|
end
|
43
|
-
|
48
|
+
|
44
49
|
def to_s
|
45
50
|
super << '-day'
|
46
51
|
end
|
47
|
-
end
|
52
|
+
end
|
@@ -1,46 +1,51 @@
|
|
1
1
|
class Chronic::RepeaterDayName < Chronic::Repeater #:nodoc:
|
2
2
|
DAY_SECONDS = 86400 # (24 * 60 * 60)
|
3
|
-
|
3
|
+
|
4
|
+
def initialize(type)
|
5
|
+
super
|
6
|
+
@current_day_start = nil
|
7
|
+
end
|
8
|
+
|
4
9
|
def next(pointer)
|
5
10
|
super
|
6
|
-
|
11
|
+
|
7
12
|
direction = pointer == :future ? 1 : -1
|
8
|
-
|
13
|
+
|
9
14
|
if !@current_day_start
|
10
15
|
@current_day_start = Time.construct(@now.year, @now.month, @now.day)
|
11
16
|
@current_day_start += direction * DAY_SECONDS
|
12
17
|
|
13
18
|
day_num = symbol_to_number(@type)
|
14
|
-
|
19
|
+
|
15
20
|
while @current_day_start.wday != day_num
|
16
21
|
@current_day_start += direction * DAY_SECONDS
|
17
22
|
end
|
18
23
|
else
|
19
24
|
@current_day_start += direction * 7 * DAY_SECONDS
|
20
25
|
end
|
21
|
-
|
26
|
+
|
22
27
|
Chronic::Span.new(@current_day_start, @current_day_start + DAY_SECONDS)
|
23
28
|
end
|
24
|
-
|
29
|
+
|
25
30
|
def this(pointer = :future)
|
26
31
|
super
|
27
|
-
|
32
|
+
|
28
33
|
pointer = :future if pointer == :none
|
29
34
|
self.next(pointer)
|
30
35
|
end
|
31
|
-
|
36
|
+
|
32
37
|
def width
|
33
38
|
DAY_SECONDS
|
34
39
|
end
|
35
|
-
|
40
|
+
|
36
41
|
def to_s
|
37
42
|
super << '-dayname-' << @type.to_s
|
38
43
|
end
|
39
|
-
|
44
|
+
|
40
45
|
private
|
41
|
-
|
46
|
+
|
42
47
|
def symbol_to_number(sym)
|
43
48
|
lookup = {:sunday => 0, :monday => 1, :tuesday => 2, :wednesday => 3, :thursday => 4, :friday => 5, :saturday => 6}
|
44
49
|
lookup[sym] || raise("Invalid symbol specified")
|
45
50
|
end
|
46
|
-
end
|
51
|
+
end
|
@@ -3,10 +3,11 @@ class Chronic::RepeaterDayPortion < Chronic::Repeater #:nodoc:
|
|
3
3
|
@@afternoon = (13 * 60 * 60)..(17 * 60 * 60) # 1pm-5pm
|
4
4
|
@@evening = (17 * 60 * 60)..(20 * 60 * 60) # 5pm-8pm
|
5
5
|
@@night = (20 * 60 * 60)..(24 * 60 * 60) # 8pm-12pm
|
6
|
-
|
6
|
+
|
7
7
|
def initialize(type)
|
8
8
|
super
|
9
|
-
|
9
|
+
@current_span = nil
|
10
|
+
|
10
11
|
if type.kind_of? Integer
|
11
12
|
@range = (@type * 60 * 60)..((@type + 12) * 60 * 60)
|
12
13
|
else
|
@@ -21,12 +22,12 @@ class Chronic::RepeaterDayPortion < Chronic::Repeater #:nodoc:
|
|
21
22
|
end
|
22
23
|
@range || raise("Range should have been set by now")
|
23
24
|
end
|
24
|
-
|
25
|
+
|
25
26
|
def next(pointer)
|
26
27
|
super
|
27
|
-
|
28
|
+
|
28
29
|
full_day = 60 * 60 * 24
|
29
|
-
|
30
|
+
|
30
31
|
if !@current_span
|
31
32
|
now_seconds = @now - Time.construct(@now.year, @now.month, @now.day)
|
32
33
|
if now_seconds < @range.begin
|
@@ -51,7 +52,7 @@ class Chronic::RepeaterDayPortion < Chronic::Repeater #:nodoc:
|
|
51
52
|
range_start = Time.construct(@now.year, @now.month, @now.day) - full_day + @range.begin
|
52
53
|
end
|
53
54
|
end
|
54
|
-
|
55
|
+
|
55
56
|
@current_span = Chronic::Span.new(range_start, range_start + (@range.end - @range.begin))
|
56
57
|
else
|
57
58
|
case pointer
|
@@ -62,21 +63,21 @@ class Chronic::RepeaterDayPortion < Chronic::Repeater #:nodoc:
|
|
62
63
|
end
|
63
64
|
end
|
64
65
|
end
|
65
|
-
|
66
|
+
|
66
67
|
def this(context = :future)
|
67
68
|
super
|
68
|
-
|
69
|
+
|
69
70
|
range_start = Time.construct(@now.year, @now.month, @now.day) + @range.begin
|
70
71
|
@current_span = Chronic::Span.new(range_start, range_start + (@range.end - @range.begin))
|
71
72
|
end
|
72
|
-
|
73
|
+
|
73
74
|
def offset(span, amount, pointer)
|
74
75
|
@now = span.begin
|
75
76
|
portion_span = self.next(pointer)
|
76
77
|
direction = pointer == :future ? 1 : -1
|
77
78
|
portion_span + (direction * (amount - 1) * Chronic::RepeaterDay::DAY_SECONDS)
|
78
79
|
end
|
79
|
-
|
80
|
+
|
80
81
|
def width
|
81
82
|
@range || raise("Range has not been set")
|
82
83
|
return @current_span.width if @current_span
|
@@ -86,8 +87,8 @@ class Chronic::RepeaterDayPortion < Chronic::Repeater #:nodoc:
|
|
86
87
|
@range.end - @range.begin
|
87
88
|
end
|
88
89
|
end
|
89
|
-
|
90
|
+
|
90
91
|
def to_s
|
91
92
|
super << '-dayportion-' << @type.to_s
|
92
93
|
end
|
93
|
-
end
|
94
|
+
end
|