chronic 0.1.5 → 0.2.1

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.
@@ -1,6 +1,55 @@
1
1
  class Chronic::RepeaterWeekend < Chronic::Repeater #:nodoc:
2
2
  WEEKEND_SECONDS = 172_800 # (2 * 24 * 60 * 60)
3
3
 
4
+ def next(pointer)
5
+ super
6
+
7
+ if !@current_week_start
8
+ case pointer
9
+ when :future
10
+ saturday_repeater = Chronic::RepeaterDayName.new(:saturday)
11
+ saturday_repeater.start = @now
12
+ next_saturday_span = saturday_repeater.next(:future)
13
+ @current_week_start = next_saturday_span.begin
14
+ when :past
15
+ saturday_repeater = Chronic::RepeaterDayName.new(:saturday)
16
+ saturday_repeater.start = (@now + Chronic::RepeaterDay::DAY_SECONDS)
17
+ last_saturday_span = saturday_repeater.next(:past)
18
+ @current_week_start = last_saturday_span.begin
19
+ end
20
+ else
21
+ direction = pointer == :future ? 1 : -1
22
+ @current_week_start += direction * Chronic::RepeaterWeek::WEEK_SECONDS
23
+ end
24
+
25
+ Chronic::Span.new(@current_week_start, @current_week_start + WEEKEND_SECONDS)
26
+ end
27
+
28
+ def this(pointer = :future)
29
+ super
30
+
31
+ case pointer
32
+ when :future, :none
33
+ saturday_repeater = Chronic::RepeaterDayName.new(:saturday)
34
+ saturday_repeater.start = @now
35
+ this_saturday_span = saturday_repeater.this(:future)
36
+ Chronic::Span.new(this_saturday_span.begin, this_saturday_span.begin + WEEKEND_SECONDS)
37
+ when :past
38
+ saturday_repeater = Chronic::RepeaterDayName.new(:saturday)
39
+ saturday_repeater.start = @now
40
+ last_saturday_span = saturday_repeater.this(:past)
41
+ Chronic::Span.new(last_saturday_span.begin, last_saturday_span.begin + WEEKEND_SECONDS)
42
+ end
43
+ end
44
+
45
+ def offset(span, amount, pointer)
46
+ direction = pointer == :future ? 1 : -1
47
+ weekend = Chronic::RepeaterWeekend.new(:weekend)
48
+ weekend.start = span.begin
49
+ start = weekend.next(pointer).begin + (amount - 1) * direction * Chronic::RepeaterWeek::WEEK_SECONDS
50
+ Chronic::Span.new(start, start + (span.end - span.begin))
51
+ end
52
+
4
53
  def width
5
54
  WEEKEND_SECONDS
6
55
  end
@@ -6,16 +6,16 @@ class Chronic::RepeaterYear < Chronic::Repeater #:nodoc:
6
6
  if !@current_year_start
7
7
  case pointer
8
8
  when :future
9
- @current_year_start = Time.local(@now.year + 1)
9
+ @current_year_start = Time.construct(@now.year + 1)
10
10
  when :past
11
- @current_year_start = Time.local(@now.year - 1)
11
+ @current_year_start = Time.construct(@now.year - 1)
12
12
  end
13
13
  else
14
14
  diff = pointer == :future ? 1 : -1
15
- @current_year_start = Time.local(@current_year_start.year + diff)
15
+ @current_year_start = Time.construct(@current_year_start.year + diff)
16
16
  end
17
17
 
18
- Chronic::Span.new(@current_year_start, Time.local(@current_year_start.year + 1))
18
+ Chronic::Span.new(@current_year_start, Time.construct(@current_year_start.year + 1))
19
19
  end
20
20
 
21
21
  def this(pointer = :future)
@@ -23,14 +23,14 @@ class Chronic::RepeaterYear < Chronic::Repeater #:nodoc:
23
23
 
24
24
  case pointer
25
25
  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)
26
+ this_year_start = Time.construct(@now.year, @now.month, @now.day) + Chronic::RepeaterDay::DAY_SECONDS
27
+ this_year_end = Time.construct(@now.year + 1, 1, 1)
28
28
  when :past
