chronic-mmlac 0.6.4.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/.gemtest +0 -0
  3. data/.gitignore +5 -0
  4. data/.yardopts +3 -0
  5. data/HISTORY.md +174 -0
  6. data/LICENSE +21 -0
  7. data/README.md +177 -0
  8. data/Rakefile +46 -0
  9. data/chronic.gemspec +17 -0
  10. data/lib/chronic/chronic.rb +325 -0
  11. data/lib/chronic/grabber.rb +31 -0
  12. data/lib/chronic/handler.rb +90 -0
  13. data/lib/chronic/handlers.rb +465 -0
  14. data/lib/chronic/mini_date.rb +38 -0
  15. data/lib/chronic/numerizer.rb +121 -0
  16. data/lib/chronic/ordinal.rb +44 -0
  17. data/lib/chronic/pointer.rb +30 -0
  18. data/lib/chronic/repeater.rb +135 -0
  19. data/lib/chronic/repeaters/repeater_day.rb +53 -0
  20. data/lib/chronic/repeaters/repeater_day_name.rb +52 -0
  21. data/lib/chronic/repeaters/repeater_day_portion.rb +94 -0
  22. data/lib/chronic/repeaters/repeater_fortnight.rb +71 -0
  23. data/lib/chronic/repeaters/repeater_hour.rb +58 -0
  24. data/lib/chronic/repeaters/repeater_minute.rb +58 -0
  25. data/lib/chronic/repeaters/repeater_month.rb +79 -0
  26. data/lib/chronic/repeaters/repeater_month_name.rb +94 -0
  27. data/lib/chronic/repeaters/repeater_season.rb +109 -0
  28. data/lib/chronic/repeaters/repeater_season_name.rb +43 -0
  29. data/lib/chronic/repeaters/repeater_second.rb +42 -0
  30. data/lib/chronic/repeaters/repeater_time.rb +128 -0
  31. data/lib/chronic/repeaters/repeater_week.rb +74 -0
  32. data/lib/chronic/repeaters/repeater_weekday.rb +85 -0
  33. data/lib/chronic/repeaters/repeater_weekend.rb +66 -0
  34. data/lib/chronic/repeaters/repeater_year.rb +77 -0
  35. data/lib/chronic/scalar.rb +109 -0
  36. data/lib/chronic/season.rb +37 -0
  37. data/lib/chronic/separator.rb +88 -0
  38. data/lib/chronic/span.rb +31 -0
  39. data/lib/chronic/tag.rb +42 -0
  40. data/lib/chronic/time_zone.rb +30 -0
  41. data/lib/chronic/token.rb +45 -0
  42. data/lib/chronic.rb +118 -0
  43. data/test/helper.rb +6 -0
  44. data/test/test_Chronic.rb +148 -0
  45. data/test/test_DaylightSavings.rb +118 -0
  46. data/test/test_Handler.rb +104 -0
  47. data/test/test_MiniDate.rb +32 -0
  48. data/test/test_Numerizer.rb +72 -0
  49. data/test/test_RepeaterDayName.rb +51 -0
  50. data/test/test_RepeaterFortnight.rb +62 -0
  51. data/test/test_RepeaterHour.rb +68 -0
  52. data/test/test_RepeaterMinute.rb +34 -0
  53. data/test/test_RepeaterMonth.rb +50 -0
  54. data/test/test_RepeaterMonthName.rb +56 -0
  55. data/test/test_RepeaterSeason.rb +40 -0
  56. data/test/test_RepeaterTime.rb +70 -0
  57. data/test/test_RepeaterWeek.rb +62 -0
  58. data/test/test_RepeaterWeekday.rb +55 -0
  59. data/test/test_RepeaterWeekend.rb +74 -0
  60. data/test/test_RepeaterYear.rb +69 -0
  61. data/test/test_Span.rb +23 -0
  62. data/test/test_Token.rb +25 -0
  63. data/test/test_parsing.rb +886 -0
  64. metadata +132 -0
