chronic 0.2.3 → 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 (57) hide show
  1. data/HISTORY.md +76 -0
  2. data/LICENSE +21 -0
  3. data/README.md +165 -0
  4. data/Rakefile +145 -18
  5. data/benchmark/benchmark.rb +13 -0
  6. data/chronic.gemspec +85 -0
  7. data/lib/chronic.rb +21 -19
  8. data/lib/chronic/chronic.rb +59 -49
  9. data/lib/chronic/grabber.rb +2 -2
  10. data/lib/chronic/handlers.rb +167 -112
  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 +26 -12
  15. data/lib/chronic/repeaters/repeater_day.rb +17 -12
  16. data/lib/chronic/repeaters/repeater_day_name.rb +17 -12
  17. data/lib/chronic/repeaters/repeater_day_portion.rb +13 -12
  18. data/lib/chronic/repeaters/repeater_fortnight.rb +14 -9
  19. data/lib/chronic/repeaters/repeater_hour.rb +15 -10
  20. data/lib/chronic/repeaters/repeater_minute.rb +15 -10
  21. data/lib/chronic/repeaters/repeater_month.rb +20 -15
  22. data/lib/chronic/repeaters/repeater_month_name.rb +21 -16
  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 +49 -42
  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 +19 -13
  31. data/lib/chronic/scalar.rb +16 -14
  32. data/lib/chronic/separator.rb +25 -10
  33. data/lib/chronic/time_zone.rb +4 -3
  34. data/test/helper.rb +7 -0
  35. data/test/test_Chronic.rb +17 -18
  36. data/test/test_DaylightSavings.rb +118 -0
  37. data/test/test_Handler.rb +37 -38
  38. data/test/test_Numerizer.rb +8 -5
  39. data/test/test_RepeaterDayName.rb +15 -16
  40. data/test/test_RepeaterFortnight.rb +16 -17
  41. data/test/test_RepeaterHour.rb +18 -19
  42. data/test/test_RepeaterMinute.rb +34 -0
  43. data/test/test_RepeaterMonth.rb +16 -17
  44. data/test/test_RepeaterMonthName.rb +17 -18
  45. data/test/test_RepeaterTime.rb +20 -22
  46. data/test/test_RepeaterWeek.rb +16 -17
  47. data/test/test_RepeaterWeekday.rb +55 -0
  48. data/test/test_RepeaterWeekend.rb +21 -22
  49. data/test/test_RepeaterYear.rb +17 -18
  50. data/test/test_Span.rb +5 -6
  51. data/test/test_Time.rb +11 -12
  52. data/test/test_Token.rb +5 -6
  53. data/test/test_parsing.rb +300 -204
  54. metadata +74 -52
  55. data/History.txt +0 -53
  56. data/README.txt +0 -149
  57. data/test/suite.rb +0 -9
@@ -44,32 +44,34 @@ class Numerizer
44
44
  ['trillion', 1_000_000_000_000],
45
45
  ]
46
46
 
47
- class << self
48
- def numerize(string)
47
+ def self.numerize(string)
49
48
  string = string.dup
50
-
49
+
51
50
  # preprocess
52
- string.gsub!(/ +|([^\d])-([^d])/, '\1 \2') # will mutilate hyphenated-words but shouldn't matter for date extraction
51
+ string.gsub!(/ +|([^\d])-([^\d])/, '\1 \2') # will mutilate hyphenated-words but shouldn't matter for date extraction
53
52
  string.gsub!(/a half/, 'haAlf') # take the 'a' out so it doesn't turn into a 1, save the half for the end
54
53
 
55
54
  # easy/direct replacements
56
-
55
+
57
56
  DIRECT_NUMS.each do |dn|
