gitlab-chronic 0.10.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/.gitlab-ci.yml +14 -0
- data/.travis.yml +10 -0
- data/Gemfile +3 -0
- data/HISTORY.md +252 -0
- data/LICENSE +21 -0
- data/README.md +188 -0
- data/Rakefile +68 -0
- data/chronic.gemspec +25 -0
- data/lib/chronic.rb +155 -0
- data/lib/chronic/date.rb +81 -0
- data/lib/chronic/definition.rb +128 -0
- data/lib/chronic/dictionary.rb +36 -0
- data/lib/chronic/handler.rb +97 -0
- data/lib/chronic/handlers.rb +672 -0
- data/lib/chronic/mini_date.rb +38 -0
- data/lib/chronic/parser.rb +222 -0
- data/lib/chronic/repeaters/repeater_day.rb +54 -0
- data/lib/chronic/repeaters/repeater_day_name.rb +53 -0
- data/lib/chronic/repeaters/repeater_day_portion.rb +109 -0
- data/lib/chronic/repeaters/repeater_fortnight.rb +72 -0
- data/lib/chronic/repeaters/repeater_hour.rb +59 -0
- data/lib/chronic/repeaters/repeater_minute.rb +59 -0
- data/lib/chronic/repeaters/repeater_month.rb +80 -0
- data/lib/chronic/repeaters/repeater_month_name.rb +95 -0
- data/lib/chronic/repeaters/repeater_quarter.rb +59 -0
- data/lib/chronic/repeaters/repeater_quarter_name.rb +40 -0
- data/lib/chronic/repeaters/repeater_season.rb +111 -0
- data/lib/chronic/repeaters/repeater_season_name.rb +43 -0
- data/lib/chronic/repeaters/repeater_second.rb +43 -0
- data/lib/chronic/repeaters/repeater_time.rb +159 -0
- data/lib/chronic/repeaters/repeater_week.rb +76 -0
- data/lib/chronic/repeaters/repeater_weekday.rb +86 -0
- data/lib/chronic/repeaters/repeater_weekend.rb +67 -0
- data/lib/chronic/repeaters/repeater_year.rb +78 -0
- data/lib/chronic/season.rb +26 -0
- data/lib/chronic/span.rb +31 -0
- data/lib/chronic/tag.rb +89 -0
- data/lib/chronic/tags/grabber.rb +29 -0
- data/lib/chronic/tags/ordinal.rb +52 -0
- data/lib/chronic/tags/pointer.rb +28 -0
- data/lib/chronic/tags/repeater.rb +160 -0
- data/lib/chronic/tags/scalar.rb +89 -0
- data/lib/chronic/tags/separator.rb +123 -0
- data/lib/chronic/tags/sign.rb +35 -0
- data/lib/chronic/tags/time_zone.rb +32 -0
- data/lib/chronic/time.rb +40 -0
- data/lib/chronic/token.rb +61 -0
- data/lib/chronic/tokenizer.rb +38 -0
- data/lib/chronic/version.rb +3 -0
- data/test/helper.rb +12 -0
- data/test/test_chronic.rb +203 -0
- data/test/test_daylight_savings.rb +122 -0
- data/test/test_handler.rb +128 -0
- data/test/test_mini_date.rb +32 -0
- data/test/test_parsing.rb +1537 -0
- data/test/test_repeater_day_name.rb +51 -0
- data/test/test_repeater_day_portion.rb +254 -0
- data/test/test_repeater_fortnight.rb +62 -0
- data/test/test_repeater_hour.rb +68 -0
- data/test/test_repeater_minute.rb +34 -0
- data/test/test_repeater_month.rb +50 -0
- data/test/test_repeater_month_name.rb +56 -0
- data/test/test_repeater_quarter.rb +70 -0
- data/test/test_repeater_quarter_name.rb +198 -0
- data/test/test_repeater_season.rb +40 -0
- data/test/test_repeater_time.rb +88 -0
- data/test/test_repeater_week.rb +115 -0
- data/test/test_repeater_weekday.rb +55 -0
- data/test/test_repeater_weekend.rb +74 -0
- data/test/test_repeater_year.rb +69 -0
- data/test/test_span.rb +23 -0
- data/test/test_token.rb +31 -0
- metadata +215 -0
@@ -0,0 +1,38 @@
|
|
1
|
+
module Chronic
|
2
|
+
class MiniDate
|
3
|
+
attr_accessor :month, :day
|
4
|
+
|
5
|
+
def self.from_time(time)
|
6
|
+
new(time.month, time.day)
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(month, day)
|
10
|
+
unless (1..12).include?(month)
|
11
|
+
raise ArgumentError, '1..12 are valid months'
|
12
|
+
end
|
13
|
+
|
14
|
+
@month = month
|
15
|
+
@day = day
|
16
|
+
end
|
17
|
+
|
18
|
+
def is_between?(md_start, md_end)
|
19
|
+
return false if (@month == md_start.month && @month == md_end.month) &&
|
20
|
+
(@day < md_start.day || @day > md_end.day)
|
21
|
+
return true if (@month == md_start.month && @day >= md_start.day) ||
|
22
|
+
(@month == md_end.month && @day <= md_end.day)
|
23
|
+
|
24
|
+
i = (md_start.month % 12) + 1
|
25
|
+
|
26
|
+
until i == md_end.month
|
27
|
+
return true if @month == i
|
28
|
+
i = (i % 12) + 1
|
29
|
+
end
|
30
|
+
|
31
|
+
return false
|
32
|
+
end
|
33
|
+
|
34
|
+
def equals?(other)
|
35
|
+
@month == other.month and @day == other.day
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,222 @@
|
|
1
|
+
require 'chronic/dictionary'
|
2
|
+
require 'chronic/handlers'
|
3
|
+
|
4
|
+
module Chronic
|
5
|
+
class Parser
|
6
|
+
include Handlers
|
7
|
+
|
8
|
+
# Hash of default configuration options.
|
9
|
+
DEFAULT_OPTIONS = {
|
10
|
+
:context => :future,
|
11
|
+
:now => nil,
|
12
|
+
:hours24 => nil,
|
13
|
+
:week_start => :sunday,
|
14
|
+
:guess => true,
|
15
|
+
:ambiguous_time_range => 6,
|
16
|
+
:endian_precedence => [:middle, :little],
|
17
|
+
:ambiguous_year_future_bias => 50
|
18
|
+
}
|
19
|
+
|
20
|
+
attr_accessor :now
|
21
|
+
attr_reader :options
|
22
|
+
|
23
|
+
# options - An optional Hash of configuration options:
|
24
|
+
# :context - If your string represents a birthday, you can set
|
25
|
+
# this value to :past and if an ambiguous string is
|
26
|
+
# given, it will assume it is in the past.
|
27
|
+
# :now - Time, all computations will be based off of time
|
28
|
+
# instead of Time.now.
|
29
|
+
# :hours24 - Time will be parsed as it would be 24 hour clock.
|
30
|
+
# :week_start - By default, the parser assesses weeks start on
|
31
|
+
# sunday but you can change this value to :monday if
|
32
|
+
# needed.
|
33
|
+
# :guess - By default the parser will guess a single point in time
|
34
|
+
# for the given date or time. If you'd rather have the
|
35
|
+
# entire time span returned, set this to false
|
36
|
+
# and a Chronic::Span will be returned. Setting :guess to :end
|
37
|
+
# will return last time from Span, to :middle for middle (same as just true)
|
38
|
+
# and :begin for first time from span.
|
39
|
+
# :ambiguous_time_range - If an Integer is given, ambiguous times
|
40
|
+
# (like 5:00) will be assumed to be within the range of
|
41
|
+
# that time in the AM to that time in the PM. For
|
42
|
+
# example, if you set it to `7`, then the parser will
|
43
|
+
# look for the time between 7am and 7pm. In the case of
|
44
|
+
# 5:00, it would assume that means 5:00pm. If `:none`
|
45
|
+
# is given, no assumption will be made, and the first
|
46
|
+
# matching instance of that time will be used.
|
47
|
+
# :endian_precedence - By default, Chronic will parse "03/04/2011"
|
48
|
+
# as the fourth day of the third month. Alternatively you
|
49
|
+
# can tell Chronic to parse this as the third day of the
|
50
|
+
# fourth month by setting this to [:little, :middle].
|
51
|
+
# :ambiguous_year_future_bias - When parsing two digit years
|
52
|
+
# (ie 79) unlike Rubys Time class, Chronic will attempt
|
53
|
+
# to assume the full year using this figure. Chronic will
|
54
|
+
# look x amount of years into the future and past. If the
|
55
|
+
# two digit year is `now + x years` it's assumed to be the
|
56
|
+
# future, `now - x years` is assumed to be the past.
|
57
|
+
def initialize(options = {})
|
58
|
+
validate_options!(options)
|
59
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
60
|
+
@now = options[:now] || Chronic.time_class.now
|
61
|
+
end
|
62
|
+
|
63
|
+
# Parse "text" with the given options
|
64
|
+
# Returns either a Time or Chronic::Span, depending on the value of options[:guess]
|
65
|
+
def parse(text)
|
66
|
+
tokens = tokenize(text, options)
|
67
|
+
span = tokens_to_span(tokens, options.merge(:text => text))
|
68
|
+
|
69
|
+
puts "+#{'-' * 51}\n| #{tokens}\n+#{'-' * 51}" if Chronic.debug
|
70
|
+
|
71
|
+
guess(span, options[:guess]) if span
|
72
|
+
end
|
73
|
+
|
74
|
+
# Clean up the specified text ready for parsing.
|
75
|
+
#
|
76
|
+
# Clean up the string by stripping unwanted characters, converting
|
77
|
+
# idioms to their canonical form, converting number words to numbers
|
78
|
+
# (three => 3), and converting ordinal words to numeric
|
79
|
+
# ordinals (third => 3rd)
|
80
|
+
#
|
81
|
+
# text - The String text to normalize.
|
82
|
+
#
|
83
|
+
# Examples:
|
84
|
+
#
|
85
|
+
# Chronic.pre_normalize('first day in May')
|
86
|
+
# #=> "1st day in may"
|
87
|
+
#
|
88
|
+
# Chronic.pre_normalize('tomorrow after noon')
|
89
|
+
# #=> "next day future 12:00"
|
90
|
+
#
|
91
|
+
# Chronic.pre_normalize('one hundred and thirty six days from now')
|
92
|
+
# #=> "136 days future this second"
|
93
|
+
#
|
94
|
+
# Returns a new String ready for Chronic to parse.
|
95
|
+
def pre_normalize(text)
|
96
|
+
text = text.to_s.downcase
|
97
|
+
text.gsub!(/\b(\d{1,2})\.(\d{1,2})\.(\d{4})\b/, '\3 / \2 / \1')
|
98
|
+
text.gsub!(/\b([ap])\.m\.?/, '\1m')
|
99
|
+
text.gsub!(/(\s+|:\d{2}|:\d{2}\.\d+)\-(\d{2}:?\d{2})\b/, '\1tzminus\2')
|
100
|
+
text.gsub!(/\./, ':')
|
101
|
+
text.gsub!(/([ap]):m:?/, '\1m')
|
102
|
+
text.gsub!(/'(\d{2})\b/) do
|
103
|
+
number = $1.to_i
|
104
|
+
|
105
|
+
if Chronic::Date::could_be_year?(number)
|
106
|
+
Chronic::Date::make_year(number, options[:ambiguous_year_future_bias])
|
107
|
+
else
|
108
|
+
number
|
109
|
+
end
|
110
|
+
end
|
111
|
+
text.gsub!(/['"]/, '')
|
112
|
+
text.gsub!(/,/, ' ')
|
113
|
+
text.gsub!(/^second /, '2nd ')
|
114
|
+
text.gsub!(/\bsecond (of|day|month|hour|minute|second|quarter)\b/, '2nd \1')
|
115
|
+
text.gsub!(/\bthird quarter\b/, '3rd q')
|
116
|
+
text.gsub!(/\bfourth quarter\b/, '4th q')
|
117
|
+
text.gsub!(/quarters?(\s+|$)(?!to|till|past|after|before)/, 'q\1')
|
118
|
+
text = Numerizer.numerize(text)
|
119
|
+
text.gsub!(/\b(\d)(?:st|nd|rd|th)\s+q\b/, 'q\1')
|
120
|
+
text.gsub!(/([\/\-\,\@])/) { ' ' + $1 + ' ' }
|
121
|
+
text.gsub!(/(?:^|\s)0(\d+:\d+\s*pm?\b)/, ' \1')
|
122
|
+
text.gsub!(/\btoday\b/, 'this day')
|
123
|
+
text.gsub!(/\btomm?orr?ow\b/, 'next day')
|
124
|
+
text.gsub!(/\byesterday\b/, 'last day')
|
125
|
+
text.gsub!(/\bnoon|midday\b/, '12:00pm')
|
126
|
+
text.gsub!(/\bmidnight\b/, '24:00')
|
127
|
+
text.gsub!(/\bnow\b/, 'this second')
|
128
|
+
text.gsub!('quarter', '15')
|
129
|
+
text.gsub!('half', '30')
|
130
|
+
text.gsub!(/(\d{1,2}) (to|till|prior to|before)\b/, '\1 minutes past')
|
131
|
+
text.gsub!(/(\d{1,2}) (after|past)\b/, '\1 minutes future')
|
132
|
+
text.gsub!(/\b(?:ago|before(?: now)?)\b/, 'past')
|
133
|
+
text.gsub!(/\bthis (?:last|past)\b/, 'last')
|
134
|
+
text.gsub!(/\b(?:in|during) the (morning)\b/, '\1')
|
135
|
+
text.gsub!(/\b(?:in the|during the|at) (afternoon|evening|night)\b/, '\1')
|
136
|
+
text.gsub!(/\btonight\b/, 'this night')
|
137
|
+
text.gsub!(/\b\d+:?\d*[ap]\b/,'\0m')
|
138
|
+
text.gsub!(/\b(\d{2})(\d{2})(am|pm)\b/, '\1:\2\3')
|
139
|
+
text.gsub!(/(\d)([ap]m|oclock)\b/, '\1 \2')
|
140
|
+
text.gsub!(/\b(hence|after|from)\b/, 'future')
|
141
|
+
text.gsub!(/^\s?an? /i, '1 ')
|
142
|
+
text.gsub!(/\b(\d{4}):(\d{2}):(\d{2})\b/, '\1 / \2 / \3') # DTOriginal
|
143
|
+
text.gsub!(/\b0(\d+):(\d{2}):(\d{2}) ([ap]m)\b/, '\1:\2:\3 \4')
|
144
|
+
text
|
145
|
+
end
|
146
|
+
|
147
|
+
# Guess a specific time within the given span.
|
148
|
+
#
|
149
|
+
# span - The Chronic::Span object to calcuate a guess from.
|
150
|
+
#
|
151
|
+
# Returns a new Time object.
|
152
|
+
def guess(span, mode = :middle)
|
153
|
+
return span if not mode
|
154
|
+
return span.begin + span.width / 2 if span.width > 1 and (mode == true or mode == :middle)
|
155
|
+
return span.end if mode == :end
|
156
|
+
span.begin
|
157
|
+
end
|
158
|
+
|
159
|
+
# List of Handler definitions. See Chronic.parse for a list of options this
|
160
|
+
# method accepts.
|
161
|
+
#
|
162
|
+
# options - An optional Hash of configuration options.
|
163
|
+
#
|
164
|
+
# Returns a Hash of Handler definitions.
|
165
|
+
def definitions(options = {})
|
166
|
+
SpanDictionary.new(options).definitions
|
167
|
+
end
|
168
|
+
|
169
|
+
private
|
170
|
+
|
171
|
+
|
172
|
+
def validate_options!(options)
|
173
|
+
given = options.keys.map(&:to_s).sort
|
174
|
+
allowed = DEFAULT_OPTIONS.keys.map(&:to_s).sort
|
175
|
+
non_permitted = given - allowed
|
176
|
+
raise ArgumentError, "Unsupported option(s): #{non_permitted.join(', ')}" if non_permitted.any?
|
177
|
+
end
|
178
|
+
|
179
|
+
def tokenize(text, options)
|
180
|
+
text = pre_normalize(text)
|
181
|
+
tokens = Tokenizer::tokenize(text)
|
182
|
+
[Repeater, Grabber, Pointer, Scalar, Ordinal, Separator, Sign, TimeZone].each do |tok|
|
183
|
+
tok.scan(tokens, options)
|
184
|
+
end
|
185
|
+
tokens.select { |token| token.tagged? }
|
186
|
+
end
|
187
|
+
|
188
|
+
def tokens_to_span(tokens, options)
|
189
|
+
definitions = definitions(options)
|
190
|
+
|
191
|
+
(definitions[:endian] + definitions[:date]).each do |handler|
|
192
|
+
if handler.match(tokens, definitions)
|
193
|
+
good_tokens = tokens.select { |o| !o.get_tag Separator }
|
194
|
+
return handler.invoke(:date, good_tokens, self, options)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
definitions[:anchor].each do |handler|
|
199
|
+
if handler.match(tokens, definitions)
|
200
|
+
good_tokens = tokens.select { |o| !o.get_tag Separator }
|
201
|
+
return handler.invoke(:anchor, good_tokens, self, options)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
definitions[:arrow].each do |handler|
|
206
|
+
if handler.match(tokens, definitions)
|
207
|
+
good_tokens = tokens.reject { |o| o.get_tag(SeparatorAt) || o.get_tag(SeparatorSlash) || o.get_tag(SeparatorDash) || o.get_tag(SeparatorComma) || o.get_tag(SeparatorAnd) }
|
208
|
+
return handler.invoke(:arrow, good_tokens, self, options)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
definitions[:narrow].each do |handler|
|
213
|
+
if handler.match(tokens, definitions)
|
214
|
+
return handler.invoke(:narrow, tokens, self, options)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
puts '-none' if Chronic.debug
|
219
|
+
return nil
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Chronic
|
2
|
+
class RepeaterDay < Repeater #:nodoc:
|
3
|
+
DAY_SECONDS = 86_400 # (24 * 60 * 60)
|
4
|
+
|
5
|
+
def initialize(type, width = nil, options = {})
|
6
|
+
super
|
7
|
+
@current_day_start = nil
|
8
|
+
end
|
9
|
+
|
10
|
+
def next(pointer)
|
11
|
+
super
|
12
|
+
|
13
|
+
unless @current_day_start
|
14
|
+
@current_day_start = Chronic.time_class.local(@now.year, @now.month, @now.day)
|
15
|
+
end
|
16
|
+
|
17
|
+
direction = pointer == :future ? 1 : -1
|
18
|
+
@current_day_start += direction * DAY_SECONDS
|
19
|
+
|
20
|
+
Span.new(@current_day_start, @current_day_start + DAY_SECONDS)
|
21
|
+
end
|
22
|
+
|
23
|
+
def this(pointer = :future)
|
24
|
+
super
|
25
|
+
|
26
|
+
case pointer
|
27
|
+
when :future
|
28
|
+
day_begin = Chronic.construct(@now.year, @now.month, @now.day, @now.hour)
|
29
|
+
day_end = Chronic.construct(@now.year, @now.month, @now.day) + DAY_SECONDS
|
30
|
+
when :past
|
31
|
+
day_begin = Chronic.construct(@now.year, @now.month, @now.day)
|
32
|
+
day_end = Chronic.construct(@now.year, @now.month, @now.day, @now.hour)
|
33
|
+
when :none
|
34
|
+
day_begin = Chronic.construct(@now.year, @now.month, @now.day)
|
35
|
+
day_end = Chronic.construct(@now.year, @now.month, @now.day) + DAY_SECONDS
|
36
|
+
end
|
37
|
+
|
38
|
+
Span.new(day_begin, day_end)
|
39
|
+
end
|
40
|
+
|
41
|
+
def offset(span, amount, pointer)
|
42
|
+
direction = pointer == :future ? 1 : -1
|
43
|
+
span + direction * amount * DAY_SECONDS
|
44
|
+
end
|
45
|
+
|
46
|
+
def width
|
47
|
+
DAY_SECONDS
|
48
|
+
end
|
49
|
+
|
50
|
+
def to_s
|
51
|
+
super << '-day'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Chronic
|
2
|
+
class RepeaterDayName < Repeater #:nodoc:
|
3
|
+
DAY_SECONDS = 86400 # (24 * 60 * 60)
|
4
|
+
|
5
|
+
def initialize(type, width = nil, options = {})
|
6
|
+
super
|
7
|
+
@current_date = nil
|
8
|
+
end
|
9
|
+
|
10
|
+
def next(pointer)
|
11
|
+
super
|
12
|
+
|
13
|
+
direction = pointer == :future ? 1 : -1
|
14
|
+
|
15
|
+
unless @current_date
|
16
|
+
@current_date = ::Date.new(@now.year, @now.month, @now.day)
|
17
|
+
@current_date += direction
|
18
|
+
|
19
|
+
day_num = symbol_to_number(@type)
|
20
|
+
|
21
|
+
while @current_date.wday != day_num
|
22
|
+
@current_date += direction
|
23
|
+
end
|
24
|
+
else
|
25
|
+
@current_date += direction * 7
|
26
|
+
end
|
27
|
+
next_date = @current_date.succ
|
28
|
+
Span.new(Chronic.construct(@current_date.year, @current_date.month, @current_date.day), Chronic.construct(next_date.year, next_date.month, next_date.day))
|
29
|
+
end
|
30
|
+
|
31
|
+
def this(pointer = :future)
|
32
|
+
super
|
33
|
+
|
34
|
+
pointer = :future if pointer == :none
|
35
|
+
self.next(pointer)
|
36
|
+
end
|
37
|
+
|
38
|
+
def width
|
39
|
+
DAY_SECONDS
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_s
|
43
|
+
super << '-dayname-' << @type.to_s
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def symbol_to_number(sym)
|
49
|
+
lookup = {:sunday => 0, :monday => 1, :tuesday => 2, :wednesday => 3, :thursday => 4, :friday => 5, :saturday => 6}
|
50
|
+
lookup[sym] || raise('Invalid symbol specified')
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
module Chronic
|
2
|
+
class RepeaterDayPortion < Repeater #:nodoc:
|
3
|
+
PORTIONS = {
|
4
|
+
:am => 0..(12 * 60 * 60 - 1),
|
5
|
+
:pm => (12 * 60 * 60)..(24 * 60 * 60 - 1),
|
6
|
+
:morning => (6 * 60 * 60)..(12 * 60 * 60), # 6am-12am,
|
7
|
+
:afternoon => (13 * 60 * 60)..(17 * 60 * 60), # 1pm-5pm,
|
8
|
+
:evening => (17 * 60 * 60)..(20 * 60 * 60), # 5pm-8pm,
|
9
|
+
:night => (20 * 60 * 60)..(24 * 60 * 60), # 8pm-12pm
|
10
|
+
}
|
11
|
+
|
12
|
+
def initialize(type, width = nil, options = {})
|
13
|
+
super
|
14
|
+
@current_span = nil
|
15
|
+
|
16
|
+
if type.kind_of? Integer
|
17
|
+
@range = (@type * 60 * 60)..((@type + 12) * 60 * 60)
|
18
|
+
else
|
19
|
+
@range = PORTIONS[type]
|
20
|
+
@range || raise("Invalid type '#{type}' for RepeaterDayPortion")
|
21
|
+
end
|
22
|
+
|
23
|
+
@range || raise('Range should have been set by now')
|
24
|
+
end
|
25
|
+
|
26
|
+
def next(pointer)
|
27
|
+
super
|
28
|
+
|
29
|
+
unless @current_span
|
30
|
+
now_seconds = @now - Chronic.construct(@now.year, @now.month, @now.day)
|
31
|
+
if now_seconds < @range.begin
|
32
|
+
case pointer
|
33
|
+
when :future
|
34
|
+
range_start = Chronic.construct(@now.year, @now.month, @now.day) + @range.begin
|
35
|
+
when :past
|
36
|
+
range_start = Chronic.construct(@now.year, @now.month, @now.day - 1) + @range.begin
|
37
|
+
end
|
38
|
+
elsif now_seconds > @range.end
|
39
|
+
case pointer
|
40
|
+
when :future
|
41
|
+
range_start = Chronic.construct(@now.year, @now.month, @now.day + 1) + @range.begin
|
42
|
+
when :past
|
43
|
+
range_start = Chronic.construct(@now.year, @now.month, @now.day) + @range.begin
|
44
|
+
end
|
45
|
+
else
|
46
|
+
case pointer
|
47
|
+
when :future
|
48
|
+
range_start = Chronic.construct(@now.year, @now.month, @now.day + 1) + @range.begin
|
49
|
+
when :past
|
50
|
+
range_start = Chronic.construct(@now.year, @now.month, @now.day - 1) + @range.begin
|
51
|
+
end
|
52
|
+
end
|
53
|
+
offset = (@range.end - @range.begin)
|
54
|
+
range_end = construct_date_from_reference_and_offset(range_start, offset)
|
55
|
+
@current_span = Span.new(range_start, range_end)
|
56
|
+
else
|
57
|
+
days_to_shift_window =
|
58
|
+
case pointer
|
59
|
+
when :future
|
60
|
+
1
|
61
|
+
when :past
|
62
|
+
-1
|
63
|
+
end
|
64
|
+
|
65
|
+
new_begin = Chronic.construct(@current_span.begin.year, @current_span.begin.month, @current_span.begin.day + days_to_shift_window, @current_span.begin.hour, @current_span.begin.min, @current_span.begin.sec)
|
66
|
+
new_end = Chronic.construct(@current_span.end.year, @current_span.end.month, @current_span.end.day + days_to_shift_window, @current_span.end.hour, @current_span.end.min, @current_span.end.sec)
|
67
|
+
@current_span = Span.new(new_begin, new_end)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def this(context = :future)
|
72
|
+
super
|
73
|
+
|
74
|
+
range_start = Chronic.construct(@now.year, @now.month, @now.day) + @range.begin
|
75
|
+
range_end = construct_date_from_reference_and_offset(range_start)
|
76
|
+
@current_span = Span.new(range_start, range_end)
|
77
|
+
end
|
78
|
+
|
79
|
+
def offset(span, amount, pointer)
|
80
|
+
@now = span.begin
|
81
|
+
portion_span = self.next(pointer)
|
82
|
+
direction = pointer == :future ? 1 : -1
|
83
|
+
portion_span + (direction * (amount - 1) * RepeaterDay::DAY_SECONDS)
|
84
|
+
end
|
85
|
+
|
86
|
+
def width
|
87
|
+
@range || raise('Range has not been set')
|
88
|
+
return @current_span.width if @current_span
|
89
|
+
if @type.kind_of? Integer
|
90
|
+
return (12 * 60 * 60)
|
91
|
+
else
|
92
|
+
@range.end - @range.begin
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def to_s
|
97
|
+
super << '-dayportion-' << @type.to_s
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
def construct_date_from_reference_and_offset(reference, offset = nil)
|
102
|
+
elapsed_seconds_for_range = offset || (@range.end - @range.begin)
|
103
|
+
second_hand = ((elapsed_seconds_for_range - (12 * 60))) % 60
|
104
|
+
minute_hand = (elapsed_seconds_for_range - second_hand) / (60) % 60
|
105
|
+
hour_hand = (elapsed_seconds_for_range - minute_hand - second_hand) / (60 * 60) + reference.hour % 24
|
106
|
+
Chronic.construct(reference.year, reference.month, reference.day, hour_hand, minute_hand, second_hand)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|