29
- this_year_start = Time.local(@now.year, 1, 1)
30
- this_year_end = Time.local(@now.year, @now.month, @now.day)
29
+ this_year_start = Time.construct(@now.year, 1, 1)
30
+ this_year_end = Time.construct(@now.year, @now.month, @now.day)
31
31
  when :none
32
- this_year_start = Time.local(@now.year, 1, 1)
33
- this_year_end = Time.local(@now.year + 1, 1, 1)
32
+ this_year_start = Time.construct(@now.year, 1, 1)
33
+ this_year_end = Time.construct(@now.year + 1, 1, 1)
34
34
  end
35
35
 
36
36
  Chronic::Span.new(this_year_start, this_year_end)
@@ -40,10 +40,10 @@ class Chronic::RepeaterYear < Chronic::Repeater #:nodoc:
40
40
  direction = pointer == :future ? 1 : -1
41
41
 
42
42
  sb = span.begin
43
- new_begin = Time.local(sb.year + (amount * direction), sb.month, sb.day, sb.hour, sb.min, sb.sec)
43
+ new_begin = Time.construct(sb.year + (amount * direction), sb.month, sb.day, sb.hour, sb.min, sb.sec)
44
44
 
45
45
  se = span.end
46
- new_end = Time.local(se.year + (amount * direction), se.month, se.day, se.hour, se.min, se.sec)
46
+ new_end = Time.construct(se.year + (amount * direction), se.month, se.day, se.hour, se.min, se.sec)
47
47
 
48
48
  Chronic::Span.new(new_begin, new_end)
49
49
  end
data/lib/chronic.rb CHANGED
@@ -7,6 +7,8 @@
7
7
  #
8
8
  #=============================================================================
9
9
 
10
+ $:.unshift File.dirname(__FILE__) # For use/testing when no gem is installed
11
+
10
12
  require 'chronic/chronic'
11
13
  require 'chronic/handlers'
12
14
 
@@ -32,9 +34,12 @@ require 'chronic/pointer'
32
34
  require 'chronic/scalar'
33
35
  require 'chronic/ordinal'
34
36
  require 'chronic/separator'
37
+ require 'chronic/time_zone'
38
+
39
+ require 'numerizer/numerizer'
35
40
 
36
41
  module Chronic
37
- VERSION = "0.1.5"
42
+ VERSION = "0.2.1"
38
43
 
39
44
  def self.debug; false; end
40
45
  end
@@ -44,4 +49,77 @@ alias p_orig p
44
49
  def p(val)
45
50
  p_orig val
46
51
  puts
52
+ end
53
+
54
+ # class Time
55
+ # def self.construct(year, month = 1, day = 1, hour = 0, minute = 0, second = 0)
56
+ # # extra_seconds = second > 60 ? second - 60 : 0
57
+ # # extra_minutes = minute > 59 ? minute - 59 : 0
58
+ # # extra_hours = hour > 23 ? hour - 23 : 0
59
+ # # extra_days = day >
60
+ #
61
+ # if month > 12
62
+ # if month % 12 == 0
63
+ # year += (month - 12) / 12
64
+ # month = 12
65
+ # else
66
+ # year += month / 12
67
+ # month = month % 12
68
+ # end
69
+ # end
70
+ #
71
+ # base = Time.local(year, month)
72
+ # puts base
73
+ # offset = ((day - 1) * 24 * 60 * 60) + (hour * 60 * 60) + (minute * 60) + second
74
+ # puts offset.to_s
75
+ # date = base + offset
76
+ # puts date
77
+ # date
78
+ # end
79
+ # end
80
+
81
+ class Time
82
+ def self.construct(year, month = 1, day = 1, hour = 0, minute = 0, second = 0)
83
+ if second >= 60
84
+ minute += second / 60
85
+ second = second % 60
86
+ end
87
+
88
+ if minute >= 60
89
+ hour += minute / 60
90
+ minute = minute % 60
91
+ end
92
+
93
+ if hour >= 24
94
+ day += hour / 24
95
+ hour = hour % 24
96
+ end
97
+
98
+ # determine if there is a day overflow. this is complicated by our crappy calendar
99
+ # system (non-constant number of days per month)
100
+ day <= 56 || raise("day must be no more than 56 (makes month resolution easier)")
101
+ if day > 28
102
+ # no month ever has fewer than 28 days, so only do this if necessary
103
+ leap_year = (year % 4 == 0) && !(year % 100 == 0)
104
+ leap_year_month_days = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
105
+ common_year_month_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
106
+ days_this_month = leap_year ? leap_year_month_days[month - 1] : common_year_month_days[month - 1]
107
+ if day > days_this_month
108
+ month += day / days_this_month
109
+ day = day % days_this_month
110
+ end
111
+ end
112
+
113
+ if month > 12
114
+ if month % 12 == 0
115
+ year += (month - 12) / 12
116
+ month = 12
117
+ else
118
+ year += month / 12
119
+ month = month % 12
120
+ end
121
+ end
122
+
123
+ Time.local(year, month, day, hour, minute, second)
124
+ end
47
125
  end
