chronic 0.9.1 → 0.10.2

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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -4
  3. data/HISTORY.md +21 -0
  4. data/README.md +8 -0
  5. data/Rakefile +28 -8
  6. data/chronic.gemspec +8 -5
  7. data/lib/chronic/date.rb +82 -0
  8. data/lib/chronic/handler.rb +34 -25
  9. data/lib/chronic/handlers.rb +22 -3
  10. data/lib/chronic/ordinal.rb +22 -20
  11. data/lib/chronic/parser.rb +31 -26
  12. data/lib/chronic/repeater.rb +18 -18
  13. data/lib/chronic/repeaters/repeater_day.rb +4 -3
  14. data/lib/chronic/repeaters/repeater_day_name.rb +5 -4
  15. data/lib/chronic/repeaters/repeater_day_portion.rb +4 -3
  16. data/lib/chronic/repeaters/repeater_fortnight.rb +4 -3
  17. data/lib/chronic/repeaters/repeater_hour.rb +4 -3
  18. data/lib/chronic/repeaters/repeater_minute.rb +4 -3
  19. data/lib/chronic/repeaters/repeater_month.rb +5 -4
  20. data/lib/chronic/repeaters/repeater_month_name.rb +4 -3
  21. data/lib/chronic/repeaters/repeater_season.rb +5 -3
  22. data/lib/chronic/repeaters/repeater_second.rb +4 -3
  23. data/lib/chronic/repeaters/repeater_time.rb +35 -25
  24. data/lib/chronic/repeaters/repeater_week.rb +4 -3
  25. data/lib/chronic/repeaters/repeater_weekday.rb +4 -3
  26. data/lib/chronic/repeaters/repeater_weekend.rb +4 -3
  27. data/lib/chronic/repeaters/repeater_year.rb +5 -4
  28. data/lib/chronic/scalar.rb +34 -69
  29. data/lib/chronic/separator.rb +107 -8
  30. data/lib/chronic/sign.rb +49 -0
  31. data/lib/chronic/tag.rb +5 -4
  32. data/lib/chronic/time.rb +40 -0
  33. data/lib/chronic.rb +16 -10
  34. data/test/helper.rb +2 -2
  35. data/test/test_chronic.rb +55 -4
  36. data/test/test_handler.rb +20 -0
  37. data/test/test_parsing.rb +75 -3
  38. data/test/test_repeater_time.rb +18 -0
  39. metadata +37 -5
@@ -2,14 +2,15 @@ module Chronic
2
2
  class RepeaterDay < Repeater #:nodoc:
3
3
  DAY_SECONDS = 86_400 # (24 * 60 * 60)
4
4
 
5
- def initialize(type)
5
+ def initialize(type, options = {})
6
6
  super
7
+ @current_day_start = nil
7
8
  end
8
9
 
9
10
  def next(pointer)
10
11
  super
11
12
 
12
- if !@current_day_start
13
+ unless @current_day_start
13
14
  @current_day_start = Chronic.time_class.local(@now.year, @now.month, @now.day)
14
15
  end
15
16
 
@@ -50,4 +51,4 @@ module Chronic
50
51
  super << '-day'
51
52
  end
52
53
  end
53
- end
54
+ end
@@ -2,8 +2,9 @@ module Chronic
2
2
  class RepeaterDayName < Repeater #:nodoc:
3
3
  DAY_SECONDS = 86400 # (24 * 60 * 60)
4
4
 
5
- def initialize(type)
5
+ def initialize(type, options = {})
6
6
  super
7
+ @current_date = nil
7
8
  end
8
9
 
9
10
  def next(pointer)
@@ -11,8 +12,8 @@ module Chronic
11
12
 
12
13
  direction = pointer == :future ? 1 : -1
13
14
 
14
- if !@current_date
15
- @current_date = Date.new(@now.year, @now.month, @now.day)
15
+ unless @current_date
16
+ @current_date = ::Date.new(@now.year, @now.month, @now.day)
16
17
  @current_date += direction
17
18
 
18
19
  day_num = symbol_to_number(@type)
@@ -49,4 +50,4 @@ module Chronic
49
50
  lookup[sym] || raise("Invalid symbol specified")
50
51
  end
51
52
  end
52
- end
53
+ end
@@ -9,8 +9,9 @@ module Chronic
9
9
  :night => (20 * 60 * 60)..(24 * 60 * 60), # 8pm-12pm
10
10
  }
11
11
 
12
- def initialize(type)
12
+ def initialize(type, options = {})
13
13
  super
14
+ @current_span = nil
14
15
 
15
16
  if type.kind_of? Integer
16
17
  @range = (@type * 60 * 60)..((@type + 12) * 60 * 60)
@@ -25,7 +26,7 @@ module Chronic
25
26
  def next(pointer)