@@ -0,0 +1,109 @@
1
+ module Chronic
2
+ class Scalar < Tag
3
+ DAY_PORTIONS = %w( am pm morning afternoon evening night )
4
+
5
+ # Scan an Array of {Token}s and apply any necessary Scalar tags to
6
+ # each token
7
+ #
8
+ # @param [Array<Token>] tokens Array of tokens to scan
9
+ # @param [Hash] options Options specified in {Chronic.parse}
10
+ # @return [Array] list of tokens
11
+ def self.scan(tokens, options)
12
+ tokens.each_index do |i|
13
+ if t = scan_for_scalars(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
14
+ if t = scan_for_days(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
15
+ if t = scan_for_months(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
16
+ if t = scan_for_years(tokens[i], tokens[i + 1], options) then tokens[i].tag(t) end
17
+ end
18
+ end
19
+
20
+ # @param [Token] token
21
+ # @param [Token] post_token
22
+ # @return [Scalar, nil]
23
+ def self.scan_for_scalars(token, post_token)
24
+ if token.word =~ /^\d*$/
25
+ unless post_token && DAY_PORTIONS.include?(post_token.word)
26
+ return Scalar.new(token.word.to_i)
27
+ end
28
+ end
29
+ end
30
+
31
+ # @param [Token] token
32
+ # @param [Token] post_token
33
+ # @return [ScalarDay, nil]
34
+ def self.scan_for_days(token, post_token)
35
+ if token.word =~ /^\d\d?$/
36
+ toi = token.word.to_i
37
+ unless toi > 31 || toi < 1 || (post_token && DAY_PORTIONS.include?(post_token.word))
38
+ return ScalarDay.new(toi)
39
+ end
40
+ end
41
+ end
42
+
43
+ # @param [Token] token
44
+ # @param [Token] post_token
45
+ # @return [ScalarMonth, nil]
46
+ def self.scan_for_months(token, post_token)
47
+ if token.word =~ /^\d\d?$/
48
+ toi = token.word.to_i
49
+ unless toi > 12 || toi < 1 || (post_token && DAY_PORTIONS.include?(post_token.word))
50
+ return ScalarMonth.new(toi)
51
+ end
52
+ end
53
+ end
54
+
55
+ # @param [Token] token
56
+ # @param [Token] post_token
57
+ # @param [Hash] options Options specified in {Chronic.parse}
58
+ # @return [ScalarYear, nil]
59
+ def self.scan_for_years(token, post_token, options)
60
+ if token.word =~ /^([1-9]\d)?\d\d?$/
61
+ unless post_token && DAY_PORTIONS.include?(post_token.word)
62
+ year = make_year(token.word.to_i, options[:ambiguous_year_future_bias])
63
+ return ScalarYear.new(year.to_i)
64
+ end
65
+ end
66
+ end
67
+
68
+ # Build a year from a 2 digit suffix
69
+ #
70
+ # @example
71
+ # make_year(96, 50) #=> 1996
72
+ # make_year(79, 20) #=> 2079
73
+ # make_year(00, 50) #=> 2000
74
+ #
75
+ # @param [Integer] year The two digit year to build from
76
+ # @param [Integer] bias The amount of future years to bias
77
+ # @return [Integer] The 4 digit year
78
+ def self.make_year(year, bias)
79
+ return year if year.to_s.size > 2
80
+ start_year = Chronic.time_class.now.year - bias
81
+ century = (start_year / 100) * 100
82
+ full_year = century + year
83
+ full_year += 100 if full_year < start_year
84
+ full_year
85
+ end
86
+
87
+ def to_s
88
+ 'scalar'
89
+ end
90
+ end
91
+
92
+ class ScalarDay < Scalar #:nodoc:
93
+ def to_s
94
+ super << '-day-' << @type.to_s
95
+ end
96
+ end
97
+
98
+ class ScalarMonth < Scalar #:nodoc:
99
+ def to_s
100
+ super << '-month-' << @type.to_s
101
+ end
102
+ end
103
+
104
+ class ScalarYear < Scalar #:nodoc:
105
+ def to_s
106
+ super << '-year-' << @type.to_s
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,37 @@
1
+ module Chronic
2
+ class Season
3
+ # @return [MiniDate]
4
+ attr_reader :start
5
+
6
+ # @return [MiniDate]
7
+ attr_reader :end
8
+
9
+ # @param [MiniDate] start_date
10
+ # @param [MiniDate] end_date
11
+ def initialize(start_date, end_date)
12
+ @start = start_date
13
+ @end = end_date
14
+ end
15
+
16
+ # @param [Symbol] season The season name
17
+ # @param [Integer] pointer The direction (-1 for past, 1 for future)
18
+ # @return [Symbol] The new season name
19
+ def self.find_next_season(season, pointer)
20
+ lookup = [:spring, :summer, :autumn, :winter]
21
+ next_season_num = (lookup.index(season) + 1 * pointer) % 4
22
+ lookup[next_season_num]
23
+ end
24
+
25
+ # @param [Symbol] season The season name
26
+ # @return [Symbol] The new season name
27
+ def self.season_after(season)
28
+ find_next_season(season, +1)
29
+ end
30
+
31
+ # @param [Symbol] season The season name
32
+ # @return [Symbol] The new season name
33
+ def self.season_before(season)
34
+ find_next_season(season, -1)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,88 @@
1
+ module Chronic
2
+ class Separator < Tag
3
+
4
+ # Scan an Array of {Token}s and apply any necessary Separator tags to
5
+ # each token
6
+ #
7
+ # @param [Array<Token>] tokens Array of tokens to scan
8
+ # @param [Hash] options Options specified in {Chronic.parse}
9
+ # @return [Array] list of tokens
10
+ def self.scan(tokens, options)
11
+ tokens.each do |token|
12
+ if t = scan_for_commas(token) then token.tag(t); next end
13
+ if t = scan_for_slash_or_dash(token) then token.tag(t); next end
14
+ if t = scan_for_at(token) then token.tag(t); next end
15
+ if t = scan_for_in(token) then token.tag(t); next end
16
+ if t = scan_for_on(token) then token.tag(t); next end
17
+ end
18
+ end
19
+
20
+ # @param [Token] token
21
+ # @return [SeparatorComma, nil]
22
+ def self.scan_for_commas(token)
23
+ scan_for token, SeparatorComma, { /^,$/ => :comma }
24
+ end
25
+
26
+ # @param [Token] token
27
+ # @return [SeparatorSlashOrDash, nil]
28
+ def self.scan_for_slash_or_dash(token)
29
+ scan_for token, SeparatorSlashOrDash,
30
+ {
31
+ /^-$/ => :dash,
32
+ /^\/$/ => :slash
33
+ }
34
+ end
35
+
36
+ # @param [Token] token
37
+ # @return [SeparatorAt, nil]
38
+ def self.scan_for_at(token)
39
+ scan_for token, SeparatorAt, { /^(at|@)$/ => :at }
40
+ end
41
+
42
+ # @param [Token] token
43
+ # @return [SeparatorIn, nil]
44
+ def self.scan_for_in(token)
45
+ scan_for token, SeparatorIn, { /^in$/ => :in }
46
+ end
47
+
48
+ # @param [Token] token
49
+ # @return [SeparatorOn, nil]
50
+ def self.scan_for_on(token)
51
+ scan_for token, SeparatorOn, { /^on$/ => :on }
52
+ end
53
+
54
+ def to_s
55
+ 'separator'
56
+ end
57
+ end
58
+
59
+ class SeparatorComma < Separator #:nodoc:
60
+ def to_s
61
+ super << '-comma'
62
+ end
63
+ end
64
+
65
+ class SeparatorSlashOrDash < Separator #:nodoc:
66
+ def to_s
67
+ super << '-slashordash-' << @type.to_s
68
+ end
69
+ end
70
+
71
+ class SeparatorAt < Separator #:nodoc:
72
+ def to_s
73
+ super << '-at'
74
+ end
75
+ end
76
+
77
+ class SeparatorIn < Separator #:nodoc:
78
+ def to_s
79
+ super << '-in'
80
+ end
81
+ end
82
+
83
+ class SeparatorOn < Separator #:nodoc:
84
+ def to_s
85
+ super << '-on'
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,31 @@
1
+ module Chronic
2
+ # A Span represents a range of time. Since this class extends
3
+ # Range, you can use #begin and #end to get the beginning and
4
+ # ending times of the span (they will be of class Time)
5
+ class Span < Range
6
+ # Returns the width of this span in seconds
7
+ def width
8
+ (self.end - self.begin).to_i
9
+ end
10
+
11
+ # Add a number of seconds to this span, returning the
12
+ # resulting Span
13
+ def +(seconds)
14
+ Span.new(self.begin + seconds, self.end + seconds)
15
+ end
16
+
17
+ # Subtract a number of seconds to this span, returning the
18
+ # resulting Span
19
+ def -(seconds)
20
+ self + -seconds
21
+ end
22
+
23
+ # Prints this span in a nice fashion
24
+ def to_s
25
+ '(' << self.begin.to_s << '..' << self.end.to_s << ')'
26
+ end
27
+
28
+ alias :cover? :include? unless RUBY_VERSION =~ /^1.9/
29
+
30
+ end
31
+ end
@@ -0,0 +1,42 @@
1
+ module Chronic
2
+ # Tokens are tagged with subclassed instances of this class when
3
+ # they match specific criteria
4
+ class Tag
5
+
6
+ # @return [Symbol]
7
+ attr_accessor :type
8
+
9
+ # @param [Symbol] type
10
+ def initialize(type)
11
+ @type = type
12
+ end
13
+
14
+ # @param [Time] s Set the start timestamp for this Tag
15
+ def start=(s)
16
+ @now = s
17
+ end
18
+
19
+ class << self
20
+ private
21
+
22
+ # @param [Token] token
23
+ # @param [Class] klass The class instance to create
24
+ # @param [Regexp, Hash] items
25
+ # @return [Object, nil] either a new instance of `klass` or `nil` if
26
+ # no match is found
27
+ def scan_for(token, klass, items={})
28
+ case items
29
+ when Regexp
30
+ return klass.new(token.word) if items =~ token.word
31
+ when Hash
32
+ items.each do |item, symbol|
33
+ return klass.new(symbol) if item =~ token.word
34
+ end
35
+ end
36
+ nil
37
+ end
38
+
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,30 @@
1
+ module Chronic
2
+ class TimeZone < Tag
3
+
4
+ # Scan an Array of {Token}s and apply any necessary TimeZone tags to
5
+ # each token
6
+ #
7
+ # @param [Array<Token>] tokens Array of tokens to scan
8
+ # @param [Hash] options Options specified in {Chronic.parse}
9
+ # @return [Array] list of tokens
10
+ def self.scan(tokens, options)
11
+ tokens.each do |token|
12
+ if t = scan_for_all(token) then token.tag(t); next end
13
+ end
14
+ end
15
+
16
+ # @param [Token] token
17
+ # @return [TimeZone, nil]
18
+ def self.scan_for_all(token)
19
+ scan_for token, self,
20
+ {
21
+ /[PMCE][DS]T|UTC/i => :tz,
22
+ /(tzminus)?\d{2}:?\d{2}/ => :tz
23
+ }
24
+ end
25
+
26
+ def to_s
27
+ 'timezone'
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,45 @@
1
+ module Chronic
2
+ class Token
3
+
4
+ # @return [String] The word this Token represents
5
+ attr_accessor :word
6
+
7
+ # @return [Array] A list of tag associated with this Token
8
+ attr_accessor :tags
9
+
10
+ def initialize(word)
11
+ @word = word
12
+ @tags = []
13
+ end
14
+
15
+ # Tag this token with the specified tag
16
+ #
17
+ # @param [Tag] new_tag An instance of {Tag} or one of its subclasses
18
+ def tag(new_tag)
19
+ @tags << new_tag
20
+ end
21
+
22
+ # Remove all tags of the given class
23
+ #
24
+ # @param [Class] The tag class to remove
25
+ def untag(tag_class)
26
+ @tags.delete_if { |m| m.kind_of? tag_class }
27
+ end
28
+
29
+ # @return [Boolean] true if this token has any tags
30
+ def tagged?
31
+ @tags.size > 0
32
+ end
33
+
34
+ # @param [Class] tag_class The tag class to search for
35
+ # @return [Tag] The first Tag that matches the given class
36
+ def get_tag(tag_class)
37
+ @tags.find { |m| m.kind_of? tag_class }
38
+ end
39
+
40
+ # Print this Token in a pretty way
41
+ def to_s
42
+ @word << '(' << @tags.join(', ') << ') '
43
+ end
44
+ end
45
+ end
data/lib/chronic.rb ADDED
@@ -0,0 +1,118 @@
1
+ require 'time'
2
+ require 'date'
3
+
4
+ # Parse natural language dates and times into Time or {Chronic::Span} objects
5
+ #
6
+ # @example
7
+ # require 'chronic'
8
+ #
9
+ # Time.now #=> Sun Aug 27 23:18:25 PDT 2006
10
+ #
11
+ # Chronic.parse('tomorrow')
12
+ # #=> Mon Aug 28 12:00:00 PDT 2006
13
+ #
14
+ # Chronic.parse('monday', :context => :past)
15
+ # #=> Mon Aug 21 12:00:00 PDT 2006
16
+ #
17
+ # Chronic.parse('this tuesday 5:00')
18
+ # #=> Tue Aug 29 17:00:00 PDT 2006
19
+ #
20
+ # Chronic.parse('this tuesday 5:00', :ambiguous_time_range => :none)
21
+ # #=> Tue Aug 29 05:00:00 PDT 2006
22
+ #
23
+ # Chronic.parse('may 27th', :now => Time.local(2000, 1, 1))
24
+ # #=> Sat May 27 12:00:00 PDT 2000
25
+ #
26
+ # Chronic.parse('may 27th', :guess => false)
27
+ # #=> Sun May 27 00:00:00 PDT 2007..Mon May 28 00:00:00 PDT 2007
28
+ #
29
+ # @author Tom Preston-Werner, Lee Jarvis
30
+ module Chronic
31
+ VERSION = "0.6.4.2"
32
+
33
+ class << self
34
+
35
+ # @return [Boolean] true when debug mode is enabled
36
+ attr_accessor :debug
37
+
38
+ # @example
39
+ # require 'chronic'
40
+ # require 'active_support/time'
41
+ #
42
+ # Time.zone = 'UTC'
43
+ # Chronic.time_class = Time.zone
44
+ # Chronic.parse('June 15 2006 at 5:54 AM')
45
+ # # => Thu, 15 Jun 2006 05:45:00 UTC +00:00
46
+ #
47
+ # @return [Time] The time class Chronic uses internally
48
+ attr_accessor :time_class
49
+
50
+ # The current Time Chronic is using to base from
51
+ #
52
+ # @example
53
+ # Time.now #=> 2011-06-06 14:13:43 +0100
54
+ # Chronic.parse('yesterday') #=> 2011-06-05 12:00:00 +0100
55
+ #
56
+ # now = Time.local(2025, 12, 24)
57
+ # Chronic.parse('tomorrow', :now => now) #=> 2025-12-25 12:00:00 +0000
58
+ #
59
+ # @return [Time, nil]
60
+ attr_accessor :now
61
+
62
+ # To ensure thread safety, this value is thread-local.
63
+ def time_class
64
+ Thread.current[:__Chronic_time_class] || ::Time
65
+ end
66
+ def time_class=(time)
67
+ Thread.current[:__Chronic_time_class] = time
68
+ end
69
+ end
70
+
71
+ self.debug = false
72
+ end
73
+
74
+ require 'chronic/chronic'
75
+ require 'chronic/handler'
76
+ require 'chronic/handlers'
77
+ require 'chronic/mini_date'
78
+ require 'chronic/tag'
79
+ require 'chronic/span'
80
+ require 'chronic/token'
81
+ require 'chronic/grabber'
82
+ require 'chronic/pointer'
83
+ require 'chronic/scalar'
84
+ require 'chronic/ordinal'
85
+ require 'chronic/separator'
86
+ require 'chronic/time_zone'
87
+ require 'chronic/numerizer'
88
+ require 'chronic/season'
89
+
90
+ require 'chronic/repeater'
91
+ require 'chronic/repeaters/repeater_year'
92
+ require 'chronic/repeaters/repeater_season'
93
+ require 'chronic/repeaters/repeater_season_name'
94
+ require 'chronic/repeaters/repeater_month'
95
+ require 'chronic/repeaters/repeater_month_name'
96
+ require 'chronic/repeaters/repeater_fortnight'
97
+ require 'chronic/repeaters/repeater_week'
98
+ require 'chronic/repeaters/repeater_weekend'
99
+ require 'chronic/repeaters/repeater_weekday'
100
+ require 'chronic/repeaters/repeater_day'
101
+ require 'chronic/repeaters/repeater_day_name'
102
+ require 'chronic/repeaters/repeater_day_portion'
103
+ require 'chronic/repeaters/repeater_hour'
104
+ require 'chronic/repeaters/repeater_minute'
105
+ require 'chronic/repeaters/repeater_second'
106
+ require 'chronic/repeaters/repeater_time'
107
+
108
+ class Time
109
+ def self.construct(year, month = 1, day = 1, hour = 0, minute = 0, second = 0)
110
+ warn "Chronic.construct will be deprecated in version 0.7.0. Please use Chronic.construct instead"
111
+ Chronic.construct(year, month, day, hour, minute, second)
112
+ end
113
+
114
+ def to_minidate
115
+ warn "Time.to_minidate will be deprecated in version 0.7.0. Please use Chronic::MiniDate.from_time(time) instead"
116
+ Chronic::MiniDate.from_time(self)
117
+ end
118
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,6 @@
1
+ unless defined? Chronic
2
+ $:.unshift File.expand_path('../../lib', __FILE__)
3
+ require 'chronic'
4
+ end
5
+
6
+ require 'test/unit'
@@ -0,0 +1,148 @@
1
+ require 'helper'
2
+
3
+ class TestChronic < Test::Unit::TestCase
4
+
5
+ def setup
6
+ # Wed Aug 16 14:00:00 UTC 2006
7
+ @now = Time.local(2006, 8, 16, 14, 0, 0, 0)
8
+ end
9
+
10
+ def test_pre_normalize_numerized_string
11
+ string = 'two and a half years'
12
+ assert_equal Chronic::Numerizer.numerize(string), Chronic.pre_normalize(string)
13
+ end
14
+
15
+ def test_post_normalize_am_pm_aliases
16
+ # affect wanted patterns
17
+
18
+ tokens = [Chronic::Token.new("5:00"), Chronic::Token.new("morning")]
19
+ tokens[0].tag(Chronic::RepeaterTime.new("5:00"))
20
+ tokens[1].tag(Chronic::RepeaterDayPortion.new(:morning))
21
+
22
+ assert_equal :morning, tokens[1].tags[0].type
23
+
24
+ tokens = Chronic::Handlers.dealias_and_disambiguate_times(tokens, {})
25
+
26
+ assert_equal :am, tokens[1].tags[0].type
27
+ assert_equal 2, tokens.size
28
+
29
+ # don't affect unwanted patterns
30
+
31
+ tokens = [Chronic::Token.new("friday"), Chronic::Token.new("morning")]
32
+ tokens[0].tag(Chronic::RepeaterDayName.new(:friday))
33
+ tokens[1].tag(Chronic::RepeaterDayPortion.new(:morning))
34
+
35
+ assert_equal :morning, tokens[1].tags[0].type
36
+
37
+ tokens = Chronic::Handlers.dealias_and_disambiguate_times(tokens, {})
38
+
39
+ assert_equal :morning, tokens[1].tags[0].type
40
+ assert_equal 2, tokens.size
41
+ end
42
+
43
+ def test_guess
44
+ span = Chronic::Span.new(Time.local(2006, 8, 16, 0), Time.local(2006, 8, 17, 0))
45
+ assert_equal Time.local(2006, 8, 16, 12), Chronic.guess(span)
46
+
47
+ span = Chronic::Span.new(Time.local(2006, 8, 16, 0), Time.local(2006, 8, 17, 0, 0, 1))
48
+ assert_equal Time.local(2006, 8, 16, 12), Chronic.guess(span)
49
+
50
+ span = Chronic::Span.new(Time.local(2006, 11), Time.local(2006, 12))
51
+ assert_equal Time.local(2006, 11, 16), Chronic.guess(span)
52
+ end
53
+
54
+ def test_now
55
+ Chronic.parse('now', :now => Time.local(2006, 01))
56
+ assert_equal Time.local(2006, 01), Chronic.now
57
+
58
+ Chronic.parse('now', :now => Time.local(2007, 01))
59
+ assert_equal Time.local(2007, 01), Chronic.now
60
+ end
61
+
62
+ def test_endian_definitions
63
+ # middle, little
64
+ endians = [
65
+ Chronic::Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_day, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sm_sd_sy),
66
+ Chronic::Handler.new([:scalar_day, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sd_sm_sy)
67
+ ]
68
+
69
+ assert_equal endians, Chronic.definitions[:endian]
70
+
71
+ defs = Chronic.definitions(:endian_precedence => :little)
72
+ assert_equal endians.reverse, defs[:endian]
73
+
74
+ defs = Chronic.definitions(:endian_precedence => [:little, :middle])
75
+ assert_equal endians.reverse, defs[:endian]
76
+
77
+ assert_raises(ArgumentError) do
78
+ Chronic.definitions(:endian_precedence => :invalid)
79
+ end
80
+ end
81
+
82
+ def test_passing_options
83
+ assert_raises(ArgumentError) do
84
+ Chronic.parse('now', :invalid => :option)
85
+ end
86
+
87
+ assert_raises(ArgumentError) do
88
+ Chronic.parse('now', :context => :invalid_context)
89
+ end
90
+ end
91
+
92
+ def test_debug
93
+ require 'stringio'
94
+ $stdout = StringIO.new
95
+ Chronic.debug = true
96
+
97
+ Chronic.parse 'now'
98
+ assert $stdout.string.include?('this(grabber-this)')
99
+ ensure
100
+ $stdout = STDOUT
101
+ Chronic.debug = false
102
+ end
103
+
104
+ # Chronic.construct
105
+
106
+ def test_normal
107
+ assert_equal Time.local(2006, 1, 2, 0, 0, 0), Chronic.construct(2006, 1, 2, 0, 0, 0)
108
+ assert_equal Time.local(2006, 1, 2, 3, 0, 0), Chronic.construct(2006, 1, 2, 3, 0, 0)
109
+ assert_equal Time.local(2006, 1, 2, 3, 4, 0), Chronic.construct(2006, 1, 2, 3, 4, 0)
110
+ assert_equal Time.local(2006, 1, 2, 3, 4, 5), Chronic.construct(2006, 1, 2, 3, 4, 5)
111
+ end
112
+
113
+ def test_second_overflow
114
+ assert_equal Time.local(2006, 1, 1, 0, 1, 30), Chronic.construct(2006, 1, 1, 0, 0, 90)
115
+ assert_equal Time.local(2006, 1, 1, 0, 5, 0), Chronic.construct(2006, 1, 1, 0, 0, 300)
116
+ end
117
+
118
+ def test_minute_overflow
119
+ assert_equal Time.local(2006, 1, 1, 1, 30), Chronic.construct(2006, 1, 1, 0, 90)
120
+ assert_equal Time.local(2006, 1, 1, 5), Chronic.construct(2006, 1, 1, 0, 300)
121
+ end
122
+
123
+ def test_hour_overflow
124
+ assert_equal Time.local(2006, 1, 2, 12), Chronic.construct(2006, 1, 1, 36)
125
+ assert_equal Time.local(2006, 1, 7), Chronic.construct(2006, 1, 1, 144)
126
+ end
127
+
128
+ def test_day_overflow
129
+ assert_equal Time.local(2006, 2, 1), Chronic.construct(2006, 1, 32)
130
+ assert_equal Time.local(2006, 3, 5), Chronic.construct(2006, 2, 33)
131
+ assert_equal Time.local(2004, 3, 4), Chronic.construct(2004, 2, 33)
132
+ assert_equal Time.local(2000, 3, 4), Chronic.construct(2000, 2, 33)
133
+
134
+ assert_nothing_raised do
135
+ Chronic.construct(2006, 1, 56)
136
+ end
137
+
138
+ assert_raise(RuntimeError) do
139
+ Chronic.construct(2006, 1, 57)
140
+ end
141
+ end
142
+
143
+ def test_month_overflow
144
+ assert_equal Time.local(2006, 1), Chronic.construct(2005, 13)
145
+ assert_equal Time.local(2005, 12), Chronic.construct(2000, 72)
146
+ end
147
+
148
+ end