chronic 0.4.0 → 0.5.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 (47) hide show
  1. data/.gemtest +0 -0
  2. data/.gitignore +5 -0
  3. data/.yardopts +3 -0
  4. data/HISTORY.md +39 -1
  5. data/Manifest.txt +7 -2
  6. data/README.md +8 -2
  7. data/Rakefile +9 -101
  8. data/chronic.gemspec +12 -77
  9. data/lib/chronic/chronic.rb +225 -91
  10. data/lib/chronic/grabber.rb +12 -3
  11. data/lib/chronic/handlers.rb +95 -199
  12. data/lib/chronic/mini_date.rb +8 -3
  13. data/lib/chronic/ordinal.rb +14 -5
  14. data/lib/chronic/pointer.rb +11 -5
  15. data/lib/chronic/repeater.rb +31 -19
  16. data/lib/chronic/repeaters/repeater_day.rb +0 -1
  17. data/lib/chronic/repeaters/repeater_day_name.rb +0 -1
  18. data/lib/chronic/repeaters/repeater_day_portion.rb +0 -1
  19. data/lib/chronic/repeaters/repeater_fortnight.rb +0 -1
  20. data/lib/chronic/repeaters/repeater_hour.rb +0 -1
  21. data/lib/chronic/repeaters/repeater_minute.rb +0 -1
  22. data/lib/chronic/repeaters/repeater_month.rb +13 -2
  23. data/lib/chronic/repeaters/repeater_month_name.rb +13 -21
  24. data/lib/chronic/repeaters/repeater_season.rb +0 -19
  25. data/lib/chronic/repeaters/repeater_season_name.rb +0 -2
  26. data/lib/chronic/repeaters/repeater_second.rb +0 -1
  27. data/lib/chronic/repeaters/repeater_time.rb +1 -2
  28. data/lib/chronic/repeaters/repeater_week.rb +0 -1
  29. data/lib/chronic/repeaters/repeater_weekday.rb +0 -1
  30. data/lib/chronic/repeaters/repeater_weekend.rb +0 -1
  31. data/lib/chronic/repeaters/repeater_year.rb +20 -9
  32. data/lib/chronic/scalar.rb +30 -5
  33. data/lib/chronic/season.rb +37 -0
  34. data/lib/chronic/separator.rb +24 -9
  35. data/lib/chronic/tag.rb +17 -3
  36. data/lib/chronic/time_zone.rb +12 -3
  37. data/lib/chronic/token.rb +14 -4
  38. data/lib/chronic.rb +62 -49
  39. data/test/test_Chronic.rb +52 -2
  40. data/test/test_MiniDate.rb +32 -0
  41. data/test/test_RepeaterMonth.rb +4 -0
  42. data/test/test_RepeaterSeason.rb +40 -0
  43. data/test/test_RepeaterYear.rb +7 -0
  44. data/test/test_Time.rb +1 -1
  45. data/test/test_parsing.rb +159 -113
  46. metadata +33 -29
  47. data/benchmark/benchmark.rb +0 -13
data/lib/chronic.rb CHANGED
@@ -1,33 +1,83 @@
1
- #=============================================================================
1
+ require 'time'
2
+ require 'date'
3
+
4
+ # Parse natural language dates and times into Time or {Chronic::Span} objects
2
5
  #
3
- # Name: Chronic
4
- # Author: Tom Preston-Werner
5
- # Purpose: Parse natural language dates and times into Time or
6
- # Chronic::Span objects
6
+ # @example
7
+ # require 'chronic'
7
8
  #
8
- #=============================================================================
9
-
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
10
30
  module Chronic
11
- VERSION = "0.4.0"
31
+ VERSION = "0.5.0"
12
32
 
13
33
  class << self
34
+
35
+ # @return [Boolean] true when debug mode is enabled
14
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
15
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
16
61
  end
17
62
 
18
63
  self.debug = false
19
64
  self.time_class = Time
20
65
  end
21
66
 
22
- require 'time'
23
- require 'date'
24
-
25
67
  require 'chronic/chronic'
26
68
  require 'chronic/handlers'
27
69
  require 'chronic/mini_date'
28
70
  require 'chronic/tag'
29
71
  require 'chronic/span'
30
72
  require 'chronic/token'
73
+ require 'chronic/grabber'
74
+ require 'chronic/pointer'
75
+ require 'chronic/scalar'
76
+ require 'chronic/ordinal'
77
+ require 'chronic/separator'
78
+ require 'chronic/time_zone'
79
+ require 'chronic/numerizer'
80
+ require 'chronic/season'
31
81
 
32
82
  require 'chronic/repeater'
33
83
  require 'chronic/repeaters/repeater_year'
@@ -47,42 +97,6 @@ require 'chronic/repeaters/repeater_minute'
47
97
  require 'chronic/repeaters/repeater_second'
