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.
Files changed (57) hide show
  1. data/HISTORY.md +76 -0
  2. data/LICENSE +21 -0
  3. data/README.md +165 -0
  4. data/Rakefile +145 -18
  5. data/benchmark/benchmark.rb +13 -0
  6. data/chronic.gemspec +85 -0
  7. data/lib/chronic.rb +21 -19
  8. data/lib/chronic/chronic.rb +59 -49
  9. data/lib/chronic/grabber.rb +2 -2
  10. data/lib/chronic/handlers.rb +167 -112
  11. data/lib/{numerizer → chronic/numerizer}/numerizer.rb +17 -23
  12. data/lib/chronic/ordinal.rb +6 -6
  13. data/lib/chronic/pointer.rb +3 -3
  14. data/lib/chronic/repeater.rb +26 -12
  15. data/lib/chronic/repeaters/repeater_day.rb +17 -12
  16. data/lib/chronic/repeaters/repeater_day_name.rb +17 -12
  17. data/lib/chronic/repeaters/repeater_day_portion.rb +13 -12
  18. data/lib/chronic/repeaters/repeater_fortnight.rb +14 -9
  19. data/lib/chronic/repeaters/repeater_hour.rb +15 -10
  20. data/lib/chronic/repeaters/repeater_minute.rb +15 -10
  21. data/lib/chronic/repeaters/repeater_month.rb +20 -15
  22. data/lib/chronic/repeaters/repeater_month_name.rb +21 -16
  23. data/lib/chronic/repeaters/repeater_season.rb +136 -9
  24. data/lib/chronic/repeaters/repeater_season_name.rb +38 -17
  25. data/lib/chronic/repeaters/repeater_second.rb +15 -10
  26. data/lib/chronic/repeaters/repeater_time.rb +49 -42
  27. data/lib/chronic/repeaters/repeater_week.rb +16 -11
  28. data/lib/chronic/repeaters/repeater_weekday.rb +77 -0
  29. data/lib/chronic/repeaters/repeater_weekend.rb +14 -9
  30. data/lib/chronic/repeaters/repeater_year.rb +19 -13
  31. data/lib/chronic/scalar.rb +16 -14
  32. data/lib/chronic/separator.rb +25 -10
  33. data/lib/chronic/time_zone.rb +4 -3
  34. data/test/helper.rb +7 -0
  35. data/test/test_Chronic.rb +17 -18
  36. data/test/test_DaylightSavings.rb +118 -0
  37. data/test/test_Handler.rb +37 -38
  38. data/test/test_Numerizer.rb +8 -5
  39. data/test/test_RepeaterDayName.rb +15 -16
  40. data/test/test_RepeaterFortnight.rb +16 -17
  41. data/test/test_RepeaterHour.rb +18 -19
  42. data/test/test_RepeaterMinute.rb +34 -0
  43. data/test/test_RepeaterMonth.rb +16 -17
  44. data/test/test_RepeaterMonthName.rb +17 -18
  45. data/test/test_RepeaterTime.rb +20 -22
  46. data/test/test_RepeaterWeek.rb +16 -17
  47. data/test/test_RepeaterWeekday.rb +55 -0
  48. data/test/test_RepeaterWeekend.rb +21 -22
  49. data/test/test_RepeaterYear.rb +17 -18
  50. data/test/test_Span.rb +5 -6
  51. data/test/test_Time.rb +11 -12
  52. data/test/test_Token.rb +5 -6
  53. data/test/test_parsing.rb +300 -204
  54. metadata +74 -52
  55. data/History.txt +0 -53
  56. data/README.txt +0 -149
  57. data/test/suite.rb +0 -9
@@ -1,8 +1,14 @@
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
@@ -14,13 +20,13 @@ class Chronic::RepeaterYear < Chronic::Repeater #:nodoc:
14
20
  diff = pointer == :future ? 1 : -1
15
21
  @current_year_start = Time.construct(@current_year_start.year + diff)
16
22
  end
17
-
23
+
18
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
32
  this_year_start = Time.construct(@now.year, @now.month, @now.day) + Chronic::RepeaterDay::DAY_SECONDS
