chronic 0.9.1 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -4
  3. data/HISTORY.md +12 -0
  4. data/README.md +8 -0
  5. data/Rakefile +28 -8
  6. data/chronic.gemspec +7 -5
  7. data/lib/chronic.rb +10 -10
  8. data/lib/chronic/date.rb +82 -0
  9. data/lib/chronic/handler.rb +34 -25
  10. data/lib/chronic/handlers.rb +22 -3
  11. data/lib/chronic/ordinal.rb +22 -20
  12. data/lib/chronic/parser.rb +31 -26
  13. data/lib/chronic/repeater.rb +18 -18
  14. data/lib/chronic/repeaters/repeater_day.rb +4 -3
  15. data/lib/chronic/repeaters/repeater_day_name.rb +5 -4
  16. data/lib/chronic/repeaters/repeater_day_portion.rb +4 -3
  17. data/lib/chronic/repeaters/repeater_fortnight.rb +4 -3
  18. data/lib/chronic/repeaters/repeater_hour.rb +4 -3
  19. data/lib/chronic/repeaters/repeater_minute.rb +4 -3
  20. data/lib/chronic/repeaters/repeater_month.rb +5 -4
  21. data/lib/chronic/repeaters/repeater_month_name.rb +4 -3
  22. data/lib/chronic/repeaters/repeater_season.rb +5 -3
  23. data/lib/chronic/repeaters/repeater_second.rb +4 -3
  24. data/lib/chronic/repeaters/repeater_time.rb +35 -25
  25. data/lib/chronic/repeaters/repeater_week.rb +4 -3
  26. data/lib/chronic/repeaters/repeater_weekday.rb +4 -3
  27. data/lib/chronic/repeaters/repeater_weekend.rb +4 -3
  28. data/lib/chronic/repeaters/repeater_year.rb +5 -4
  29. data/lib/chronic/scalar.rb +34 -69
  30. data/lib/chronic/separator.rb +107 -8
  31. data/lib/chronic/sign.rb +49 -0
  32. data/lib/chronic/tag.rb +5 -4
  33. data/lib/chronic/time.rb +40 -0
  34. data/test/helper.rb +2 -2
  35. data/test/test_chronic.rb +4 -4
  36. data/test/test_handler.rb +20 -0
  37. data/test/test_parsing.rb +72 -3
  38. data/test/test_repeater_time.rb +18 -0
  39. metadata +24 -6
@@ -8,8 +8,10 @@ module Chronic
8
8
  :winter => Season.new(MiniDate.new(12,22), MiniDate.new(3,19))
9
9
  }
10
10
 
11
- def initialize(type)
11
+ def initialize(type, options = {})
12
12
  super
13
+ @next_season_start = nil
14
+ @next_season_end = nil
13
15
  end
14
16
 
15
17
  def next(pointer)
@@ -63,7 +65,7 @@ module Chronic
63
65
  private
64
66
 
65
67
  def find_next_season_span(direction, next_season)
66
- unless @next_season_start or @next_season_end
68
+ unless @next_season_start || @next_season_end
67
69
  @next_season_start = Chronic.construct(@now.year, @now.month, @now.day)
68
70
  @next_season_end = Chronic.construct(@now.year, @now.month, @now.day)
69
71
  end
@@ -106,4 +108,4 @@ module Chronic
106
108
  )
107
109
  end
108
110
  end
109
- end
111
+ end
@@ -2,8 +2,9 @@ module Chronic
2
2
  class RepeaterSecond < Repeater #:nodoc:
3
3
  SECOND_SECONDS = 1 # haha, awesome
4
4
 
5
- def initialize(type)
5
+ def initialize(type, options = {})
6
6
  super
7
+ @second_start = nil
7
8
  end
8
9
 
9
10
  def next(pointer = :future)
@@ -11,7 +12,7 @@ module Chronic
11
12
 
12
13
  direction = pointer == :future ? 1 : -1
13
14
 
14
- if !@second_start
15
+ unless @second_start
15
16
  @second_start = @now + (direction * SECOND_SECONDS)
16
17
  else
17
18
  @second_start += SECOND_SECONDS * direction
@@ -39,4 +40,4 @@ module Chronic
39
40
  super << '-second'
40
41
  end
41
42
  end
42
- end
43
+ end
@@ -26,31 +26,41 @@ module Chronic
26
26
 
27
27
  end
28
28
 
