time_calc 0.0.1
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/LICENSE.txt +22 -0
- data/README.md +149 -0
- data/lib/time_calc.rb +273 -0
- data/lib/time_calc/diff.rb +291 -0
- data/lib/time_calc/dst.rb +54 -0
- data/lib/time_calc/op.rb +70 -0
- data/lib/time_calc/sequence.rb +130 -0
- data/lib/time_calc/types.rb +58 -0
- data/lib/time_calc/units.rb +57 -0
- data/lib/time_calc/value.rb +223 -0
- data/lib/time_calc/version.rb +6 -0
- metadata +211 -0
@@ -0,0 +1,291 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class TimeCalc
|
4
|
+
# Represents difference between two time-or-date values.
|
5
|
+
#
|
6
|
+
# Typically created with just
|
7
|
+
#
|
8
|
+
# ```ruby
|
9
|
+
# TimeCalc.(t1) - t2
|
10
|
+
# ```
|
11
|
+
#
|
12
|
+
# Allows to easily and correctly calculate number of years/monthes/days/etc between two points in
|
13
|
+
# time.
|
14
|
+
#
|
15
|
+
# @example
|
16
|
+
# t1 = Time.parse('2019-06-01 14:50')
|
17
|
+
# t2 = Time.parse('2019-06-15 12:10')
|
18
|
+
# (TimeCalc.(t2) - t1).div(:day)
|
19
|
+
# # => 13
|
20
|
+
# # the same:
|
21
|
+
# (TimeCalc.(t2) - t1).days
|
22
|
+
# # => 13
|
23
|
+
# (TimeCalc.(t2) - t1).div(3, :hours)
|
24
|
+
# # => 111
|
25
|
+
#
|
26
|
+
# (TimeCalc.(t2) - t1).factorize
|
27
|
+
# # => {:year=>0, :month=>0, :week=>1, :day=>6, :hour=>21, :min=>20, :sec=>0}
|
28
|
+
# (TimeCalc.(t2) - t1).factorize(weeks: false)
|
29
|
+
# # => {:year=>0, :month=>0, :day=>13, :hour=>21, :min=>20, :sec=>0}
|
30
|
+
# (TimeCalc.(t2) - t1).factorize(weeks: false, zeroes: false)
|
31
|
+
# # => {:day=>13, :hour=>21, :min=>20, :sec=>0}
|
32
|
+
#
|
33
|
+
class Diff
|
34
|
+
# @private
|
35
|
+
attr_reader :from, :to
|
36
|
+
|
37
|
+
# @note
|
38
|
+
# Typically you should prefer {TimeCalc#-} to create Diff.
|
39
|
+
#
|
40
|
+
# @param from [Time,Date,DateTime]
|
41
|
+
# @param to [Time,Date,DateTime]
|
42
|
+
def initialize(from, to)
|
43
|
+
@from, @to = coerce(try_unwrap(from), try_unwrap(to)).map(&Value.method(:wrap))
|
44
|
+
end
|
45
|
+
|
46
|
+
# @private
|
47
|
+
def inspect
|
48
|
+
'#<%s(%s − %s)>' % [self.class, from.unwrap, to.unwrap]
|
49
|
+
end
|
50
|
+
|
51
|
+
# "Negates" the diff by swapping its operands.
|
52
|
+
# @return [Diff]
|
53
|
+
def -@
|
54
|
+
Diff.new(to, from)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Combination of {#div} and {#modulo} in one operation.
|
58
|
+
#
|
59
|
+
# @overload divmod(span, unit)
|
60
|
+
# @param span [Integer]
|
61
|
+
# @param unit [Symbol] Any of supported units (see {TimeCalc})
|
62
|
+
#
|
63
|
+
# @overload divmod(unit)
|
64
|
+
# Shortcut for `divmod(1, unit)`
|
65
|
+
# @param unit [Symbol] Any of supported units (see {TimeCalc})
|
66
|
+
#
|
67
|
+
# @return [(Integer, Time or Date or DateTime)]
|
68
|
+
def divmod(span, unit = nil)
|
69
|
+
span, unit = 1, span if unit.nil?
|
70
|
+
div(span, unit).then { |res| [res, to.+(res * span, unit).unwrap] }
|
71
|
+
end
|
72
|
+
|
73
|
+
# @example
|
74
|
+
# t1 = Time.parse('2019-06-01 14:50')
|
75
|
+
# t2 = Time.parse('2019-06-15 12:10')
|
76
|
+
# (TimeCalc.(t2) - t1).div(:day)
|
77
|
+
# # => 13
|
78
|
+
# (TimeCalc.(t2) - t1).div(3, :hours)
|
79
|
+
# # => 111
|
80
|
+
#
|
81
|
+
# @overload div(span, unit)
|
82
|
+
# @param span [Integer]
|
83
|
+
# @param unit [Symbol] Any of supported units (see {TimeCalc})
|
84
|
+
#
|
85
|
+
# @overload div(unit)
|
86
|
+
# Shortcut for `div(1, unit)`. Also can called as just `.<units>` methods (like {#years})
|
87
|
+
# @param unit [Symbol] Any of supported units (see {TimeCalc})
|
88
|
+
#
|
89
|
+
# @return [Integer] Number of whole `<unit>`s between `Diff`'s operands.
|
90
|
+
def div(span, unit = nil)
|
91
|
+
return -(-self).div(span, unit) if negative?
|
92
|
+
|
93
|
+
span, unit = 1, span if unit.nil?
|
94
|
+
unit = Units.(unit)
|
95
|
+
singular_div(unit).div(span)
|
96
|
+
end
|
97
|
+
|
98
|
+
# @!method years
|
99
|
+
# Whole years in diff.
|
100
|
+
# @return [Integer]
|
101
|
+
# @!method months
|
102
|
+
# Whole months in diff.
|
103
|
+
# @return [Integer]
|
104
|
+
# @!method weeks
|
105
|
+
# Whole weeks in diff.
|
106
|
+
# @return [Integer]
|
107
|
+
# @!method days
|
108
|
+
# Whole days in diff.
|
109
|
+
# @return [Integer]
|
110
|
+
# @!method hours
|
111
|
+
# Whole hours in diff.
|
112
|
+
# @return [Integer]
|
113
|
+
# @!method minutes
|
114
|
+
# Whole minutes in diff.
|
115
|
+
# @return [Integer]
|
116
|
+
# @!method seconds
|
117
|
+
# Whole seconds in diff.
|
118
|
+
# @return [Integer]
|
119
|
+
|
120
|
+
# Same as integer modulo: the "rest" of whole division of the distance between two time points by
|
121
|
+
# `<span> <units>`. This rest will be also time point, equal to `first diff operand - span units`
|
122
|
+
#
|
123
|
+
# @overload modulo(span, unit)
|
124
|
+
# @param span [Integer]
|
125
|
+
# @param unit [Symbol] Any of supported units (see {TimeCalc})
|
126
|
+
#
|
127
|
+
# @overload modulo(unit)
|
128
|
+
# Shortcut for `modulo(1, unit)`.
|
129
|
+
# @param unit [Symbol] Any of supported units (see {TimeCalc})
|
130
|
+
#
|
131
|
+
# @return [Time, Date or DateTime] Value is always the same type as first diff operand
|
132
|
+
def modulo(span, unit = nil)
|
133
|
+
divmod(span, unit).last
|
134
|
+
end
|
135
|
+
|
136
|
+
alias / div
|
137
|
+
alias % modulo
|
138
|
+
|
139
|
+
# "Factorizes" the distance between two points in time into units: years, months, weeks, days.
|
140
|
+
#
|
141
|
+
# @example
|
142
|
+
# t1 = Time.parse('2019-06-01 14:50')
|
143
|
+
# t2 = Time.parse('2019-06-15 12:10')
|
144
|
+
# (TimeCalc.(t2) - t1).factorize
|
145
|
+
# # => {:year=>0, :month=>0, :week=>1, :day=>6, :hour=>21, :min=>20, :sec=>0}
|
146
|
+
# (TimeCalc.(t2) - t1).factorize(weeks: false)
|
147
|
+
# # => {:year=>0, :month=>0, :day=>13, :hour=>21, :min=>20, :sec=>0}
|
148
|
+
# (TimeCalc.(t2) - t1).factorize(weeks: false, zeroes: false)
|
149
|
+
# # => {:day=>13, :hour=>21, :min=>20, :sec=>0}
|
150
|
+
# (TimeCalc.(t2) - t1).factorize(max: :hour)
|
151
|
+
# # => {:hour=>333, :min=>20, :sec=>0}
|
152
|
+
# (TimeCalc.(t2) - t1).factorize(max: :hour, min: :min)
|
153
|
+
# # => {:hour=>333, :min=>20}
|
154
|
+
#
|
155
|
+
# @param zeroes [true, false] Include big units (for ex., year), if they are zero
|
156
|
+
# @param weeks [true, false] Include weeks
|
157
|
+
# @param max [Symbol] Max unit to factorize into, from all supported units list
|
158
|
+
# @param min [Symbol] Min unit to factorize into, from all supported units list
|
159
|
+
# @return [Hash<Symbol => Integer>]
|
160
|
+
def factorize(zeroes: true, max: :year, min: :sec, weeks: true)
|
161
|
+
t = to
|
162
|
+
f = from
|
163
|
+
select_units(max: Units.(max), min: Units.(min), weeks: weeks)
|
164
|
+
.inject({}) { |res, unit|
|
165
|
+
span, t = Diff.new(f, t).divmod(unit)
|
166
|
+
res.merge(unit => span)
|
167
|
+
}.then { |res|
|
168
|
+
next res if zeroes
|
169
|
+
|
170
|
+
res.drop_while { |_, v| v.zero? }.to_h
|
171
|
+
}
|
172
|
+
end
|
173
|
+
|
174
|
+
Units::SYNONYMS.to_a.flatten.each { |u| define_method(u) { div(u) } }
|
175
|
+
|
176
|
+
# @private
|
177
|
+
def exact
|
178
|
+
from.unwrap.to_time - to.unwrap.to_time
|
179
|
+
end
|
180
|
+
|
181
|
+
# @return [true, false]
|
182
|
+
def negative?
|
183
|
+
exact.negative?
|
184
|
+
end
|
185
|
+
|
186
|
+
# @return [true, false]
|
187
|
+
def positive?
|
188
|
+
exact.positive?
|
189
|
+
end
|
190
|
+
|
191
|
+
# @return [-1, 0, 1]
|
192
|
+
def <=>(other)
|
193
|
+
return unless other.is_a?(Diff)
|
194
|
+
|
195
|
+
exact <=> other.exact
|
196
|
+
end
|
197
|
+
|
198
|
+
include Comparable
|
199
|
+
|
200
|
+
private
|
201
|
+
|
202
|
+
def singular_div(unit)
|
203
|
+
case unit
|
204
|
+
when :sec, :min, :hour, :day
|
205
|
+
simple_div(from.unwrap, to.unwrap, unit)
|
206
|
+
when :week
|
207
|
+
div(7, :day)
|
208
|
+
when :month
|
209
|
+
month_div
|
210
|
+
when :year
|
211
|
+
year_div
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
def simple_div(t1, t2, unit)
|
216
|
+
return simple_div(t1.to_time, t2.to_time, unit) unless Types.compatible?(t1, t2)
|
217
|
+
|
218
|
+
t1.-(t2).div(Units.multiplier_for(t1.class, unit, precise: true))
|
219
|
+
.then { |res| unit == :day ? DST.fix_day_diff(t1, t2, res) : res }
|
220
|
+
end
|
221
|
+
|
222
|
+
def month_div # rubocop:disable Metrics/AbcSize -- well... at least it is short
|
223
|
+
((from.year - to.year) * 12 + (from.month - to.month))
|
224
|
+
.then { |res| from.day >= to.day ? res : res - 1 }
|
225
|
+
end
|
226
|
+
|
227
|
+
def year_div
|
228
|
+
from.year.-(to.year).then { |res| to.merge(year: from.year) <= from ? res : res - 1 }
|
229
|
+
end
|
230
|
+
|
231
|
+
def select_units(max:, min:, weeks:)
|
232
|
+
Units::ALL
|
233
|
+
.drop_while { |u| u != max }
|
234
|
+
.reverse.drop_while { |u| u != min }.reverse
|
235
|
+
.then { |list|
|
236
|
+
next list if weeks
|
237
|
+
|
238
|
+
list - %i[week]
|
239
|
+
}
|
240
|
+
end
|
241
|
+
|
242
|
+
def try_unwrap(tm)
|
243
|
+
tm.respond_to?(:unwrap) ? tm.unwrap : tm
|
244
|
+
end
|
245
|
+
|
246
|
+
def coerce(from, to)
|
247
|
+
case
|
248
|
+
when from.class != to.class
|
249
|
+
coerce_classes(from, to)
|
250
|
+
when zone(from) != zone(to)
|
251
|
+
coerce_zones(from, to)
|
252
|
+
else
|
253
|
+
[from, to]
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
def zone(tm)
|
258
|
+
case tm
|
259
|
+
when Time
|
260
|
+
# "" is JRuby's way to say "I don't know zone"
|
261
|
+
tm.zone&.then { |z| z == '' ? nil : z } || tm.utc_offset
|
262
|
+
when Date
|
263
|
+
nil
|
264
|
+
when DateTime
|
265
|
+
tm.zone
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
def coerce_classes(from, to)
|
270
|
+
case
|
271
|
+
when from.class == Date # not is_a?(Date), it will catch DateTime
|
272
|
+
[coerce_date(from, to), to]
|
273
|
+
when to.class == Date
|
274
|
+
[from, coerce_date(to, from)]
|
275
|
+
else
|
276
|
+
[from, to.public_send("to_#{from.class.downcase}")].then(&method(:coerce_zones))
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
def coerce_zones(from, to)
|
281
|
+
# TODO: to should be in from zone, even if different classes!
|
282
|
+
[from, to]
|
283
|
+
end
|
284
|
+
|
285
|
+
# Will coerce Date to Time or DateTime, with the _zone of the latter_
|
286
|
+
def coerce_date(date, other)
|
287
|
+
TimeCalc.(other)
|
288
|
+
.merge(Units::DEFAULTS.merge(year: date.year, month: date.month, day: date.day))
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class TimeCalc
|
4
|
+
# @private
|
5
|
+
module DST
|
6
|
+
extend self
|
7
|
+
|
8
|
+
def fix_value(val, origin)
|
9
|
+
case (c = compare(origin.unwrap, val.unwrap))
|
10
|
+
when nil, 0
|
11
|
+
val
|
12
|
+
else
|
13
|
+
val.+(c, :hour)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def fix_day_diff(from, to, diff)
|
18
|
+
# Just add one day when it is (DST - non-DST)
|
19
|
+
compare(from, to) == 1 ? diff + 1 : diff
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
# it returns nil if dst? is not applicable to the value
|
25
|
+
def is?(tm)
|
26
|
+
# it is not something we can reliably process
|
27
|
+
return unless tm.respond_to?(:zone) && tm.respond_to?(:dst?)
|
28
|
+
|
29
|
+
# We can't say "it is not DST" (like `Time#dst?` will say), only "It is time without DST info"
|
30
|
+
# Empty string is what JRuby does when it doesn't know.
|
31
|
+
return if tm.zone.nil? || tm.zone == ''
|
32
|
+
|
33
|
+
# Workaround for: https://bugs.ruby-lang.org/issues/15988
|
34
|
+
# In Ruby 2.6, Time with "real" Timezone always return `dst? => true` for some zones.
|
35
|
+
# Relates on TZInfo API (which is NOT guaranteed to be present, but practically should be)
|
36
|
+
tm.zone.respond_to?(:dst?) ? tm.zone.dst?(tm) : tm.dst?
|
37
|
+
end
|
38
|
+
|
39
|
+
def compare(v1, v2)
|
40
|
+
dst1 = is?(v1)
|
41
|
+
dst2 = is?(v2)
|
42
|
+
case
|
43
|
+
when [dst1, dst2].any?(&:nil?)
|
44
|
+
nil
|
45
|
+
when dst1 == dst2
|
46
|
+
0
|
47
|
+
when dst1 # and !dst2
|
48
|
+
1
|
49
|
+
else # !dst1 and dst2
|
50
|
+
-1
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/time_calc/op.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class TimeCalc
|
4
|
+
# Abstraction over chain of time math operations that can be applied to a time or date.
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
# op = TimeCalc.+(1, :day).floor(:hour)
|
8
|
+
# # => <TimeCalc::Op +(1 day).floor(hour)>
|
9
|
+
# op.call(Time.now)
|
10
|
+
# # => 2019-07-04 22:00:00 +0300
|
11
|
+
# array_of_time_values.map(&op)
|
12
|
+
# # => array of "next day, floor to hour" for each element
|
13
|
+
class Op
|
14
|
+
# @private
|
15
|
+
attr_reader :chain
|
16
|
+
|
17
|
+
# @note
|
18
|
+
# Prefer `TimeCalc.<operation>` (for example {TimeCalc#+}) to create operations.
|
19
|
+
def initialize(chain = [])
|
20
|
+
@chain = chain
|
21
|
+
end
|
22
|
+
|
23
|
+
# @private
|
24
|
+
def inspect
|
25
|
+
'<%s %s>' % [self.class, @chain.map { |name, *args| "#{name}(#{args.join(' ')})" }.join('.')]
|
26
|
+
end
|
27
|
+
|
28
|
+
TimeCalc::MATH_OPERATIONS.each do |name|
|
29
|
+
define_method(name) { |*args| Op.new([*@chain, [name, *args]]) }
|
30
|
+
end
|
31
|
+
|
32
|
+
# @!method +(span, unit)
|
33
|
+
# Adds `+(span, unit)` to method chain
|
34
|
+
# @see TimeCalc#+
|
35
|
+
# @return [Op]
|
36
|
+
# @!method -(span, unit)
|
37
|
+
# Adds `-(span, unit)` to method chain
|
38
|
+
# @see TimeCalc#-
|
39
|
+
# @return [Op]
|
40
|
+
# @!method floor(unit)
|
41
|
+
# Adds `floor(span, unit)` to method chain
|
42
|
+
# @see TimeCalc#floor
|
43
|
+
# @return [Op]
|
44
|
+
# @!method ceil(unit)
|
45
|
+
# Adds `ceil(span, unit)` to method chain
|
46
|
+
# @see TimeCalc#ceil
|
47
|
+
# @return [Op]
|
48
|
+
# @!method round(unit)
|
49
|
+
# Adds `round(span, unit)` to method chain
|
50
|
+
# @see TimeCalc#round
|
51
|
+
# @return [Op]
|
52
|
+
|
53
|
+
# Performs the whole chain of operation on parameter, returning the result.
|
54
|
+
#
|
55
|
+
# @param date_or_time [Date, Time, DateTime]
|
56
|
+
# @return [Date, Time, DateTime] Type of the result is always the same as type of the parameter
|
57
|
+
def call(date_or_time)
|
58
|
+
@chain.reduce(Value.new(date_or_time)) { |val, (name, *args)|
|
59
|
+
val.public_send(name, *args)
|
60
|
+
}.unwrap
|
61
|
+
end
|
62
|
+
|
63
|
+
# Allows to pass operation with `&operation`.
|
64
|
+
#
|
65
|
+
# @return [Proc]
|
66
|
+
def to_proc
|
67
|
+
method(:call).to_proc
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class TimeCalc
|
4
|
+
# `Sequence` is a Enumerable, allowing to iterate from start point in time over defined step, till
|
5
|
+
# end point or endlessly.
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# seq = TimeCalc.(Time.parse('2019-06-01 14:50')).step(1, :day).for(2, :weeks)
|
9
|
+
# # => #<TimeCalc::Sequence (2019-06-01 14:50:00 +0300 - 2019-06-15 14:50:00 +0300):step(1 day)>
|
10
|
+
# seq.to_a
|
11
|
+
# # => [2019-06-01 14:50:00 +0300, 2019-06-02 14:50:00 +0300, ....
|
12
|
+
# seq.select(&:monday?)
|
13
|
+
# # => [2019-06-03 14:50:00 +0300, 2019-06-10 14:50:00 +0300]
|
14
|
+
#
|
15
|
+
# # Endless sequences are useful too:
|
16
|
+
# seq = TimeCalc.(Time.parse('2019-06-01 14:50')).step(1, :day)
|
17
|
+
# # => #<TimeCalc::Sequence (2019-06-01 14:50:00 +0300 - ...):step(1 day)>
|
18
|
+
# seq.lazy.select(&:monday?).first(4)
|
19
|
+
# # => [2019-06-03 14:50:00 +0300, 2019-06-10 14:50:00 +0300, 2019-06-17 14:50:00 +0300, 2019-06-24 14:50:00 +0300]
|
20
|
+
class Sequence
|
21
|
+
# @return [Value] Wrapped sequence start.
|
22
|
+
attr_reader :from
|
23
|
+
|
24
|
+
# @note
|
25
|
+
# Prefer TimeCalc#to or TimeCalc#step for producing sequences.
|
26
|
+
# @param from [Time, Date, DateTime]
|
27
|
+
# @param to [Time, Date, DateTime, nil] `nil` produces endless sequence, which can be
|
28
|
+
# limited later with {#to} method.
|
29
|
+
# @param step [(Integer, Symbol), nil] Pair of span and unit to advance sequence; no `step`
|
30
|
+
# produces incomplete sequence ({#each} will raise), which can be completed later with
|
31
|
+
# {#step} method.
|
32
|
+
def initialize(from:, to: nil, step: nil)
|
33
|
+
@from = Value.wrap(from)
|
34
|
+
@to = to&.then(&Value.method(:wrap))
|
35
|
+
@step = step
|
36
|
+
end
|
37
|
+
|
38
|
+
# @private
|
39
|
+
def inspect
|
40
|
+
'#<%s (%s - %s):step(%s)>' %
|
41
|
+
[self.class, @from.unwrap, @to&.unwrap || '...', @step&.join(' ') || '???']
|
42
|
+
end
|
43
|
+
|
44
|
+
alias to_s inspect
|
45
|
+
|
46
|
+
# @overload each
|
47
|
+
# @yield [Date/Time/DateTime] Next element in sequence
|
48
|
+
# @return [self]
|
49
|
+
# @overload each
|
50
|
+
# @return [Enumerator]
|
51
|
+
# @yield [Date/Time/DateTime] Next element in sequence
|
52
|
+
# @return [Enumerator or self]
|
53
|
+
def each
|
54
|
+
fail TypeError, "No step defined for #{self}" unless @step
|
55
|
+
|
56
|
+
return to_enum(__method__) unless block_given?
|
57
|
+
|
58
|
+
return unless matching_direction?(@from)
|
59
|
+
|
60
|
+
cur = @from
|
61
|
+
while matching_direction?(cur)
|
62
|
+
yield cur.unwrap
|
63
|
+
cur = cur.+(*@step) # rubocop:disable Style/SelfAssignment
|
64
|
+
end
|
65
|
+
yield cur.unwrap if cur == @to
|
66
|
+
|
67
|
+
self
|
68
|
+
end
|
69
|
+
|
70
|
+
include Enumerable
|
71
|
+
|
72
|
+
# @overload step
|
73
|
+
# @return [(Integer, Symbol)] current step
|
74
|
+
# @overload step(unit)
|
75
|
+
# Shortcut for `step(1, unit)`
|
76
|
+
# @param unit [Symbol] Any of supported units.
|
77
|
+
# @return [Sequence]
|
78
|
+
# @overload step(span, unit)
|
79
|
+
# Produces new sequence with changed step.
|
80
|
+
# @param span [Ineger]
|
81
|
+
# @param unit [Symbol] Any of supported units.
|
82
|
+
# @return [Sequence]
|
83
|
+
def step(span = nil, unit = nil)
|
84
|
+
return @step if span.nil?
|
85
|
+
|
86
|
+
span, unit = 1, span if unit.nil?
|
87
|
+
Sequence.new(from: @from, to: @to, step: [span, unit])
|
88
|
+
end
|
89
|
+
|
90
|
+
# @overload to
|
91
|
+
# @return [Value] current sequence end, wrapped into {Value}
|
92
|
+
# @overload to(date_or_time)
|
93
|
+
# Produces new sequence with end changed
|
94
|
+
# @param date_or_time [Date, Time, DateTime]
|
95
|
+
# @return [Sequence]
|
96
|
+
def to(date_or_time = nil)
|
97
|
+
return @to if date_or_time.nil?
|
98
|
+
|
99
|
+
Sequence.new(from: @from, to: date_or_time, step: @step)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Produces sequence ending at `from.+(span, unit)`.
|
103
|
+
#
|
104
|
+
# @example
|
105
|
+
# TimeCalc.(Time.parse('2019-06-01 14:50')).step(1, :day).for(2, :weeks).count
|
106
|
+
# # => 15
|
107
|
+
#
|
108
|
+
# @param span [Integer]
|
109
|
+
# @param unit [Symbol] Any of supported units.
|
110
|
+
# @return [Sequence]
|
111
|
+
def for(span, unit)
|
112
|
+
to(from.+(span, unit))
|
113
|
+
end
|
114
|
+
|
115
|
+
# @private
|
116
|
+
def ==(other)
|
117
|
+
other.is_a?(self.class) && from == other.from && to == other.to && step == other.step
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
def direction
|
123
|
+
(@step.first / @step.first.abs)
|
124
|
+
end
|
125
|
+
|
126
|
+
def matching_direction?(val)
|
127
|
+
!@to || (@to <=> val) == direction
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|