@@ -32,27 +38,27 @@ class Chronic::RepeaterYear < Chronic::Repeater #:nodoc:
32
38
  this_year_start = Time.construct(@now.year, 1, 1)
33
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
49
  new_begin = Time.construct(sb.year + (amount * direction), sb.month, sb.day, sb.hour, sb.min, sb.sec)
44
-
50
+
45
51
  se = span.end
46
52
  new_end = Time.construct(se.year + (amount * direction), se.month, se.day, se.hour, se.min, se.sec)
47
-
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
@@ -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
- unless token.word.to_i > 31 || (post_token && %w{am pm morning afternoon evening night}.include?(post_token))
27
- return ScalarDay.new(token.word.to_i)
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
- unless token.word.to_i > 12 || (post_token && %w{am pm morning afternoon evening night}.include?(post_token))
36
- return ScalarMonth.new(token.word.to_i)
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
@@ -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
- end
85
+ class SeparatorOn < Separator #:nodoc:
86
+ def to_s
87
+ super << '-on'
88
+ end
89
+ end
90
+
91
+ end
@@ -1,5 +1,5 @@
1
1
  module Chronic
2
- class TimeZone < Tag #:nodoc:
2
+ class TimeZone < Tag #:nodoc:
3
3
  def self.scan(tokens)
4
4
  tokens.each_index do |i|
5
5
  if t = self.scan_for_all(tokens[i]) then tokens[i].tag(t); next end
@@ -8,7 +8,8 @@ module Chronic
8
8
  end
9
9
 
10
10
  def self.scan_for_all(token)
11
- scanner = {/[PMCE][DS]T/i => :tz}
11
+ scanner = {/[PMCE][DS]T/i => :tz,
12
+ /(tzminus)?\d{4}/ => :tz}
12
13
  scanner.keys.each do |scanner_item|
13
14
  return self.new(scanner[scanner_item]) if scanner_item =~ token.word
14
15
  end
@@ -19,4 +20,4 @@ module Chronic
19
20
  'timezone'
20
21
  end
21
22
  end