29
- def initialize(time)
30
- t = time.gsub(/\:/, '')
31
-
32
- @type =
33
- case t.size
34
- when 1..2
35
- hours = t.to_i
36
- Tick.new((hours == 12 ? 0 : hours) * 60 * 60, true)
37
- when 3
38
- hours = t[0..0].to_i
39
- ambiguous = hours > 0
40
- Tick.new((hours * 60 * 60) + (t[1..2].to_i * 60), ambiguous)
41
- when 4
42
- ambiguous = time =~ /:/ && t[0..0].to_i != 0 && t[0..1].to_i <= 12
43
- hours = t[0..1].to_i
44
- hours == 12 ? Tick.new(0 * 60 * 60 + t[2..3].to_i * 60, ambiguous) : Tick.new(hours * 60 * 60 + t[2..3].to_i * 60, ambiguous)
45
- when 5
46
- Tick.new(t[0..0].to_i * 60 * 60 + t[1..2].to_i * 60 + t[3..4].to_i, true)
47
- when 6
48
- ambiguous = time =~ /:/ && t[0..0].to_i != 0 && t[0..1].to_i <= 12
49
- hours = t[0..1].to_i
50
- hours == 12 ? Tick.new(0 * 60 * 60 + t[2..3].to_i * 60 + t[4..5].to_i, ambiguous) : Tick.new(hours * 60 * 60 + t[2..3].to_i * 60 + t[4..5].to_i, ambiguous)
51
- else
52
- raise("Time cannot exceed six digits")
29
+ def initialize(time, options = {})
30
+ @current_time = nil
31
+ @options = options
32
+ time_parts = time.split(':')
33
+ raise ArgumentError, "Time cannot have more than 4 groups of ':'" if time_parts.count > 4
34
+
35
+ if time_parts.first.length > 2 and time_parts.count == 1
36
+ if time_parts.first.length > 4
37
+ second_index = time_parts.first.length - 2
38
+ time_parts.insert(1, time_parts.first[second_index..time_parts.first.length])
39
+ time_parts[0] = time_parts.first[0..second_index - 1]
40
+ end
41
+ minute_index = time_parts.first.length - 2
42
+ time_parts.insert(1, time_parts.first[minute_index..time_parts.first.length])
43
+ time_parts[0] = time_parts.first[0..minute_index - 1]
44
+ end
45
+
46
+ ambiguous = false
47
+ hours = time_parts.first.to_i
48
+
49
+ if @options[:hours24].nil? or (not @options[:hours24].nil? and @options[:hours24] != true)
50
+ ambiguous = true if (time_parts.first.length == 1 and hours > 0) or (hours >= 10 and hours <= 12) or (@options[:hours24] == false and hours > 0)
51
+ hours = 0 if hours == 12 and ambiguous
53
52
  end
53
+
54
+ hours *= 60 * 60
55
+ minutes = 0
56
+ seconds = 0
57
+ subseconds = 0
58
+
59
+ minutes = time_parts[1].to_i * 60 if time_parts.count > 1
60
+ seconds = time_parts[2].to_i if time_parts.count > 2
61
+ subseconds = time_parts[3].to_f / (10 ** time_parts[3].length) if time_parts.count > 3
62
+
63
+ @type = Tick.new(hours + minutes + seconds + subseconds, ambiguous)
54
64
  end
55
65
 
56
66
  # Return the next past or future Span for the time that this Repeater represents
@@ -125,4 +135,4 @@ module Chronic
125
135
  super << '-time-' << @type.to_s
126
136
  end
127
137
  end
128
- end
138
+ end
@@ -2,14 +2,15 @@ module Chronic
2
2
  class RepeaterWeek < Repeater #:nodoc:
3
3
  WEEK_SECONDS = 604800 # (7 * 24 * 60 * 60)
4
4
 
5
- def initialize(type)
5
+ def initialize(type, options = {})
6
6
  super
7
+ @current_week_start = nil
7
8
  end
8
9
 
9
10
  def next(pointer)
10
11
  super
11
12
 
12
- if !@current_week_start
13
+ unless @current_week_start
13
14
  case pointer
14
15
  when :future
15
16
  sunday_repeater = RepeaterDayName.new(:sunday)
@@ -71,4 +72,4 @@ module Chronic
71
72
  super << '-week'
72
73
  end
73
74
  end
74
- end
75
+ end
@@ -11,8 +11,9 @@ module Chronic
11
11
  :saturday => 6
12
12
  }
13
13
 
