chronic_2011 0.1.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 (62) hide show
  1. data/.gitignore +6 -0
  2. data/HISTORY.md +4 -0
  3. data/LICENSE +21 -0
  4. data/README.md +180 -0
  5. data/Rakefile +46 -0
  6. data/chronic.gemspec +18 -0
  7. data/lib/chronic.rb +117 -0
  8. data/lib/chronic/chronic.rb +346 -0
  9. data/lib/chronic/grabber.rb +33 -0
  10. data/lib/chronic/handler.rb +88 -0
  11. data/lib/chronic/handlers.rb +553 -0
  12. data/lib/chronic/mini_date.rb +38 -0
  13. data/lib/chronic/numerizer.rb +121 -0
  14. data/lib/chronic/ordinal.rb +47 -0
  15. data/lib/chronic/pointer.rb +32 -0
  16. data/lib/chronic/repeater.rb +142 -0
  17. data/lib/chronic/repeaters/repeater_day.rb +53 -0
  18. data/lib/chronic/repeaters/repeater_day_name.rb +52 -0
  19. data/lib/chronic/repeaters/repeater_day_portion.rb +108 -0
  20. data/lib/chronic/repeaters/repeater_fortnight.rb +71 -0
  21. data/lib/chronic/repeaters/repeater_hour.rb +58 -0
  22. data/lib/chronic/repeaters/repeater_minute.rb +58 -0
  23. data/lib/chronic/repeaters/repeater_month.rb +79 -0
  24. data/lib/chronic/repeaters/repeater_month_name.rb +94 -0
  25. data/lib/chronic/repeaters/repeater_season.rb +109 -0
  26. data/lib/chronic/repeaters/repeater_season_name.rb +43 -0
  27. data/lib/chronic/repeaters/repeater_second.rb +42 -0
  28. data/lib/chronic/repeaters/repeater_time.rb +128 -0
  29. data/lib/chronic/repeaters/repeater_week.rb +74 -0
  30. data/lib/chronic/repeaters/repeater_weekday.rb +85 -0
  31. data/lib/chronic/repeaters/repeater_weekend.rb +66 -0
  32. data/lib/chronic/repeaters/repeater_year.rb +77 -0
  33. data/lib/chronic/scalar.rb +116 -0
  34. data/lib/chronic/season.rb +26 -0
  35. data/lib/chronic/separator.rb +94 -0
  36. data/lib/chronic/span.rb +31 -0
  37. data/lib/chronic/tag.rb +36 -0
  38. data/lib/chronic/time_zone.rb +32 -0
  39. data/lib/chronic/token.rb +47 -0
  40. data/test/helper.rb +12 -0
  41. data/test/test_chronic.rb +148 -0
  42. data/test/test_daylight_savings.rb +118 -0
  43. data/test/test_handler.rb +104 -0
  44. data/test/test_mini_date.rb +32 -0
  45. data/test/test_numerizer.rb +72 -0
  46. data/test/test_parsing.rb +977 -0
  47. data/test/test_repeater_day_name.rb +51 -0
  48. data/test/test_repeater_day_portion.rb +254 -0
  49. data/test/test_repeater_fortnight.rb +62 -0
  50. data/test/test_repeater_hour.rb +68 -0
  51. data/test/test_repeater_minute.rb +34 -0
  52. data/test/test_repeater_month.rb +50 -0
  53. data/test/test_repeater_month_name.rb +56 -0
  54. data/test/test_repeater_season.rb +40 -0
  55. data/test/test_repeater_time.rb +70 -0
  56. data/test/test_repeater_week.rb +62 -0
  57. data/test/test_repeater_weekday.rb +55 -0
  58. data/test/test_repeater_weekend.rb +74 -0
  59. data/test/test_repeater_year.rb +69 -0
  60. data/test/test_span.rb +23 -0
  61. data/test/test_token.rb +25 -0
  62. metadata +156 -0
