chronic_2011 0.1.0

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