14
- def initialize(type)
14
+ def initialize(type, options = {})
15
15
  super
16
+ @current_weekday_start = nil
16
17
  end
17
18
 
18
19
  def next(pointer)
@@ -20,7 +21,7 @@ module Chronic
20
21
 
21
22
  direction = pointer == :future ? 1 : -1
22
23
 
23
- if !@current_weekday_start
24
+ unless @current_weekday_start
24
25
  @current_weekday_start = Chronic.construct(@now.year, @now.month, @now.day)
25
26
  @current_weekday_start += direction * DAY_SECONDS
26
27
 
@@ -82,4 +83,4 @@ module Chronic
82
83
  DAYS[sym] || raise("Invalid symbol specified")
83
84
  end
84
85
  end
85
- end
86
+ end
@@ -2,14 +2,15 @@ module Chronic
2
2
  class RepeaterWeekend < Repeater #:nodoc:
3
3
  WEEKEND_SECONDS = 172_800 # (2 * 24 * 60 * 60)
4
4
 
5
- def initialize(type)
5
+ def initialize(type, options = {})
6
6
  super
7
+ @current_week_start = nil
7
8
  end
8
9
 
9
10
  def next(pointer)
10
11
  super
11
12
 
12
- if !@current_week_start
13
+ unless @current_week_start
13
14
  case pointer
14
15
  when :future
15
16
  saturday_repeater = RepeaterDayName.new(:saturday)
@@ -63,4 +64,4 @@ module Chronic
63
64
  super << '-weekend'
64
65
  end
65
66
  end
66
- end
67
+ end
@@ -2,14 +2,15 @@ module Chronic
2
2
  class RepeaterYear < Repeater #:nodoc:
3
3
  YEAR_SECONDS = 31536000 # 365 * 24 * 60 * 60
4
4
 
5
- def initialize(type)
5
+ def initialize(type, options = {})
6
6
  super
7
+ @current_year_start = nil
7
8
  end
8
9
 
9
10
  def next(pointer)
10
11
  super
11
12
 
12
- if !@current_year_start
13
+ unless @current_year_start
13
14
  case pointer
14
15
  when :future
15
16
  @current_year_start = Chronic.construct(@now.year + 1)
@@ -67,11 +68,11 @@ module Chronic
67
68
  end
68
69
 
69
70
  def month_days(year, month)
70
- if Date.leap?(year)
71
+ if ::Date.leap?(year)
71
72
  RepeaterMonth::MONTH_DAYS_LEAP[month - 1]
72
73
  else
73
74
  RepeaterMonth::MONTH_DAYS[month - 1]
74
75
  end
75
76
  end
76
77
  end
77
- end
78
+ end
@@ -11,88 +11,53 @@ module Chronic
11
11
  # Returns an Array of tokens.
12
12
  def self.scan(tokens, options)
13
13
  tokens.each_index do |i|
