opening_hours_converter 1.16.0 → 1.17.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.
- checksums.yaml +4 -4
- data/lib/opening_hours_converter/constants.rb +3 -0
- data/lib/opening_hours_converter/date_range.rb +3 -1
- data/lib/opening_hours_converter/interval.rb +16 -2
- data/lib/opening_hours_converter/open_intervals.rb +80 -11
- data/lib/opening_hours_converter/opening_hours_builder.rb +130 -4
- data/lib/opening_hours_converter/opening_hours_date.rb +11 -5
- data/lib/opening_hours_converter/opening_hours_parser.rb +327 -61
- data/lib/opening_hours_converter/opening_hours_rule.rb +38 -7
- data/lib/opening_hours_converter/opening_hours_time.rb +15 -2
- data/lib/opening_hours_converter/regex_handler.rb +64 -7
- data/lib/opening_hours_converter/token.rb +24 -1
- data/lib/opening_hours_converter/tokens_handler.rb +99 -3
- data/lib/opening_hours_converter/week_index.rb +11 -1
- data/lib/opening_hours_converter/wide_interval.rb +56 -4
- data/lib/opening_hours_converter/year.rb +3 -3
- data/readme.md +6 -0
- metadata +3 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ecd7eb467184d013601216945d337758dd52b4824640a61fc5826e28f74c2e4f
|
|
4
|
+
data.tar.gz: 5804ea9f19e7d0f6f5f538cba377a8d7aa1b0700384588b8214aaf6c2634b933
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2d697f34e86039dd4854e3e7dd0ab8332654d239971d3fcd12f69703ebf629ea2ab0380811bd460afde0ab06aceca1ae0ffac7eda30592c557e27c1ea76fdfef
|
|
7
|
+
data.tar.gz: 985cf1ab1967b7c7d69e4d1b593e83976ba4799bca329849fb26582d55d66ce10bba1a76de4d75cf206d4ff434f153628248b8dbb0d4640d3ea83b183b8a008e
|
|
@@ -11,6 +11,8 @@ module OpeningHoursConverter
|
|
|
11
11
|
DIMANCHE: 6
|
|
12
12
|
}.freeze
|
|
13
13
|
OSM_DAYS = %w[Mo Tu We Th Fr Sa Su].freeze
|
|
14
|
+
# Not specification, but common enough in the data to be read.
|
|
15
|
+
OSM_DAYS_ABBREVIATIONS = %w[Mon Tue Wed Thu Fri Sat Sun].freeze
|
|
14
16
|
IRL_DAYS = %w[Lundi Mardi Mercredi Jeudi Vendredi Samedi Dimanche].freeze
|
|
15
17
|
OSM_MONTHS = %w[Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec].freeze
|
|
16
18
|
IRL_MONTHS = %W[Janvier Fevrier Mars Avril Mai Juin Juillet Aout Septembre
|
|
@@ -20,5 +22,6 @@ module OpeningHoursConverter
|
|
|
20
22
|
DAYS_MAX = 6
|
|
21
23
|
YEAR_DAYS_MAX = 365
|
|
22
24
|
PH_WEEKDAY = -2
|
|
25
|
+
EASTER_WEEKDAY = -3
|
|
23
26
|
end
|
|
24
27
|
end
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
module OpeningHoursConverter
|
|
2
2
|
class DateRange
|
|
3
|
-
attr_accessor :wide_interval, :typical, :comment
|
|
3
|
+
attr_accessor :wide_interval, :typical, :comment, :fallback_suffix, :fallback_ranges
|
|
4
4
|
|
|
5
5
|
def initialize(wide_interval = nil)
|
|
6
6
|
@wide_interval = nil
|
|
7
7
|
@typical = nil
|
|
8
8
|
@comment = ''
|
|
9
|
+
@fallback_suffix = nil
|
|
10
|
+
@fallback_ranges = []
|
|
9
11
|
update_range(wide_interval)
|
|
10
12
|
end
|
|
11
13
|
|
|
@@ -3,14 +3,28 @@ require 'opening_hours_converter/constants'
|
|
|
3
3
|
module OpeningHoursConverter
|
|
4
4
|
class Interval
|
|
5
5
|
include Constants
|
|
6
|
-
attr_reader :day_start, :day_end, :start, :end, :is_off
|
|
6
|
+
attr_reader :day_start, :day_end, :start, :end, :is_off, :index, :is_unknown, :open_ended, :day_offset
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
# is_unknown is the OSM third state ("unknown", alongside open/off) and
|
|
9
|
+
# open_ended is a time with no known closing time ("18:00+"). Both are
|
|
10
|
+
# carried purely for round-trip serialization; for interval computation
|
|
11
|
+
# is_unknown behaves like is_off (not open) and open_ended is treated as
|
|
12
|
+
# running to the end of the day.
|
|
13
|
+
#
|
|
14
|
+
# day_offset is the OSM day offset ("easter +1 day"): the interval falls
|
|
15
|
+
# that many days after the date day_start selects.
|
|
16
|
+
def initialize(day_start, min_start, day_end = 0, min_end = 0, is_off = false, index = nil, is_unknown = false, open_ended = false, day_offset = 0)
|
|
9
17
|
@day_start = day_start
|
|
10
18
|
@day_end = day_end
|
|
11
19
|
@start = min_start
|
|
12
20
|
@end = min_end
|
|
13
21
|
@is_off = is_off
|
|
22
|
+
# The weekday index is an OSM nth_entry list ("Sa[1,3]"), so it is always
|
|
23
|
+
# held as an array; a lone index is wrapped rather than special-cased.
|
|
24
|
+
@index = index.nil? ? nil : Array(index)
|
|
25
|
+
@is_unknown = is_unknown
|
|
26
|
+
@open_ended = open_ended
|
|
27
|
+
@day_offset = day_offset || 0
|
|
14
28
|
|
|
15
29
|
if @day_end == 0 && @end == 0
|
|
16
30
|
@day_end = DAYS_MAX
|
|
@@ -8,10 +8,19 @@ module OpeningHoursConverter
|
|
|
8
8
|
# an earlier one on the minutes it selects, as the OpenStreetMap
|
|
9
9
|
# specification requires: "Jul-Aug off; Mo-Fr 09:00-17:00" is open in July,
|
|
10
10
|
# while "Mo-Fr 09:00-17:00; Jul-Aug off" is closed.
|
|
11
|
+
#
|
|
12
|
+
# A rule behind a "||" is the exception. It is a fallback: it speaks only for
|
|
13
|
+
# the minutes the rules before it leave closed, and never over the ones they
|
|
14
|
+
# open. "Mo-Fr 08:00-12:00 || Sa 10:00-12:00" opens on Saturday and changes
|
|
15
|
+
# nothing from Monday to Friday.
|
|
11
16
|
class OpenIntervals
|
|
12
17
|
include Constants
|
|
13
18
|
include Utils
|
|
14
19
|
|
|
20
|
+
# The state of a minute an open-ended time covers, told apart from a plain
|
|
21
|
+
# open minute so that the two never run into a single interval.
|
|
22
|
+
OPEN_ENDED = :open_ended
|
|
23
|
+
|
|
15
24
|
def self.call(opening_hours_string, from, to)
|
|
16
25
|
date_ranges = OpeningHoursConverter::OpeningHoursParser.new.parse(opening_hours_string)
|
|
17
26
|
new(from, to).apply(date_ranges).intervals
|
|
@@ -25,12 +34,20 @@ module OpeningHoursConverter
|
|
|
25
34
|
@first_date = from.to_date - 1
|
|
26
35
|
@last_date = to.to_date
|
|
27
36
|
@masks = {}
|
|
37
|
+
@fallback = false
|
|
28
38
|
@holidays = {}
|
|
29
39
|
end
|
|
30
40
|
|
|
31
41
|
def apply(date_ranges)
|
|
32
42
|
expand(date_ranges).each { |date_range, bounds| apply_date_range(date_range, bounds) }
|
|
33
|
-
|
|
43
|
+
|
|
44
|
+
fallback = date_ranges.first&.fallback_ranges
|
|
45
|
+
return self if fallback.nil? || fallback.empty?
|
|
46
|
+
|
|
47
|
+
# Everything past a "||" is a fallback, and stays one: a chained
|
|
48
|
+
# "a || b || c" nests, c falling back on what a and b leave closed.
|
|
49
|
+
@fallback = true
|
|
50
|
+
apply(fallback)
|
|
34
51
|
end
|
|
35
52
|
|
|
36
53
|
def intervals
|
|
@@ -51,11 +68,24 @@ module OpeningHoursConverter
|
|
|
51
68
|
case date_range.wide_interval.type
|
|
52
69
|
when 'week' then expand_to_days(date_range, :get_weeks_for_year)
|
|
53
70
|
when 'holiday' then expand_to_days(date_range, :get_public_holidays_for_year)
|
|
54
|
-
|
|
71
|
+
when 'variable_day' then expand_to_days(date_range, :get_variable_days_for_year)
|
|
72
|
+
else [[clamp_open_ended_year(date_range), nil]]
|
|
55
73
|
end
|
|
56
74
|
end
|
|
57
75
|
end
|
|
58
76
|
|
|
77
|
+
# "2020+" applies from its start year through the end of the window:
|
|
78
|
+
# Year#build_day_array_from_date_range has no concept of an unbounded
|
|
79
|
+
# range, so it needs a concrete (if window-dependent) end year here.
|
|
80
|
+
def clamp_open_ended_year(date_range)
|
|
81
|
+
wide_interval = date_range.wide_interval
|
|
82
|
+
return date_range unless wide_interval.type == 'year' && wide_interval.open_ended && wide_interval.end.nil?
|
|
83
|
+
|
|
84
|
+
copy = date_range.dup
|
|
85
|
+
copy.update_range(OpeningHoursConverter::WideInterval.new.year(wide_interval.start[:year], @last_date.year))
|
|
86
|
+
copy
|
|
87
|
+
end
|
|
88
|
+
|
|
59
89
|
# An ISO week declared with a year can start in the previous year or end in
|
|
60
90
|
# the next one ("2025 week 1" starts on 2025-12-29), so the neighbouring
|
|
61
91
|
# years are expanded too and their surplus days are cut back by the declared
|
|
@@ -119,9 +149,27 @@ module OpeningHoursConverter
|
|
|
119
149
|
end
|
|
120
150
|
|
|
121
151
|
def selects?(interval, date)
|
|
152
|
+
# A day offset shifts the date the selector names onto another one, so
|
|
153
|
+
# the selector is asked about the day it counts from rather than the day
|
|
154
|
+
# the interval is written on.
|
|
155
|
+
date -= interval.day_offset
|
|
122
156
|
return public_holiday?(date) if interval.day_start == PH_WEEKDAY
|
|
157
|
+
return easter?(date) if interval.day_start == EASTER_WEEKDAY
|
|
158
|
+
return false unless interval.day_start == reindex_sunday_week_to_monday_week(date.wday)
|
|
159
|
+
return true if interval.index.nil?
|
|
123
160
|
|
|
124
|
-
interval.
|
|
161
|
+
interval.index.any? { |index| nth_weekday_of_month?(index, interval.day_start, date) }
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def easter?(date)
|
|
165
|
+
OpeningHoursConverter::PublicHoliday.easter(date.year).to_date == date
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def nth_weekday_of_month?(index, weekday, date)
|
|
169
|
+
target = OpeningHoursConverter::WeekIndex.nth_wday_of_month(index, weekday, date.month, date.year)
|
|
170
|
+
target == date
|
|
171
|
+
rescue ArgumentError
|
|
172
|
+
false
|
|
125
173
|
end
|
|
126
174
|
|
|
127
175
|
def public_holiday?(date)
|
|
@@ -133,6 +181,10 @@ module OpeningHoursConverter
|
|
|
133
181
|
# An interval runs from (day_start, start) to (day_end, end). Only midnight
|
|
134
182
|
# crossings give day_end > day_start, and their remaining minutes are
|
|
135
183
|
# written on the dates that follow.
|
|
184
|
+
#
|
|
185
|
+
# A fallback rule skips the minutes that are already open: what the rules
|
|
186
|
+
# before the "||" leave closed is what it answers for, whether they were
|
|
187
|
+
# silent about it or closed it with an "off".
|
|
136
188
|
def write(date, interval)
|
|
137
189
|
remaining = (interval.day_end - interval.day_start) * MINUTES_MAX + interval.end
|
|
138
190
|
first_minute = interval.start
|
|
@@ -141,11 +193,25 @@ module OpeningHoursConverter
|
|
|
141
193
|
|
|
142
194
|
while remaining > 0
|
|
143
195
|
mask = mask_for(current)
|
|
144
|
-
|
|
196
|
+
minutes = [first_minute, 0].max...[remaining, MINUTES_MAX].min
|
|
197
|
+
minutes.each { |minute| mask[minute] = open unless @fallback && mask[minute] } unless mask.nil?
|
|
145
198
|
remaining -= MINUTES_MAX
|
|
146
199
|
first_minute = 0
|
|
147
200
|
current += 1
|
|
148
201
|
end
|
|
202
|
+
|
|
203
|
+
write_open_end(date, interval) if interval.open_ended && !interval.is_off
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# An open end names no closing time ("10:00-12:00+"), so the rest of the
|
|
207
|
+
# day stays open without being certain. It is written as a state of its
|
|
208
|
+
# own, which keeps it from merging with the hours the string does name:
|
|
209
|
+
# the reference reports the two as two intervals.
|
|
210
|
+
def write_open_end(date, interval)
|
|
211
|
+
mask = mask_for(date + (interval.day_end - interval.day_start))
|
|
212
|
+
return if mask.nil?
|
|
213
|
+
|
|
214
|
+
(interval.end...MINUTES_MAX).each { |minute| mask[minute] = OPEN_ENDED }
|
|
149
215
|
end
|
|
150
216
|
|
|
151
217
|
def mask_for(date)
|
|
@@ -155,10 +221,13 @@ module OpeningHoursConverter
|
|
|
155
221
|
end
|
|
156
222
|
|
|
157
223
|
# Walks the window as a continuous timeline, so a run of open minutes that
|
|
158
|
-
# spans midnight comes out as a single interval.
|
|
224
|
+
# spans midnight comes out as a single interval. A minute carries the state
|
|
225
|
+
# that opened it rather than a plain flag, so an open end closes the
|
|
226
|
+
# interval before it instead of extending it.
|
|
159
227
|
def runs
|
|
160
228
|
result = []
|
|
161
229
|
start = nil
|
|
230
|
+
state = nil
|
|
162
231
|
|
|
163
232
|
(@first_date..(@last_date + 1)).each do |date|
|
|
164
233
|
mask = @masks[date]
|
|
@@ -168,16 +237,16 @@ module OpeningHoursConverter
|
|
|
168
237
|
|
|
169
238
|
result << { start: start, end: time_at(date, 0) }
|
|
170
239
|
start = nil
|
|
240
|
+
state = nil
|
|
171
241
|
next
|
|
172
242
|
end
|
|
173
243
|
|
|
174
244
|
(0...MINUTES_MAX).each do |minute|
|
|
175
|
-
if mask[minute]
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
end
|
|
245
|
+
next if mask[minute] == state
|
|
246
|
+
|
|
247
|
+
result << { start: start, end: time_at(date, minute) } if start
|
|
248
|
+
start = mask[minute] ? time_at(date, minute) : nil
|
|
249
|
+
state = mask[minute]
|
|
181
250
|
end
|
|
182
251
|
end
|
|
183
252
|
|
|
@@ -19,7 +19,7 @@ module OpeningHoursConverter
|
|
|
19
19
|
phs = []
|
|
20
20
|
|
|
21
21
|
date_range.typical.intervals.each_with_index do |interval, interval_id|
|
|
22
|
-
next unless interval&.day_start ==
|
|
22
|
+
next unless interval&.day_start == PH_WEEKDAY && interval&.day_start == interval&.day_end
|
|
23
23
|
|
|
24
24
|
if interval.is_off
|
|
25
25
|
off_day_ph = true
|
|
@@ -31,6 +31,23 @@ module OpeningHoursConverter
|
|
|
31
31
|
date_range.typical.remove_interval(interval_id)
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
+
day_easter = false
|
|
35
|
+
off_day_easter = false
|
|
36
|
+
easters = []
|
|
37
|
+
|
|
38
|
+
date_range.typical.intervals.each_with_index do |interval, interval_id|
|
|
39
|
+
next unless interval&.day_start == EASTER_WEEKDAY && interval&.day_start == interval&.day_end
|
|
40
|
+
|
|
41
|
+
if interval.is_off
|
|
42
|
+
off_day_easter = true
|
|
43
|
+
else
|
|
44
|
+
day_easter = true
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
easters << interval
|
|
48
|
+
date_range.typical.remove_interval(interval_id)
|
|
49
|
+
end
|
|
50
|
+
|
|
34
51
|
range_general = nil
|
|
35
52
|
range_general_for = nil
|
|
36
53
|
range_general_id = date_range_index - 1
|
|
@@ -120,6 +137,36 @@ module OpeningHoursConverter
|
|
|
120
137
|
end
|
|
121
138
|
end
|
|
122
139
|
|
|
140
|
+
if off_day_easter || day_easter
|
|
141
|
+
if date_range.typical.intervals.uniq == [nil]
|
|
142
|
+
oh_rule.date.first.weekdays = [EASTER_WEEKDAY]
|
|
143
|
+
if off_day_easter
|
|
144
|
+
oh_rule.is_defined_off = off_day_easter
|
|
145
|
+
easters = []
|
|
146
|
+
else
|
|
147
|
+
easters.reverse.each do |interval|
|
|
148
|
+
oh_rule.add_time(OpeningHoursConverter::OpeningHoursTime.new(interval.start, interval.end))
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
else
|
|
152
|
+
if times_are_compatible?(oh_rule, easters)
|
|
153
|
+
easter_intervals = get_compatible_intervals(oh_rule, easters)
|
|
154
|
+
oh_rule.add_easter_weekday if easter_intervals[:compatible].length > 0
|
|
155
|
+
rules << oh_rule if !oh_rule_added
|
|
156
|
+
oh_rule_added = true
|
|
157
|
+
easter_intervals[:incompatible].each do |interval|
|
|
158
|
+
rules += build_off_easter(date_range) if off_day_easter
|
|
159
|
+
rules += build_easter(date_range) if day_easter
|
|
160
|
+
end
|
|
161
|
+
else
|
|
162
|
+
rules << oh_rule if !oh_rule_added
|
|
163
|
+
oh_rule_added = true
|
|
164
|
+
rules += build_off_easter(date_range) if off_day_easter
|
|
165
|
+
rules += build_easter(date_range) if day_easter
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
123
170
|
rules << oh_rule if !oh_rule_added
|
|
124
171
|
|
|
125
172
|
next unless oh_rule == oh_rules.last && oh_rule.has_overwritten_weekday?
|
|
@@ -142,7 +189,9 @@ module OpeningHoursConverter
|
|
|
142
189
|
result += rules.map(&:get).join('; ')
|
|
143
190
|
end
|
|
144
191
|
|
|
145
|
-
result.strip
|
|
192
|
+
result = result.strip
|
|
193
|
+
result += " || #{date_ranges.first.fallback_suffix}" if date_ranges.first&.fallback_suffix
|
|
194
|
+
result
|
|
146
195
|
end
|
|
147
196
|
|
|
148
197
|
def times_are_compatible?(oh_rule, phs)
|
|
@@ -220,6 +269,53 @@ module OpeningHoursConverter
|
|
|
220
269
|
[rule]
|
|
221
270
|
end
|
|
222
271
|
|
|
272
|
+
def build_off_easter(date_range)
|
|
273
|
+
start_year = date_range.wide_interval.start&.key?(:year) ? date_range.wide_interval.start[:year] : date_range.wide_interval.start
|
|
274
|
+
end_year = date_range.wide_interval.end&.key?(:year) ? date_range.wide_interval.end[:year] : date_range.wide_interval.end
|
|
275
|
+
|
|
276
|
+
date_range = OpeningHoursConverter::DateRange.new(OpeningHoursConverter::WideInterval.new.holiday('easter', start_year, end_year))
|
|
277
|
+
|
|
278
|
+
rule = OpeningHoursConverter::OpeningHoursRule.new
|
|
279
|
+
date = OpeningHoursConverter::OpeningHoursDate.new(date_range.wide_interval, [-1])
|
|
280
|
+
rule.add_date(date)
|
|
281
|
+
rule.is_defined_off = true
|
|
282
|
+
|
|
283
|
+
[rule]
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def build_easter(date_range)
|
|
287
|
+
start_year = date_range.wide_interval.start&.key?(:year) ? date_range.wide_interval.start[:year] : date_range.wide_interval.start
|
|
288
|
+
end_year = date_range.wide_interval.end&.key?(:year) ? date_range.wide_interval.end[:year] : date_range.wide_interval.end
|
|
289
|
+
intervals = date_range.typical.get_intervals(true)
|
|
290
|
+
|
|
291
|
+
if date_range.wide_interval.type == 'week'
|
|
292
|
+
intervals.each do |interval|
|
|
293
|
+
date_range.typical.add_interval(OpeningHoursConverter::Interval.new(EASTER_WEEKDAY, interval.start, EASTER_WEEKDAY, interval.end, interval.is_off))
|
|
294
|
+
end
|
|
295
|
+
else
|
|
296
|
+
date_range = OpeningHoursConverter::DateRange.new(OpeningHoursConverter::WideInterval.new.holiday('easter', start_year, end_year))
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
for i in 0..6
|
|
300
|
+
intervals.each do |interval|
|
|
301
|
+
if !interval.nil?
|
|
302
|
+
date_range.typical.add_interval(OpeningHoursConverter::Interval.new(i, interval.start, i, interval.end, interval.is_off))
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
rule = OpeningHoursConverter::OpeningHoursRule.new
|
|
307
|
+
date = OpeningHoursConverter::OpeningHoursDate.new(date_range.wide_interval, [-1])
|
|
308
|
+
rule.add_date(date)
|
|
309
|
+
|
|
310
|
+
date_range.typical.intervals.each do |interval|
|
|
311
|
+
if !interval.nil?
|
|
312
|
+
rule.add_time(OpeningHoursConverter::OpeningHoursTime.new(interval.start, interval.end))
|
|
313
|
+
rule.is_defined_off = rule.is_defined_off || interval.is_off
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
[rule]
|
|
317
|
+
end
|
|
318
|
+
|
|
223
319
|
def build_day(date_range)
|
|
224
320
|
intervals = date_range.typical.get_intervals(true)
|
|
225
321
|
|
|
@@ -240,7 +336,7 @@ module OpeningHoursConverter
|
|
|
240
336
|
def build_week(date_range)
|
|
241
337
|
result = []
|
|
242
338
|
|
|
243
|
-
intervals = date_range.typical
|
|
339
|
+
intervals = clean_intervals_keeping_index(date_range.typical)
|
|
244
340
|
days = create_time_intervals(date_range.wide_interval, intervals)
|
|
245
341
|
|
|
246
342
|
days_status = Array.new(OSM_DAYS.length, 0)
|
|
@@ -385,6 +481,26 @@ module OpeningHoursConverter
|
|
|
385
481
|
result
|
|
386
482
|
end
|
|
387
483
|
|
|
484
|
+
# An index ("We[1]"), the "unknown" state, and an open-ended time
|
|
485
|
+
# ("18:00+") don't survive Week#get_intervals(true), which rebuilds
|
|
486
|
+
# intervals from a per-minute true/false/"off" grid that has no room for
|
|
487
|
+
# any of them. All three are pulled out first and passed through
|
|
488
|
+
# untouched instead; none of them can collide with anything the grid
|
|
489
|
+
# pass produces (an index never applies to more than a single day, and
|
|
490
|
+
# "unknown"/open-ended are only ever full-interval markers here).
|
|
491
|
+
def clean_intervals_keeping_index(typical)
|
|
492
|
+
special = typical.intervals.compact.select { |interval| carries_metadata?(interval) }
|
|
493
|
+
return typical.get_intervals(true) if special.empty?
|
|
494
|
+
|
|
495
|
+
plain = OpeningHoursConverter::Week.new
|
|
496
|
+
typical.intervals.compact.each { |interval| plain.add_interval(interval) unless carries_metadata?(interval) }
|
|
497
|
+
plain.get_intervals(true) + special
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
def carries_metadata?(interval)
|
|
501
|
+
!interval.index.nil? || interval.is_unknown || interval.open_ended
|
|
502
|
+
end
|
|
503
|
+
|
|
388
504
|
def create_time_intervals(wide_interval, intervals)
|
|
389
505
|
days = []
|
|
390
506
|
|
|
@@ -398,16 +514,26 @@ module OpeningHoursConverter
|
|
|
398
514
|
|
|
399
515
|
begin
|
|
400
516
|
if interval.day_start == interval.day_end
|
|
401
|
-
days[interval.day_start].add_time(OpeningHoursConverter::OpeningHoursTime.new(interval.start, interval.end))
|
|
517
|
+
days[interval.day_start].add_time(OpeningHoursConverter::OpeningHoursTime.new(interval.start, interval.end, interval.open_ended))
|
|
402
518
|
days[interval.day_start].is_defined_off = days[interval.day_start].is_defined_off ? true : interval.is_off
|
|
519
|
+
days[interval.day_start].is_defined_unknown = days[interval.day_start].is_defined_unknown ? true : interval.is_unknown
|
|
520
|
+
if interval.index
|
|
521
|
+
# One weekday can carry several intervals, one per entry of its
|
|
522
|
+
# index list ("Sa[2],Sa[4]"), and the list is their union.
|
|
523
|
+
date = days[interval.day_start].date.first
|
|
524
|
+
date.weekday_index = Array(date.weekday_index) | interval.index
|
|
525
|
+
end
|
|
403
526
|
elsif interval.day_end - interval.day_start == 1
|
|
404
527
|
days[interval.day_start].add_time(OpeningHoursConverter::OpeningHoursTime.new(interval.start, MINUTES_MAX))
|
|
405
528
|
days[interval.day_start].is_defined_off = days[interval.day_start].is_defined_off ? true : interval.is_off
|
|
529
|
+
days[interval.day_start].is_defined_unknown = days[interval.day_start].is_defined_unknown ? true : interval.is_unknown
|
|
406
530
|
days[interval.day_end].add_time(OpeningHoursConverter::OpeningHoursTime.new(0, interval.end))
|
|
407
531
|
days[interval.day_end].is_defined_off = days[interval.day_end].is_defined_off ? true : interval.is_off
|
|
532
|
+
days[interval.day_end].is_defined_unknown = days[interval.day_end].is_defined_unknown ? true : interval.is_unknown
|
|
408
533
|
else
|
|
409
534
|
for j in interval.day_start..interval.day_end
|
|
410
535
|
days[j].is_defined_off = days[j].is_defined_off ? true : interval.is_off
|
|
536
|
+
days[j].is_defined_unknown = days[j].is_defined_unknown ? true : interval.is_unknown
|
|
411
537
|
if j == interval.day_start
|
|
412
538
|
days[j].add_time(OpeningHoursConverter::OpeningHoursTime.new(interval.start, MINUTES_MAX))
|
|
413
539
|
elsif j == interval.day_end
|
|
@@ -3,10 +3,10 @@ require 'opening_hours_converter/constants'
|
|
|
3
3
|
module OpeningHoursConverter
|
|
4
4
|
class OpeningHoursDate
|
|
5
5
|
include Constants
|
|
6
|
-
attr_accessor :weekdays, :weekdays_over
|
|
6
|
+
attr_accessor :weekdays, :weekdays_over, :weekday_index
|
|
7
7
|
attr_reader :wide_interval
|
|
8
8
|
|
|
9
|
-
def initialize(wide_interval, weekdays)
|
|
9
|
+
def initialize(wide_interval, weekdays, weekday_index = nil)
|
|
10
10
|
if wide_interval.nil? || weekdays.nil? || !wide_interval.is_a?(OpeningHoursConverter::WideInterval)
|
|
11
11
|
raise ArgumentError
|
|
12
12
|
end
|
|
@@ -14,14 +14,15 @@ module OpeningHoursConverter
|
|
|
14
14
|
@wide_interval = wide_interval
|
|
15
15
|
@weekdays = weekdays.sort
|
|
16
16
|
@weekdays_over = []
|
|
17
|
+
@weekday_index = weekday_index
|
|
17
18
|
end
|
|
18
19
|
|
|
19
20
|
def get_weekdays
|
|
20
21
|
result = ''
|
|
21
22
|
weekdays = @weekdays.concat(@weekdays_over).sort.uniq
|
|
22
23
|
|
|
23
|
-
if !weekdays.empty? && weekdays[0] ==
|
|
24
|
-
result = 'PH'
|
|
24
|
+
if !weekdays.empty? && (weekdays[0] == PH_WEEKDAY || weekdays[0] == EASTER_WEEKDAY)
|
|
25
|
+
result = weekdays[0] == PH_WEEKDAY ? 'PH' : 'easter'
|
|
25
26
|
weekdays.shift
|
|
26
27
|
end
|
|
27
28
|
|
|
@@ -94,6 +95,7 @@ module OpeningHoursConverter
|
|
|
94
95
|
end
|
|
95
96
|
|
|
96
97
|
result = '' if result == 'Mo-Su'
|
|
98
|
+
result += "[#{Array(@weekday_index).join(',')}]" if weekdays.length == 1 && !@weekday_index.nil?
|
|
97
99
|
result
|
|
98
100
|
end
|
|
99
101
|
|
|
@@ -105,7 +107,11 @@ module OpeningHoursConverter
|
|
|
105
107
|
end
|
|
106
108
|
|
|
107
109
|
def add_ph_weekday
|
|
108
|
-
add_weekday(
|
|
110
|
+
add_weekday(PH_WEEKDAY)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def add_easter_weekday
|
|
114
|
+
add_weekday(EASTER_WEEKDAY)
|
|
109
115
|
end
|
|
110
116
|
|
|
111
117
|
def add_overwritten_weekday(weekday)
|