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.
Files changed (58) hide show
  1. data/HISTORY.md +76 -0
  2. data/LICENSE +21 -0
  3. data/Manifest.txt +2 -0
  4. data/README.md +165 -0
  5. data/Rakefile +137 -32
  6. data/benchmark/benchmark.rb +13 -0
  7. data/chronic.gemspec +85 -0
  8. data/lib/chronic/chronic.rb +60 -50
  9. data/lib/chronic/grabber.rb +2 -2
  10. data/lib/chronic/handlers.rb +221 -111
  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 +27 -12
  15. data/lib/chronic/repeaters/repeater_day.rb +23 -18
  16. data/lib/chronic/repeaters/repeater_day_name.rb +19 -14
  17. data/lib/chronic/repeaters/repeater_day_portion.rb +23 -22
  18. data/lib/chronic/repeaters/repeater_fortnight.rb +16 -11
  19. data/lib/chronic/repeaters/repeater_hour.rb +20 -15
  20. data/lib/chronic/repeaters/repeater_minute.rb +31 -16
  21. data/lib/chronic/repeaters/repeater_month.rb +33 -24
  22. data/lib/chronic/repeaters/repeater_month_name.rb +31 -26
  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 +53 -43
  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 +31 -25
  31. data/lib/chronic/scalar.rb +16 -14
  32. data/lib/chronic/separator.rb +25 -10
  33. data/lib/chronic/time_zone.rb +23 -0
  34. data/lib/chronic.rb +69 -14
  35. data/test/helper.rb +7 -0
  36. data/test/test_Chronic.rb +17 -18
  37. data/test/test_DaylightSavings.rb +118 -0
  38. data/test/test_Handler.rb +37 -38
  39. data/test/test_Numerizer.rb +8 -5
  40. data/test/test_RepeaterDayName.rb +15 -16
  41. data/test/test_RepeaterFortnight.rb +16 -17
  42. data/test/test_RepeaterHour.rb +18 -19
  43. data/test/test_RepeaterMinute.rb +34 -0
  44. data/test/test_RepeaterMonth.rb +16 -17
  45. data/test/test_RepeaterMonthName.rb +17 -18
  46. data/test/test_RepeaterTime.rb +20 -22
  47. data/test/test_RepeaterWeek.rb +16 -17
  48. data/test/test_RepeaterWeekday.rb +55 -0
  49. data/test/test_RepeaterWeekend.rb +21 -22
  50. data/test/test_RepeaterYear.rb +17 -18
  51. data/test/test_Span.rb +5 -6
  52. data/test/test_Time.rb +49 -0
  53. data/test/test_Token.rb +5 -6
  54. data/test/test_parsing.rb +338 -178
  55. metadata +78 -50
  56. data/History.txt +0 -38
  57. data/README.txt +0 -149
  58. 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 = Time.local(@now.year, @now.month, @now.day, @now.hour) + Chronic::RepeaterHour::HOUR_SECONDS
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 = Time.local(@now.year, @now.month, @now.day, @now.hour)
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.local(@now.year + 1)
15
+ @current_year_start = Time.construct(@now.year + 1)
10
16
  when :past
11
- @current_year_start = Time.local(@now.year - 1)
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.local(@current_year_start.year + diff)
21
+ @current_year_start = Time.construct(@current_year_start.year + diff)
16
22
  end
17
-
18
- Chronic::Span.new(@current_year_start, Time.local(@current_year_start.year + 1))
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.local(@now.year, @now.month, @now.day) + Chronic::RepeaterDay::DAY_SECONDS
27
- this_year_end = Time.local(@now.year + 1, 1, 1)
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.local(@now.year, 1, 1)
30
- this_year_end = Time.local(@now.year, @now.month, @now.day)
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.local(@now.year, 1, 1)
33
- this_year_end = Time.local(@now.year + 1, 1, 1)
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.local(sb.year + (amount * direction), sb.month, sb.day, sb.hour, sb.min, sb.sec)
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.local(se.year + (amount * direction), se.month, se.day, se.hour, se.min, se.sec)
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
@@ -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
@@ -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.2.0"
42
-
43
- def self.debug; false; end
44
- end
45
+ VERSION = "0.3.0"
45
46
 
46
- alias p_orig p
47
+ class << self
48
+ attr_accessor :debug
49
+ attr_accessor :time_class
50
+ end
47
51
 
48
- def p(val)
49
- p_orig val
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
- Time.local(year, month, day, hour, minute, second)
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
@@ -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'
data/test/test_Chronic.rb CHANGED
@@ -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