26
27
  super
27
28
 
28
- if !@current_span
29
+ unless @current_span
29
30
  now_seconds = @now - Chronic.construct(@now.year, @now.month, @now.day)
30
31
  if now_seconds < @range.begin
31
32
  case pointer
@@ -105,4 +106,4 @@ module Chronic
105
106
  Chronic.construct(reference.year, reference.month, reference.day, hour_hand, minute_hand, second_hand)
106
107
  end
107
108
  end
108
- end
109
+ end
@@ -2,14 +2,15 @@ module Chronic
2
2
  class RepeaterFortnight < Repeater #:nodoc:
3
3
  FORTNIGHT_SECONDS = 1_209_600 # (14 * 24 * 60 * 60)
4
4
 
5
- def initialize(type)
5
+ def initialize(type, options = {})
6
6
  super
7
+ @current_fortnight_start = nil
7
8
  end
8
9
 
9
10
  def next(pointer)
10
11
  super
11
12
 
12
- if !@current_fortnight_start
13
+ unless @current_fortnight_start
13
14
  case pointer
14
15
  when :future
15
16
  sunday_repeater = RepeaterDayName.new(:sunday)
@@ -68,4 +69,4 @@ module Chronic
68
69
  super << '-fortnight'
69
70
  end
70
71
  end
71
- end
72
+ end
@@ -2,14 +2,15 @@ module Chronic
2
2
  class RepeaterHour < Repeater #:nodoc:
3
3
  HOUR_SECONDS = 3600 # 60 * 60
4
4
 
5
- def initialize(type)
5
+ def initialize(type, options = {})
6
6
  super
7
+ @current_hour_start = nil
7
8
  end
8
9
 
9
10
  def next(pointer)
10
11
  super
11
12
 
12
- if !@current_hour_start
13
+ unless @current_hour_start
13
14
  case pointer
14
15
  when :future
15
16
  @current_hour_start = Chronic.construct(@now.year, @now.month, @now.day, @now.hour + 1)
@@ -55,4 +56,4 @@ module Chronic
55
56
  super << '-hour'
56
57
  end
57
58
  end
58
- end
59
+ end
@@ -2,14 +2,15 @@ module Chronic
2
2
  class RepeaterMinute < Repeater #:nodoc:
3
3
  MINUTE_SECONDS = 60
4
4
 
5
- def initialize(type)
5
+ def initialize(type, options = {})
6
6
  super
7
+ @current_minute_start = nil
7
8
  end
8
9
 
9
10
  def next(pointer = :future)
10
11
  super
11
12
 
12
- if !@current_minute_start
13
+ unless @current_minute_start
13
14
  case pointer
14
15
  when :future
15
16
  @current_minute_start = Chronic.construct(@now.year, @now.month, @now.day, @now.hour, @now.min + 1)
@@ -55,4 +56,4 @@ module Chronic
55
56
  super << '-minute'
56
57
  end
57
58
  end
58
- end
59
+ end
@@ -5,14 +5,15 @@ module Chronic
5
5
  MONTH_DAYS = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
6
6
  MONTH_DAYS_LEAP = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
7
7
 
8
- def initialize(type)
8
+ def initialize(type, options = {})
9
9
  super
10
+ @current_month_start = nil
10
11
  end
11
12
 
12
13
  def next(pointer)
13
14
  super
14
15
 
15
- if !@current_month_start
16
+ unless @current_month_start
16
17
  @current_month_start = offset_by(Chronic.construct(@now.year, @now.month), 1, pointer)
17
18
  else
18
19
  @current_month_start = offset_by(Chronic.construct(@current_month_start.year, @current_month_start.month), 1, pointer)
@@ -73,7 +74,7 @@ module Chronic
73
74
  private
74
75
 
75
76
  def month_days(year, month)
76
- Date.leap?(year) ? MONTH_DAYS_LEAP[month - 1] : MONTH_DAYS[month - 1]
77
+ ::Date.leap?(year) ? MONTH_DAYS_LEAP[month - 1] : MONTH_DAYS[month - 1]
77
78
  end
78
79
  end
79
- end
80
+ end
@@ -16,14 +16,15 @@ module Chronic
16
16
  :december => 12
17
17
  }
18
18
 
19
- def initialize(type)
19
+ def initialize(type, options = {})
20
20
  super
21
+ @current_month_begin = nil
21
22
  end
22
23
 
23
24
  def next(pointer)
24
25
  super
25
26
 
26
- if !@current_month_begin
27
+ unless @current_month_begin
27
28
  case pointer
28
29
  when :future
29
30
  if @now.month < index
@@ -91,4 +92,4 @@ module Chronic
91
92
  super << '-monthname-' << @type.to_s
92
93
  end
93
94
  end
94
- end
95
+ end
@@ -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