14
- if t = scan_for_scalars(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
15
- if t = scan_for_days(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
16
- if t = scan_for_months(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
17
- if t = scan_for_years(tokens[i], tokens[i + 1], options) then tokens[i].tag(t) end
18
- end
19
- end
20
-
21
- # token - The Token object we want to scan.
22
- # post_token - The next Token object.
23
- #
24
- # Returns a new Scalar object.
25
- def self.scan_for_scalars(token, post_token)
26
- if token.word =~ /^\d*$/
27
- unless post_token && DAY_PORTIONS.include?(post_token.word)
28
- return Scalar.new(token.word.to_i)
14
+ token = tokens[i]
15
+ post_token = tokens[i + 1]
16
+ if token.word =~ /^\d+$/
17
+ scalar = token.word.to_i
18
+ token.tag(Scalar.new(scalar))
19
+ token.tag(ScalarSubsecond.new(scalar)) if Chronic::Time::could_be_subsecond?(scalar)
20
+ token.tag(ScalarSecond.new(scalar)) if Chronic::Time::could_be_second?(scalar)
21
+ token.tag(ScalarMinute.new(scalar)) if Chronic::Time::could_be_minute?(scalar)
22
+ token.tag(ScalarHour.new(scalar)) if Chronic::Time::could_be_hour?(scalar)
23
+ unless post_token and DAY_PORTIONS.include?(post_token.word)
24
+ token.tag(ScalarDay.new(scalar)) if Chronic::Date::could_be_day?(scalar)
25
+ token.tag(ScalarMonth.new(scalar)) if Chronic::Date::could_be_month?(scalar)
26
+ if Chronic::Date::could_be_year?(scalar)
27
+ year = Chronic::Date::make_year(scalar, options[:ambiguous_year_future_bias])
28
+ token.tag(ScalarYear.new(year.to_i))
29
+ end
30
+ end
29
31
  end
30
32
  end
31
33
  end
32
34
 
33
- # token - The Token object we want to scan.
34
- # post_token - The next Token object.
35
- #
36
- # Returns a new Scalar object.
37
- def self.scan_for_days(token, post_token)
38
- if token.word =~ /^\d\d?$/
39
- toi = token.word.to_i
40
- unless toi > 31 || toi < 1 || (post_token && DAY_PORTIONS.include?(post_token.word))
41
- return ScalarDay.new(toi)
42
- end
43
- end
35
+ def to_s
36
+ 'scalar'
44
37
  end
38
+ end
45
39
 
46
- # token - The Token object we want to scan.
47
- # post_token - The next Token object.
48
- #
49
- # Returns a new Scalar object.
50
- def self.scan_for_months(token, post_token)
51
- if token.word =~ /^\d\d?$/
52
- toi = token.word.to_i
53
- unless toi > 12 || toi < 1 || (post_token && DAY_PORTIONS.include?(post_token.word))
54
- return ScalarMonth.new(toi)
55
- end
56
- end
40
+ class ScalarSubsecond < Scalar #:nodoc:
41
+ def to_s
42
+ super << '-subsecond-' << @type.to_s
57
43
  end
44
+ end
58
45
 
59
- # token - The Token object we want to scan.
60
- # post_token - The next Token object.
61
- # options - The Hash of options specified in Chronic::parse.
62
- #
63
- # Returns a new Scalar object.
64
- def self.scan_for_years(token, post_token, options)
65
- if token.word =~ /^([1-9]\d)?\d\d?$/
66
- unless post_token && DAY_PORTIONS.include?(post_token.word)
67
- year = make_year(token.word.to_i, options[:ambiguous_year_future_bias])
68
- return ScalarYear.new(year.to_i)
69
- end
70
- end
46
+ class ScalarSecond < Scalar #:nodoc:
47
+ def to_s
48
+ super << '-second-' << @type.to_s
71
49
  end
50
+ end
72
51
 
73
- # Build a year from a 2 digit suffix.
74
- #
75
- # year - The two digit Integer year to build from.
76
- # bias - The Integer amount of future years to bias.
77
- #
78
- # Examples:
79
- #
80
- # make_year(96, 50) #=> 1996
81
- # make_year(79, 20) #=> 2079
82
- # make_year(00, 50) #=> 2000
83
- #
84
- # Returns The Integer 4 digit year.
85
- def self.make_year(year, bias)
86
- return year if year.to_s.size > 2
87
- start_year = Chronic.time_class.now.year - bias
88
- century = (start_year / 100) * 100
89
- full_year = century + year
90
- full_year += 100 if full_year < start_year
91
- full_year
52
+ class ScalarMinute < Scalar #:nodoc:
53
+ def to_s
54
+ super << '-minute-' << @type.to_s
92
55
  end
56
+ end
93
57
 
58
+ class ScalarHour < Scalar #:nodoc:
94
59
  def to_s
95
- 'scalar'
60
+ super << '-hour-' << @type.to_s
96
61
  end
97
62
  end
98
63
 
@@ -11,11 +11,18 @@ module Chronic
11
11
  def self.scan(tokens, options)
12
12
  tokens.each do |token|
13
13
  if t = scan_for_commas(token) then token.tag(t); next end
14
- if t = scan_for_slash_or_dash(token) then token.tag(t); next end
14
+ if t = scan_for_dots(token) then token.tag(t); next end
15
+ if t = scan_for_colon(token) then token.tag(t); next end
16
+ if t = scan_for_space(token) then token.tag(t); next end
17
+ if t = scan_for_slash(token) then token.tag(t); next end
18
+ if t = scan_for_dash(token) then token.tag(t); next end
19
+ if t = scan_for_quote(token) then token.tag(t); next end
15
20
  if t = scan_for_at(token) then token.tag(t); next end
16
21
  if t = scan_for_in(token) then token.tag(t); next end
17
22
  if t = scan_for_on(token) then token.tag(t); next end
18
23
  if t = scan_for_and(token) then token.tag(t); next end
24
+ if t = scan_for_t(token) then token.tag(t); next end
25
+ if t = scan_for_w(token) then token.tag(t); next end
19
26
  end
20
27
  end
21
28
 
@@ -28,12 +35,47 @@ module Chronic
28
35
 
29
36
  # token - The Token object we want to scan.
30
37
  #
31
- # Returns a new SeparatorSlashOrDash object.
32
- def self.scan_for_slash_or_dash(token)
33
- scan_for token, SeparatorSlashOrDash,
38
+ # Returns a new SeparatorDot object.
39
+ def self.scan_for_dots(token)
40
+ scan_for token, SeparatorDot, { /^\.$/ => :dot }
41
+ end
42
+
43
+ # token - The Token object we want to scan.
44
+ #
45
+ # Returns a new SeparatorColon object.
46
+ def self.scan_for_colon(token)
47
+ scan_for token, SeparatorColon, { /^:$/ => :colon }
48
+ end
49
+
50
+ # token - The Token object we want to scan.
51
+ #
52
+ # Returns a new SeparatorSpace object.
53
+ def self.scan_for_space(token)
54
+ scan_for token, SeparatorSpace, { /^ $/ => :space }
55
+ end
56
+
57
+ # token - The Token object we want to scan.
58
+ #
59
+ # Returns a new SeparatorSlash object.
60
+ def self.scan_for_slash(token)
61
+ scan_for token, SeparatorSlash, { /^\/$/ => :slash }
62
+ end
63
+
64
+ # token - The Token object we want to scan.
65
+ #
66
+ # Returns a new SeparatorDash object.
67
+ def self.scan_for_dash(token)
68
+ scan_for token, SeparatorDash, { /^-$/ => :dash }
69
+ end
70
+
71
+ # token - The Token object we want to scan.
72
+ #
73
+ # Returns a new SeparatorQuote object.
74
+ def self.scan_for_quote(token)
75
+ scan_for token, SeparatorQuote,
34
76
  {
35
- /^-$/ => :dash,
36
- /^\/$/ => :slash
77
+ /^'$/ => :single_quote,
78
+ /^"$/ => :double_quote
37
79
  }
38
80
  end
39
81
 
@@ -65,6 +107,20 @@ module Chronic
65
107
  scan_for token, SeparatorAnd, { /^and$/ => :and }
66
108
  end
67
109
 
110
+ # token - The Token object we want to scan.
111
+ #
112
+ # Returns a new SeperatorT Object object.
113
+ def self.scan_for_t(token)
114
+ scan_for token, SeparatorT, { /^t$/ => :T }
115
+ end
116
+
117
+ # token - The Token object we want to scan.
118
+ #
119
+ # Returns a new SeperatorW Object object.
120
+ def self.scan_for_w(token)
121
+ scan_for token, SeparatorW, { /^w$/ => :W }
122
+ end
123
+
68
124
  def to_s
69
125
  'separator'
70
126
  end
@@ -76,9 +132,39 @@ module Chronic
76
132
  end
77
133
  end
78
134
 
79
- class SeparatorSlashOrDash < Separator #:nodoc:
135
+ class SeparatorDot < Separator #:nodoc:
80
136
  def to_s
81
- super << '-slashordash-' << @type.to_s
137
+ super << '-dot'
138
+ end
139
+ end
140
+
141
+ class SeparatorColon < Separator #:nodoc:
142
+ def to_s
143
+ super << '-colon'
144
+ end
145
+ end
146
+
147
+ class SeparatorSpace < Separator #:nodoc:
148
+ def to_s
149
+ super << '-space'
150
+ end
151
+ end
152
+
153
+ class SeparatorSlash < Separator #:nodoc:
154
+ def to_s
155
+ super << '-slash'
156
+ end
157
+ end
158
+
159
+ class SeparatorDash < Separator #:nodoc:
160
+ def to_s
161
+ super << '-dash'
162
+ end
163
+ end
164
+
165
+ class SeparatorQuote < Separator #:nodoc:
166
+ def to_s
167
+ super << '-quote-' << @type.to_s
82
168
  end
83
169
  end
84
170
 
@@ -105,4 +191,17 @@ module Chronic
105
191
  super << '-and'
106
192
  end
107
193
  end
194
+
195
+ class SeparatorT < Separator #:nodoc:
196
+ def to_s
197
+ super << '-T'
198
+ end
199
+ end
200
+
201
+ class SeparatorW < Separator #:nodoc:
202
+ def to_s
203
+ super << '-W'
204
+ end
205
+ end
206
+
108
207
  end