chronic 0.6.3 → 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 (67) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -3
  3. data/.travis.yml +8 -0
  4. data/HISTORY.md +76 -0
  5. data/README.md +47 -42
  6. data/Rakefile +30 -12
  7. data/chronic.gemspec +10 -4
  8. data/lib/chronic/date.rb +82 -0
  9. data/lib/chronic/grabber.rb +9 -7
  10. data/lib/chronic/handler.rb +47 -40
  11. data/lib/chronic/handlers.rb +240 -28
  12. data/lib/chronic/numerizer.rb +11 -2
  13. data/lib/chronic/ordinal.rb +28 -23
  14. data/lib/chronic/parser.rb +268 -0
  15. data/lib/chronic/pointer.rb +9 -7
  16. data/lib/chronic/repeater.rb +58 -48
  17. data/lib/chronic/repeaters/repeater_day.rb +4 -3
  18. data/lib/chronic/repeaters/repeater_day_name.rb +5 -4
  19. data/lib/chronic/repeaters/repeater_day_portion.rb +33 -18
  20. data/lib/chronic/repeaters/repeater_fortnight.rb +4 -3
  21. data/lib/chronic/repeaters/repeater_hour.rb +4 -3
  22. data/lib/chronic/repeaters/repeater_minute.rb +4 -3
  23. data/lib/chronic/repeaters/repeater_month.rb +5 -4
  24. data/lib/chronic/repeaters/repeater_month_name.rb +4 -3
  25. data/lib/chronic/repeaters/repeater_season.rb +5 -3
  26. data/lib/chronic/repeaters/repeater_second.rb +4 -3
  27. data/lib/chronic/repeaters/repeater_time.rb +35 -25
  28. data/lib/chronic/repeaters/repeater_week.rb +4 -3
  29. data/lib/chronic/repeaters/repeater_weekday.rb +4 -3
  30. data/lib/chronic/repeaters/repeater_weekend.rb +4 -3
  31. data/lib/chronic/repeaters/repeater_year.rb +6 -5
  32. data/lib/chronic/scalar.rb +40 -68
  33. data/lib/chronic/season.rb +1 -12
  34. data/lib/chronic/separator.rb +142 -23
  35. data/lib/chronic/sign.rb +49 -0
  36. data/lib/chronic/span.rb +2 -2
  37. data/lib/chronic/tag.rb +10 -15
  38. data/lib/chronic/time.rb +40 -0
  39. data/lib/chronic/time_zone.rb +9 -7
  40. data/lib/chronic/token.rb +16 -10
  41. data/lib/chronic.rb +109 -70
  42. data/test/helper.rb +8 -2
  43. data/test/{test_Chronic.rb → test_chronic.rb} +69 -34
  44. data/test/{test_DaylightSavings.rb → test_daylight_savings.rb} +1 -1
  45. data/test/{test_Handler.rb → test_handler.rb} +38 -14
  46. data/test/{test_MiniDate.rb → test_mini_date.rb} +9 -9
  47. data/test/{test_Numerizer.rb → test_numerizer.rb} +16 -2
  48. data/test/test_parsing.rb +392 -22
  49. data/test/{test_RepeaterDayName.rb → test_repeater_day_name.rb} +1 -1
  50. data/test/test_repeater_day_portion.rb +254 -0
  51. data/test/{test_RepeaterFortnight.rb → test_repeater_fortnight.rb} +1 -1
  52. data/test/{test_RepeaterHour.rb → test_repeater_hour.rb} +1 -1
  53. data/test/{test_RepeaterMinute.rb → test_repeater_minute.rb} +1 -1
  54. data/test/{test_RepeaterMonth.rb → test_repeater_month.rb} +1 -1
  55. data/test/{test_RepeaterMonthName.rb → test_repeater_month_name.rb} +1 -1
  56. data/test/{test_RepeaterSeason.rb → test_repeater_season.rb} +1 -1
  57. data/test/{test_RepeaterTime.rb → test_repeater_time.rb} +19 -1
  58. data/test/{test_RepeaterWeek.rb → test_repeater_week.rb} +1 -1
  59. data/test/{test_RepeaterWeekday.rb → test_repeater_weekday.rb} +1 -1
  60. data/test/{test_RepeaterWeekend.rb → test_repeater_weekend.rb} +1 -1
  61. data/test/{test_RepeaterYear.rb → test_repeater_year.rb} +1 -1
  62. data/test/{test_Span.rb → test_span.rb} +2 -2
  63. data/test/{test_Token.rb → test_token.rb} +1 -1
  64. metadata +129 -87
  65. data/.gemtest +0 -0
  66. data/.yardopts +0 -3
  67. data/lib/chronic/chronic.rb +0 -323
@@ -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
- hours == 12 ? Tick.new(0 * 60 * 60, true) : Tick.new(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)
@@ -29,7 +30,7 @@ module Chronic
29
30
 
30
31
  case pointer
31
32
  when :future
32
- this_year_start = Chronic.construct(@now.year, @now.month, @now.day) + RepeaterDay::DAY_SECONDS
33
+ this_year_start = Chronic.construct(@now.year, @now.month, @now.day + 1)
33
34
  this_year_end = Chronic.construct(@now.year + 1, 1, 1)
34
35
  when :past
