gitlab-chronic 0.10.3
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.
- 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,111 @@
|
|
1
|
+
module Chronic
|
2
|
+
class RepeaterSeason < Repeater #:nodoc:
|
3
|
+
SEASON_SECONDS = 7_862_400 # 91 * 24 * 60 * 60
|
4
|
+
SEASONS = {
|
5
|
+
:spring => Season.new(MiniDate.new(3,20), MiniDate.new(6,20)),
|
6
|
+
:summer => Season.new(MiniDate.new(6,21), MiniDate.new(9,22)),
|
7
|
+
:autumn => Season.new(MiniDate.new(9,23), MiniDate.new(12,21)),
|
8
|
+
:winter => Season.new(MiniDate.new(12,22), MiniDate.new(3,19))
|
9
|
+
}
|
10
|
+
|
11
|
+
def initialize(type, width = nil, options = {})
|
12
|
+
super
|
13
|
+
@next_season_start = nil
|
14
|
+
@next_season_end = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def next(pointer)
|
18
|
+
super
|
19
|
+
|
20
|
+
direction = pointer == :future ? 1 : -1
|
21
|
+
next_season = Season.find_next_season(find_current_season(MiniDate.from_time(@now)), direction)
|
22
|
+
|
23
|
+
find_next_season_span(direction, next_season)
|
24
|
+
end
|
25
|
+
|
26
|
+
def this(pointer = :future)
|
27
|
+
super
|
28
|
+
|
29
|
+
direction = pointer == :future ? 1 : -1
|
30
|
+
|
31
|
+
today = Chronic.construct(@now.year, @now.month, @now.day)
|
32
|
+
this_ssn = find_current_season(MiniDate.from_time(@now))
|
33
|
+
case pointer
|
34
|
+
when :past
|
35
|
+
this_ssn_start = today + direction * num_seconds_til_start(this_ssn, direction)
|
36
|
+
this_ssn_end = today
|
37
|
+
when :future
|
38
|
+
this_ssn_start = today + RepeaterDay::DAY_SECONDS
|
39
|
+
this_ssn_end = today + direction * num_seconds_til_end(this_ssn, direction)
|
40
|
+
when :none
|
41
|
+
this_ssn_start = today + direction * num_seconds_til_start(this_ssn, direction)
|
42
|
+
this_ssn_end = today + direction * num_seconds_til_end(this_ssn, direction)
|
43
|
+
end
|
44
|
+
|
45
|
+
construct_season(this_ssn_start, this_ssn_end)
|
46
|
+
end
|
47
|
+
|
48
|
+
def offset(span, amount, pointer)
|
49
|
+
Span.new(offset_by(span.begin, amount, pointer), offset_by(span.end, amount, pointer))
|
50
|
+
end
|
51
|
+
|
52
|
+
def offset_by(time, amount, pointer)
|
53
|
+
direction = pointer == :future ? 1 : -1
|
54
|
+
time + amount * direction * SEASON_SECONDS
|
55
|
+
end
|
56
|
+
|
57
|
+
def width
|
58
|
+
SEASON_SECONDS
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_s
|
62
|
+
super << '-season'
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def find_next_season_span(direction, next_season)
|
68
|
+
unless @next_season_start || @next_season_end
|
69
|
+
@next_season_start = Chronic.construct(@now.year, @now.month, @now.day)
|
70
|
+
@next_season_end = Chronic.construct(@now.year, @now.month, @now.day)
|
71
|
+
end
|
72
|
+
|
73
|
+
@next_season_start += direction * num_seconds_til_start(next_season, direction)
|
74
|
+
@next_season_end += direction * num_seconds_til_end(next_season, direction)
|
75
|
+
|
76
|
+
construct_season(@next_season_start, @next_season_end)
|
77
|
+
end
|
78
|
+
|
79
|
+
def find_current_season(md)
|
80
|
+
[:spring, :summer, :autumn, :winter].find do |season|
|
81
|
+
md.is_between?(SEASONS[season].start, SEASONS[season].end)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def num_seconds_til(goal, direction)
|
86
|
+
start = Chronic.construct(@now.year, @now.month, @now.day)
|
87
|
+
seconds = 0
|
88
|
+
|
89
|
+
until MiniDate.from_time(start + direction * seconds).equals?(goal)
|
90
|
+
seconds += RepeaterDay::DAY_SECONDS
|
91
|
+
end
|
92
|
+
|
93
|
+
seconds
|
94
|
+
end
|
95
|
+
|
96
|
+
def num_seconds_til_start(season_symbol, direction)
|
97
|
+
num_seconds_til(SEASONS[season_symbol].start, direction)
|
98
|
+
end
|
99
|
+
|
100
|
+
def num_seconds_til_end(season_symbol, direction)
|
101
|
+
num_seconds_til(SEASONS[season_symbol].end, direction)
|
102
|
+
end
|
103
|
+
|
104
|
+
def construct_season(start, finish)
|
105
|
+
Span.new(
|
106
|
+
Chronic.construct(start.year, start.month, start.day),
|
107
|
+
Chronic.construct(finish.year, finish.month, finish.day)
|
108
|
+
)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Chronic
|
2
|
+
class RepeaterSeasonName < RepeaterSeason #:nodoc:
|
3
|
+
SEASON_SECONDS = 7_862_400 # 91 * 24 * 60 * 60
|
4
|
+
DAY_SECONDS = 86_400 # (24 * 60 * 60)
|
5
|
+
|
6
|
+
def next(pointer)
|
7
|
+
direction = pointer == :future ? 1 : -1
|
8
|
+
find_next_season_span(direction, @type)
|
9
|
+
end
|
10
|
+
|
11
|
+
def this(pointer = :future)
|
12
|
+
direction = pointer == :future ? 1 : -1
|
13
|
+
|
14
|
+
today = Chronic.construct(@now.year, @now.month, @now.day)
|
15
|
+
goal_ssn_start = today + direction * num_seconds_til_start(@type, direction)
|
16
|
+
goal_ssn_end = today + direction * num_seconds_til_end(@type, direction)
|
17
|
+
curr_ssn = find_current_season(MiniDate.from_time(@now))
|
18
|
+
case pointer
|
19
|
+
when :past
|
20
|
+
this_ssn_start = goal_ssn_start
|
21
|
+
this_ssn_end = (curr_ssn == @type) ? today : goal_ssn_end
|
22
|
+
when :future
|
23
|
+
this_ssn_start = (curr_ssn == @type) ? today + RepeaterDay::DAY_SECONDS : goal_ssn_start
|
24
|
+
this_ssn_end = goal_ssn_end
|
25
|
+
when :none
|
26
|
+
this_ssn_start = goal_ssn_start
|
27
|
+
this_ssn_end = goal_ssn_end
|
28
|
+
end
|
29
|
+
|
30
|
+
construct_season(this_ssn_start, this_ssn_end)
|
31
|
+
end
|
32
|
+
|
33
|
+
def offset(span, amount, pointer)
|
34
|
+
Span.new(offset_by(span.begin, amount, pointer), offset_by(span.end, amount, pointer))
|
35
|
+
end
|
36
|
+
|
37
|
+
def offset_by(time, amount, pointer)
|
38
|
+
direction = pointer == :future ? 1 : -1
|
39
|
+
time + amount * direction * RepeaterYear::YEAR_SECONDS
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Chronic
|
2
|
+
class RepeaterSecond < Repeater #:nodoc:
|
3
|
+
SECOND_SECONDS = 1 # haha, awesome
|
4
|
+
|
5
|
+
def initialize(type, width = nil, options = {})
|
6
|
+
super
|
7
|
+
@second_start = nil
|
8
|
+
end
|
9
|
+
|
10
|
+
def next(pointer = :future)
|
11
|
+
super
|
12
|
+
|
13
|
+
direction = pointer == :future ? 1 : -1
|
14
|
+
|
15
|
+
unless @second_start
|
16
|
+
@second_start = @now + (direction * SECOND_SECONDS)
|
17
|
+
else
|
18
|
+
@second_start += SECOND_SECONDS * direction
|
19
|
+
end
|
20
|
+
|
21
|
+
Span.new(@second_start, @second_start + SECOND_SECONDS)
|
22
|
+
end
|
23
|
+
|
24
|
+
def this(pointer = :future)
|
25
|
+
super
|
26
|
+
|
27
|
+
Span.new(@now, @now + 1)
|
28
|
+
end
|
29
|
+
|
30
|
+
def offset(span, amount, pointer)
|
31
|
+
direction = pointer == :future ? 1 : -1
|
32
|
+
span + direction * amount * SECOND_SECONDS
|
33
|
+
end
|
34
|
+
|
35
|
+
def width
|
36
|
+
SECOND_SECONDS
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_s
|
40
|
+
super << '-second'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
module Chronic
|
2
|
+
class RepeaterTime < Repeater #:nodoc:
|
3
|
+
class Tick #:nodoc:
|
4
|
+
attr_accessor :time
|
5
|
+
|
6
|
+
def initialize(time, ambiguous = false)
|
7
|
+
@time = time
|
8
|
+
@ambiguous = ambiguous
|
9
|
+
end
|
10
|
+
|
11
|
+
def ambiguous?
|
12
|
+
@ambiguous
|
13
|
+
end
|
14
|
+
|
15
|
+
def *(other)
|
16
|
+
Tick.new(@time * other, @ambiguous)
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_f
|
20
|
+
@time.to_f
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
@time.to_s + (@ambiguous ? '?' : '')
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize(time, width = nil, 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
|
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)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Return the next past or future Span for the time that this Repeater represents
|
67
|
+
# pointer - Symbol representing which temporal direction to fetch the next day
|
68
|
+
# must be either :past or :future
|
69
|
+
def next(pointer)
|
70
|
+
super
|
71
|
+
|
72
|
+
half_day = 60 * 60 * 12
|
73
|
+
full_day = 60 * 60 * 24
|
74
|
+
|
75
|
+
first = false
|
76
|
+
|
77
|
+
unless @current_time
|
78
|
+
first = true
|
79
|
+
midnight = Chronic.time_class.local(@now.year, @now.month, @now.day)
|
80
|
+
|
81
|
+
yesterday_midnight = midnight - full_day
|
82
|
+
tomorrow_midnight = midnight + full_day
|
83
|
+
|
84
|
+
catch :done do
|
85
|
+
if pointer == :future
|
86
|
+
if @type.ambiguous?
|
87
|
+
[midnight, midnight + half_day, tomorrow_midnight].each do |base_time|
|
88
|
+
t = adjust_daylight_savings_offset(midnight, base_time + @type.time)
|
89
|
+
(@current_time = t; throw :done) if t >= @now
|
90
|
+
end
|
91
|
+
else
|
92
|
+
[midnight, tomorrow_midnight].each do |base_time|
|
93
|
+
t = adjust_daylight_savings_offset(midnight, base_time + @type.time)
|
94
|
+
(@current_time = t; throw :done) if t >= @now
|
95
|
+
end
|
96
|
+
end
|
97
|
+
else # pointer == :past
|
98
|
+
if @type.ambiguous?
|
99
|
+
[midnight + half_day, midnight, yesterday_midnight + half_day].each do |base_time|
|
100
|
+
t = adjust_daylight_savings_offset(midnight, base_time + @type.time)
|
101
|
+
(@current_time = t; throw :done) if t <= @now
|
102
|
+
end
|
103
|
+
else
|
104
|
+
[midnight, yesterday_midnight].each do |base_time|
|
105
|
+
t = adjust_daylight_savings_offset(midnight, base_time + @type.time)
|
106
|
+
(@current_time = t; throw :done) if t <= @now
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
@current_time || raise('Current time cannot be nil at this point')
|
113
|
+
end
|
114
|
+
|
115
|
+
unless first
|
116
|
+
increment = @type.ambiguous? ? half_day : full_day
|
117
|
+
@current_time += pointer == :future ? increment : -increment
|
118
|
+
end
|
119
|
+
|
120
|
+
Span.new(@current_time, @current_time + width)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Every time a time crosses a Daylight Savings interval we must adjust the
|
124
|
+
# current time by that amount. For example, if we take midnight of Daylight
|
125
|
+
# Savings and only add an hour, the offset does not change:
|
126
|
+
#
|
127
|
+
# Time.parse('2008-03-09 00:00')
|
128
|
+
# => 2008-03-09 00:00:00 -0800
|
129
|
+
# Time.parse('2008-03-09 00:00') + (60 * 60)
|
130
|
+
# => 2008-03-09 01:00:00 -0800
|
131
|
+
#
|
132
|
+
# However, if we add 2 hours, we notice the time advances to 03:00 instead of 02:00:
|
133
|
+
#
|
134
|
+
# Time.parse('2008-03-09 00:00') + (60 * 60 * 2)
|
135
|
+
# => 2008-03-09 03:00:00 -0700
|
136
|
+
#
|
137
|
+
# Since we gained an hour and we actually want 02:00, we subtract an hour.
|
138
|
+
def adjust_daylight_savings_offset(base_time, current_time)
|
139
|
+
offset_fix = base_time.gmt_offset - current_time.gmt_offset
|
140
|
+
current_time + offset_fix
|
141
|
+
end
|
142
|
+
|
143
|
+
def this(context = :future)
|
144
|
+
super
|
145
|
+
|
146
|
+
context = :future if context == :none
|
147
|
+
|
148
|
+
self.next(context)
|
149
|
+
end
|
150
|
+
|
151
|
+
def width
|
152
|
+
1
|
153
|
+
end
|
154
|
+
|
155
|
+
def to_s
|
156
|
+
super << '-time-' << @type.to_s
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Chronic
|
2
|
+
class RepeaterWeek < Repeater #:nodoc:
|
3
|
+
WEEK_SECONDS = 604800 # (7 * 24 * 60 * 60)
|
4
|
+
|
5
|
+
def initialize(type, width = nil, options = {})
|
6
|
+
super
|
7
|
+
@repeater_day_name = options[:week_start] || :sunday
|
8
|
+
@current_week_start = nil
|
9
|
+
end
|
10
|
+
|
11
|
+
def next(pointer)
|
12
|
+
super
|
13
|
+
|
14
|
+
unless @current_week_start
|
15
|
+
case pointer
|
16
|
+
when :future
|
17
|
+
first_week_day_repeater = RepeaterDayName.new(@repeater_day_name)
|
18
|
+
first_week_day_repeater.start = @now
|
19
|
+
next_span = first_week_day_repeater.next(:future)
|
20
|
+
@current_week_start = next_span.begin
|
21
|
+
when :past
|
22
|
+
first_week_day_repeater = RepeaterDayName.new(@repeater_day_name)
|
23
|
+
first_week_day_repeater.start = (@now + RepeaterDay::DAY_SECONDS)
|
24
|
+
first_week_day_repeater.next(:past)
|
25
|
+
last_span = first_week_day_repeater.next(:past)
|
26
|
+
@current_week_start = last_span.begin
|
27
|
+
end
|
28
|
+
else
|
29
|
+
direction = pointer == :future ? 1 : -1
|
30
|
+
@current_week_start += direction * WEEK_SECONDS
|
31
|
+
end
|
32
|
+
|
33
|
+
Span.new(@current_week_start, @current_week_start + WEEK_SECONDS)
|
34
|
+
end
|
35
|
+
|
36
|
+
def this(pointer = :future)
|
37
|
+
super
|
38
|
+
|
39
|
+
case pointer
|
40
|
+
when :future
|
41
|
+
this_week_start = Chronic.time_class.local(@now.year, @now.month, @now.day, @now.hour) + RepeaterHour::HOUR_SECONDS
|
42
|
+
first_week_day_repeater = RepeaterDayName.new(@repeater_day_name)
|
43
|
+
first_week_day_repeater.start = @now
|
44
|
+
this_span = first_week_day_repeater.this(:future)
|
45
|
+
this_week_end = this_span.begin
|
46
|
+
Span.new(this_week_start, this_week_end)
|
47
|
+
when :past
|
48
|
+
this_week_end = Chronic.time_class.local(@now.year, @now.month, @now.day, @now.hour)
|
49
|
+
first_week_day_repeater = RepeaterDayName.new(@repeater_day_name)
|
50
|
+
first_week_day_repeater.start = @now
|
51
|
+
last_span = first_week_day_repeater.next(:past)
|
52
|
+
this_week_start = last_span.begin
|
53
|
+
Span.new(this_week_start, this_week_end)
|
54
|
+
when :none
|
55
|
+
first_week_day_repeater = RepeaterDayName.new(@repeater_day_name)
|
56
|
+
first_week_day_repeater.start = @now
|
57
|
+
last_span = first_week_day_repeater.next(:past)
|
58
|
+
this_week_start = last_span.begin
|
59
|
+
Span.new(this_week_start, this_week_start + WEEK_SECONDS)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def offset(span, amount, pointer)
|
64
|
+
direction = pointer == :future ? 1 : -1
|
65
|
+
span + direction * amount * WEEK_SECONDS
|
66
|
+
end
|
67
|
+
|
68
|
+
def width
|
69
|
+
WEEK_SECONDS
|
70
|
+
end
|
71
|
+
|
72
|
+
def to_s
|
73
|
+
super << '-week'
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module Chronic
|
2
|
+
class RepeaterWeekday < Repeater #:nodoc:
|
3
|
+
DAY_SECONDS = 86400 # (24 * 60 * 60)
|
4
|
+
DAYS = {
|
5
|
+
:sunday => 0,
|
6
|
+
:monday => 1,
|
7
|
+
:tuesday => 2,
|
8
|
+
:wednesday => 3,
|
9
|
+
:thursday => 4,
|
10
|
+
:friday => 5,
|
11
|
+
:saturday => 6
|
12
|
+
}
|
13
|
+
|
14
|
+
def initialize(type, width = nil, options = {})
|
15
|
+
super
|
16
|
+
@current_weekday_start = nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def next(pointer)
|
20
|
+
super
|
21
|
+
|
22
|
+
direction = pointer == :future ? 1 : -1
|
23
|
+
|
24
|
+
unless @current_weekday_start
|
25
|
+
@current_weekday_start = Chronic.construct(@now.year, @now.month, @now.day)
|
26
|
+
@current_weekday_start += direction * DAY_SECONDS
|
27
|
+
|
28
|
+
until is_weekday?(@current_weekday_start.wday)
|
29
|
+
@current_weekday_start += direction * DAY_SECONDS
|
30
|
+
end
|
31
|
+
else
|
32
|
+
loop do
|
33
|
+
@current_weekday_start += direction * DAY_SECONDS
|
34
|
+
break if is_weekday?(@current_weekday_start.wday)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
Span.new(@current_weekday_start, @current_weekday_start + DAY_SECONDS)
|
39
|
+
end
|
40
|
+
|
41
|
+
def this(pointer = :future)
|
42
|
+
super
|
43
|
+
|
44
|
+
case pointer
|
45
|
+
when :past
|
46
|
+
self.next(:past)
|
47
|
+
when :future, :none
|
48
|
+
self.next(:future)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def offset(span, amount, pointer)
|
53
|
+
direction = pointer == :future ? 1 : -1
|
54
|
+
|
55
|
+
num_weekdays_passed = 0; offset = 0
|
56
|
+
until num_weekdays_passed == amount
|
57
|
+
offset += direction * DAY_SECONDS
|
58
|
+
num_weekdays_passed += 1 if is_weekday?((span.begin+offset).wday)
|
59
|
+
end
|
60
|
+
|
61
|
+
span + offset
|
62
|
+
end
|
63
|
+
|
64
|
+
def width
|
65
|
+
DAY_SECONDS
|
66
|
+
end
|
67
|
+
|
68
|
+
def to_s
|
69
|
+
super << '-weekday'
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def is_weekend?(day)
|
75
|
+
day == symbol_to_number(:saturday) || day == symbol_to_number(:sunday)
|
76
|
+
end
|
77
|
+
|
78
|
+
def is_weekday?(day)
|
79
|
+
!is_weekend?(day)
|
80
|
+
end
|
81
|
+
|
82
|
+
def symbol_to_number(sym)
|
83
|
+
DAYS[sym] || raise('Invalid symbol specified')
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|