22
- end
23
+ end
@@ -0,0 +1,7 @@
1
+ require 'test/unit'
2
+
3
+ dir = File.dirname(File.expand_path(__FILE__))
4
+ $LOAD_PATH.unshift(File.join(dir, '..', 'lib'))
5
+ $LOAD_PATH.unshift(dir)
6
+
7
+ require 'chronic'
@@ -1,50 +1,49 @@
1
- require 'chronic'
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
@@ -0,0 +1,118 @@
1
+ require File.join(File.dirname(__FILE__), *%w[helper])
2
+
3
+ class TestDaylightSavings < Test::Unit::TestCase
4
+
5
+ def setup
6
+ @begin_daylight_savings = Time.local(2008, 3, 9, 5, 0, 0, 0)
7
+ @end_daylight_savings = Time.local(2008, 11, 2, 5, 0, 0, 0)
8
+ end
9
+
10
+ def test_begin_past
11
+ # ambiguous - resolve to last night
12
+ t = Chronic::RepeaterTime.new('900')
13
+ t.start = @begin_daylight_savings
14
+ assert_equal Time.local(2008, 3, 8, 21), t.next(:past).begin
15
+
16
+ # ambiguous - resolve to this afternoon
17
+ t = Chronic::RepeaterTime.new('900')
18
+ t.start = Time.local(2008, 3, 9, 22, 0, 0, 0)
19
+ assert_equal Time.local(2008, 3, 9, 21), t.next(:past).begin
20
+
21
+ # ambiguous - resolve to this morning
22
+ t = Chronic::RepeaterTime.new('400')
23
+ t.start = @begin_daylight_savings
24
+ assert_equal Time.local(2008, 3, 9, 4), t.next(:past).begin
25
+
26
+ # unambiguous - resolve to today
27
+ t = Chronic::RepeaterTime.new('0400')
28
+ t.start = @begin_daylight_savings
29
+ assert_equal Time.local(2008, 3, 9, 4), t.next(:past).begin
30
+
31
+ # unambiguous - resolve to yesterday
32
+ t = Chronic::RepeaterTime.new('1300')
33
+ t.start = @begin_daylight_savings
34
+ assert_equal Time.local(2008, 3, 8, 13), t.next(:past).begin
35
+ end
36
+
37
+ def test_begin_future
38
+ # ambiguous - resolve to this morning
39
+ t = Chronic::RepeaterTime.new('900')
40
+ t.start = @begin_daylight_savings
41
+ assert_equal Time.local(2008, 3, 9, 9), t.next(:future).begin
42
+
43
+ # ambiguous - resolve to this afternoon
44
+ t = Chronic::RepeaterTime.new('900')
45
+ t.start = Time.local(2008, 3, 9, 13, 0, 0, 0)
46
+ assert_equal Time.local(2008, 3, 9, 21), t.next(:future).begin
47
+
48
+ # ambiguous - resolve to tomorrow
49
+ t = Chronic::RepeaterTime.new('900')
50
+ t.start = Time.local(2008, 3, 9, 22, 0, 0, 0)
51
+ assert_equal Time.local(2008, 3, 10, 9), t.next(:future).begin
52
+
53
+ # unambiguous - resolve to today
54
+ t = Chronic::RepeaterTime.new('0900')
55
+ t.start = @begin_daylight_savings
56
+ assert_equal Time.local(2008, 3, 9, 9), t.next(:future).begin
57
+
58
+ # unambiguous - resolve to tomorrow
59
+ t = Chronic::RepeaterTime.new('0400')
60
+ t.start = @begin_daylight_savings
61
+ assert_equal Time.local(2008, 3, 10, 4), t.next(:future).begin
62
+ end
63
+
64
+ def test_end_past
65
+ # ambiguous - resolve to last night
66
+ t = Chronic::RepeaterTime.new('900')
67
+ t.start = @end_daylight_savings
68
+ assert_equal Time.local(2008, 11, 1, 21), t.next(:past).begin
69
+
70
+ # ambiguous - resolve to this afternoon
71
+ t = Chronic::RepeaterTime.new('900')
72
+ t.start = Time.local(2008, 11, 2, 22, 0, 0, 0)
73
+ assert_equal Time.local(2008, 11, 2, 21), t.next(:past).begin
74
+
75
+ # ambiguous - resolve to this morning
76
+ t = Chronic::RepeaterTime.new('400')
77
+ t.start = @end_daylight_savings
78
+ assert_equal Time.local(2008, 11, 2, 4), t.next(:past).begin
79
+
80
+ # unambiguous - resolve to today
81
+ t = Chronic::RepeaterTime.new('0400')
82
+ t.start = @end_daylight_savings
83
+ assert_equal Time.local(2008, 11, 2, 4), t.next(:past).begin
84
+
85
+ # unambiguous - resolve to yesterday
86
+ t = Chronic::RepeaterTime.new('1300')
87
+ t.start = @end_daylight_savings
88
+ assert_equal Time.local(2008, 11, 1, 13), t.next(:past).begin
89
+ end
90
+
91
+ def test_end_future
92
+ # ambiguous - resolve to this morning
93
+ t = Chronic::RepeaterTime.new('900')
94
+ t.start = @end_daylight_savings
95
+ assert_equal Time.local(2008, 11, 2, 9), t.next(:future).begin
96
+
97
+ # ambiguous - resolve to this afternoon
98
+ t = Chronic::RepeaterTime.new('900')
99
+ t.start = Time.local(2008, 11, 2, 13, 0, 0, 0)
100
+ assert_equal Time.local(2008, 11, 2, 21), t.next(:future).begin
101
+
102
+ # ambiguous - resolve to tomorrow
103
+ t = Chronic::RepeaterTime.new('900')
104
+ t.start = Time.local(2008, 11, 2, 22, 0, 0, 0)
105
+ assert_equal Time.local(2008, 11, 3, 9), t.next(:future).begin
106
+
107
+ # unambiguous - resolve to today
108
+ t = Chronic::RepeaterTime.new('0900')
109
+ t.start = @end_daylight_savings
110
+ assert_equal Time.local(2008, 11, 2, 9), t.next(:future).begin
111
+
112
+ # unambiguous - resolve to tomorrow
113
+ t = Chronic::RepeaterTime.new('0400')
114
+ t.start = @end_daylight_savings
115
+ assert_equal Time.local(2008, 11, 3, 4), t.next(:future).begin
116
+ end
117
+
118
+ end