48
98
  require 'chronic/repeaters/repeater_time'
49
99
 
50
- require 'chronic/grabber'
51
- require 'chronic/pointer'
52
- require 'chronic/scalar'
53
- require 'chronic/ordinal'
54
- require 'chronic/separator'
55
- require 'chronic/time_zone'
56
-
57
- require 'chronic/numerizer'
58
-
59
- # class Time
60
- # def self.construct(year, month = 1, day = 1, hour = 0, minute = 0, second = 0)
61
- # # extra_seconds = second > 60 ? second - 60 : 0
62
- # # extra_minutes = minute > 59 ? minute - 59 : 0
63
- # # extra_hours = hour > 23 ? hour - 23 : 0
64
- # # extra_days = day >
65
- #
66
- # if month > 12
67
- # if month % 12 == 0
68
- # year += (month - 12) / 12
69
- # month = 12
70
- # else
71
- # year += month / 12
72
- # month = month % 12
73
- # end
74
- # end
75
- #
76
- # base = Time.local(year, month)
77
- # puts base
78
- # offset = ((day - 1) * 24 * 60 * 60) + (hour * 60 * 60) + (minute * 60) + second
79
- # puts offset.to_s
80
- # date = base + offset
81
- # puts date
82
- # date
83
- # end
84
- # end
85
-
86
100
  class Time
87
101
  def self.construct(year, month = 1, day = 1, hour = 0, minute = 0, second = 0)
88
102
  if second >= 60
@@ -105,10 +119,9 @@ class Time
105
119
  day <= 56 || raise("day must be no more than 56 (makes month resolution easier)")
106
120
  if day > 28
107
121
  # no month ever has fewer than 28 days, so only do this if necessary
108
- leap_year = (year % 4 == 0) && !(year % 100 == 0)
109
122
  leap_year_month_days = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
110
123
  common_year_month_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
111
- days_this_month = leap_year ? leap_year_month_days[month - 1] : common_year_month_days[month - 1]
124
+ days_this_month = Date.leap?(year) ? leap_year_month_days[month - 1] : common_year_month_days[month - 1]
112
125
  if day > days_this_month
113
126
  month += day / days_this_month
114
127
  day = day % days_this_month
data/test/test_Chronic.rb CHANGED
@@ -21,7 +21,7 @@ class TestChronic < Test::Unit::TestCase
21
21
 
22
22
  assert_equal :morning, tokens[1].tags[0].type
23
23
 
24
- tokens = Chronic.dealias_and_disambiguate_times(tokens, {})
24
+ tokens = Chronic::Handlers.dealias_and_disambiguate_times(tokens, {})
25
25
 
26
26
  assert_equal :am, tokens[1].tags[0].type
27
27
  assert_equal 2, tokens.size
@@ -34,7 +34,7 @@ class TestChronic < Test::Unit::TestCase
34
34
 
35
35
  assert_equal :morning, tokens[1].tags[0].type
36
36
 
37
- tokens = Chronic.dealias_and_disambiguate_times(tokens, {})
37
+ tokens = Chronic::Handlers.dealias_and_disambiguate_times(tokens, {})
38
38
 
39
39
  assert_equal :morning, tokens[1].tags[0].type
40
40
  assert_equal 2, tokens.size
@@ -51,4 +51,54 @@ class TestChronic < Test::Unit::TestCase
51
51
  assert_equal Time.local(2006, 11, 16), Chronic.guess(span)
52
52
  end
53
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(Chronic::InvalidArgumentException) do
78
+ Chronic.definitions(:endian_precedence => :invalid)
79
+ end
80
+ end
81
+
82
+ def test_passing_options
83
+ assert_raises(Chronic::InvalidArgumentException) do
84
+ Chronic.parse('now', :invalid => :option)
85
+ end
86
+
87
+ assert_raises(Chronic::InvalidArgumentException) 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
+
54
104
  end
