chronic 0.2.0 → 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/Manifest.txt +2 -0
- data/README.md +165 -0
- data/Rakefile +137 -32
- data/benchmark/benchmark.rb +13 -0
- data/chronic.gemspec +85 -0
- data/lib/chronic/chronic.rb +60 -50
- data/lib/chronic/grabber.rb +2 -2
- data/lib/chronic/handlers.rb +221 -111
- 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 +27 -12
- data/lib/chronic/repeaters/repeater_day.rb +23 -18
- data/lib/chronic/repeaters/repeater_day_name.rb +19 -14
- data/lib/chronic/repeaters/repeater_day_portion.rb +23 -22
- data/lib/chronic/repeaters/repeater_fortnight.rb +16 -11
- data/lib/chronic/repeaters/repeater_hour.rb +20 -15
- data/lib/chronic/repeaters/repeater_minute.rb +31 -16
- data/lib/chronic/repeaters/repeater_month.rb +33 -24
- data/lib/chronic/repeaters/repeater_month_name.rb +31 -26
- 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 +53 -43
- 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 +31 -25
- data/lib/chronic/scalar.rb +16 -14
- data/lib/chronic/separator.rb +25 -10
- data/lib/chronic/time_zone.rb +23 -0
- data/lib/chronic.rb +69 -14
- 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 +49 -0
- data/test/test_Token.rb +5 -6
- data/test/test_parsing.rb +338 -178
- metadata +78 -50
- data/History.txt +0 -38
- data/README.txt +0 -149
- data/test/suite.rb +0 -9
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
class Chronic::RepeaterWeek < Chronic::Repeater #:nodoc:
|
|
2
2
|
WEEK_SECONDS = 604800 # (7 * 24 * 60 * 60)
|
|
3
|
-
|
|
3
|
+
|
|
4
|
+
def initialize(type)
|
|
5
|
+
super
|
|
6
|
+
@current_week_start = nil
|
|
7
|
+
end
|
|
8
|
+
|
|
4
9
|
def next(pointer)
|
|
5
10
|
super
|
|
6
|
-
|
|
11
|
+
|
|
7
12
|
if !@current_week_start
|
|
8
13
|
case pointer
|
|
9
14
|
when :future
|
|
@@ -22,23 +27,23 @@ class Chronic::RepeaterWeek < Chronic::Repeater #:nodoc:
|
|
|
22
27
|
direction = pointer == :future ? 1 : -1
|
|
23
28
|
@current_week_start += direction * WEEK_SECONDS
|
|
24
29
|
end
|
|
25
|
-
|
|
30
|
+
|
|
26
31
|
Chronic::Span.new(@current_week_start, @current_week_start + WEEK_SECONDS)
|
|
27
32
|
end
|
|
28
|
-
|
|
33
|
+
|
|
29
34
|
def this(pointer = :future)
|
|
30
35
|
super
|
|
31
|
-
|
|
36
|
+
|
|
32
37
|
case pointer
|
|
33
38
|
when :future
|
|
34
|
-
this_week_start =
|
|
39
|
+
this_week_start = Chronic.time_class.local(@now.year, @now.month, @now.day, @now.hour) + Chronic::RepeaterHour::HOUR_SECONDS
|
|
35
40
|
sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
|
|
36
41
|
sunday_repeater.start = @now
|
|
37
42
|
this_sunday_span = sunday_repeater.this(:future)
|
|
38
43
|
this_week_end = this_sunday_span.begin
|
|
39
44
|
Chronic::Span.new(this_week_start, this_week_end)
|
|
40
45
|
when :past
|
|
41
|
-
this_week_end =
|
|
46
|
+
this_week_end = Chronic.time_class.local(@now.year, @now.month, @now.day, @now.hour)
|
|
42
47
|
sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
|
|
43
48
|
sunday_repeater.start = @now
|
|
44
49
|
last_sunday_span = sunday_repeater.next(:past)
|
|
@@ -52,17 +57,17 @@ class Chronic::RepeaterWeek < Chronic::Repeater #:nodoc:
|
|
|
52
57
|
Chronic::Span.new(this_week_start, this_week_start + WEEK_SECONDS)
|
|
53
58
|
end
|
|
54
59
|
end
|
|
55
|
-
|
|
60
|
+
|
|
56
61
|
def offset(span, amount, pointer)
|
|
57
62
|
direction = pointer == :future ? 1 : -1
|
|
58
63
|
span + direction * amount * WEEK_SECONDS
|
|
59
64
|
end
|
|
60
|
-
|
|
65
|
+
|
|
61
66
|
def width
|
|
62
67
|
WEEK_SECONDS
|
|
63
68
|
end
|
|
64
|
-
|
|
69
|
+
|
|
65
70
|
def to_s
|
|
66
71
|
super << '-week'
|
|
67
72
|
end
|
|
68
|
-
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
class Chronic::RepeaterWeekday < Chronic::Repeater #:nodoc:
|
|
2
|
+
WEEK_WEEKDAYS = 5
|
|
3
|
+
DAY_SECONDS = 86400 # (24 * 60 * 60)
|
|
4
|
+
|
|
5
|
+
def initialize(type)
|
|
6
|
+
super
|
|
7
|
+
@current_weekday_start = nil
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def next(pointer)
|
|
11
|
+
super
|
|
12
|
+
|
|
13
|
+
direction = pointer == :future ? 1 : -1
|
|
14
|
+
|
|
15
|
+
if !@current_weekday_start
|
|
16
|
+
@current_weekday_start = Time.construct(@now.year, @now.month, @now.day)
|
|
17
|
+
@current_weekday_start += direction * DAY_SECONDS
|
|
18
|
+
|
|
19
|
+
until is_weekday?(@current_weekday_start.wday)
|
|
20
|
+
@current_weekday_start += direction * DAY_SECONDS
|
|
21
|
+
end
|
|
22
|
+
else
|
|
23
|
+
loop do
|
|
24
|
+
@current_weekday_start += direction * DAY_SECONDS
|
|
25
|
+
break if is_weekday?(@current_weekday_start.wday)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
Chronic::Span.new(@current_weekday_start, @current_weekday_start + DAY_SECONDS)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def this(pointer = :future)
|
|
33
|
+
super
|
|
34
|
+
|
|
35
|
+
case pointer
|
|
36
|
+
when :past
|
|
37
|
+
self.next(:past)
|
|
38
|
+
when :future, :none
|
|
39
|
+
self.next(:future)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def offset(span, amount, pointer)
|
|
44
|
+
direction = pointer == :future ? 1 : -1
|
|
45
|
+
|
|
46
|
+
num_weekdays_passed = 0; offset = 0
|
|
47
|
+
until num_weekdays_passed == amount
|
|
48
|
+
offset += direction * DAY_SECONDS
|
|
49
|
+
num_weekdays_passed += 1 if is_weekday?((span.begin+offset).wday)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
span + offset
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def width
|
|
56
|
+
DAY_SECONDS
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def to_s
|
|
60
|
+
super << '-weekday'
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def is_weekend?(day)
|
|
66
|
+
day == symbol_to_number(:saturday) || day == symbol_to_number(:sunday)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def is_weekday?(day)
|
|
70
|
+
!is_weekend?(day)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def symbol_to_number(sym)
|
|
74
|
+
lookup = {:sunday => 0, :monday => 1, :tuesday => 2, :wednesday => 3, :thursday => 4, :friday => 5, :saturday => 6}
|
|
75
|
+
lookup[sym] || raise("Invalid symbol specified")
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
class Chronic::RepeaterWeekend < Chronic::Repeater #:nodoc:
|
|
2
2
|
WEEKEND_SECONDS = 172_800 # (2 * 24 * 60 * 60)
|
|
3
|
-
|
|
3
|
+
|
|
4
|
+
def initialize(type)
|
|
5
|
+
super
|
|
6
|
+
@current_week_start = nil
|
|
7
|
+
end
|
|
8
|
+
|
|
4
9
|
def next(pointer)
|
|
5
10
|
super
|
|
6
|
-
|
|
11
|
+
|
|
7
12
|
if !@current_week_start
|
|
8
13
|
case pointer
|
|
9
14
|
when :future
|
|
@@ -21,13 +26,13 @@ class Chronic::RepeaterWeekend < Chronic::Repeater #:nodoc:
|
|
|
21
26
|
direction = pointer == :future ? 1 : -1
|
|
22
27
|
@current_week_start += direction * Chronic::RepeaterWeek::WEEK_SECONDS
|
|
23
28
|
end
|
|
24
|
-
|
|
29
|
+
|
|
25
30
|
Chronic::Span.new(@current_week_start, @current_week_start + WEEKEND_SECONDS)
|
|
26
31
|
end
|
|
27
|
-
|
|
32
|
+
|
|
28
33
|
def this(pointer = :future)
|
|
29
34
|
super
|
|
30
|
-
|
|
35
|
+
|
|
31
36
|
case pointer
|
|
32
37
|
when :future, :none
|
|
33
38
|
saturday_repeater = Chronic::RepeaterDayName.new(:saturday)
|
|
@@ -41,7 +46,7 @@ class Chronic::RepeaterWeekend < Chronic::Repeater #:nodoc:
|
|
|
41
46
|
Chronic::Span.new(last_saturday_span.begin, last_saturday_span.begin + WEEKEND_SECONDS)
|
|
42
47
|
end
|
|
43
48
|
end
|
|
44
|
-
|
|
49
|
+
|
|
45
50
|
def offset(span, amount, pointer)
|
|
46
51
|
direction = pointer == :future ? 1 : -1
|
|
47
52
|
weekend = Chronic::RepeaterWeekend.new(:weekend)
|
|
@@ -49,12 +54,12 @@ class Chronic::RepeaterWeekend < Chronic::Repeater #:nodoc:
|
|
|
49
54
|
start = weekend.next(pointer).begin + (amount - 1) * direction * Chronic::RepeaterWeek::WEEK_SECONDS
|
|
50
55
|
Chronic::Span.new(start, start + (span.end - span.begin))
|
|
51
56
|
end
|
|
52
|
-
|
|
57
|
+
|
|
53
58
|
def width
|
|
54
59
|
WEEKEND_SECONDS
|
|
55
60
|
end
|
|
56
|
-
|
|
61
|
+
|
|
57
62
|
def to_s
|
|
58
63
|
super << '-weekend'
|
|
59
64
|
end
|
|
60
|
-
end
|
|
65
|
+
end
|
|
@@ -1,58 +1,64 @@
|
|
|
1
1
|
class Chronic::RepeaterYear < Chronic::Repeater #:nodoc:
|
|
2
|
-
|
|
2
|
+
YEAR_SECONDS = 31536000 # 365 * 24 * 60 * 60
|
|
3
|
+
|
|
4
|
+
def initialize(type)
|
|
5
|
+
super
|
|
6
|
+
@current_year_start = nil
|
|
7
|
+
end
|
|
8
|
+
|
|
3
9
|
def next(pointer)
|
|
4
10
|
super
|
|
5
|
-
|
|
11
|
+
|
|
6
12
|
if !@current_year_start
|
|
7
13
|
case pointer
|
|
8
14
|
when :future
|
|
9
|
-
@current_year_start = Time.
|
|
15
|
+
@current_year_start = Time.construct(@now.year + 1)
|
|
10
16
|
when :past
|
|
11
|
-
@current_year_start = Time.
|
|
17
|
+
@current_year_start = Time.construct(@now.year - 1)
|
|
12
18
|
end
|
|
13
19
|
else
|
|
14
20
|
diff = pointer == :future ? 1 : -1
|
|
15
|
-
@current_year_start = Time.
|
|
21
|
+
@current_year_start = Time.construct(@current_year_start.year + diff)
|
|
16
22
|
end
|
|
17
|
-
|
|
18
|
-
Chronic::Span.new(@current_year_start, Time.
|
|
23
|
+
|
|
24
|
+
Chronic::Span.new(@current_year_start, Time.construct(@current_year_start.year + 1))
|
|
19
25
|
end
|
|
20
|
-
|
|
26
|
+
|
|
21
27
|
def this(pointer = :future)
|
|
22
28
|
super
|
|
23
|
-
|
|
29
|
+
|
|
24
30
|
case pointer
|
|
25
31
|
when :future
|
|
26
|
-
this_year_start = Time.
|
|
27
|
-
this_year_end = Time.
|
|
32
|
+
this_year_start = Time.construct(@now.year, @now.month, @now.day) + Chronic::RepeaterDay::DAY_SECONDS
|
|
33
|
+
this_year_end = Time.construct(@now.year + 1, 1, 1)
|
|
28
34
|
when :past
|
|
29
|
-
this_year_start = Time.
|
|
30
|
-
this_year_end = Time.
|
|
35
|
+
this_year_start = Time.construct(@now.year, 1, 1)
|
|
36
|
+
this_year_end = Time.construct(@now.year, @now.month, @now.day)
|
|
31
37
|
when :none
|
|
32
|
-
this_year_start = Time.
|
|
33
|
-
this_year_end = Time.
|
|
38
|
+
this_year_start = Time.construct(@now.year, 1, 1)
|
|
39
|
+
this_year_end = Time.construct(@now.year + 1, 1, 1)
|
|
34
40
|
end
|
|
35
|
-
|
|
41
|
+
|
|
36
42
|
Chronic::Span.new(this_year_start, this_year_end)
|
|
37
43
|
end
|
|
38
|
-
|
|
44
|
+
|
|
39
45
|
def offset(span, amount, pointer)
|
|
40
46
|
direction = pointer == :future ? 1 : -1
|
|
41
|
-
|
|
47
|
+
|
|
42
48
|
sb = span.begin
|
|
43
|
-
new_begin = Time.
|
|
44
|
-
|
|
49
|
+
new_begin = Time.construct(sb.year + (amount * direction), sb.month, sb.day, sb.hour, sb.min, sb.sec)
|
|
50
|
+
|
|
45
51
|
se = span.end
|
|
46
|
-
new_end = Time.
|
|
47
|
-
|
|
52
|
+
new_end = Time.construct(se.year + (amount * direction), se.month, se.day, se.hour, se.min, se.sec)
|
|
53
|
+
|
|
48
54
|
Chronic::Span.new(new_begin, new_end)
|
|
49
55
|
end
|
|
50
|
-
|
|
56
|
+
|
|
51
57
|
def width
|
|
52
58
|
(365 * 24 * 60 * 60)
|
|
53
59
|
end
|
|
54
|
-
|
|
60
|
+
|
|
55
61
|
def to_s
|
|
56
62
|
super << '-year'
|
|
57
63
|
end
|
|
58
|
-
end
|
|
64
|
+
end
|
data/lib/chronic/scalar.rb
CHANGED
|
@@ -11,7 +11,7 @@ module Chronic
|
|
|
11
11
|
end
|
|
12
12
|
tokens
|
|
13
13
|
end
|
|
14
|
-
|
|
14
|
+
|
|
15
15
|
def self.scan_for_scalars(token, post_token)
|
|
16
16
|
if token.word =~ /^\d*$/
|
|
17
17
|
unless post_token && %w{am pm morning afternoon evening night}.include?(post_token)
|
|
@@ -20,55 +20,57 @@ module Chronic
|
|
|
20
20
|
end
|
|
21
21
|
return nil
|
|
22
22
|
end
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
def self.scan_for_days(token, post_token)
|
|
25
25
|
if token.word =~ /^\d\d?$/
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
toi = token.word.to_i
|
|
27
|
+
unless toi > 31 || toi < 1 || (post_token && %w{am pm morning afternoon evening night}.include?(post_token.word))
|
|
28
|
+
return ScalarDay.new(toi)
|
|
28
29
|
end
|
|
29
30
|
end
|
|
30
31
|
return nil
|
|
31
32
|
end
|
|
32
|
-
|
|
33
|
+
|
|
33
34
|
def self.scan_for_months(token, post_token)
|
|
34
35
|
if token.word =~ /^\d\d?$/
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
toi = token.word.to_i
|
|
37
|
+
unless toi > 12 || toi < 1 || (post_token && %w{am pm morning afternoon evening night}.include?(post_token.word))
|
|
38
|
+
return ScalarMonth.new(toi)
|
|
37
39
|
end
|
|
38
40
|
end
|
|
39
41
|
return nil
|
|
40
42
|
end
|
|
41
|
-
|
|
43
|
+
|
|
42
44
|
def self.scan_for_years(token, post_token)
|
|
43
45
|
if token.word =~ /^([1-9]\d)?\d\d?$/
|
|
44
|
-
unless post_token && %w{am pm morning afternoon evening night}.include?(post_token)
|
|
46
|
+
unless post_token && %w{am pm morning afternoon evening night}.include?(post_token.word)
|
|
45
47
|
return ScalarYear.new(token.word.to_i)
|
|
46
48
|
end
|
|
47
49
|
end
|
|
48
50
|
return nil
|
|
49
51
|
end
|
|
50
|
-
|
|
52
|
+
|
|
51
53
|
def to_s
|
|
52
54
|
'scalar'
|
|
53
55
|
end
|
|
54
56
|
end
|
|
55
|
-
|
|
57
|
+
|
|
56
58
|
class ScalarDay < Scalar #:nodoc:
|
|
57
59
|
def to_s
|
|
58
60
|
super << '-day-' << @type.to_s
|
|
59
61
|
end
|
|
60
62
|
end
|
|
61
|
-
|
|
63
|
+
|
|
62
64
|
class ScalarMonth < Scalar #:nodoc:
|
|
63
65
|
def to_s
|
|
64
66
|
super << '-month-' << @type.to_s
|
|
65
67
|
end
|
|
66
68
|
end
|
|
67
|
-
|
|
69
|
+
|
|
68
70
|
class ScalarYear < Scalar #:nodoc:
|
|
69
71
|
def to_s
|
|
70
72
|
super << '-year-' << @type.to_s
|
|
71
73
|
end
|
|
72
74
|
end
|
|
73
75
|
|
|
74
|
-
end
|
|
76
|
+
end
|
data/lib/chronic/separator.rb
CHANGED
|
@@ -7,10 +7,11 @@ module Chronic
|
|
|
7
7
|
if t = self.scan_for_slash_or_dash(tokens[i]) then tokens[i].tag(t); next end
|
|
8
8
|
if t = self.scan_for_at(tokens[i]) then tokens[i].tag(t); next end
|
|
9
9
|
if t = self.scan_for_in(tokens[i]) then tokens[i].tag(t); next end
|
|
10
|
+
if t = self.scan_for_on(tokens[i]) then tokens[i].tag(t); next end
|
|
10
11
|
end
|
|
11
12
|
tokens
|
|
12
13
|
end
|
|
13
|
-
|
|
14
|
+
|
|
14
15
|
def self.scan_for_commas(token)
|
|
15
16
|
scanner = {/^,$/ => :comma}
|
|
16
17
|
scanner.keys.each do |scanner_item|
|
|
@@ -18,7 +19,7 @@ module Chronic
|
|
|
18
19
|
end
|
|
19
20
|
return nil
|
|
20
21
|
end
|
|
21
|
-
|
|
22
|
+
|
|
22
23
|
def self.scan_for_slash_or_dash(token)
|
|
23
24
|
scanner = {/^-$/ => :dash,
|
|
24
25
|
/^\/$/ => :slash}
|
|
@@ -27,7 +28,7 @@ module Chronic
|
|
|
27
28
|
end
|
|
28
29
|
return nil
|
|
29
30
|
end
|
|
30
|
-
|
|
31
|
+
|
|
31
32
|
def self.scan_for_at(token)
|
|
32
33
|
scanner = {/^(at|@)$/ => :at}
|
|
33
34
|
scanner.keys.each do |scanner_item|
|
|
@@ -35,7 +36,7 @@ module Chronic
|
|
|
35
36
|
end
|
|
36
37
|
return nil
|
|
37
38
|
end
|
|
38
|
-
|
|
39
|
+
|
|
39
40
|
def self.scan_for_in(token)
|
|
40
41
|
scanner = {/^in$/ => :in}
|
|
41
42
|
scanner.keys.each do |scanner_item|
|
|
@@ -43,34 +44,48 @@ module Chronic
|
|
|
43
44
|
end
|
|
44
45
|
return nil
|
|
45
46
|
end
|
|
46
|
-
|
|
47
|
+
|
|
48
|
+
def self.scan_for_on(token)
|
|
49
|
+
scanner = {/^on$/ => :on}
|
|
50
|
+
scanner.keys.each do |scanner_item|
|
|
51
|
+
return SeparatorOn.new(scanner[scanner_item]) if scanner_item =~ token.word
|
|
52
|
+
end
|
|
53
|
+
return nil
|
|
54
|
+
end
|
|
55
|
+
|
|
47
56
|
def to_s
|
|
48
57
|
'separator'
|
|
49
58
|
end
|
|
50
59
|
end
|
|
51
|
-
|
|
60
|
+
|
|
52
61
|
class SeparatorComma < Separator #:nodoc:
|
|
53
62
|
def to_s
|
|
54
63
|
super << '-comma'
|
|
55
64
|
end
|
|
56
65
|
end
|
|
57
|
-
|
|
66
|
+
|
|
58
67
|
class SeparatorSlashOrDash < Separator #:nodoc:
|
|
59
68
|
def to_s
|
|
60
69
|
super << '-slashordash-' << @type.to_s
|
|
61
70
|
end
|
|
62
71
|
end
|
|
63
|
-
|
|
72
|
+
|
|
64
73
|
class SeparatorAt < Separator #:nodoc:
|
|
65
74
|
def to_s
|
|
66
75
|
super << '-at'
|
|
67
76
|
end
|
|
68
77
|
end
|
|
69
|
-
|
|
78
|
+
|
|
70
79
|
class SeparatorIn < Separator #:nodoc:
|
|
71
80
|
def to_s
|
|
72
81
|
super << '-in'
|
|
73
82
|
end
|
|
74
83
|
end
|
|
75
84
|
|
|
76
|
-
|
|
85
|
+
class SeparatorOn < Separator #:nodoc:
|
|
86
|
+
def to_s
|
|
87
|
+
super << '-on'
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Chronic
|
|
2
|
+
class TimeZone < Tag #:nodoc:
|
|
3
|
+
def self.scan(tokens)
|
|
4
|
+
tokens.each_index do |i|
|
|
5
|
+
if t = self.scan_for_all(tokens[i]) then tokens[i].tag(t); next end
|
|
6
|
+
end
|
|
7
|
+
tokens
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.scan_for_all(token)
|
|
11
|
+
scanner = {/[PMCE][DS]T/i => :tz,
|
|
12
|
+
/(tzminus)?\d{4}/ => :tz}
|
|
13
|
+
scanner.keys.each do |scanner_item|
|
|
14
|
+
return self.new(scanner[scanner_item]) if scanner_item =~ token.word
|
|
15
|
+
end
|
|
16
|
+
return nil
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def to_s
|
|
20
|
+
'timezone'
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
data/lib/chronic.rb
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
|
|
10
10
|
$:.unshift File.dirname(__FILE__) # For use/testing when no gem is installed
|
|
11
11
|
|
|
12
|
+
require 'time'
|
|
13
|
+
|
|
12
14
|
require 'chronic/chronic'
|
|
13
15
|
require 'chronic/handlers'
|
|
14
16
|
|
|
@@ -21,6 +23,7 @@ require 'chronic/repeaters/repeater_month_name'
|
|
|
21
23
|
require 'chronic/repeaters/repeater_fortnight'
|
|
22
24
|
require 'chronic/repeaters/repeater_week'
|
|
23
25
|
require 'chronic/repeaters/repeater_weekend'
|
|
26
|
+
require 'chronic/repeaters/repeater_weekday'
|
|
24
27
|
require 'chronic/repeaters/repeater_day'
|
|
25
28
|
require 'chronic/repeaters/repeater_day_name'
|
|
26
29
|
require 'chronic/repeaters/repeater_day_portion'
|
|
@@ -34,39 +37,91 @@ require 'chronic/pointer'
|
|
|
34
37
|
require 'chronic/scalar'
|
|
35
38
|
require 'chronic/ordinal'
|
|
36
39
|
require 'chronic/separator'
|
|
40
|
+
require 'chronic/time_zone'
|
|
37
41
|
|
|
38
|
-
require 'numerizer/numerizer'
|
|
42
|
+
require 'chronic/numerizer/numerizer'
|
|
39
43
|
|
|
40
44
|
module Chronic
|
|
41
|
-
VERSION = "0.
|
|
42
|
-
|
|
43
|
-
def self.debug; false; end
|
|
44
|
-
end
|
|
45
|
+
VERSION = "0.3.0"
|
|
45
46
|
|
|
46
|
-
|
|
47
|
+
class << self
|
|
48
|
+
attr_accessor :debug
|
|
49
|
+
attr_accessor :time_class
|
|
50
|
+
end
|
|
47
51
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
puts
|
|
52
|
+
self.debug = false
|
|
53
|
+
self.time_class = Time
|
|
51
54
|
end
|
|
52
55
|
|
|
56
|
+
# class Time
|
|
57
|
+
# def self.construct(year, month = 1, day = 1, hour = 0, minute = 0, second = 0)
|
|
58
|
+
# # extra_seconds = second > 60 ? second - 60 : 0
|
|
59
|
+
# # extra_minutes = minute > 59 ? minute - 59 : 0
|
|
60
|
+
# # extra_hours = hour > 23 ? hour - 23 : 0
|
|
61
|
+
# # extra_days = day >
|
|
62
|
+
#
|
|
63
|
+
# if month > 12
|
|
64
|
+
# if month % 12 == 0
|
|
65
|
+
# year += (month - 12) / 12
|
|
66
|
+
# month = 12
|
|
67
|
+
# else
|
|
68
|
+
# year += month / 12
|
|
69
|
+
# month = month % 12
|
|
70
|
+
# end
|
|
71
|
+
# end
|
|
72
|
+
#
|
|
73
|
+
# base = Time.local(year, month)
|
|
74
|
+
# puts base
|
|
75
|
+
# offset = ((day - 1) * 24 * 60 * 60) + (hour * 60 * 60) + (minute * 60) + second
|
|
76
|
+
# puts offset.to_s
|
|
77
|
+
# date = base + offset
|
|
78
|
+
# puts date
|
|
79
|
+
# date
|
|
80
|
+
# end
|
|
81
|
+
# end
|
|
82
|
+
|
|
53
83
|
class Time
|
|
54
84
|
def self.construct(year, month = 1, day = 1, hour = 0, minute = 0, second = 0)
|
|
55
85
|
if second >= 60
|
|
56
86
|
minute += second / 60
|
|
57
87
|
second = second % 60
|
|
58
88
|
end
|
|
59
|
-
|
|
89
|
+
|
|
60
90
|
if minute >= 60
|
|
61
91
|
hour += minute / 60
|
|
62
92
|
minute = minute % 60
|
|
63
93
|
end
|
|
64
|
-
|
|
94
|
+
|
|
65
95
|
if hour >= 24
|
|
66
96
|
day += hour / 24
|
|
67
97
|
hour = hour % 24
|
|
68
98
|
end
|
|
69
|
-
|
|
70
|
-
|
|
99
|
+
|
|
100
|
+
# determine if there is a day overflow. this is complicated by our crappy calendar
|
|
101
|
+
# system (non-constant number of days per month)
|
|
102
|
+
day <= 56 || raise("day must be no more than 56 (makes month resolution easier)")
|
|
103
|
+
if day > 28
|
|
104
|
+
# no month ever has fewer than 28 days, so only do this if necessary
|
|
105
|
+
leap_year = (year % 4 == 0) && !(year % 100 == 0)
|
|
106
|
+
leap_year_month_days = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
|
107
|
+
common_year_month_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
|
108
|
+
days_this_month = leap_year ? leap_year_month_days[month - 1] : common_year_month_days[month - 1]
|
|
109
|
+
if day > days_this_month
|
|
110
|
+
month += day / days_this_month
|
|
111
|
+
day = day % days_this_month
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
if month > 12
|
|
116
|
+
if month % 12 == 0
|
|
117
|
+
year += (month - 12) / 12
|
|
118
|
+
month = 12
|
|
119
|
+
else
|
|
120
|
+
year += month / 12
|
|
121
|
+
month = month % 12
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
Chronic.time_class.local(year, month, day, hour, minute, second)
|
|
71
126
|
end
|
|
72
|
-
end
|
|
127
|
+
end
|
data/test/helper.rb
ADDED
data/test/test_Chronic.rb
CHANGED
|
@@ -1,50 +1,49 @@
|
|
|
1
|
-
require
|
|
2
|
-
require 'test/unit'
|
|
1
|
+
require File.join(File.dirname(__FILE__), *%w[helper])
|
|
3
2
|
|
|
4
3
|
class TestChronic < Test::Unit::TestCase
|
|
5
|
-
|
|
4
|
+
|
|
6
5
|
def setup
|
|
7
6
|
# Wed Aug 16 14:00:00 UTC 2006
|
|
8
7
|
@now = Time.local(2006, 8, 16, 14, 0, 0, 0)
|
|
9
8
|
end
|
|
10
|
-
|
|
9
|
+
|
|
11
10
|
def test_post_normalize_am_pm_aliases
|
|
12
11
|
# affect wanted patterns
|
|
13
|
-
|
|
12
|
+
|
|
14
13
|
tokens = [Chronic::Token.new("5:00"), Chronic::Token.new("morning")]
|
|
15
14
|
tokens[0].tag(Chronic::RepeaterTime.new("5:00"))
|
|
16
15
|
tokens[1].tag(Chronic::RepeaterDayPortion.new(:morning))
|
|
17
|
-
|
|
16
|
+
|
|
18
17
|
assert_equal :morning, tokens[1].tags[0].type
|
|
19
|
-
|
|
18
|
+
|
|
20
19
|
tokens = Chronic.dealias_and_disambiguate_times(tokens, {})
|
|
21
|
-
|
|
20
|
+
|
|
22
21
|
assert_equal :am, tokens[1].tags[0].type
|
|
23
22
|
assert_equal 2, tokens.size
|
|
24
|
-
|
|
23
|
+
|
|
25
24
|
# don't affect unwanted patterns
|
|
26
|
-
|
|
25
|
+
|
|
27
26
|
tokens = [Chronic::Token.new("friday"), Chronic::Token.new("morning")]
|
|
28
27
|
tokens[0].tag(Chronic::RepeaterDayName.new(:friday))
|
|
29
28
|
tokens[1].tag(Chronic::RepeaterDayPortion.new(:morning))
|
|
30
|
-
|
|
29
|
+
|
|
31
30
|
assert_equal :morning, tokens[1].tags[0].type
|
|
32
|
-
|
|
31
|
+
|
|
33
32
|
tokens = Chronic.dealias_and_disambiguate_times(tokens, {})
|
|
34
|
-
|
|
33
|
+
|
|
35
34
|
assert_equal :morning, tokens[1].tags[0].type
|
|
36
35
|
assert_equal 2, tokens.size
|
|
37
36
|
end
|
|
38
|
-
|
|
37
|
+
|
|
39
38
|
def test_guess
|
|
40
39
|
span = Chronic::Span.new(Time.local(2006, 8, 16, 0), Time.local(2006, 8, 17, 0))
|
|
41
40
|
assert_equal Time.local(2006, 8, 16, 12), Chronic.guess(span)
|
|
42
|
-
|
|
41
|
+
|
|
43
42
|
span = Chronic::Span.new(Time.local(2006, 8, 16, 0), Time.local(2006, 8, 17, 0, 0, 1))
|
|
44
43
|
assert_equal Time.local(2006, 8, 16, 12), Chronic.guess(span)
|
|
45
|
-
|
|
44
|
+
|
|
46
45
|
span = Chronic::Span.new(Time.local(2006, 11), Time.local(2006, 12))
|
|
47
46
|
assert_equal Time.local(2006, 11, 16), Chronic.guess(span)
|
|
48
47
|
end
|
|
49
|
-
|
|
50
|
-
end
|
|
48
|
+
|
|
49
|
+
end
|