chronic 0.2.3 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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