chronic 0.2.3 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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