@@ -0,0 +1,103 @@
1
+ require 'strscan'
2
+
3
+ class Numerizer
4
+
5
+ DIRECT_NUMS = [
6
+ ['eleven', '11'],
7
+ ['twelve', '12'],
8
+ ['thirteen', '13'],
9
+ ['fourteen', '14'],
10
+ ['fifteen', '15'],
11
+ ['sixteen', '16'],
12
+ ['seventeen', '17'],
13
+ ['eighteen', '18'],
14
+ ['nineteen', '19'],
15
+ ['ninteen', '19'], # Common mis-spelling
16
+ ['zero', '0'],
17
+ ['one', '1'],
18
+ ['two', '2'],
19
+ ['three', '3'],
20
+ ['four(\W|$)', '4\1'], # The weird regex is so that it matches four but not fourty
21
+ ['five', '5'],
22
+ ['six(\W|$)', '6\1'],
23
+ ['seven(\W|$)', '7\1'],
24
+ ['eight(\W|$)', '8\1'],
25
+ ['nine(\W|$)', '9\1'],
26
+ ['ten', '10'],
27
+ ['\ba[\b^$]', '1'] # doesn't make sense for an 'a' at the end to be a 1
28
+ ]
29
+
30
+ TEN_PREFIXES = [ ['twenty', 20],
31
+ ['thirty', 30],
32
+ ['fourty', 40],
33
+ ['fifty', 50],
34
+ ['sixty', 60],
35
+ ['seventy', 70],
36
+ ['eighty', 80],
37
+ ['ninety', 90]
38
+ ]
39
+
40
+ BIG_PREFIXES = [ ['hundred', 100],
41
+ ['thousand', 1000],
42
+ ['million', 1_000_000],
43
+ ['billion', 1_000_000_000],
44
+ ['trillion', 1_000_000_000_000],
45
+ ]
46
+
47
+ class << self
48
+ def numerize(string)
49
+ string = string.dup
50
+
51
+ # preprocess
52
+ string.gsub!(/ +|([^\d])-([^d])/, '\1 \2') # will mutilate hyphenated-words but shouldn't matter for date extraction
53
+ string.gsub!(/a half/, 'haAlf') # take the 'a' out so it doesn't turn into a 1, save the half for the end
54
+
55
+ # easy/direct replacements
56
+
57
+ DIRECT_NUMS.each do |dn|
58
+ string.gsub!(/#{dn[0]}/i, dn[1])
59
+ end
60
+
61
+ # ten, twenty, etc.
62
+
63
+ TEN_PREFIXES.each do |tp|
64
+ string.gsub!(/(?:#{tp[0]})( *\d(?=[^\d]|$))*/i) { (tp[1] + $1.to_i).to_s }
65
+ end
66
+
67
+ # hundreds, thousands, millions, etc.
68
+
69
+ BIG_PREFIXES.each do |bp|
70
+ string.gsub!(/(\d*) *#{bp[0]}/i) { (bp[1] * $1.to_i).to_s}
71
+ andition(string)
72
+ #combine_numbers(string) # Should to be more efficient way to do this
73
+ end
74
+
75
+ # fractional addition
76
+ # I'm not combining this with the previous block as using float addition complicates the strings
77
+ # (with extraneous .0's and such )
78
+ string.gsub!(/(\d+)(?: | and |-)*haAlf/i) { ($1.to_f + 0.5).to_s }
79
+
80
+ string
81
+ end
82
+
83
+ private
84
+ def andition(string)
85
+ sc = StringScanner.new(string)
86
+ while(sc.scan_until(/(\d+)( | and )(\d+)(?=[^\w]|$)/i))
87
+ if sc[2] =~ /and/ || sc[1].size > sc[3].size
88
+ string[(sc.pos - sc.matched_size)..(sc.pos-1)] = (sc[1].to_i + sc[3].to_i).to_s
89
+ sc.reset
90
+ end
91
+ end
92
+ end
93
+
94
+ # def combine_numbers(string)
95
+ # sc = StringScanner.new(string)
96
+ # while(sc.scan_until(/(\d+)(?: | and |-)(\d+)(?=[^\w]|$)/i))
97
+ # string[(sc.pos - sc.matched_size)..(sc.pos-1)] = (sc[1].to_i + sc[2].to_i).to_s
98
+ # sc.reset
99
+ # end
100
+ # end
101
+
102
+ end
103
+ end
@@ -1,11 +1,10 @@
1
- __END__
2
-
3
1
  require 'test/unit'
2
+ require 'chronic'
4
3
 
5
4
  class ParseNumbersTest < Test::Unit::TestCase
6
5
 
7
- def test_parse
8
- strings = {1 => 'one',
6
+ def test_straight_parsing
7
+ strings = { 1 => 'one',
9
8
  5 => 'five',
10
9
  10 => 'ten',
11
10
  11 => 'eleven',
@@ -24,7 +23,7 @@ class ParseNumbersTest < Test::Unit::TestCase
24
23
  100 => 'a hundred',
25
24
  100 => 'one hundred',
26
25
  150 => 'one hundred and fifty',
27
- 150 => 'one fifty',
26
+ # 150 => 'one fifty',
28
27
  200 => 'two-hundred',
29
28
  500 => '5 hundred',
30
29
  999 => 'nine hundred and ninety nine',
@@ -40,11 +39,10 @@ class ParseNumbersTest < Test::Unit::TestCase
40
39
  1_000_000 => 'one million',
41
40
  1_250_007 => 'one million two hundred fifty thousand and seven',
42
41
  1_000_000_000 => 'one billion',
43
- 1_000_000_001 => 'one billion and one'}
42
+ 1_000_000_001 => 'one billion and one' }
44
43
 
45
44
  strings.keys.sort.each do |key|
46
- assert_equal key, JW::NumberMagick.convert(strings[key])
45
+ assert_equal key, Numerizer.numerize(strings[key]).to_i
47
46
  end
48
47
  end
49
-
50
48
  end
@@ -0,0 +1,75 @@
1
+ require 'chronic'
2
+ require 'test/unit'
3
+
4
+ class TestRepeaterWeekend < Test::Unit::TestCase
5
+
6
+ def setup
7
+ # Wed Aug 16 14:00:00 2006
8
+ @now = Time.local(2006, 8, 16, 14, 0, 0, 0)
9
+ end
10
+
11
+ def test_next_future
12
+ weekend = Chronic::RepeaterWeekend.new(:weekend)
13
+ weekend.start = @now
14
+
15
+ next_weekend = weekend.next(:future)
16
+ assert_equal Time.local(2006, 8, 19), next_weekend.begin
17
+ assert_equal Time.local(2006, 8, 21), next_weekend.end
18
+ end
19
+
20
+ def test_next_past
21
+ weekend = Chronic::RepeaterWeekend.new(:weekend)
22
+ weekend.start = @now
23
+
24
+ next_weekend = weekend.next(:past)
25
+ assert_equal Time.local(2006, 8, 12), next_weekend.begin
26
+ assert_equal Time.local(2006, 8, 14), next_weekend.end
27
+ end
28
+
29
+ def test_this_future
30
+ weekend = Chronic::RepeaterWeekend.new(:weekend)
31
+ weekend.start = @now
32
+
33
+ next_weekend = weekend.this(:future)
34
+ assert_equal Time.local(2006, 8, 19), next_weekend.begin
35
+ assert_equal Time.local(2006, 8, 21), next_weekend.end
36
+ end
37
+
38
+ def test_this_past
39
+ weekend = Chronic::RepeaterWeekend.new(:weekend)
40
+ weekend.start = @now
41
+
42
+ next_weekend = weekend.this(:past)
43
+ assert_equal Time.local(2006, 8, 12), next_weekend.begin
44
+ assert_equal Time.local(2006, 8, 14), next_weekend.end
45
+ end
46
+
47
+ def test_this_none
48
+ weekend = Chronic::RepeaterWeekend.new(:weekend)
49
+ weekend.start = @now
50
+
51
+ next_weekend = weekend.this(:future)
52
+ assert_equal Time.local(2006, 8, 19), next_weekend.begin
53
+ assert_equal Time.local(2006, 8, 21), next_weekend.end
54
+ end
55
+
56
+ def test_offset
57
+ span = Chronic::Span.new(@now, @now + 1)
58
+
59
+ offset_span = Chronic::RepeaterWeekend.new(:weekend).offset(span, 3, :future)
60
+
61
+ assert_equal Time.local(2006, 9, 2), offset_span.begin
62
+ assert_equal Time.local(2006, 9, 2, 0, 0, 1), offset_span.end
63
+
64
+ offset_span = Chronic::RepeaterWeekend.new(:weekend).offset(span, 1, :past)
65
+
66
+ assert_equal Time.local(2006, 8, 12), offset_span.begin
67
+ assert_equal Time.local(2006, 8, 12, 0, 0, 1), offset_span.end
68
+
69
+ offset_span = Chronic::RepeaterWeekend.new(:weekend).offset(span, 0, :future)
70
+
71
+ assert_equal Time.local(2006, 8, 12), offset_span.begin
72
+ assert_equal Time.local(2006, 8, 12, 0, 0, 1), offset_span.end
73
+ end
74
+
75
+ end
data/test/test_Time.rb ADDED
@@ -0,0 +1,50 @@
1
+ require 'chronic'
2
+ require 'test/unit'
3
+
4
+ class TestTime < Test::Unit::TestCase
5
+
6
+ def setup
7
+ end
8
+
9
+ def test_normal
10
+ assert_equal Time.local(2006, 1, 2, 0, 0, 0), Time.construct(2006, 1, 2, 0, 0, 0)
11
+ assert_equal Time.local(2006, 1, 2, 3, 0, 0), Time.construct(2006, 1, 2, 3, 0, 0)
12
+ assert_equal Time.local(2006, 1, 2, 3, 4, 0), Time.construct(2006, 1, 2, 3, 4, 0)
13
+ assert_equal Time.local(2006, 1, 2, 3, 4, 5), Time.construct(2006, 1, 2, 3, 4, 5)
14
+ end
15
+
16
+ def test_second_overflow
17
+ assert_equal Time.local(2006, 1, 1, 0, 1, 30), Time.construct(2006, 1, 1, 0, 0, 90)
18
+ assert_equal Time.local(2006, 1, 1, 0, 5, 0), Time.construct(2006, 1, 1, 0, 0, 300)
19
+ end
20
+
21
+ def test_minute_overflow
22
+ assert_equal Time.local(2006, 1, 1, 1, 30), Time.construct(2006, 1, 1, 0, 90)
23
+ assert_equal Time.local(2006, 1, 1, 5), Time.construct(2006, 1, 1, 0, 300)
24
+ end
25
+
26
+ def test_hour_overflow
27
+ assert_equal Time.local(2006, 1, 2, 12), Time.construct(2006, 1, 1, 36)
28
+ assert_equal Time.local(2006, 1, 7), Time.construct(2006, 1, 1, 144)
29
+ end
30
+
31
+ def test_day_overflow
32
+ assert_equal Time.local(2006, 2, 1), Time.construct(2006, 1, 32)
33
+ assert_equal Time.local(2006, 3, 5), Time.construct(2006, 2, 33)
34
+ assert_equal Time.local(2004, 3, 4), Time.construct(2004, 2, 33)
35
+ assert_equal Time.local(2000, 3, 5), Time.construct(2000, 2, 33)
36
+
37
+ assert_nothing_raised do
38
+ Time.construct(2006, 1, 56)
39
+ end
40
+
41
+ assert_raise(RuntimeError) do
42
+ Time.construct(2006, 1, 57)
43
+ end
44
+ end
45
+
46
+ def test_month_overflow
47
+ assert_equal Time.local(2006, 1), Time.construct(2005, 13)
48
+ assert_equal Time.local(2005, 12), Time.construct(2000, 72)
49
+ end
50
+ end