time_calc 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Changelog.md +5 -1
- data/README.md +25 -2
- data/lib/time_calc/diff.rb +1 -1
- data/lib/time_calc/op.rb +8 -4
- data/lib/time_calc/value.rb +44 -1
- data/lib/time_calc/version.rb +1 -1
- data/lib/time_calc.rb +50 -5
- metadata +16 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3222784ca4e6f520d4402ce84f5ea6f8a9becccb66e3640071d0a29b332297b7
|
4
|
+
data.tar.gz: 9746e03e5a3e560a5f63780e87ea75f87597faa44e594228f075ae5daca3a83b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a6f3315fe5e4bc9334cbee7f089f50aebf7dfa854a6580f7cc53488afa0c8cb9ef0601f0d47c691b8427cbeea4dcc2842c97022666009e3b01b8e4056883b67c
|
7
|
+
data.tar.gz: d0ccd1540d9010f5c4cec6c3fb4708b75c3fc795cbe8913f7a7e43455caa6bf209f6d39ca828dd83bae8d73f4a7e8b7a0a7a432eaeaaec33442e6ef1afe5716d
|
data/Changelog.md
CHANGED
@@ -1,9 +1,13 @@
|
|
1
1
|
# TimeCalc changelog
|
2
2
|
|
3
|
+
## 0.0.3 / 2019-12-14
|
4
|
+
|
5
|
+
* Add `TimeCalc#iterate` to easily operate in "business date/time" contexts.
|
6
|
+
|
3
7
|
## 0.0.2 / 2019-07-08
|
4
8
|
|
5
9
|
* Alias `TimeCalc[tm]` for those who disapporve on `TimeCalc.(tm)`;
|
6
|
-
* More accurate zone
|
10
|
+
* More accurate zone info preservation when time is in local timezone of current machine.
|
7
11
|
|
8
12
|
## 0.0.1 / 2019-07-05
|
9
13
|
|
data/README.md
CHANGED
@@ -67,6 +67,29 @@ TimeCalc.(t).+(3, :months) # jump over DST: we have +3 in summer and +2 in winte
|
|
67
67
|
```
|
68
68
|
<small>(Random fun fact: it is Kyiv, not Kiev!)</small>
|
69
69
|
|
70
|
+
### Math with skipping "non-business time"
|
71
|
+
|
72
|
+
[TimeCalc#iterate](https://www.rubydoc.info/gems/time_calc/TimeCalc#iterate-instance_method) allows to advance or decrease time values by skipping some of them (like weekends, holidays, and non-working hours):
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
# add 10 working days (weekends are not counted)
|
76
|
+
TimeCalc.(Time.parse('2019-07-03 23:28:54')).iterate(10, :days) { |t| (1..5).cover?(t.wday) }
|
77
|
+
# => 2019-07-17 23:28:54 +0300
|
78
|
+
|
79
|
+
# add 12 working hours
|
80
|
+
TimeCalc.(Time.parse('2019-07-03 13:28:54')).iterate(12, :hours) { |t| (9...18).cover?(t.hour) }
|
81
|
+
# => 2019-07-04 16:28:54 +0300
|
82
|
+
|
83
|
+
# negative spans are working, too:
|
84
|
+
TimeCalc.(Time.parse('2019-07-03 13:28:54')).iterate(-12, :hours) { |t| (9...18).cover?(t.hour) }
|
85
|
+
# => 2019-07-02 10:28:54 +0300
|
86
|
+
|
87
|
+
# zero span could be used to robustly enforce value into acceptable range
|
88
|
+
# (increasing forward till block is true):
|
89
|
+
TimeCalc.(Time.parse('2019-07-03 23:28:54')).iterate(0, :hours) { |t| (9...18).cover?(t.hour) }
|
90
|
+
# => 2019-07-04 09:28:54 +0300
|
91
|
+
```
|
92
|
+
|
70
93
|
### Difference of two values
|
71
94
|
|
72
95
|
```ruby
|
@@ -118,7 +141,7 @@ TBH, using the library myself only eventually, I have never been too happy with
|
|
118
141
|
# "Formalized": now - 2 days
|
119
142
|
|
120
143
|
# ActiveSupport:
|
121
|
-
Time.now
|
144
|
+
Time.now - 2.days
|
122
145
|
# also there is 2.days.ago, but I am not a big fan of "1000 synonyms just for naturality"
|
123
146
|
|
124
147
|
# TimeMath:
|
@@ -146,4 +169,4 @@ The rest of the design (see examples above) just followed naturally. There could
|
|
146
169
|
## Author & license
|
147
170
|
|
148
171
|
* [Victor Shepelev](https://zverok.github.io)
|
149
|
-
* [MIT](https://github.com/zverok/time_calc/blob/master/LICENSE.txt).
|
172
|
+
* [MIT](https://github.com/zverok/time_calc/blob/master/LICENSE.txt).
|
data/lib/time_calc/diff.rb
CHANGED
@@ -285,7 +285,7 @@ class TimeCalc
|
|
285
285
|
# Will coerce Date to Time or DateTime, with the _zone of the latter_
|
286
286
|
def coerce_date(date, other)
|
287
287
|
TimeCalc.(other)
|
288
|
-
.merge(Units::DEFAULTS.merge(year: date.year, month: date.month, day: date.day))
|
288
|
+
.merge(**Units::DEFAULTS.merge(year: date.year, month: date.month, day: date.day))
|
289
289
|
end
|
290
290
|
end
|
291
291
|
end
|
data/lib/time_calc/op.rb
CHANGED
@@ -22,17 +22,21 @@ class TimeCalc
|
|
22
22
|
|
23
23
|
# @private
|
24
24
|
def inspect
|
25
|
-
'<%s %s>' % [self.class, @chain.map { |name,
|
25
|
+
'<%s %s>' % [self.class, @chain.map { |name, args, _| "#{name}(#{args.join(' ')})" }.join('.')]
|
26
26
|
end
|
27
27
|
|
28
28
|
TimeCalc::MATH_OPERATIONS.each do |name|
|
29
|
-
define_method(name) { |*args| Op.new([*@chain, [name,
|
29
|
+
define_method(name) { |*args, &block| Op.new([*@chain, [name, args, block].compact]) }
|
30
30
|
end
|
31
31
|
|
32
32
|
# @!method +(span, unit)
|
33
33
|
# Adds `+(span, unit)` to method chain
|
34
34
|
# @see TimeCalc#+
|
35
35
|
# @return [Op]
|
36
|
+
# @!method iterate(span, unit, &block)
|
37
|
+
# Adds `iterate(span, unit, &block)` to method chain
|
38
|
+
# @see TimeCalc#iterate
|
39
|
+
# @return [Op]
|
36
40
|
# @!method -(span, unit)
|
37
41
|
# Adds `-(span, unit)` to method chain
|
38
42
|
# @see TimeCalc#-
|
@@ -55,8 +59,8 @@ class TimeCalc
|
|
55
59
|
# @param date_or_time [Date, Time, DateTime]
|
56
60
|
# @return [Date, Time, DateTime] Type of the result is always the same as type of the parameter
|
57
61
|
def call(date_or_time)
|
58
|
-
@chain.reduce(Value.new(date_or_time)) { |val, (name,
|
59
|
-
val.public_send(name, *args)
|
62
|
+
@chain.reduce(Value.new(date_or_time)) { |val, (name, args, block)|
|
63
|
+
val.public_send(name, *args, &block)
|
60
64
|
}.unwrap
|
61
65
|
end
|
62
66
|
|
data/lib/time_calc/value.rb
CHANGED
@@ -7,6 +7,27 @@ require 'backports/2.6.0/kernel/then'
|
|
7
7
|
require 'backports/2.5.0/hash/slice'
|
8
8
|
require 'backports/2.5.0/enumerable/all'
|
9
9
|
|
10
|
+
if RUBY_VERSION < '2.7'
|
11
|
+
# @private
|
12
|
+
# TODO: Replace with backports after 2.7 release
|
13
|
+
class Enumerator
|
14
|
+
NOVALUE = Object.new.freeze
|
15
|
+
|
16
|
+
def self.produce(initial = NOVALUE)
|
17
|
+
fail ArgumentError, 'No block given' unless block_given?
|
18
|
+
|
19
|
+
Enumerator.new do |y|
|
20
|
+
val = initial == NOVALUE ? yield() : initial
|
21
|
+
|
22
|
+
loop do
|
23
|
+
y << val
|
24
|
+
val = yield(val)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
10
31
|
class TimeCalc
|
11
32
|
# Wrapper (one can say "monad") around date/time value, allowing to perform several TimeCalc
|
12
33
|
# operations in a chain.
|
@@ -99,7 +120,7 @@ class TimeCalc
|
|
99
120
|
.drop_while { |u| u != unit }
|
100
121
|
.drop(1)
|
101
122
|
.then { |keys| Units::DEFAULTS.slice(*keys) }
|
102
|
-
.then(&method(:merge)
|
123
|
+
.then { |attrs| merge(**attrs) } # can't simplify to &method(:merge) due to 2.7 keyword param problem
|
103
124
|
end
|
104
125
|
|
105
126
|
alias floor truncate
|
@@ -163,6 +184,28 @@ class TimeCalc
|
|
163
184
|
unit.nil? ? Diff.new(self, span_or_other) : self.+(-span_or_other, unit)
|
164
185
|
end
|
165
186
|
|
187
|
+
# Like {#+}, but allows conditional skipping of some periods. Increases value by `unit`
|
188
|
+
# at least `span` times, on each iteration checking with block provided if this point
|
189
|
+
# matches desired period; if it is not, it is skipped without increasing iterations
|
190
|
+
# counter. Useful for "business date/time" algorithms.
|
191
|
+
#
|
192
|
+
# See {TimeCalc#iterate} for examples.
|
193
|
+
#
|
194
|
+
# @param span [Integer]
|
195
|
+
# @param unit [Symbol]
|
196
|
+
# @return [Value]
|
197
|
+
# @yield [Time/Date/DateTime] Object of wrapped class
|
198
|
+
# @yieldreturn [true, false] If this point in time is "suitable". If the falsey value is returned,
|
199
|
+
# iteration is skipped without increasing the counter.
|
200
|
+
def iterate(span, unit)
|
201
|
+
block_given? or fail ArgumentError, 'No block given'
|
202
|
+
Integer === span or fail ArgumentError, 'Only integer spans are supported' # rubocop:disable Style/CaseEquality
|
203
|
+
|
204
|
+
Enumerator.produce(self) { |v| v.+((span <=> 0).nonzero? || 1, unit) }
|
205
|
+
.lazy.select { |v| yield(v.internal) }
|
206
|
+
.drop(span.abs).first
|
207
|
+
end
|
208
|
+
|
166
209
|
# Produces {Sequence} from this value to `date_or_time`
|
167
210
|
#
|
168
211
|
# @param date_or_time [Date, Time, DateTime]
|
data/lib/time_calc/version.rb
CHANGED
data/lib/time_calc.rb
CHANGED
@@ -190,6 +190,37 @@ class TimeCalc
|
|
190
190
|
# @param unit [Symbol]
|
191
191
|
# @return [Date, Time, DateTime] value of the same type that was initial wrapped value.
|
192
192
|
|
193
|
+
# @!method iterate(span, unit)
|
194
|
+
# Like {#+}, but allows conditional skipping of some periods. Increases value by `unit`
|
195
|
+
# at least `span` times, on each iteration checking with block provided if this point
|
196
|
+
# matches desired period; if it is not, it is skipped without increasing iterations
|
197
|
+
# counter. Useful for "business date/time" algorithms.
|
198
|
+
#
|
199
|
+
# @example
|
200
|
+
# # add 10 working days.
|
201
|
+
# TimeCalc.(Time.parse('2019-07-03 23:28:54')).iterate(10, :days) { |t| (1..5).cover?(t.wday) }
|
202
|
+
# # => 2019-07-17 23:28:54 +0300
|
203
|
+
#
|
204
|
+
# # add 12 working hours
|
205
|
+
# TimeCalc.(Time.parse('2019-07-03 13:28:54')).iterate(12, :hours) { |t| (9...18).cover?(t.hour) }
|
206
|
+
# # => 2019-07-04 16:28:54 +0300
|
207
|
+
#
|
208
|
+
# # negative spans are working, too:
|
209
|
+
# TimeCalc.(Time.parse('2019-07-03 13:28:54')).iterate(-12, :hours) { |t| (9...18).cover?(t.hour) }
|
210
|
+
# # => 2019-07-02 10:28:54 +0300
|
211
|
+
#
|
212
|
+
# # zero span could be used to robustly enforce value into acceptable range
|
213
|
+
# # (increasing forward till block is true):
|
214
|
+
# TimeCalc.(Time.parse('2019-07-03 23:28:54')).iterate(0, :hours) { |t| (9...18).cover?(t.hour) }
|
215
|
+
# # => 2019-07-04 09:28:54 +0300
|
216
|
+
#
|
217
|
+
# @param span [Integer] Could be positive or negative
|
218
|
+
# @param unit [Symbol]
|
219
|
+
# @return [Date, Time, DateTime] value of the same type that was initial wrapped value.
|
220
|
+
# @yield [Time/Date/DateTime] Object of wrapped class
|
221
|
+
# @yieldreturn [true, false] If this point in time is "suitable". If the falsey value is returned,
|
222
|
+
# iteration is skipped without increasing the counter.
|
223
|
+
|
193
224
|
# @!method -(span_or_other, unit=nil)
|
194
225
|
# @overload -(span, unit)
|
195
226
|
# Subtracts `span units` from wrapped value.
|
@@ -238,25 +269,39 @@ class TimeCalc
|
|
238
269
|
# @return [Sequence]
|
239
270
|
|
240
271
|
# @private
|
241
|
-
MATH_OPERATIONS = %i[merge truncate floor ceil round + -].freeze
|
272
|
+
MATH_OPERATIONS = %i[merge truncate floor ceil round + - iterate].freeze
|
242
273
|
# @private
|
243
274
|
OPERATIONS = MATH_OPERATIONS.+(%i[to step for]).freeze
|
244
275
|
|
245
276
|
OPERATIONS.each do |name|
|
246
|
-
|
247
|
-
|
248
|
-
|
277
|
+
# https://bugs.ruby-lang.org/issues/16421 :shrug:
|
278
|
+
# FIXME: In fact, the only kwargs op seem to be merge(...). If the problem is unsolvable,
|
279
|
+
# it is easier to define it separately.
|
280
|
+
if RUBY_VERSION < '2.7'
|
281
|
+
define_method(name) { |*args, &block|
|
282
|
+
@value.public_send(name, *args, &block)
|
283
|
+
.then { |res| res.is_a?(Value) ? res.unwrap : res }
|
284
|
+
}
|
285
|
+
else
|
286
|
+
define_method(name) { |*args, **kwargs, &block|
|
287
|
+
@value.public_send(name, *args, **kwargs, &block)
|
288
|
+
.then { |res| res.is_a?(Value) ? res.unwrap : res }
|
289
|
+
}
|
290
|
+
end
|
249
291
|
end
|
250
292
|
|
251
293
|
class << self
|
252
294
|
MATH_OPERATIONS.each do |name|
|
253
|
-
define_method(name) { |*args| Op.new([[name,
|
295
|
+
define_method(name) { |*args, &block| Op.new([[name, args, block].compact]) }
|
254
296
|
end
|
255
297
|
|
256
298
|
# @!parse
|
257
299
|
# # Creates operation to perform {#+}`(span, unit)`
|
258
300
|
# # @return [Op]
|
259
301
|
# def TimeCalc.+(span, unit); end
|
302
|
+
# # Creates operation to perform {#iterate}`(span, unit, &block)`
|
303
|
+
# # @return [Op]
|
304
|
+
# def TimeCalc.iterate(span, unit, &block); end
|
260
305
|
# # Creates operation to perform {#-}`(span, unit)`
|
261
306
|
# # @return [Op]
|
262
307
|
# def TimeCalc.-(span, unit); end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: time_calc
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Victor Shepelev
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-12-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: backports
|
@@ -30,28 +30,28 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 0.
|
33
|
+
version: 0.77.0
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: 0.
|
40
|
+
version: 0.77.0
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rubocop-rspec
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- - "
|
45
|
+
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: 1.
|
47
|
+
version: 1.37.0
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- - "
|
52
|
+
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: 1.
|
54
|
+
version: 1.37.0
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: rspec
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -86,14 +86,14 @@ dependencies:
|
|
86
86
|
requirements:
|
87
87
|
- - ">="
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version:
|
89
|
+
version: 0.0.6
|
90
90
|
type: :development
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
94
|
- - ">="
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version:
|
96
|
+
version: 0.0.6
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
98
|
name: simplecov
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -188,7 +188,12 @@ files:
|
|
188
188
|
homepage: https://github.com/zverok/time_calc
|
189
189
|
licenses:
|
190
190
|
- MIT
|
191
|
-
metadata:
|
191
|
+
metadata:
|
192
|
+
bug_tracker_uri: https://github.com/zverok/time_calc/issues
|
193
|
+
changelog_uri: https://github.com/zverok/time_calc/blob/master/Changelog.md
|
194
|
+
documentation_uri: https://www.rubydoc.info/gems/time_calc/
|
195
|
+
homepage_uri: https://github.com/zverok/time_calc
|
196
|
+
source_code_uri: https://github.com/zverok/time_calc
|
192
197
|
post_install_message:
|
193
198
|
rdoc_options: []
|
194
199
|
require_paths:
|