reprise 0.1.0-arm64-darwin
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/Cargo.lock +856 -0
- data/Cargo.toml +8 -0
- data/LICENSE.txt +21 -0
- data/README.md +362 -0
- data/Rakefile +48 -0
- data/lib/reprise/3.1/reprise.bundle +0 -0
- data/lib/reprise/3.2/reprise.bundle +0 -0
- data/lib/reprise/3.3/reprise.bundle +0 -0
- data/lib/reprise/core/occurrence.rb +21 -0
- data/lib/reprise/schedule.rb +299 -0
- data/lib/reprise/time_of_day.rb +75 -0
- data/lib/reprise/time_zone_identifier.rb +51 -0
- data/lib/reprise/version.rb +5 -0
- data/lib/reprise.rb +10 -0
- metadata +251 -0
@@ -0,0 +1,299 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Reprise
|
4
|
+
class Error < StandardError; end
|
5
|
+
class InvalidRangeError < Error; end
|
6
|
+
|
7
|
+
# The +Reprise::Schedule+ class is the primary interface of the Reprise gem.
|
8
|
+
#
|
9
|
+
# It offers methods that enable you to:
|
10
|
+
# - Initialize a new schedule.
|
11
|
+
# - Add recurring series to the schedule via +#repeat_*+ methods.
|
12
|
+
# - Mark specific intervals of time as excluded from the schedule via {Reprise::Schedule#add_exclusion} and {Reprise::Schedule#add_exclusions}.
|
13
|
+
# - Query for the presence of occurrences within intervals of time via {Reprise::Schedule#occurs_between?}.
|
14
|
+
# - Generate an array of all of the schedule's occurrences via {Reprise::Schedule#occurrences}.
|
15
|
+
#
|
16
|
+
# @private On the interface of this class: all of the methods on +Reprise::Schedule+ could have been
|
17
|
+
# transparently delegated to +Reprise::Core::Schedule+, the internal schedule class that is
|
18
|
+
# implemented in Rust; instead, we define explicit proxy methods with obvious kwargs duplication,
|
19
|
+
# both to make it easier to generate YARD docs and to offer decent autocomplete support in IDEs.
|
20
|
+
# For any changes in the implementation of the interface, prefer DevX over DRY and save our
|
21
|
+
# sophistication budget for the underlying Rust extension.
|
22
|
+
class Schedule
|
23
|
+
# All schedules must be constructed with a valid +starts_at+ and +ends_at+ time.
|
24
|
+
# Reprise does not support infinitely-recurring schedules, or the bounding
|
25
|
+
# of schedules on the basis of a maximum occurrence count.
|
26
|
+
#
|
27
|
+
# @param starts_at [Time, ActiveSupport::TimeWithZone]
|
28
|
+
# The beginning of the schedule; the earliest possible moment for a valid occurrence.
|
29
|
+
# If no +time_zone+ is given, the schedule's time zone will be inferred from +starts_at+,
|
30
|
+
# defaulting to UTC if it lacks time zone information (e.g. it is a plain +Time+).
|
31
|
+
# @param ends_at [Time, ActiveSupport::TimeWithZone]
|
32
|
+
# The end of the schedule; the latest possible moment for a valid occurrence.
|
33
|
+
# @param time_zone [String]
|
34
|
+
# Must be an unambiguous, valid Rails time zone string or IANA time-zone identifier
|
35
|
+
# according to +ActiveSupport::TimeZone::find_tzinfo+.
|
36
|
+
# See https://github.com/tzinfo/tzinfo/issues/53
|
37
|
+
# @raise [Reprise::InvalidTimeZoneError] if the time zone is ambiguous or invalid.
|
38
|
+
def initialize(starts_at:, ends_at:, time_zone: nil)
|
39
|
+
raise InvalidRangeError, "The end time cannot precede the start time" if ends_at < starts_at
|
40
|
+
|
41
|
+
@starts_at = starts_at
|
42
|
+
@ends_at = ends_at
|
43
|
+
@time_zone = TimeZoneIdentifier.new(time_zone:, datetime_source: starts_at).to_s
|
44
|
+
@default_time_of_day = TimeOfDay.new(starts_at)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns an array of occurrences sorted in order of ascending occurrence start time.
|
48
|
+
# This method is not cached; on every call, it will recompute all of the schedule's occurrences.
|
49
|
+
# @return [Array<Reprise::Core::Occurrence>]
|
50
|
+
def occurrences
|
51
|
+
internal_schedule.occurrences
|
52
|
+
end
|
53
|
+
|
54
|
+
# @!macro [new] weekday
|
55
|
+
# @param weekday [Symbol] Accepts +:monday+, +:tuesday+, +:wednesday+, +:thursday+, or +:friday+.
|
56
|
+
|
57
|
+
# @!macro [new] time_of_day
|
58
|
+
# @param time_of_day [Hash,Time,nil]
|
59
|
+
# Either a local time value from which the hour, minute, and second
|
60
|
+
# should be derived, or a hash containing at least one of +hour+, +minute+,
|
61
|
+
# or +second+. If +nil+, the time of day will be inferred from the schedule's
|
62
|
+
# +starts_at+ value.
|
63
|
+
# @option time_of_day [Integer] :hour, >= 0 && <= 23
|
64
|
+
# @option time_of_day [Integer] :minute, >= 0 && <= 59
|
65
|
+
# @option time_of_day [Integer] :second, >= 0 && <= 59
|
66
|
+
# @raise [UnsupportedTypeError] if +time_of_day+ is neither a +Hash+ nor a +Time+.
|
67
|
+
# @raise [InvalidHashError] if the hash representation of the time is invalid.
|
68
|
+
# @raise [RangeError] if either the hour, minute, or second is out-of-range.
|
69
|
+
|
70
|
+
# @!macro [new] interval
|
71
|
+
# @param interval [Integer]
|
72
|
+
# This determines whether or not occurrences should be skipped.
|
73
|
+
# A value of +1+ means that every occurrence for the series should be returned;
|
74
|
+
# +2+, every other occurrence should be returned, etc.
|
75
|
+
|
76
|
+
# @!macro [new] recurring_series_start_and_end_times
|
77
|
+
# @param starts_at [Time, nil] The time that the series should begin. If left blank,
|
78
|
+
# the series will start at the same time as the parent schedule.
|
79
|
+
# @param ends_at [Time, nil] The time that the series should end. If left blank,
|
80
|
+
# the series will end at the same time as the parent schedule.
|
81
|
+
|
82
|
+
# @!macro [new] duration_in_seconds
|
83
|
+
# @param duration_in_seconds [Integer]
|
84
|
+
# This determines the end time of each occurrence ({Reprise::Core::Occurrence#end_time}), and also
|
85
|
+
# influences occurrence queries, and whether any added exclusions conflict with any of the schedule's
|
86
|
+
# occurrences.
|
87
|
+
|
88
|
+
# @!macro [new] label
|
89
|
+
# @param label [String, nil] An optional label to apply to all of the occurrences
|
90
|
+
# that are generated from the series. See {Reprise::Core::Occurrence#label}.
|
91
|
+
|
92
|
+
# @!macro time_of_day
|
93
|
+
# @!macro duration_in_seconds
|
94
|
+
# @!macro interval
|
95
|
+
# @!macro recurring_series_start_and_end_times
|
96
|
+
# @!macro label
|
97
|
+
# @return [void]
|
98
|
+
def repeat_minutely(time_of_day: nil, duration_in_seconds:, interval: 1, starts_at: nil, ends_at: nil, label: nil)
|
99
|
+
internal_schedule.repeat_minutely(
|
100
|
+
time_of_day: TimeOfDay.new(time_of_day || self.starts_at).to_h,
|
101
|
+
duration_in_seconds:,
|
102
|
+
interval:,
|
103
|
+
starts_at_unix_timestamp: starts_at.presence&.to_i,
|
104
|
+
ends_at_unix_timestamp: ends_at.presence&.to_i,
|
105
|
+
label:
|
106
|
+
)
|
107
|
+
end
|
108
|
+
|
109
|
+
# @!macro time_of_day
|
110
|
+
# @!macro duration_in_seconds
|
111
|
+
# @!macro interval
|
112
|
+
# @!macro recurring_series_start_and_end_times
|
113
|
+
# @!macro label
|
114
|
+
# @return [void]
|
115
|
+
def repeat_hourly(time_of_day: nil, duration_in_seconds:, interval: 1, starts_at: nil, ends_at: nil, label: nil)
|
116
|
+
internal_schedule.repeat_hourly(
|
117
|
+
time_of_day: TimeOfDay.new(time_of_day || self.starts_at).to_h,
|
118
|
+
duration_in_seconds:,
|
119
|
+
interval:,
|
120
|
+
starts_at_unix_timestamp: starts_at.presence&.to_i,
|
121
|
+
ends_at_unix_timestamp: ends_at.presence&.to_i,
|
122
|
+
label:
|
123
|
+
)
|
124
|
+
end
|
125
|
+
|
126
|
+
# @!macro time_of_day
|
127
|
+
# @!macro duration_in_seconds
|
128
|
+
# @!macro interval
|
129
|
+
# @!macro recurring_series_start_and_end_times
|
130
|
+
# @!macro label
|
131
|
+
# @return [void]
|
132
|
+
def repeat_daily(time_of_day: nil, duration_in_seconds:, interval: 1, starts_at: nil, ends_at: nil, label: nil)
|
133
|
+
internal_schedule.repeat_daily(
|
134
|
+
time_of_day: TimeOfDay.new(time_of_day || self.starts_at).to_h,
|
135
|
+
duration_in_seconds:,
|
136
|
+
interval:,
|
137
|
+
starts_at_unix_timestamp: starts_at.presence&.to_i,
|
138
|
+
ends_at_unix_timestamp: ends_at.presence&.to_i,
|
139
|
+
label:
|
140
|
+
)
|
141
|
+
end
|
142
|
+
|
143
|
+
# @!macro weekday
|
144
|
+
# @!macro time_of_day
|
145
|
+
# @!macro duration_in_seconds
|
146
|
+
# @!macro interval
|
147
|
+
# @!macro recurring_series_start_and_end_times
|
148
|
+
# @!macro label
|
149
|
+
# @return [void]
|
150
|
+
# @example with a +time_of_day+ hash
|
151
|
+
# schedule.repeat_weekly(:monday, time_of_day: { hour: 6 }, duration_in_seconds: 30)
|
152
|
+
# @example with a local time for +time_of_day+
|
153
|
+
# local_time = Time.current.in_time_zone(my_current_time_zone)
|
154
|
+
# schedule.repeat_weekly(:monday, time_of_day: local_time, duration_in_seconds: 30)
|
155
|
+
def repeat_weekly(weekday, time_of_day: nil, duration_in_seconds:, interval: 1, starts_at: nil, ends_at: nil, label: nil)
|
156
|
+
internal_schedule.repeat_weekly(
|
157
|
+
weekday,
|
158
|
+
time_of_day: TimeOfDay.new(time_of_day || self.starts_at).to_h,
|
159
|
+
duration_in_seconds:,
|
160
|
+
interval:,
|
161
|
+
starts_at_unix_timestamp: starts_at.presence&.to_i,
|
162
|
+
ends_at_unix_timestamp: ends_at.presence&.to_i,
|
163
|
+
label:
|
164
|
+
)
|
165
|
+
end
|
166
|
+
|
167
|
+
# @param day_number [Integer] The number of the day in the month; >= 1 && <= 31
|
168
|
+
# @!macro time_of_day
|
169
|
+
# @!macro duration_in_seconds
|
170
|
+
# @!macro interval
|
171
|
+
# @!macro recurring_series_start_and_end_times
|
172
|
+
# @!macro label
|
173
|
+
# @return [void]
|
174
|
+
# @example
|
175
|
+
# schedule.repeat_monthly_by_day(15, time_of_day: { hour: 9 }, duration_in_seconds: 30)
|
176
|
+
def repeat_monthly_by_day(day_number, time_of_day:, duration_in_seconds:, interval: 1, starts_at: nil, ends_at: nil, label: nil)
|
177
|
+
internal_schedule.repeat_monthly_by_day(
|
178
|
+
day_number,
|
179
|
+
time_of_day: TimeOfDay.new(time_of_day || self.starts_at).to_h,
|
180
|
+
duration_in_seconds:,
|
181
|
+
interval:,
|
182
|
+
starts_at_unix_timestamp: starts_at.presence&.to_i,
|
183
|
+
ends_at_unix_timestamp: ends_at.presence&.to_i,
|
184
|
+
label:
|
185
|
+
)
|
186
|
+
end
|
187
|
+
|
188
|
+
# @!macro weekday
|
189
|
+
# @param nth_day [Integer] The nth weekday, 0-indexed; e.g. 0 might represent the first wednesday
|
190
|
+
# @!macro time_of_day
|
191
|
+
# @!macro duration_in_seconds
|
192
|
+
# @!macro interval
|
193
|
+
# @!macro recurring_series_start_and_end_times
|
194
|
+
# @!macro label
|
195
|
+
# @return [void]
|
196
|
+
def repeat_monthly_by_nth_weekday(weekday, nth_day, time_of_day:, duration_in_seconds:, interval: 1, starts_at: nil, ends_at: nil, label: nil)
|
197
|
+
internal_schedule.repeat_monthly_by_nth_weekday(
|
198
|
+
weekday,
|
199
|
+
nth_day,
|
200
|
+
time_of_day: TimeOfDay.new(time_of_day || self.starts_at).to_h,
|
201
|
+
duration_in_seconds:,
|
202
|
+
interval:,
|
203
|
+
starts_at_unix_timestamp: starts_at.presence&.to_i,
|
204
|
+
ends_at_unix_timestamp: ends_at.presence&.to_i,
|
205
|
+
label:
|
206
|
+
)
|
207
|
+
end
|
208
|
+
|
209
|
+
# @param day_number [Integer] The number of the day in the year; >= 1 && <= 366
|
210
|
+
# @!macro time_of_day
|
211
|
+
# @!macro duration_in_seconds
|
212
|
+
# @!macro interval
|
213
|
+
# @!macro recurring_series_start_and_end_times
|
214
|
+
# @!macro label
|
215
|
+
# @return [void]
|
216
|
+
# @example
|
217
|
+
# schedule.repeat_annually_by_day(200, duration_in_seconds: 30)
|
218
|
+
def repeat_annually_by_day(day_number, time_of_day:, duration_in_seconds:, interval: 1, starts_at: nil, ends_at: nil, label: nil)
|
219
|
+
internal_schedule.repeat_annually_by_day(
|
220
|
+
day_number,
|
221
|
+
time_of_day: TimeOfDay.new(time_of_day || self.starts_at).to_h,
|
222
|
+
duration_in_seconds:,
|
223
|
+
interval:,
|
224
|
+
starts_at_unix_timestamp: starts_at.presence&.to_i,
|
225
|
+
ends_at_unix_timestamp: ends_at.presence&.to_i,
|
226
|
+
label:
|
227
|
+
)
|
228
|
+
end
|
229
|
+
|
230
|
+
# Add a time interval between which no occurrences are valid.
|
231
|
+
# Any occurrences that overlap with an exclusion are removed from the schedule's occurrences.
|
232
|
+
# @param starts_at [Time] The time that the exclusion starts at
|
233
|
+
# @param ends_at [Time] The time that the exclusion ends at
|
234
|
+
def add_exclusion(starts_at:, ends_at:)
|
235
|
+
internal_schedule.add_exclusion(
|
236
|
+
starts_at_unix_timestamp: starts_at.to_i,
|
237
|
+
ends_at_unix_timestamp: ends_at.to_i
|
238
|
+
)
|
239
|
+
end
|
240
|
+
|
241
|
+
# Add time intervals between which no occurrences are valid.
|
242
|
+
# Any occurrences that overlap with an exclusion are removed from the schedule's occurrences.
|
243
|
+
# @param exclusions [Array<Array<Time,Time>>] An array of exclusion arrays, consisting of start
|
244
|
+
# and end +Time+ values.
|
245
|
+
# @return [void]
|
246
|
+
# @example
|
247
|
+
# schedule.add_exclusions([
|
248
|
+
# [exclusion_1_starts_at, exclusion_1_ends_at],
|
249
|
+
# [exclusion_2_starts_at, exclusion_2_ends_at],
|
250
|
+
# ])
|
251
|
+
def add_exclusions(exclusions)
|
252
|
+
internal_schedule.add_exclusions(
|
253
|
+
exclusions.map {|e| e.map(&:to_i) }
|
254
|
+
)
|
255
|
+
end
|
256
|
+
|
257
|
+
# @!macro [new] include_overlapping
|
258
|
+
# @param include_overlapping [Boolean] when true, the query will also consider
|
259
|
+
# occurrences that partially overlap with the given interval, not just the occurrences
|
260
|
+
# that are entirely contained within the interval.
|
261
|
+
|
262
|
+
# Indicates whether one or more of your schedule's occurrences fall within the given interval.
|
263
|
+
# @param starts_at [Time] The start of the interval to query
|
264
|
+
# @param ends_at [Time] The end of the interval to query
|
265
|
+
# @!macro include_overlapping
|
266
|
+
# @return [Boolean]
|
267
|
+
def occurs_between?(starts_at, ends_at, include_overlapping: false)
|
268
|
+
occurrences_between(starts_at, ends_at, include_overlapping:).any?
|
269
|
+
end
|
270
|
+
|
271
|
+
# This method efficiently queries your schedule for occurrences that fall within a given interval.
|
272
|
+
# @param starts_at [Time] The start of the interval to query
|
273
|
+
# @param ends_at [Time] The end of the interval to query
|
274
|
+
# @!macro include_overlapping
|
275
|
+
# @return [Array<Reprise::Core::Occurrence>] an array of occurrences that occur between
|
276
|
+
# the given +starts_at+ and +ends_at+ bookends.
|
277
|
+
def occurrences_between(starts_at, ends_at, include_overlapping: false)
|
278
|
+
if include_overlapping
|
279
|
+
internal_schedule.occurrences_overlapping_with_interval(starts_at.to_i, ends_at.to_i)
|
280
|
+
else
|
281
|
+
internal_schedule.occurrences_contained_within_interval(starts_at.to_i, ends_at.to_i)
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
private
|
286
|
+
|
287
|
+
attr_reader :starts_at, :ends_at, :time_zone, :default_time_of_day
|
288
|
+
|
289
|
+
def internal_schedule
|
290
|
+
return @_internal_schedule if defined?(@_internal_schedule)
|
291
|
+
|
292
|
+
@_internal_schedule = ::Reprise::Core::Schedule.new(
|
293
|
+
starts_at.to_i,
|
294
|
+
ends_at.to_i,
|
295
|
+
time_zone
|
296
|
+
)
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support"
|
4
|
+
require "active_support/core_ext"
|
5
|
+
|
6
|
+
module Reprise
|
7
|
+
# @api private
|
8
|
+
class TimeOfDay
|
9
|
+
class UnsupportedTypeError < Reprise::Error; end
|
10
|
+
class InvalidHashError < Reprise::Error; end
|
11
|
+
class RangeError < Reprise::Error; end
|
12
|
+
|
13
|
+
DEFAULT_TIME_OF_DAY = { hour: 0, minute: 0, second: 0 }.freeze
|
14
|
+
TIME_OF_DAY_ATTRIBUTES = DEFAULT_TIME_OF_DAY.keys.freeze
|
15
|
+
attr_accessor(*TIME_OF_DAY_ATTRIBUTES)
|
16
|
+
|
17
|
+
# @param [Time, Hash] time_of_day
|
18
|
+
# Accepts either a local time value from which the hour, minute, and second
|
19
|
+
# should be derived, or a hash containing at least one of +hour+, +minute+,
|
20
|
+
# or +second+.
|
21
|
+
# @option time_of_day [Integer] :hour, >= 0 && <= 23
|
22
|
+
# @option time_of_day [Integer] :minute, >= 0 && <= 59
|
23
|
+
# @option time_of_day [Integer] :second, >= 0 && <= 59
|
24
|
+
# @example
|
25
|
+
# { hour: 1, minute: 30, second: 15 }
|
26
|
+
# @raise [UnsupportedTypeError] if +time_of_day+ is neither a +Hash+ nor a +Time+.
|
27
|
+
# @raise [InvalidHashError] if the hash representation of the time is invalid.
|
28
|
+
# @raise [RangeError] if either the hour, minute, or second is out-of-range.
|
29
|
+
def initialize(time_of_day)
|
30
|
+
return initialize_from_hash(time_of_day) if time_of_day.is_a?(Hash)
|
31
|
+
return initialize_from_time(time_of_day) if time_of_day.is_a?(Time)
|
32
|
+
|
33
|
+
raise UnsupportedTypeError, "#{time_of_day.class} is not a supported type"
|
34
|
+
end
|
35
|
+
|
36
|
+
# @return [Hash]
|
37
|
+
# @example
|
38
|
+
# { hour: 1, minute: 30, second: 15 }
|
39
|
+
def to_h
|
40
|
+
{
|
41
|
+
hour:,
|
42
|
+
minute:,
|
43
|
+
second:
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
# @private
|
50
|
+
def initialize_from_time(time)
|
51
|
+
self.hour = time.hour
|
52
|
+
self.minute = time.min
|
53
|
+
self.second = time.sec
|
54
|
+
end
|
55
|
+
|
56
|
+
# @private
|
57
|
+
def initialize_from_hash(hms_opts)
|
58
|
+
raise InvalidHashError if (hms_opts.keys - TIME_OF_DAY_ATTRIBUTES).any?
|
59
|
+
|
60
|
+
DEFAULT_TIME_OF_DAY.merge(hms_opts).slice(*TIME_OF_DAY_ATTRIBUTES).each do |key, value|
|
61
|
+
public_send("#{key}=", value)
|
62
|
+
end
|
63
|
+
|
64
|
+
validate_time_of_day
|
65
|
+
end
|
66
|
+
|
67
|
+
def validate_time_of_day
|
68
|
+
return if (hour >= 0 && hour <= 23) &&
|
69
|
+
(minute >= 0 && minute <= 59) &&
|
70
|
+
(second >= 0 && second <= 59)
|
71
|
+
|
72
|
+
raise RangeError, "The time of day is out of range (#{to_h})"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support"
|
4
|
+
require "active_support/core_ext"
|
5
|
+
|
6
|
+
module Reprise
|
7
|
+
class InvalidTimeZoneError < Reprise::Error; end
|
8
|
+
|
9
|
+
# @api private
|
10
|
+
class TimeZoneIdentifier
|
11
|
+
# https://github.com/rails/rails/blob/19eebf6d33dd15a0172e3ed2481bec57a89a2404/activesupport/lib/active_support/values/time_zone.rb#L76
|
12
|
+
UTC_TIME_ZONE_IDENTIFIER = "Etc/UTC"
|
13
|
+
|
14
|
+
# @param time_zone [String]
|
15
|
+
# Must be an unambiguous, valid Rails time zone string or IANA time-zone identifier
|
16
|
+
# according to +ActiveSupport::TimeZone::find_tzinfo+.
|
17
|
+
# See https://github.com/tzinfo/tzinfo/issues/53
|
18
|
+
# @param datetime_source [Time, ActiveSupport::TimeWithZone]
|
19
|
+
# A time value from which the time zone will be inferred.
|
20
|
+
# Only considered if no explicit +time_zone+ option is given.
|
21
|
+
def initialize(time_zone: nil, datetime_source:)
|
22
|
+
@time_zone = time_zone
|
23
|
+
@datetime_source = datetime_source
|
24
|
+
end
|
25
|
+
|
26
|
+
# Defaults to UTC if no time zone is passed and the datetime source lacks time zone information.
|
27
|
+
# @return [String] IANA Time Zone Database identifier
|
28
|
+
# @raise [Reprise::InvalidTimeZoneError] if the time zone is ambiguous or invalid.
|
29
|
+
def to_s
|
30
|
+
return ActiveSupport::TimeZone.find_tzinfo(time_zone).identifier if time_zone
|
31
|
+
return datetime_source.time_zone.tzinfo.identifier if datetime_source.is_a?(ActiveSupport::TimeWithZone)
|
32
|
+
|
33
|
+
UTC_TIME_ZONE_IDENTIFIER
|
34
|
+
rescue TZInfo::InvalidTimezoneIdentifier
|
35
|
+
raise InvalidTimeZoneError, invalid_time_zone_identifier_error
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
attr_reader :time_zone, :datetime_source
|
41
|
+
|
42
|
+
def invalid_time_zone_identifier_error
|
43
|
+
<<~ERROR
|
44
|
+
"#{time_zone}" is not a valid, unambiguous IANA Time Zone Database identifier.
|
45
|
+
For more information, see:
|
46
|
+
- https://github.com/tzinfo/tzinfo/issues/53
|
47
|
+
- https://github.com/rails/rails/blob/19eebf6d33dd15a0172e3ed2481bec57a89a2404/activesupport/lib/active_support/values/time_zone.rb#L33-L185
|
48
|
+
ERROR
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/reprise.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support"
|
4
|
+
require "active_support/core_ext/integer/time"
|
5
|
+
require "active_support/core_ext/time"
|
6
|
+
require "reprise/reprise"
|
7
|
+
require "reprise/schedule"
|
8
|
+
require "reprise/time_of_day"
|
9
|
+
require "reprise/time_zone_identifier"
|
10
|
+
require "reprise/version"
|
metadata
ADDED
@@ -0,0 +1,251 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: reprise
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: arm64-darwin
|
6
|
+
authors:
|
7
|
+
- Jordan Hiltunen
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-07-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: benchmark-ips
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: benchmark-memory
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: ice_cube
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: memory_profiler
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: montrose
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rake
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '13.2'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '13.2'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rake-compiler
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 1.2.0
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 1.2.0
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: redcarpet
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: rspec
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '3.0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - "~>"
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '3.0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: rubocop
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: yard
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ">="
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0'
|
181
|
+
- !ruby/object:Gem::Dependency
|
182
|
+
name: activesupport
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - ">="
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: 7.0.0
|
188
|
+
type: :runtime
|
189
|
+
prerelease: false
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - ">="
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: 7.0.0
|
195
|
+
force_ruby_platform: false
|
196
|
+
description: Generates date & time events from recurrence rules to support various
|
197
|
+
calendar and scheduling use cases, with an emphasis on speed; the core of the Reprise
|
198
|
+
gem is implemented as a Rust extension to enable its use for performance-sensitive
|
199
|
+
workloads.
|
200
|
+
email:
|
201
|
+
- oss@jordanhiltunen.com
|
202
|
+
executables: []
|
203
|
+
extensions: []
|
204
|
+
extra_rdoc_files: []
|
205
|
+
files:
|
206
|
+
- Cargo.lock
|
207
|
+
- Cargo.toml
|
208
|
+
- LICENSE.txt
|
209
|
+
- README.md
|
210
|
+
- Rakefile
|
211
|
+
- lib/reprise.rb
|
212
|
+
- lib/reprise/3.1/reprise.bundle
|
213
|
+
- lib/reprise/3.2/reprise.bundle
|
214
|
+
- lib/reprise/3.3/reprise.bundle
|
215
|
+
- lib/reprise/core/occurrence.rb
|
216
|
+
- lib/reprise/schedule.rb
|
217
|
+
- lib/reprise/time_of_day.rb
|
218
|
+
- lib/reprise/time_zone_identifier.rb
|
219
|
+
- lib/reprise/version.rb
|
220
|
+
homepage: https://github.com/jordanhiltunen/reprise
|
221
|
+
licenses:
|
222
|
+
- MIT
|
223
|
+
metadata:
|
224
|
+
allowed_push_host: https://rubygems.org
|
225
|
+
homepage_uri: https://github.com/jordanhiltunen/reprise
|
226
|
+
source_code_uri: https://github.com/jordanhiltunen/reprise
|
227
|
+
changelog_uri: https://github.com/jordanhiltunen/reprise/blob/main/CHANGELOG.md
|
228
|
+
rubygems_mfa_required: 'true'
|
229
|
+
post_install_message:
|
230
|
+
rdoc_options: []
|
231
|
+
require_paths:
|
232
|
+
- lib
|
233
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
234
|
+
requirements:
|
235
|
+
- - ">="
|
236
|
+
- !ruby/object:Gem::Version
|
237
|
+
version: '3.1'
|
238
|
+
- - "<"
|
239
|
+
- !ruby/object:Gem::Version
|
240
|
+
version: 3.4.dev
|
241
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
242
|
+
requirements:
|
243
|
+
- - ">="
|
244
|
+
- !ruby/object:Gem::Version
|
245
|
+
version: '0'
|
246
|
+
requirements: []
|
247
|
+
rubygems_version: 3.4.4
|
248
|
+
signing_key:
|
249
|
+
specification_version: 4
|
250
|
+
summary: Fast recurring event generation for Ruby.
|
251
|
+
test_files: []
|