time_calc 0.0.2 → 0.0.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 +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:
|