58
- string.gsub!(/#{dn[0]}/i, dn[1])
57
+ string.gsub!(/#{dn[0]}/i, '<num>' + dn[1])
59
58
  end
60
59
 
61
60
  # ten, twenty, etc.
62
61
 
63
62
  TEN_PREFIXES.each do |tp|
64
- string.gsub!(/(?:#{tp[0]})( *\d(?=[^\d]|$))*/i) { (tp[1] + $1.to_i).to_s }
63
+ string.gsub!(/(?:#{tp[0]}) *<num>(\d(?=[^\d]|$))*/i) { '<num>' + (tp[1] + $1.to_i).to_s }
64
+ end
65
+
66
+ TEN_PREFIXES.each do |tp|
67
+ string.gsub!(/#{tp[0]}/i) { '<num>' + tp[1].to_s }
65
68
  end
66
69
 
67
70
  # hundreds, thousands, millions, etc.
68
71
 
69
72
  BIG_PREFIXES.each do |bp|
70
- string.gsub!(/(\d*) *#{bp[0]}/i) { (bp[1] * $1.to_i).to_s}
73
+ string.gsub!(/(?:<num>)?(\d*) *#{bp[0]}/i) { '<num>' + (bp[1] * $1.to_i).to_s}
71
74
  andition(string)
72
- #combine_numbers(string) # Should to be more efficient way to do this
73
75
  end
74
76
 
75
77
  # fractional addition
@@ -77,27 +79,19 @@ class << self
77
79
  # (with extraneous .0's and such )
78
80
  string.gsub!(/(\d+)(?: | and |-)*haAlf/i) { ($1.to_f + 0.5).to_s }
79
81
 
80
- string
82
+ string.gsub(/<num>/, '')
81
83
  end
82
84
 
83
- private
84
- def andition(string)
85
+ private
86
+
87
+ def self.andition(string)
85
88
  sc = StringScanner.new(string)
86
- while(sc.scan_until(/(\d+)( | and )(\d+)(?=[^\w]|$)/i))
89
+ while(sc.scan_until(/<num>(\d+)( | and )<num>(\d+)(?=[^\w]|$)/i))
87
90
  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
91
+ string[(sc.pos - sc.matched_size)..(sc.pos-1)] = '<num>' + (sc[1].to_i + sc[3].to_i).to_s
89
92
  sc.reset
90
93
  end
91
94
  end
92
95
  end
93
96
 
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
97
  end
103
- end
@@ -9,32 +9,32 @@ module Chronic
9
9
  end
10
10
  tokens
11
11
  end
12
-
12
+
13
13
  def self.scan_for_ordinals(token)
14
14
  if token.word =~ /^(\d*)(st|nd|rd|th)$/
15
15
  return Ordinal.new($1.to_i)
16
16
  end
17
17
  return nil
18
18
  end
19
-
19
+
20
20
  def self.scan_for_days(token)
21
21
  if token.word =~ /^(\d*)(st|nd|rd|th)$/
22
- unless $1.to_i > 31
22
+ unless $1.to_i > 31 || $1.to_i < 1
23
23
  return OrdinalDay.new(token.word.to_i)
24
24
  end
25
25
  end
26
26
  return nil
27
27
  end
28
-
28
+
29
29
  def to_s
30
30
  'ordinal'
31
31
  end
32
32
  end
33
-
33
+
34
34
  class OrdinalDay < Ordinal #:nodoc:
35
35
  def to_s
36
36
  super << '-day-' << @type.to_s
37
37
  end
38
38
  end
39
39
 
40
- end
40
+ end
@@ -8,7 +8,7 @@ module Chronic
8
8
  end
9
9
  tokens
10
10
  end
11
-
11
+
12
12
  def self.scan_for_all(token)
13
13
  scanner = {/\bpast\b/ => :past,
14
14
  /\bfuture\b/ => :future,
@@ -18,10 +18,10 @@ module Chronic
18
18
  end
19
19
  return nil
20
20
  end
21
-
21
+
22
22
  def to_s
23
23
  'pointer-' << @type.to_s
24
24
  end
25
25
  end
26
26
 
27
- end
27
+ end
@@ -2,6 +2,7 @@ class Chronic::Repeater < Chronic::Tag #:nodoc:
2
2
  def self.scan(tokens, options)
3
3
  # for each token
4
4
  tokens.each_index do |i|
5
+ if t = self.scan_for_season_names(tokens[i]) then tokens[i].tag(t); next end
5
6
  if t = self.scan_for_month_names(tokens[i]) then tokens[i].tag(t); next end
6
7
  if t = self.scan_for_day_names(tokens[i]) then tokens[i].tag(t); next end
7
8
  if t = self.scan_for_day_portions(tokens[i]) then tokens[i].tag(t); next end
@@ -10,7 +11,19 @@ class Chronic::Repeater < Chronic::Tag #:nodoc:
10
11
  end
11
12
  tokens
12
13
  end
13
-
14
+
15
+ def self.scan_for_season_names(token)
16
+ scanner = {/^springs?$/ => :spring,
17
+ /^summers?$/ => :summer,
18
+ /^(autumn)|(fall)s?$/ => :autumn,
19
+ /^winters?$/ => :winter}
20
+ scanner.keys.each do |scanner_item|
21
+ return Chronic::RepeaterSeasonName.new(scanner[scanner_item]) if scanner_item =~ token.word
22
+ end
23
+
24
+ return nil
25
+ end
26
+
14
27
  def self.scan_for_month_names(token)
15
28
  scanner = {/^jan\.?(uary)?$/ => :january,
16
29
  /^feb\.?(ruary)?$/ => :february,
@@ -29,7 +42,7 @@ class Chronic::Repeater < Chronic::Tag #:nodoc:
29
42
  end
30
43
  return nil
31
44
  end
32
-
45
+
33
46
  def self.scan_for_day_names(token)
34
47
  scanner = {/^m[ou]n(day)?$/ => :monday,
35
48
  /^t(ue|eu|oo|u|)s(day)?$/ => :tuesday,
@@ -46,7 +59,7 @@ class Chronic::Repeater < Chronic::Tag #:nodoc:
46
59
  end
47
60
  return nil
48
61
  end
49
-
62
+
50
63
  def self.scan_for_day_portions(token)
51
64
  scanner = {/^ams?$/ => :am,
52
65
  /^pms?$/ => :pm,
@@ -59,14 +72,14 @@ class Chronic::Repeater < Chronic::Tag #:nodoc:
59
72
  end
60
73
  return nil
61
74
  end
62
-
75
+
63
76
  def self.scan_for_times(token, options)
64
77
  if token.word =~ /^\d{1,2}(:?\d{2})?([\.:]?\d{2})?$/
65
78
  return Chronic::RepeaterTime.new(token.word, options)
66
79
  end
67
80
  return nil
68
81
  end
69
-
82
+
70
83
  def self.scan_for_units(token)
71
84
  scanner = {/^years?$/ => :year,
72
85
  /^seasons?$/ => :season,
@@ -74,6 +87,7 @@ class Chronic::Repeater < Chronic::Tag #:nodoc:
74
87
  /^fortnights?$/ => :fortnight,
75
88
  /^weeks?$/ => :week,
76
89
  /^weekends?$/ => :weekend,
90
+ /^(week|business)days?$/ => :weekday,
77
91
  /^days?$/ => :day,
78
92
  /^hours?$/ => :hour,
79
93
  /^minutes?$/ => :minute,
@@ -82,34 +96,34 @@ class Chronic::Repeater < Chronic::Tag #:nodoc:
82
96
  if scanner_item =~ token.word
83
97
  klass_name = 'Chronic::Repeater' + scanner[scanner_item].to_s.capitalize
84
98
  klass = eval(klass_name)
85
- return klass.new(scanner[scanner_item])
99
+ return klass.new(scanner[scanner_item])
86
100
  end
87
101
  end
88
102
  return nil
89
103
  end
90
-
104
+
91
105
  def <=>(other)
92
106
  width <=> other.width
93
107
  end
94
-
108
+
95
109
  # returns the width (in seconds or months) of this repeatable.
96
110
  def width
97
111
  raise("Repeatable#width must be overridden in subclasses")
98
112
  end
99
-
113
+
100
114
  # returns the next occurance of this repeatable.
101
115
  def next(pointer)
102
116
  !@now.nil? || raise("Start point must be set before calling #next")
103
117
  [:future, :none, :past].include?(pointer) || raise("First argument 'pointer' must be one of :past or :future")
104
118
  #raise("Repeatable#next must be overridden in subclasses")
105
119
  end
106
-
120
+
107
121
  def this(pointer)
108
122
  !@now.nil? || raise("Start point must be set before calling #this")
109
123
  [:future, :past, :none].include?(pointer) || raise("First argument 'pointer' must be one of :past, :future, :none")
110
124
  end
111
-
125
+
112
126
  def to_s
113
127
  'repeater'
114
128
  end
115
- end
129
+ end
@@ -1,22 +1,27 @@
1
1
  class Chronic::RepeaterDay < Chronic::Repeater #:nodoc:
2
2
  DAY_SECONDS = 86_400 # (24 * 60 * 60)
3
-
3
+
4
+ def initialize(type)
5
+ super
6
+ @current_day_start = nil
7
+ end
8
+
4
9
  def next(pointer)
5
10
  super
6
-
11
+
7
12
  if !@current_day_start
8
- @current_day_start = Time.local(@now.year, @now.month, @now.day)
13
+ @current_day_start = Chronic.time_class.local(@now.year, @now.month, @now.day)
9
14
  end
10
-
15
+
11
16
  direction = pointer == :future ? 1 : -1
12
17
  @current_day_start += direction * DAY_SECONDS
13
-
18
+
14
19
  Chronic::Span.new(@current_day_start, @current_day_start + DAY_SECONDS)
15
20
  end
16
-
21
+
17
22
  def this(pointer = :future)
18
23
  super
19
-
24
+
20
25
  case pointer
21
26
  when :future
22
27
  day_begin = Time.construct(@now.year, @now.month, @now.day, @now.hour + 1)
@@ -28,20 +33,20 @@ class Chronic::RepeaterDay < Chronic::Repeater #:nodoc:
28
33
  day_begin = Time.construct(@now.year, @now.month, @now.day)
29
34
  day_end = Time.construct(@now.year, @now.month, @now.day) + DAY_SECONDS
30
35
  end
31
-
36
+
32
37
  Chronic::Span.new(day_begin, day_end)
33
38
  end
34
-
39
+
35
40
  def offset(span, amount, pointer)
36
41
  direction = pointer == :future ? 1 : -1
37
42
  span + direction * amount * DAY_SECONDS
38
43
  end
39
-
44
+
40
45
  def width
41
46
  DAY_SECONDS
42
47
  end
43
-
48
+
44
49
  def to_s
45
50
  super << '-day'
46
51
  end
47
- end
52
+ end
@@ -1,46 +1,51 @@
1
1
  class Chronic::RepeaterDayName < Chronic::Repeater #:nodoc:
2
2
  DAY_SECONDS = 86400 # (24 * 60 * 60)
3
-
3
+
4
+ def initialize(type)
5
+ super
6
+ @current_day_start = nil
7
+ end
8
+
4
9
  def next(pointer)
5
10
  super
6
-
11
+
7
12
  direction = pointer == :future ? 1 : -1
8
-
13
+
9
14
  if !@current_day_start
10
15
  @current_day_start = Time.construct(@now.year, @now.month, @now.day)
11
16
  @current_day_start += direction * DAY_SECONDS
12
17
 
13
18
  day_num = symbol_to_number(@type)
14
-
19
+
15
20
  while @current_day_start.wday != day_num
16
21
  @current_day_start += direction * DAY_SECONDS
17
22
  end
18
23
  else
19
24
  @current_day_start += direction * 7 * DAY_SECONDS
20
25
  end
21
-
26
+
22
27
  Chronic::Span.new(@current_day_start, @current_day_start + DAY_SECONDS)
23
28
  end
24
-
29
+
25
30
  def this(pointer = :future)
26
31
  super
27
-
32
+
28
33
  pointer = :future if pointer == :none
29
34
  self.next(pointer)
30
35
  end
31
-
36
+
32
37
  def width
33
38
  DAY_SECONDS
34
39
  end
35
-
40
+
36
41
  def to_s
37
42
  super << '-dayname-' << @type.to_s
38
43
  end
39
-
44
+
40
45
  private
41
-
46
+
42
47
  def symbol_to_number(sym)
43
48
  lookup = {:sunday => 0, :monday => 1, :tuesday => 2, :wednesday => 3, :thursday => 4, :friday => 5, :saturday => 6}
44
49
  lookup[sym] || raise("Invalid symbol specified")
45
50
  end
46
- end
51
+ end
@@ -3,10 +3,11 @@ class Chronic::RepeaterDayPortion < Chronic::Repeater #:nodoc:
3
3
  @@afternoon = (13 * 60 * 60)..(17 * 60 * 60) # 1pm-5pm
4
4
  @@evening = (17 * 60 * 60)..(20 * 60 * 60) # 5pm-8pm
5
5
  @@night = (20 * 60 * 60)..(24 * 60 * 60) # 8pm-12pm
6
-
6
+
7
7
  def initialize(type)
8
8
  super
9
-
9
+ @current_span = nil
10
+
10
11
  if type.kind_of? Integer
11
12
  @range = (@type * 60 * 60)..((@type + 12) * 60 * 60)
12
13
  else
@@ -21,12 +22,12 @@ class Chronic::RepeaterDayPortion < Chronic::Repeater #:nodoc:
21
22
  end
22
23
  @range || raise("Range should have been set by now")
23
24
  end
24
-
25
+
25
26
  def next(pointer)
26
27
  super
27
-
28
+
28
29
  full_day = 60 * 60 * 24
29
-
30
+
30
31
  if !@current_span
31
32
  now_seconds = @now - Time.construct(@now.year, @now.month, @now.day)
32
33
  if now_seconds < @range.begin
@@ -51,7 +52,7 @@ class Chronic::RepeaterDayPortion < Chronic::Repeater #:nodoc:
51
52
  range_start = Time.construct(@now.year, @now.month, @now.day) - full_day + @range.begin
52
53
  end
53
54
  end
54
-
55
+
55
56
  @current_span = Chronic::Span.new(range_start, range_start + (@range.end - @range.begin))
56
57
  else
57
58
  case pointer
@@ -62,21 +63,21 @@ class Chronic::RepeaterDayPortion < Chronic::Repeater #:nodoc:
62
63
  end
63
64
  end
64
65
  end
65
-
66
+
66
67
  def this(context = :future)
67
68
  super
68
-
69
+
69
70
  range_start = Time.construct(@now.year, @now.month, @now.day) + @range.begin
70
71
  @current_span = Chronic::Span.new(range_start, range_start + (@range.end - @range.begin))
71
72
  end
72
-
73
+
73
74
  def offset(span, amount, pointer)
74
75
  @now = span.begin
75
76
  portion_span = self.next(pointer)
76
77
  direction = pointer == :future ? 1 : -1
77
78
  portion_span + (direction * (amount - 1) * Chronic::RepeaterDay::DAY_SECONDS)
78
79
  end
79
-
80
+
80
81
  def width
81
82
  @range || raise("Range has not been set")
82
83
  return @current_span.width if @current_span
@@ -86,8 +87,8 @@ class Chronic::RepeaterDayPortion < Chronic::Repeater #:nodoc:
86
87
  @range.end - @range.begin
87
88
  end
88
89
  end
89
-
90
+
90
91
  def to_s
91
92
  super << '-dayportion-' << @type.to_s
92
93
  end
93
- end
94
+ end