@@ -0,0 +1,66 @@
1
+ module Chronic
2
+ class RepeaterWeekend < Repeater #:nodoc:
3
+ WEEKEND_SECONDS = 172_800 # (2 * 24 * 60 * 60)
4
+
5
+ def initialize(type)
6
+ super
7
+ end
8
+
9
+ def next(pointer)
10
+ super
11
+
12
+ if !@current_week_start
13
+ case pointer
14
+ when :future
15
+ saturday_repeater = RepeaterDayName.new(:saturday)
16
+ saturday_repeater.start = @now
17
+ next_saturday_span = saturday_repeater.next(:future)
18
+ @current_week_start = next_saturday_span.begin
19
+ when :past
20
+ saturday_repeater = RepeaterDayName.new(:saturday)
21
+ saturday_repeater.start = (@now + RepeaterDay::DAY_SECONDS)
22
+ last_saturday_span = saturday_repeater.next(:past)
23
+ @current_week_start = last_saturday_span.begin
24
+ end
25
+ else
26
+ direction = pointer == :future ? 1 : -1
27
+ @current_week_start += direction * RepeaterWeek::WEEK_SECONDS
28
+ end
29
+
30
+ Span.new(@current_week_start, @current_week_start + WEEKEND_SECONDS)
31
+ end
32
+
33
+ def this(pointer = :future)
34
+ super
35
+
36
+ case pointer
37
+ when :future, :none
38
+ saturday_repeater = RepeaterDayName.new(:saturday)
39
+ saturday_repeater.start = @now
40
+ this_saturday_span = saturday_repeater.this(:future)
41
+ Span.new(this_saturday_span.begin, this_saturday_span.begin + WEEKEND_SECONDS)
42
+ when :past
43
+ saturday_repeater = RepeaterDayName.new(:saturday)
44
+ saturday_repeater.start = @now
45
+ last_saturday_span = saturday_repeater.this(:past)
46
+ Span.new(last_saturday_span.begin, last_saturday_span.begin + WEEKEND_SECONDS)
47
+ end
48
+ end
49
+
50
+ def offset(span, amount, pointer)
51
+ direction = pointer == :future ? 1 : -1
52
+ weekend = RepeaterWeekend.new(:weekend)
53
+ weekend.start = span.begin
54
+ start = weekend.next(pointer).begin + (amount - 1) * direction * RepeaterWeek::WEEK_SECONDS
55
+ Span.new(start, start + (span.end - span.begin))
56
+ end
57
+
58
+ def width
59
+ WEEKEND_SECONDS
60
+ end
61
+
62
+ def to_s
63
+ super << '-weekend'
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,77 @@
1
+ module Chronic
2
+ class RepeaterYear < Repeater #:nodoc:
3
+ YEAR_SECONDS = 31536000 # 365 * 24 * 60 * 60
4
+
5
+ def initialize(type)
6
+ super
7
+ end
8
+
9
+ def next(pointer)
10
+ super
11
+
12
+ if !@current_year_start
13
+ case pointer
14
+ when :future
15
+ @current_year_start = Chronic.construct(@now.year + 1)
16
+ when :past
17
+ @current_year_start = Chronic.construct(@now.year - 1)
18
+ end
19
+ else
20
+ diff = pointer == :future ? 1 : -1
21
+ @current_year_start = Chronic.construct(@current_year_start.year + diff)
22
+ end
23
+
24
+ Span.new(@current_year_start, Chronic.construct(@current_year_start.year + 1))
25
+ end
26
+
27
+ def this(pointer = :future)
28
+ super
29
+
30
+ case pointer
31
+ when :future
32
+ this_year_start = Chronic.construct(@now.year, @now.month, @now.day + 1)
33
+ this_year_end = Chronic.construct(@now.year + 1, 1, 1)
34
+ when :past
35
+ this_year_start = Chronic.construct(@now.year, 1, 1)
36
+ this_year_end = Chronic.construct(@now.year, @now.month, @now.day)
37
+ when :none
38
+ this_year_start = Chronic.construct(@now.year, 1, 1)
39
+ this_year_end = Chronic.construct(@now.year + 1, 1, 1)
40
+ end
41
+
42
+ Span.new(this_year_start, this_year_end)
43
+ end
44
+
45
+ def offset(span, amount, pointer)
46
+ direction = pointer == :future ? 1 : -1
47
+ new_begin = build_offset_time(span.begin, amount, direction)
48
+ new_end = build_offset_time(span.end, amount, direction)
49
+ Span.new(new_begin, new_end)
50
+ end
51
+
52
+ def width
53
+ YEAR_SECONDS
54
+ end
55
+
56
+ def to_s
57
+ super << '-year'
58
+ end
59
+
60
+ private
61
+
62
+ def build_offset_time(time, amount, direction)
63
+ year = time.year + (amount * direction)
64
+ days = month_days(year, time.month)
65
+ day = time.day > days ? days : time.day
66
+ Chronic.construct(year, time.month, day, time.hour, time.min, time.sec)
67
+ end
68
+
69
+ def month_days(year, month)
70
+ if Date.leap?(year)
71
+ RepeaterMonth::MONTH_DAYS_LEAP[month - 1]
72
+ else
73
+ RepeaterMonth::MONTH_DAYS[month - 1]
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,116 @@
1
+ module Chronic
2
+ class Scalar < Tag
3
+ DAY_PORTIONS = %w( am pm morning afternoon evening night )
4
+
5
+ # Scan an Array of Token objects and apply any necessary Scalar
6
+ # tags to each token.
7
+ #
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.
12
+ def self.scan(tokens, options)
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)
29
+ end
30
+ end
31
+ end
32
+
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
44
+ end
45
+
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
57
+ end
58
+
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
71
+ end
72
+
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
92
+ end
93
+
94
+ def to_s
95
+ 'scalar'
96
+ end
97
+ end
98
+
99
+ class ScalarDay < Scalar #:nodoc:
100
+ def to_s
101
+ super << '-day-' << @type.to_s
102
+ end
103
+ end
104
+
105
+ class ScalarMonth < Scalar #:nodoc:
106
+ def to_s
107
+ super << '-month-' << @type.to_s
108
+ end
109
+ end
110
+
111
+ class ScalarYear < Scalar #:nodoc:
112
+ def to_s
113
+ super << '-year-' << @type.to_s
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,26 @@
1
+ module Chronic
2
+ class Season
3
+
4
+ attr_reader :start
5
+ attr_reader :end
6
+
7
+ def initialize(start_date, end_date)
8
+ @start = start_date
9
+ @end = end_date
10
+ end
11
+
12
+ def self.find_next_season(season, pointer)
13
+ lookup = [:spring, :summer, :autumn, :winter]
14
+ next_season_num = (lookup.index(season) + 1 * pointer) % 4
15
+ lookup[next_season_num]
16
+ end
17
+
18
+ def self.season_after(season)
19
+ find_next_season(season, +1)
20
+ end
21
+
22
+ def self.season_before(season)
23
+ find_next_season(season, -1)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,94 @@
1
+ module Chronic
2
+ class Separator < Tag
3
+
4
+ # Scan an Array of Token objects and apply any necessary Separator
5
+ # tags to each token.
6
+ #
7
+ # tokens - An Array of tokens to scan.
8
+ # options - The Hash of options specified in Chronic::parse.
9
+ #
10
+ # Returns an Array of tokens.
11
+ def self.scan(tokens, options)
12
+ tokens.each do |token|
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
15
+ if t = scan_for_at(token) then token.tag(t); next end
16
+ if t = scan_for_in(token) then token.tag(t); next end
17
+ if t = scan_for_on(token) then token.tag(t); next end
18
+ end
19
+ end
20
+
21
+ # token - The Token object we want to scan.
22
+ #
23
+ # Returns a new SeparatorComma object.
24
+ def self.scan_for_commas(token)
25
+ scan_for token, SeparatorComma, { /^,$/ => :comma }
26
+ end
27
+
28
+ # token - The Token object we want to scan.
29
+ #
30
+ # Returns a new SeparatorSlashOrDash object.
31
+ def self.scan_for_slash_or_dash(token)
32
+ scan_for token, SeparatorSlashOrDash,
33
+ {
34
+ /^-$/ => :dash,
35
+ /^\/$/ => :slash
36
+ }
37
+ end
38
+
39
+ # token - The Token object we want to scan.
40
+ #
41
+ # Returns a new SeparatorAt object.
42
+ def self.scan_for_at(token)
43
+ scan_for token, SeparatorAt, { /^(at|@)$/ => :at }
44
+ end
45
+
46
+ # token - The Token object we want to scan.
47
+ #
48
+ # Returns a new SeparatorIn object.
49
+ def self.scan_for_in(token)
50
+ scan_for token, SeparatorIn, { /^in$/ => :in }
51
+ end
52
+
53
+ # token - The Token object we want to scan.
54
+ #
55
+ # Returns a new SeparatorOn object.
56
+ def self.scan_for_on(token)
57
+ scan_for token, SeparatorOn, { /^on$/ => :on }
58
+ end
59
+
60
+ def to_s
61
+ 'separator'
62
+ end
63
+ end
64
+
65
+ class SeparatorComma < Separator #:nodoc:
66
+ def to_s
67
+ super << '-comma'
68
+ end
69
+ end
70
+
71
+ class SeparatorSlashOrDash < Separator #:nodoc:
72
+ def to_s
73
+ super << '-slashordash-' << @type.to_s
74
+ end
75
+ end
76
+
77
+ class SeparatorAt < Separator #:nodoc:
78
+ def to_s
79
+ super << '-at'
80
+ end
81
+ end
82
+
83
+ class SeparatorIn < Separator #:nodoc:
84
+ def to_s
85
+ super << '-in'
86
+ end
87
+ end
88
+
89
+ class SeparatorOn < Separator #:nodoc:
90
+ def to_s
91
+ super << '-on'
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,31 @@
1
+ module Chronic
2
+ # A Span represents a range of time. Since this class extends
3
+ # Range, you can use #begin and #end to get the beginning and
4
+ # ending times of the span (they will be of class Time)
5
+ class Span < Range
6
+ # Returns the width of this span in seconds
7
+ def width
8
+ (self.end - self.begin).to_i
9
+ end
10
+
11
+ # Add a number of seconds to this span, returning the
12
+ # resulting Span
13
+ def +(seconds)
14
+ Span.new(self.begin + seconds, self.end + seconds)
15
+ end
16
+
17
+ # Subtract a number of seconds to this span, returning the
18
+ # resulting Span
19
+ def -(seconds)
20
+ self + -seconds
21
+ end
22
+
23
+ # Prints this span in a nice fashion
24
+ def to_s
25
+ '(' << self.begin.to_s << '..' << self.end.to_s << ')'
26
+ end
27
+
28
+ alias :cover? :include? unless RUBY_VERSION =~ /^1.9/
29
+
30
+ end
31
+ end
@@ -0,0 +1,36 @@
1
+ module Chronic
2
+ # Tokens are tagged with subclassed instances of this class when
3
+ # they match specific criteria.
4
+ class Tag
5
+
6
+ attr_accessor :type
7
+
8
+ # type - The Symbol type of this tag.
9
+ def initialize(type)
10
+ @type = type
11
+ end
12
+
13
+ # time - Set the start Time for this Tag.
14
+ def start=(time)
15
+ @now = time
16
+ end
17
+
18
+ class << self
19
+ private
20
+
21
+ def scan_for(token, klass, items={})
22
+ case items
23
+ when Regexp
24
+ return klass.new(token.word) if items =~ token.word
25
+ when Hash
26
+ items.each do |item, symbol|
27
+ return klass.new(symbol) if item =~ token.word
28
+ end
29
+ end
30
+ nil
31
+ end
32
+
33
+ end
34
+
35
+ end
36
+ end