@@ -0,0 +1,32 @@
1
+ require File.join(File.dirname(__FILE__), *%w[helper])
2
+
3
+ class TestMiniDate < Test::Unit::TestCase
4
+ def test_valid_month
5
+ assert_raise(Chronic::InvalidArgumentException){ Chronic::MiniDate.new(0,12) }
6
+ assert_raise(Chronic::InvalidArgumentException){ Chronic::MiniDate.new(13,1) }
7
+ end
8
+
9
+ def test_is_between
10
+ m=Chronic::MiniDate.new(3,2)
11
+ assert m.is_between?(Chronic::MiniDate.new(2,4), Chronic::MiniDate.new(4,7))
12
+ assert !m.is_between?(Chronic::MiniDate.new(1,5), Chronic::MiniDate.new(2,7))
13
+
14
+ #There was a hang if date tested is in december and outside the testing range
15
+ m=Chronic::MiniDate.new(12,24)
16
+ assert !m.is_between?(Chronic::MiniDate.new(10,1), Chronic::MiniDate.new(12,21))
17
+ end
18
+
19
+ def test_is_between_short_range
20
+ m=Chronic::MiniDate.new(5,10)
21
+ assert m.is_between?(Chronic::MiniDate.new(5,3), Chronic::MiniDate.new(5,12))
22
+ assert !m.is_between?(Chronic::MiniDate.new(5,11), Chronic::MiniDate.new(5,15))
23
+ end
24
+
25
+ def test_is_between_wrapping_range
26
+ m=Chronic::MiniDate.new(1,1)
27
+ assert m.is_between?(Chronic::MiniDate.new(11,11), Chronic::MiniDate.new(2,2))
28
+ m=Chronic::MiniDate.new(12,12)
29
+ assert m.is_between?(Chronic::MiniDate.new(11,11), Chronic::MiniDate.new(1,5))
30
+ end
31
+
32
+ end
@@ -23,6 +23,10 @@ class TestRepeaterMonth < Test::Unit::TestCase
23
23
 
24
24
  time = Chronic::RepeaterMonth.new(:month).offset_by(@now, 10, :past)
25
25
  assert_equal Time.local(2005, 10, 16, 14), time
26
+
27
+ time = Chronic::RepeaterMonth.new(:month).offset_by(Time.local(2010, 3, 29), 1, :past)
28
+ assert_equal 2, time.month
29
+ assert_equal 28, time.day
26
30
  end
27
31
 
28
32
  def test_offset
@@ -0,0 +1,40 @@
1
+ require File.join(File.dirname(__FILE__), *%w[helper])
2
+
3
+ class TestRepeaterSeason < Test::Unit::TestCase
4
+
5
+ def setup
6
+ @now = Time.local(2006, 8, 16, 14, 0, 0, 0)
7
+ end
8
+
9
+ def test_next_future
10
+ seasons = Chronic::RepeaterSeason.new(:season)
11
+ seasons.start = @now
12
+
13
+ next_season = seasons.next(:future)
14
+ assert_equal Time.local(2006, 9, 23), next_season.begin
15
+ assert_equal Time.local(2006, 12, 21), next_season.end
16
+ end
17
+
18
+ def test_next_past
19
+ seasons = Chronic::RepeaterSeason.new(:season)
20
+ seasons.start = @now
21
+
22
+ last_season = seasons.next(:past)
23
+ assert_equal Time.local(2006, 3, 20), last_season.begin
24
+ assert_equal Time.local(2006, 6, 20), last_season.end
25
+ end
26
+
27
+ def test_this
28
+ seasons = Chronic::RepeaterSeason.new(:season)
29
+ seasons.start = @now
30
+
31
+ this_season = seasons.this(:future)
32
+ assert_equal Time.local(2006, 8, 17), this_season.begin
33
+ assert_equal Time.local(2006, 9, 22), this_season.end
34
+
35
+ this_season = seasons.this(:past)
36
+ assert_equal Time.local(2006, 6, 21), this_season.begin
37
+ assert_equal Time.local(2006, 8, 16), this_season.end
38
+ end
39
+
40
+ end
@@ -57,6 +57,13 @@ class TestRepeaterYear < Test::Unit::TestCase
57
57
 
58
58
  assert_equal Time.local(1996, 8, 16, 14), offset_span.begin
59
59
  assert_equal Time.local(1996, 8, 16, 14, 0, 1), offset_span.end
60
+
61
+ now = Time.local(2008, 2, 29)
62
+ span = Chronic::Span.new(now, now + 1)
63
+ offset_span = Chronic::RepeaterYear.new(:year).offset(span, 1, :past)
64
+
65
+ assert_equal Time.local(2007, 2, 28), offset_span.begin
66
+ assert_equal Time.local(2007, 2, 28, 0, 0, 1), offset_span.end
60
67
  end
61
68
 
62
69
  end
data/test/test_Time.rb CHANGED
@@ -31,7 +31,7 @@ class TestTime < Test::Unit::TestCase
31
31
  assert_equal Time.local(2006, 2, 1), Time.construct(2006, 1, 32)
32
32
  assert_equal Time.local(2006, 3, 5), Time.construct(2006, 2, 33)
33
33
  assert_equal Time.local(2004, 3, 4), Time.construct(2004, 2, 33)
34
- assert_equal Time.local(2000, 3, 5), Time.construct(2000, 2, 33)
34
+ assert_equal Time.local(2000, 3, 4), Time.construct(2000, 2, 33)
35
35
 
36
36
  assert_nothing_raised do
37
37
  Time.construct(2006, 1, 56)