chronic 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/HISTORY.md +27 -0
  2. data/Manifest.txt +16 -5
  3. data/README.md +14 -8
  4. data/Rakefile +2 -8
  5. data/chronic.gemspec +8 -11
  6. data/lib/chronic.rb +21 -14
  7. data/lib/chronic/chronic.rb +38 -130
  8. data/lib/chronic/grabber.rb +11 -15
  9. data/lib/chronic/handlers.rb +63 -40
  10. data/lib/chronic/mini_date.rb +27 -0
  11. data/lib/chronic/numerizer.rb +120 -0
  12. data/lib/chronic/ordinal.rb +5 -10
  13. data/lib/chronic/pointer.rb +8 -10
  14. data/lib/chronic/repeater.rb +106 -109
  15. data/lib/chronic/repeaters/repeater_day.rb +43 -41
  16. data/lib/chronic/repeaters/repeater_day_name.rb +38 -36
  17. data/lib/chronic/repeaters/repeater_day_portion.rb +74 -73
  18. data/lib/chronic/repeaters/repeater_fortnight.rb +57 -55
  19. data/lib/chronic/repeaters/repeater_hour.rb +46 -44
  20. data/lib/chronic/repeaters/repeater_minute.rb +46 -44
  21. data/lib/chronic/repeaters/repeater_month.rb +52 -50
  22. data/lib/chronic/repeaters/repeater_month_name.rb +84 -80
  23. data/lib/chronic/repeaters/repeater_season.rb +97 -119
  24. data/lib/chronic/repeaters/repeater_season_name.rb +39 -39
  25. data/lib/chronic/repeaters/repeater_second.rb +32 -30
  26. data/lib/chronic/repeaters/repeater_time.rb +106 -101
  27. data/lib/chronic/repeaters/repeater_week.rb +60 -58
  28. data/lib/chronic/repeaters/repeater_weekday.rb +67 -58
  29. data/lib/chronic/repeaters/repeater_weekend.rb +54 -52
  30. data/lib/chronic/repeaters/repeater_year.rb +50 -48
  31. data/lib/chronic/scalar.rb +24 -16
  32. data/lib/chronic/separator.rb +15 -33
  33. data/lib/chronic/span.rb +31 -0
  34. data/lib/chronic/tag.rb +26 -0
  35. data/lib/chronic/time_zone.rb +7 -9
  36. data/lib/chronic/token.rb +35 -0
  37. data/test/helper.rb +5 -6
  38. data/test/test_Chronic.rb +5 -0
  39. data/test/test_Numerizer.rb +60 -39
  40. data/test/test_RepeaterHour.rb +4 -0
  41. data/test/test_parsing.rb +104 -13
  42. metadata +14 -20
  43. data/lib/chronic/numerizer/numerizer.rb +0 -97
@@ -1,53 +1,61 @@
1
1
  module Chronic
2
2
 
3
3
  class Scalar < Tag #:nodoc:
4
- def self.scan(tokens)
4
+ DAY_PORTIONS = %w( am pm morning afternoon evening night )
5
+
6
+ def self.scan(tokens, options)
5
7
  # for each token
6
8
  tokens.each_index do |i|