35
36
  this_year_start = Chronic.construct(@now.year, 1, 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
@@ -2,90 +2,62 @@ module Chronic
2
2
  class Scalar < Tag
3
3
  DAY_PORTIONS = %w( am pm morning afternoon evening night )
4
4
 
5
- # Scan an Array of {Token}s and apply any necessary Scalar tags to
6
- # each token
5
+ # Scan an Array of Token objects and apply any necessary Scalar
6
+ # tags to each token.
7
7
  #
8
- # @param [Array<Token>] tokens Array of tokens to scan
9
- # @param [Hash] options Options specified in {Chronic.parse}
10
- # @return [Array] list of tokens
8
+ # tokens - An Array of tokens to scan.
9
+ # options - The Hash of options specified in Chronic::parse.
10
+ #
11
+ # Returns an Array of tokens.
11
12
  def self.scan(tokens, options)
12
13
  tokens.each_index do |i|
13
- if t = scan_for_scalars(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
14
- if t = scan_for_days(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
15
- if t = scan_for_months(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
16
- if t = scan_for_years(tokens[i], tokens[i + 1], options) then tokens[i].tag(t) end
17
- end
18
- end
19
-
20
- # @param [Token] token
21
- # @param [Token] post_token
22
- # @return [Scalar, nil]
23
- def self.scan_for_scalars(token, post_token)
24
- if token.word =~ /^\d*$/
25
- unless post_token && DAY_PORTIONS.include?(post_token.word)
26
- 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
27
31
  end
28
32
  end
29
33
  end
30
34
 
31
- # @param [Token] token
32
- # @param [Token] post_token
33
- # @return [ScalarDay, nil]
34
- def self.scan_for_days(token, post_token)
35
- if token.word =~ /^\d\d?$/
36
- toi = token.word.to_i
37
- unless toi > 31 || toi < 1 || (post_token && DAY_PORTIONS.include?(post_token.word))
38
- return ScalarDay.new(toi)
39
- end
40
- end
35
+ def to_s
36
+ 'scalar'
41
37
  end
38
+ end
42
39
 
43
- # @param [Token] token
44
- # @param [Token] post_token
45
- # @return [ScalarMonth, nil]
46
- def self.scan_for_months(token, post_token)
47
- if token.word =~ /^\d\d?$/
48
- toi = token.word.to_i
49
- unless toi > 12 || toi < 1 || (post_token && DAY_PORTIONS.include?(post_token.word))
50
- return ScalarMonth.new(toi)
51
- end
52
- end
40
+ class ScalarSubsecond < Scalar #:nodoc:
41
+ def to_s
42
+ super << '-subsecond-' << @type.to_s
53
43
  end
44
+ end
54
45
 
55
- # @param [Token] token
56
- # @param [Token] post_token
57
- # @param [Hash] options Options specified in {Chronic.parse}
58
- # @return [ScalarYear, nil]
59
- def self.scan_for_years(token, post_token, options)
60
- if token.word =~ /^([1-9]\d)?\d\d?$/
61
- unless post_token && DAY_PORTIONS.include?(post_token.word)
62
- year = make_year(token.word.to_i, options[:ambiguous_year_future_bias])
63
- return ScalarYear.new(year.to_i)
64
- end
65
- end
46
+ class ScalarSecond < Scalar #:nodoc:
47
+ def to_s
48
+ super << '-second-' << @type.to_s
66
49
  end
50
+ end
67
51
 
68
- # Build a year from a 2 digit suffix
69
- #
70
- # @example
71
- # make_year(96, 50) #=> 1996
72
- # make_year(79, 20) #=> 2079
73
- # make_year(00, 50) #=> 2000
74
- #
75
- # @param [Integer] year The two digit year to build from
76
- # @param [Integer] bias The amount of future years to bias
77
- # @return [Integer] The 4 digit year
78
- def self.make_year(year, bias)
79
- return year if year.to_s.size > 2
80
- start_year = Chronic.time_class.now.year - bias
81
- century = (start_year / 100) * 100
82
- full_year = century + year
83
- full_year += 100 if full_year < start_year
84
- full_year
52
+ class ScalarMinute < Scalar #:nodoc:
53
+ def to_s
54
+ super << '-minute-' << @type.to_s
85
55
  end
56
+ end
86
57
 
58
+ class ScalarHour < Scalar #:nodoc:
87
59
  def to_s
88
- 'scalar'
60
+ super << '-hour-' << @type.to_s
89
61
  end
90
62
  end
91
63
 
@@ -1,35 +1,24 @@
1
1
  module Chronic
2
2
  class Season
3
- # @return [MiniDate]
4
- attr_reader :start
5
3
 
6
- # @return [MiniDate]
4
+ attr_reader :start
7
5
  attr_reader :end
8
6
 
9
- # @param [MiniDate] start_date
10
- # @param [MiniDate] end_date
11
7
  def initialize(start_date, end_date)
12
8
  @start = start_date
13
9
  @end = end_date
14
10
  end
15
11
 
16
- # @param [Symbol] season The season name
17
- # @param [Integer] pointer The direction (-1 for past, 1 for future)
18
- # @return [Symbol] The new season name
19
12
  def self.find_next_season(season, pointer)
20
13
  lookup = [:spring, :summer, :autumn, :winter]
21
14
  next_season_num = (lookup.index(season) + 1 * pointer) % 4
22
15
  lookup[next_season_num]
23
16
  end
24
17
 
25
- # @param [Symbol] season The season name
26
- # @return [Symbol] The new season name
27
18
  def self.season_after(season)
28
19
  find_next_season(season, +1)
29
20
  end
30
21
 
31
- # @param [Symbol] season The season name
32
- # @return [Symbol] The new season name
33
22
  def self.season_before(season)
34
23
  find_next_season(season, -1)
35
24
  end