7
- if t = self.scan_for_scalars(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
8
- if t = self.scan_for_days(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
9
- if t = self.scan_for_months(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
10
- if t = self.scan_for_years(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
9
+ if t = scan_for_scalars(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
10
+ if t = scan_for_days(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
11
+ if t = scan_for_months(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
12
+ if t = scan_for_years(tokens[i], tokens[i + 1], options) then tokens[i].tag(t) end
11
13
  end
12
- tokens
13
14
  end
14
15
 
15
16
  def self.scan_for_scalars(token, post_token)
16
17
  if token.word =~ /^\d*$/
17
- unless post_token && %w{am pm morning afternoon evening night}.include?(post_token)
18
+ unless post_token && DAY_PORTIONS.include?(post_token.word)
18
19
  return Scalar.new(token.word.to_i)
19
20
  end
20
21
  end
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
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))
27
+ unless toi > 31 || toi < 1 || (post_token && DAY_PORTIONS.include?(post_token.word))
28
28
  return ScalarDay.new(toi)
29
29
  end
30
30
  end
31
- return nil
32
31
  end
33
32
 
34
33
  def self.scan_for_months(token, post_token)
35
34
  if token.word =~ /^\d\d?$/
36
35
  toi = token.word.to_i
37
- unless toi > 12 || toi < 1 || (post_token && %w{am pm morning afternoon evening night}.include?(post_token.word))
36
+ unless toi > 12 || toi < 1 || (post_token && DAY_PORTIONS.include?(post_token.word))
38
37
  return ScalarMonth.new(toi)
39
38
  end
40
39
  end
41
- return nil
42
40
  end
43
41
 
44
- def self.scan_for_years(token, post_token)
42
+ def self.scan_for_years(token, post_token, options)
45
43
  if token.word =~ /^([1-9]\d)?\d\d?$/
46
- unless post_token && %w{am pm morning afternoon evening night}.include?(post_token.word)
47
- return ScalarYear.new(token.word.to_i)
44
+ unless post_token && DAY_PORTIONS.include?(post_token.word)
45
+ year = make_year(token.word.to_i, options[:ambiguous_year_future_bias])
46
+ return ScalarYear.new(year.to_i)
48
47
  end
49
48
  end
50
- return nil
49
+ end
50
+
51
+ # Build a year from a 2 digit suffix
52
+ def self.make_year(year, bias)
53
+ return year if year.to_s.size > 2
54
+ start_year = Chronic.time_class.now.year - bias
55
+ century = (start_year / 100) * 100
56
+ full_year = century + year
57
+ full_year += 100 if full_year < start_year
58
+ full_year
51
59
  end
52
60
 
53
61
  def to_s
@@ -1,56 +1,38 @@
1
1
  module Chronic
2
2
 
3
3
  class Separator < Tag #:nodoc:
4
- def self.scan(tokens)
4
+ def self.scan(tokens, options)
5
5
  tokens.each_index do |i|
6
- if t = self.scan_for_commas(tokens[i]) then tokens[i].tag(t); next end
7
- if t = self.scan_for_slash_or_dash(tokens[i]) then tokens[i].tag(t); next end
8
- if t = self.scan_for_at(tokens[i]) then tokens[i].tag(t); next end
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
6
+ if t = scan_for_commas(tokens[i]) then tokens[i].tag(t); next end
7
+ if t = scan_for_slash_or_dash(tokens[i]) then tokens[i].tag(t); next end
8
+ if t = scan_for_at(tokens[i]) then tokens[i].tag(t); next end
9
+ if t = scan_for_in(tokens[i]) then tokens[i].tag(t); next end
10
+ if t = scan_for_on(tokens[i]) then tokens[i].tag(t); next end
11
11
  end
12
- tokens
13
12
  end
14
13
 
15
14
  def self.scan_for_commas(token)
16
- scanner = {/^,$/ => :comma}
17
- scanner.keys.each do |scanner_item|
18
- return SeparatorComma.new(scanner[scanner_item]) if scanner_item =~ token.word
19
- end
20
- return nil
15
+ scan_for token, SeparatorComma, { /^,$/ => :comma }
21
16
  end
22
17
 
23
18
  def self.scan_for_slash_or_dash(token)
24
- scanner = {/^-$/ => :dash,
25
- /^\/$/ => :slash}
26
- scanner.keys.each do |scanner_item|
27
- return SeparatorSlashOrDash.new(scanner[scanner_item]) if scanner_item =~ token.word
28
- end
29
- return nil
19
+ scan_for token, SeparatorSlashOrDash,
20
+ {
21
+ /^-$/ => :dash,
22
+ /^\/$/ => :slash
23
+ }
30
24
  end
31
25
 
32
26
  def self.scan_for_at(token)
33
- scanner = {/^(at|@)$/ => :at}
34
- scanner.keys.each do |scanner_item|
35
- return SeparatorAt.new(scanner[scanner_item]) if scanner_item =~ token.word
36
- end
37
- return nil
27
+ scan_for token, SeparatorAt, { /^(at|@)$/ => :at }
38
28
  end
39
29
 
40
30
  def self.scan_for_in(token)
41
- scanner = {/^in$/ => :in}
42
- scanner.keys.each do |scanner_item|
43
- return SeparatorIn.new(scanner[scanner_item]) if scanner_item =~ token.word
44
- end
45
- return nil
31
+ scan_for token, SeparatorIn, { /^in$/ => :in }
46
32
  end
47
33
 
48
34
  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
35
+ scan_for token, SeparatorOn, { /^on$/ => :on }
54
36
  end
55
37
 
56
38
  def to_s
@@ -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,26 @@
1
+ module Chronic
2
+ # Tokens are tagged with subclassed instances of this class when
3
+ # they match specific criteria
4
+ class Tag #:nodoc:
5
+ attr_accessor :type
6
+
7
+ def initialize(type)
8
+ @type = type
9
+ end
10
+
11
+ def start=(s)
12
+ @now = s
13
+ end
14
+
15
+ class << self
16
+ private
17
+
18
+ def scan_for(token, klass, items={})
19
+ items.each do |item, symbol|
20
+ return klass.new(symbol) if item =~ token.word
21
+ end
22
+ nil
23
+ end
24
+ end
25
+ end
26
+ end
@@ -1,19 +1,17 @@
1
1
  module Chronic
2
2
  class TimeZone < Tag #:nodoc:
3
- def self.scan(tokens)
3
+ def self.scan(tokens, options)
4
4
  tokens.each_index do |i|
5
- if t = self.scan_for_all(tokens[i]) then tokens[i].tag(t); next end
5
+ if t = scan_for_all(tokens[i]) then tokens[i].tag(t); next end
6
6
  end
7
- tokens
8
7
  end
9
8
 
10
9
  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
10
+ scan_for token, self,
11
+ {
12
+ /[PMCE][DS]T/i => :tz,
13
+ /(tzminus)?\d{4}/ => :tz
14
+ }
17
15
  end
18
16
 
19
17
  def to_s
@@ -0,0 +1,35 @@
1
+ module Chronic
2
+ class Token #:nodoc:
3
+ attr_accessor :word, :tags
4
+
5
+ def initialize(word)
6
+ @word = word
7
+ @tags = []
8
+ end
9
+
10
+ # Tag this token with the specified tag
11
+ def tag(new_tag)
12
+ @tags << new_tag
13
+ end
14
+
15
+ # Remove all tags of the given class
16
+ def untag(tag_class)
17
+ @tags.delete_if { |m| m.kind_of? tag_class }
18
+ end
19
+
20
+ # Return true if this token has any tags
21
+ def tagged?
22
+ @tags.size > 0
23
+ end
24
+
25
+ # Return the Tag that matches the given class
26
+ def get_tag(tag_class)
27
+ @tags.find { |m| m.kind_of? tag_class }
28
+ end
29
+
30
+ # Print this Token in a pretty way
31
+ def to_s
32
+ @word << '(' << @tags.join(', ') << ') '
33
+ end
34
+ end
35
+ end
@@ -1,7 +1,6 @@
1
- require 'test/unit'
1
+ unless defined? Chronic
2
+ $:.unshift File.expand_path('../../lib')
3
+ require 'chronic'
4
+ end
2
5
 
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'
6
+ require 'test/unit'
@@ -7,6 +7,11 @@ class TestChronic < Test::Unit::TestCase
7
7
  @now = Time.local(2006, 8, 16, 14, 0, 0, 0)
8
8
  end
9
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
+
10
15
  def test_post_normalize_am_pm_aliases
11
16
  # affect wanted patterns
12
17
 
@@ -3,49 +3,70 @@ require File.join(File.dirname(__FILE__), *%w[helper])
3
3
  class ParseNumbersTest < Test::Unit::TestCase
4
4
 
5
5
  def test_straight_parsing
6
- strings = { 1 => 'one',
7
- 5 => 'five',
8
- 10 => 'ten',
9
- 11 => 'eleven',
10
- 12 => 'twelve',
11
- 13 => 'thirteen',
12
- 14 => 'fourteen',
13
- 15 => 'fifteen',
14
- 16 => 'sixteen',
15
- 17 => 'seventeen',
16
- 18 => 'eighteen',
17
- 19 => 'nineteen',
18
- 20 => 'twenty',
19
- 27 => 'twenty seven',
20
- 31 => 'thirty-one',
21
- 59 => 'fifty nine',
22
- 100 => 'a hundred',
23
- 100 => 'one hundred',
24
- 150 => 'one hundred and fifty',
25
- # 150 => 'one fifty',
26
- 200 => 'two-hundred',
27
- 500 => '5 hundred',
28
- 999 => 'nine hundred and ninety nine',
29
- 1_000 => 'one thousand',
30
- 1_200 => 'twelve hundred',
31
- 1_200 => 'one thousand two hundred',
32
- 17_000 => 'seventeen thousand',
33
- 21_473 => 'twentyone-thousand-four-hundred-and-seventy-three',
34
- 74_002 => 'seventy four thousand and two',
35
- 99_999 => 'ninety nine thousand nine hundred ninety nine',
36
- 100_000 => '100 thousand',
37
- 250_000 => 'two hundred fifty thousand',
38
- 1_000_000 => 'one million',
39
- 1_250_007 => 'one million two hundred fifty thousand and seven',
40
- 1_000_000_000 => 'one billion',
41
- 1_000_000_001 => 'one billion and one' }
6
+ strings = {
7
+ 'one' => 1,
8
+ 'five' => 5,
9
+ 'ten' => 10,
10
+ 'eleven' => 11,
11
+ 'twelve' => 12,
12
+ 'thirteen' => 13,
13
+ 'fourteen' => 14,
14
+ 'fifteen' => 15,
15
+ 'sixteen' => 16,
16
+ 'seventeen' => 17,
17
+ 'eighteen' => 18,
18
+ 'nineteen' => 19,
19
+ 'twenty' => 20,
20
+ 'twenty seven' => 27,
21
+ 'thirty-one' => 31,
22
+ 'thirty-seven' => 37,
23
+ 'thirty seven' => 37,
24
+ 'fifty nine' => 59,
25
+ 'forty two' => 42,
26
+ 'fourty two' => 42,
27
+ # 'a hundred' => 100,
28
+ 'one hundred' => 100,
29
+ 'one hundred and fifty' => 150,
30
+ # 'one fifty' => 150,
31
+ 'two-hundred' => 200,
32
+ '5 hundred' => 500,
33
+ 'nine hundred and ninety nine' => 999,
34
+ 'one thousand' => 1000,
35
+ 'twelve hundred' => 1200,
36
+ 'one thousand two hundred' => 1_200,
37
+ 'seventeen thousand' => 17_000,
38
+ 'twentyone-thousand-four-hundred-and-seventy-three' => 21_473,
39
+ 'seventy four thousand and two' => 74_002,
40
+ 'ninety nine thousand nine hundred ninety nine' => 99_999,
41
+ '100 thousand' => 100_000,
42
+ 'two hundred fifty thousand' => 250_000,
43
+ 'one million' => 1_000_000,
44
+ 'one million two hundred fifty thousand and seven' => 1_250_007,
45
+ 'one billion' => 1_000_000_000,
46
+ 'one billion and one' => 1_000_000_001}
42
47
 
43
- strings.keys.sort.each do |key|
44
- assert_equal key, Numerizer.numerize(strings[key]).to_i
48
+ strings.each do |key, val|
49
+ assert_equal val, Chronic::Numerizer.numerize(key).to_i
50
+ end
51
+ end
52
+
53
+ def test_ordinal_strings
54
+ {
55
+ 'first' => '1st',
56
+ 'second' => 'second',
57
+ 'second day' => '2nd day',
58
+ 'second of may' => '2nd of may',
59
+ 'fifth' => '5th',
60
+ 'twenty third' => '23rd',
61
+ 'first day month two' => '1st day month 2'
62
+ }.each do |key, val|
63
+ # Use pre_normalize here instead of Numerizer directly because
64
+ # pre_normalize deals with parsing 'second' appropriately
65
+ assert_equal val, Chronic.pre_normalize(key)
45
66
  end
46
67
  end
47
68
 
48
69
  def test_edges
49
- assert_equal "27 Oct 2006 7:30am", Numerizer.numerize("27 Oct 2006 7:30am")
70
+ assert_equal "27 Oct 2006 7:30am", Chronic::Numerizer.numerize("27 Oct 2006 7:30am")
50
71
  end
51
72
  end
@@ -45,6 +45,10 @@ class TestRepeaterHour < Test::Unit::TestCase
45
45
  this_hour = hours.this(:past)
46
46
  assert_equal Time.local(2006, 8, 16, 14), this_hour.begin
47
47
  assert_equal Time.local(2006, 8, 16, 14, 30), this_hour.end
48
+
49
+ this_hour = hours.this(:none)
50
+ assert_equal Time.local(2006, 8, 16, 14), this_hour.begin
51
+ assert_equal Time.local(2006, 8, 16, 15), this_hour.end
48
52
  end
49
53
 
50
54
  def test_offset
@@ -26,6 +26,20 @@ class TestParsing < Test::Unit::TestCase
26
26
  time = parse_now("may 28 at 5:32.19pm", :context => :past)
27
27
  assert_equal Time.local(2006, 5, 28, 17, 32, 19), time
28
28
 
29
+ # rm_sd for current month
30
+
31
+ time = parse_now("aug 3")
32
+ assert_equal Time.local(2006, 8, 3, 12), time
33
+
34
+ time = parse_now("aug 3", :context => :past)
35
+ assert_equal Time.local(2006, 8, 3, 12), time
36
+
37
+ time = parse_now("aug 20")
38
+ assert_equal Time.local(2006, 8, 20, 12), time
39
+
40
+ time = parse_now("aug 20", :context => :future)
41
+ assert_equal Time.local(2006, 8, 20, 12), time
42
+
29
43
  # rm_sd_on
30
44
 
31
45
  time = parse_now("5pm on may 28")
@@ -59,6 +73,9 @@ class TestParsing < Test::Unit::TestCase
59
73
  time = parse_now("5:00 pm may 27th", :context => :past)
60
74
  assert_equal Time.local(2006, 5, 27, 17), time
61
75
 
76
+ time = parse_now("05:00 pm may 27th", :context => :past)
77
+ assert_equal Time.local(2006, 5, 27, 17), time
78
+
62
79
  time = parse_now("5pm on may 27th", :context => :past)
63
80
  assert_equal Time.local(2006, 5, 27, 17), time
64
81
 
@@ -73,8 +90,46 @@ class TestParsing < Test::Unit::TestCase
73
90
  time = parse_now("dec 79")
74
91
  assert_equal Time.local(1979, 12, 16, 12), time
75
92
 
93
+ # rm_od_sy
94
+
95
+ time = parse_now("November 18th 2010")
96
+ assert_equal Time.local(2010, 11, 18, 12), time
97
+
98
+ time = parse_now("November 18th, 2010")
99
+ assert_equal Time.local(2010, 11, 18, 12), time
100
+
101
+ time = parse_now("November 18th 2010 midnight")
102
+ assert_equal Time.local(2010, 11, 19, 0), time
103
+
104
+ time = parse_now("November 18th 2010 at midnight")
105
+ assert_equal Time.local(2010, 11, 19, 0), time
106
+
107
+ time = parse_now("November 18th 2010 at 4")
108
+ assert_equal Time.local(2010, 11, 18, 16), time
109
+
110
+ time = parse_now("November 18th 2010 at 4", :ambiguous_time_range => :none)
111
+ assert_equal Time.local(2010, 11, 18, 4), time
112
+
113
+ time = parse_now("March 30th, 1979")
114
+ assert_equal Time.local(1979, 3, 30, 12), time
115
+
116
+ time = parse_now("March 30th 79")
117
+ assert_equal Time.local(1979, 3, 30, 12), time
118
+
119
+ time = parse_now("March 30th 79 4:30")
120
+ assert_equal Time.local(1979, 3, 30, 16, 30), time
121
+
122
+ time = parse_now("March 30th 79 at 4:30", :ambiguous_time_range => :none)
123
+ assert_equal Time.local(1979, 3, 30, 4, 30), time
124
+
76
125
  # rm_sd_sy
77
126
 
127
+ time = parse_now("November 18, 2010")
128
+ assert_equal Time.local(2010, 11, 18, 12), time
129
+
130
+ time = parse_now("February 14, 2004")
131
+ assert_equal Time.local(2004, 2, 14, 12), time
132
+
78
133
  time = parse_now("jan 3 2010")
79
134
  assert_equal Time.local(2010, 1, 3, 12), time
80
135
 
@@ -170,7 +225,7 @@ class TestParsing < Test::Unit::TestCase
170
225
 
171
226
  now = Time.now
172
227
  time = parse_now(now.to_s)
173
- assert_equal now.to_s, time.to_s
228
+ # assert_equal now.to_s, time.to_s
174
229
 
175
230
  # rm_sd_rt
176
231
 
@@ -180,15 +235,26 @@ class TestParsing < Test::Unit::TestCase
180
235
  # old dates
181
236
 
182
237
  time = parse_now("may 40")
183
- assert_equal Time.local(40, 5, 16, 12, 0, 0), time
238
+ assert_equal Time.local(2040, 5, 16, 12, 0, 0), time
184
239
 
185
240
  time = parse_now("may 27 40")
186
- assert_equal Time.local(40, 5, 27, 12, 0, 0), time
241
+ assert_equal Time.local(2040, 5, 27, 12, 0, 0), time
187
242
 
188
243
  time = parse_now("1800-08-20")
189
244
  assert_equal Time.local(1800, 8, 20, 12, 0, 0), time
190
245
  end
191
246
 
247
+ def test_parse_two_digit_years
248
+ time = parse_now("may 97")
249
+ assert_equal Time.local(1997, 5, 16, 12), time
250
+
251
+ time = parse_now("may 1st 01")
252
+ assert_equal Time.local(2001, 5, 1, 12), time
253
+
254
+ time = parse_now("may 79", :ambiguous_year_future_bias => 10)
255
+ assert_equal Time.local(2079, 5, 16, 12), time
256
+ end
257
+
192
258
  def test_parse_guess_r
193
259
  time = parse_now("friday")
194
260
  assert_equal Time.local(2006, 8, 18, 12), time
@@ -246,6 +312,9 @@ class TestParsing < Test::Unit::TestCase
246
312
  time = parse_now("4:00 in the morning")
247
313
  assert_equal Time.local(2006, 8, 16, 4), time
248
314
 
315
+ time = parse_now("0:10")
316
+ assert_equal Time.local(2006, 8, 17, 0, 10), time
317
+
249
318
  time = parse_now("november 4")
250
319
  assert_equal Time.local(2006, 11, 4, 12), time
251
320
 
@@ -325,17 +394,21 @@ class TestParsing < Test::Unit::TestCase
325
394
  # day
326
395
 
327
396
  time = parse_now("this day")
328
- assert_equal Time.local(2006, 8, 16, 19, 30), time
397
+ assert_equal Time.local(2006, 8, 16, 19), time
329
398
 
330
399
  time = parse_now("this day", :context => :past)
331
400
  assert_equal Time.local(2006, 8, 16, 7), time
332
401
 
333
402
  time = parse_now("today")
334
- assert_equal Time.local(2006, 8, 16, 19, 30), time
403
+ assert_equal Time.local(2006, 8, 16, 19), time
335
404
 
336
405
  time = parse_now("yesterday")
337
406
  assert_equal Time.local(2006, 8, 15, 12), time
338
407
 
408
+ now = Time.parse("2011-05-27 23:10") # after 11pm
409
+ time = parse_now("yesterday", :now => now)
410
+ assert_equal Time.local(2011, 05, 26, 12), time
411
+
339
412
  time = parse_now("tomorrow")
340
413
  assert_equal Time.local(2006, 8, 17, 12), time
341
414
 
@@ -452,6 +525,10 @@ class TestParsing < Test::Unit::TestCase
452
525
 
453
526
  time = parse_now("next monday at 12:01 pm")
454
527
  assert_equal Time.local(2006, 8, 21, 12, 1), time
528
+
529
+ # with context
530
+ time = parse_now("sunday at 8:15pm", :context => :past)
531
+ assert_equal Time.local(2006, 8, 13, 20, 15), time
455
532
  end
456
533
 
457
534
  def test_parse_guess_rgr
@@ -578,12 +655,19 @@ class TestParsing < Test::Unit::TestCase
578
655
  end
579
656
 
580
657
  def test_parse_guess_o_r_g_r
581
- time = parse_now("3rd month next year")
582
- assert_equal Time.local(2007, 3, 16, 12, 30), time
658
+ time = parse_now("3rd month next year", :guess => false)
659
+ assert_equal Time.local(2007, 3), time.begin
660
+
661
+ time = parse_now("3rd month next year", :guess => false)
662
+ assert_equal Time.local(2007, 3, 1), time.begin
583
663
 
584
664
  time = parse_now("3rd thursday this september")
585
665
  assert_equal Time.local(2006, 9, 21, 12), time
586
666
 
667
+ now = Time.parse("1/10/2010")
668
+ time = parse_now("3rd thursday this november", :now => now)
669
+ assert_equal Time.local(2010, 11, 18, 12), time
670
+
587
671
  time = parse_now("4th day last week")
588
672
  assert_equal Time.local(2006, 8, 9, 12), time
589
673
  end
@@ -662,16 +746,16 @@ class TestParsing < Test::Unit::TestCase
662
746
  assert_equal Time.local(2007, 6, 20), t.end
663
747
 
664
748
  t = parse_now("this winter", :guess => false)
665
- assert_equal Time.local(2006, 12, 22, 23), t.begin
749
+ assert_equal Time.local(2006, 12, 22), t.begin
666
750
  assert_equal Time.local(2007, 3, 19), t.end
667
751
 
668
752
  t = parse_now("last spring", :guess => false)
669
- assert_equal Time.local(2006, 3, 20, 23), t.begin
753
+ assert_equal Time.local(2006, 3, 20), t.begin
670
754
  assert_equal Time.local(2006, 6, 20), t.end
671
755
 
672
756
  t = parse_now("last winter", :guess => false)
673
- assert_equal Time.local(2005, 12, 22, 23), t.begin
674
- assert_equal Time.local(2006, 3, 19, 23), t.end
757
+ assert_equal Time.local(2005, 12, 22), t.begin
758
+ assert_equal Time.local(2006, 3, 19), t.end
675
759
 
676
760
  t = parse_now("next spring", :guess => false)
677
761
  assert_equal Time.local(2007, 3, 20), t.begin
@@ -694,8 +778,8 @@ class TestParsing < Test::Unit::TestCase
694
778
  t1 = Chronic.parse('1st saturday in november', :now => Time.local(2007))
695
779
  assert_equal Time.local(2007, 11, 3, 12), t1
696
780
 
697
- t1 = Chronic.parse('1st sunday in november', :now => Time.local(2007))
698
- assert_equal Time.local(2007, 11, 4, 11), t1
781
+ # t1 = Chronic.parse('1st sunday in november', :now => Time.local(2007))
782
+ # assert_equal Time.local(2007, 11, 4, 12), t1
699
783
 
700
784
  # Chronic.debug = true
701
785
  #
@@ -703,6 +787,13 @@ class TestParsing < Test::Unit::TestCase
703
787
  # assert_equal Time.local(2007, 11, 5, 11), t1
704
788
  end
705
789
 
790
+ def test_now_changes
791
+ t1 = Chronic.parse("now")
792
+ sleep 0.1
793
+ t2 = Chronic.parse("now")
794
+ assert_not_equal t1, t2
795
+ end
796
+
706
797
  private
707
798
  def parse_now(string, options={})
708
799
  Chronic.parse(string, {:now => TIME_2006_08_16_14_00_00 }.merge(options))