time_math2 0.0.4 → 0.0.5
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 +16 -3
- data/README.md +135 -40
- data/lib/time_math.rb +50 -11
- data/lib/time_math/backports.rb +8 -0
- data/lib/time_math/op.rb +209 -0
- data/lib/time_math/resamplers.rb +76 -0
- data/lib/time_math/sequence.rb +159 -49
- data/lib/time_math/units/base.rb +184 -70
- data/lib/time_math/units/day.rb +2 -2
- data/lib/time_math/units/month.rb +4 -4
- data/lib/time_math/units/simple.rb +1 -1
- data/lib/time_math/units/week.rb +9 -6
- data/lib/time_math/units/year.rb +2 -2
- data/lib/time_math/util.rb +10 -0
- data/lib/time_math/version.rb +1 -1
- metadata +6 -4
- data/lib/time_math/core_ext.rb +0 -52
- data/lib/time_math/span.rb +0 -53
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8fe3eec624d634487ddc4b74d08a3d39ec3f34d5
|
4
|
+
data.tar.gz: 87c691b0990e5e4a03f7bad4602e8bf100d1d526
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8a7e17f5d2f9768c734649768b95c8ad63f168ca3c8686f2d52440caa952f8c7a20bc1af223cb5238eacfdec7e5924e814c0d8385887e83a5623da0024d15a55
|
7
|
+
data.tar.gz: a39344389ba42191d780257d97e4c53e6e2e84a5512e7deac17ba731a543e5decab9e01e833b266dc6f93efbbdec1d11135deff8cb78110071535ef382c713a7
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,18 @@
|
|
1
|
-
#
|
1
|
+
# TimeMath Changelog
|
2
2
|
|
3
|
-
# 0.0.
|
3
|
+
# 0.0.5 (2016-06-25)
|
4
4
|
|
5
|
-
* Add
|
5
|
+
* Add optional second argument to rounding functions (`floor`, `ceil` and
|
6
|
+
so on), for "floor to 3-hour mark";
|
7
|
+
* Allow this argument, as well as in `advance`/`decrease`, to be non-integer;
|
8
|
+
so, you can do `hour.advance(tm, 1/2r)` now;
|
9
|
+
* Drop any `core_ext`s completely, even despite it was optional;
|
10
|
+
* Add `Op` chainable operations concept (and drop `Span`, which
|
11
|
+
is inferior to it);
|
12
|
+
* Redesign `Sequence` creation, allow include/exclude end;
|
13
|
+
* Add (experimental) resampling feature.
|
14
|
+
|
15
|
+
# 0.0.4 (2016-05-28)
|
16
|
+
|
17
|
+
* First "real" release with current name, `Time` and `DateTime` support,
|
18
|
+
proper documentation and stuff.
|
data/README.md
CHANGED
@@ -11,23 +11,45 @@ arithmetics easier. It provides you with simple, easy-to-remember API, without
|
|
11
11
|
any monkey-patching of core Ruby classes, so it can be used alongside
|
12
12
|
Rails or without it, for any purpose.
|
13
13
|
|
14
|
+
## Table Of Contents
|
15
|
+
|
16
|
+
* [Features](#features)
|
17
|
+
* [Naming](#naming)
|
18
|
+
* [Reasons](#reasons)
|
19
|
+
* [Installation](#installation)
|
20
|
+
* [Usage](#usage)
|
21
|
+
- [Full list of simple arithmetic methods](#full-list-of-simple-arithmetic-methods)
|
22
|
+
- [Set of operations as a value object](#set-of-operations-as-a-value-object)
|
23
|
+
- [Time sequence abstraction](#time-sequence-abstraction)
|
24
|
+
- [Measuring time periods](#measuring-time-periods)
|
25
|
+
- [Resampling](#resampling)
|
26
|
+
* [Notes on timezones](#notes-on-timezones)
|
27
|
+
* [Compatibility notes](#compatibility-notes)
|
28
|
+
* [Alternatives](#alternatives)
|
29
|
+
* [Links](#links)
|
30
|
+
* [Author](#author)
|
31
|
+
* [License](#license)
|
32
|
+
|
14
33
|
## Features
|
15
34
|
|
16
|
-
* No monkey-patching of core classes (
|
17
|
-
|
18
|
-
* Works with Time and DateTime;
|
35
|
+
* No monkey-patching of core classes (now **strict**; previously existing opt-in
|
36
|
+
core ext removed in 0.0.5);
|
37
|
+
* Works with Time, Date and DateTime;
|
19
38
|
* Accurately preserves timezone info;
|
20
39
|
* Simple arithmetics: floor/ceil/round to any time unit (second, hour, year
|
21
40
|
or whatnot), advance/decrease by any unit;
|
22
|
-
*
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
41
|
+
* Chainable [operations](#set-of-operations-as-a-value-object), including
|
42
|
+
construction of "set of operations" value object (like "10:20 at next
|
43
|
+
month first day"), clean and powerful;
|
44
|
+
* Easy generation of [time sequences](#time-sequence-abstraction)
|
45
|
+
(like "each day from _this_ to _that_ date");
|
46
|
+
* Measuring of time distances between two timestamps in any units;
|
47
|
+
* Powerful and flexible [resampling](#resampling) of arbitrary time value
|
48
|
+
arrays/hashes into regular sequences.
|
27
49
|
|
28
50
|
## Naming
|
29
51
|
|
30
|
-
`TimeMath` is the
|
52
|
+
`TimeMath` is the best name I know for the task library does, yet
|
31
53
|
it is [already taken](https://rubygems.org/gems/time_math). So, with no
|
32
54
|
other thoughts I came with the ugly solution.
|
33
55
|
|
@@ -96,21 +118,55 @@ TimeMath.day.advance(Time.now, +10) # => 2016-06-07 14:06:57 +0300
|
|
96
118
|
* `<unit>.range(tm, amount)` -- creates range of `tm ... tm + amount <units>`;
|
97
119
|
* `<unit>.range_back(tm, amount)` -- creates range of `tm - amount <units> ... tm`.
|
98
120
|
|
121
|
+
**Things to note**:
|
122
|
+
|
123
|
+
* rounding methods (`floor`, `ceil` and company) support optional second
|
124
|
+
argument—amount of units to round to, like "each 3 hours": `hour.floor(tm, 3)`;
|
125
|
+
* both rounding and advance/decrease methods allow their last argument to
|
126
|
+
be float/rational, so you can `hour.advance(tm, 1/2r)` and this would
|
127
|
+
work as you may expect. Non-integer arguments are only supported for
|
128
|
+
units less than week (because "half of month" have no exact mathematical
|
129
|
+
sense).
|
130
|
+
|
99
131
|
See also [Units::Base](http://www.rubydoc.info/gems/time_math2/TimeMath/Units/Base).
|
100
132
|
|
101
|
-
###
|
133
|
+
### Set of operations as a value object
|
102
134
|
|
103
|
-
|
104
|
-
|
135
|
+
For example, you want "10 am at next monday". By using atomic time unit
|
136
|
+
operations, you'll need the code like:
|
105
137
|
|
106
138
|
```ruby
|
107
|
-
|
108
|
-
# => #<TimeMath::Span(day): +5>
|
109
|
-
span.before(Time.now)
|
110
|
-
# => 2016-05-23 17:46:13 +0300
|
139
|
+
TimeMath.hour.advance(TimeMath.week.ceil(Time.now), 10)
|
111
140
|
```
|
141
|
+
...which is not really readable, to say the least. So, `TimeMath` provides
|
142
|
+
one top-level method allowing to chain any operations you want:
|
143
|
+
|
144
|
+
```ruby
|
145
|
+
TimeMath(Time.now).ceil(:week).advance(:hour, 10).call
|
146
|
+
```
|
147
|
+
|
148
|
+
Much more readable, huh?
|
112
149
|
|
113
|
-
|
150
|
+
The best thing about it, that you can prepare "operations list" value
|
151
|
+
object, and then use it (or pass to methods, or
|
152
|
+
serialize to YAML and deserialize in some Sidekiq task and so on):
|
153
|
+
|
154
|
+
```ruby
|
155
|
+
op = TimeMath().ceil(:week).advance(:hour, 10)
|
156
|
+
# => #<TimeMath::Op ceil(:week).advance(:hour, 10)>
|
157
|
+
op.call(Time.now)
|
158
|
+
# => 2016-06-27 10:00:00 +0300
|
159
|
+
|
160
|
+
# It also can be called on several arguments/array of arguments:
|
161
|
+
op.call(tm1, tm2, tm3)
|
162
|
+
op.call(array_of_timestamps)
|
163
|
+
# ...or even used as a block-ish object:
|
164
|
+
array_of_timestamps.map(&op)
|
165
|
+
```
|
166
|
+
|
167
|
+
See also [TimeMath()](http://www.rubydoc.info/gems/time_math2/toplevel#TimeMath-instance_method)
|
168
|
+
and underlying [TimeMath::Op](http://www.rubydoc.info/gems/time_math2/TimeMath/Op)
|
169
|
+
class docs.
|
114
170
|
|
115
171
|
### Time sequence abstraction
|
116
172
|
|
@@ -122,8 +178,8 @@ to = Time.now
|
|
122
178
|
# => 2016-05-28 17:47:30 +0300
|
123
179
|
from = TimeMath.day.floor(to)
|
124
180
|
# => 2016-05-28 00:00:00 +0300
|
125
|
-
seq = TimeMath.hour.sequence(from
|
126
|
-
# => #<TimeMath::Sequence(2016-05-28 00:00:00 +0300
|
181
|
+
seq = TimeMath.hour.sequence(from...to)
|
182
|
+
# => #<TimeMath::Sequence(:hour, 2016-05-28 00:00:00 +0300...2016-05-28 17:47:30 +0300)>
|
127
183
|
p(*seq)
|
128
184
|
# 2016-05-28 00:00:00 +0300
|
129
185
|
# 2016-05-28 01:00:00 +0300
|
@@ -136,11 +192,21 @@ p(*seq)
|
|
136
192
|
# ...and so on
|
137
193
|
```
|
138
194
|
|
195
|
+
Note that sequence also play well with operation chain described above,
|
196
|
+
so you can
|
197
|
+
|
198
|
+
```ruby
|
199
|
+
seq = TimeMath.day.sequence(Time.parse('2016-05-01')...Time.parse('2016-05-04')).advance(:hour, 10).decrease(:min, 5)
|
200
|
+
# => #<TimeMath::Sequence(:day, 2016-05-01 00:00:00 +0300...2016-05-04 00:00:00 +0300).advance(:hour, 10).decrease(:min, 5)>
|
201
|
+
seq.to_a
|
202
|
+
# => [2016-05-01 09:55:00 +0300, 2016-05-02 09:55:00 +0300, 2016-05-03 09:55:00 +0300]
|
203
|
+
```
|
204
|
+
|
139
205
|
See also [Sequence YARD docs](http://www.rubydoc.info/gems/time_math2/TimeMath/Sequence).
|
140
206
|
|
141
207
|
### Measuring time periods
|
142
208
|
|
143
|
-
Simple measure: just "how many `<unit>`s from date A to date B:
|
209
|
+
Simple measure: just "how many `<unit>`s from date A to date B":
|
144
210
|
|
145
211
|
```ruby
|
146
212
|
TimeMath.week.measure(Time.parse('2016-05-01'), Time.parse('2016-06-01'))
|
@@ -181,23 +247,53 @@ TimeMath.measure(birthday, Time.now, upto: :day)
|
|
181
247
|
# => {:days=>12157, :hours=>2, :minutes=>26, :seconds=>55}
|
182
248
|
```
|
183
249
|
|
184
|
-
###
|
250
|
+
### Resampling
|
251
|
+
|
252
|
+
**Resampling** is useful for situations when you have some timestamped
|
253
|
+
data (with variable holes between values), and wantto make it regular,
|
254
|
+
e.g. for charts drawing.
|
255
|
+
|
256
|
+
The most simple (and not very useful) resampling just turns array of
|
257
|
+
irregular timestamps into regular one:
|
258
|
+
|
259
|
+
```ruby
|
260
|
+
dates = %w[2016-06-01 2016-06-03 2016-06-06].map(&Date.method(:parse))
|
261
|
+
# => [#<Date: 2016-06-01>, #<Date: 2016-06-03>, #<Date: 2016-06-06>]
|
262
|
+
TimeMath.day.resample(dates)
|
263
|
+
# => [#<Date: 2016-06-01>, #<Date: 2016-06-02>, #<Date: 2016-06-03>, #<Date: 2016-06-04>, #<Date: 2016-06-05>, #<Date: 2016-06-06>]
|
264
|
+
TimeMath.week.resample(dates)
|
265
|
+
# => [#<Date: 2016-05-30>, #<Date: 2016-06-06>]
|
266
|
+
TimeMath.month.resample(dates)
|
267
|
+
# => [#<Date: 2016-06-01>]
|
268
|
+
```
|
185
269
|
|
186
|
-
|
187
|
-
|
188
|
-
it just adds a couple of new ones:
|
270
|
+
Much more useful is _hash resampling_: when you have a hash of `{timestamp => value}`
|
271
|
+
and...
|
189
272
|
|
190
273
|
```ruby
|
191
|
-
|
274
|
+
data = {Date.parse('2016-06-01') => 18, Date.parse('2016-06-03') => 8, Date.parse('2016-06-06') => -4}
|
275
|
+
# => {#<Date: 2016-06-01>=>18, #<Date: 2016-06-03>=>8, #<Date: 2016-06-06>=>-4}
|
276
|
+
TimeMath.day.resample(data)
|
277
|
+
# => {#<Date: 2016-06-01>=>[18], #<Date: 2016-06-02>=>[], #<Date: 2016-06-03>=>[8], #<Date: 2016-06-04>=>[], #<Date: 2016-06-05>=>[], #<Date: 2016-06-06>=>[-4]}
|
278
|
+
TimeMath.week.resample(data)
|
279
|
+
# => {#<Date: 2016-05-30>=>[18, 8], #<Date: 2016-06-06>=>[-4]}
|
280
|
+
TimeMath.month.resample(data)
|
281
|
+
# => {#<Date: 2016-06-01>=>[18, 8, -4]}
|
282
|
+
```
|
283
|
+
|
284
|
+
For values grouping strategy, `resample` accepts symbol and block arguments:
|
192
285
|
|
193
|
-
|
194
|
-
|
286
|
+
```ruby
|
287
|
+
TimeMath.week.resample(data, :first)
|
288
|
+
# => {#<Date: 2016-05-30>=>18, #<Date: 2016-06-06>=>-4}
|
289
|
+
TimeMath.week.resample(data) { |vals| vals.inject(:+) }
|
290
|
+
=> {#<Date: 2016-05-30>=>26, #<Date: 2016-06-06>=>-4}
|
195
291
|
```
|
196
292
|
|
197
|
-
|
198
|
-
|
293
|
+
The functionality currently considered experimental, please notify me
|
294
|
+
about your ideas and use cases via [GitHub issues](https://github.com/zverok/time_math2/issues)!
|
199
295
|
|
200
|
-
|
296
|
+
## Notes on timezones
|
201
297
|
|
202
298
|
TimeMath tries its best to preserve timezones of original values. Currently,
|
203
299
|
it means:
|
@@ -208,19 +304,18 @@ it means:
|
|
208
304
|
it is preserved by TimeMath (but be careful about jumping around DST,
|
209
305
|
offset would not change).
|
210
306
|
|
211
|
-
##
|
307
|
+
## Compatibility notes
|
212
308
|
|
213
|
-
TimeMath
|
309
|
+
TimeMath is known to work on MRI Ruby >= 1.9.
|
214
310
|
|
215
|
-
|
216
|
-
|
217
|
-
|
311
|
+
On JRuby it works, too, though there could be _slightly_ unexpected results,
|
312
|
+
when JRuby fails to create time by timezone name (see [bug](https://github.com/jruby/jruby/issues/3978)).
|
313
|
+
TimeMath in this case fallbacks to the same solution that used for `DateTime`,
|
314
|
+
and at least preserves utc offset.
|
218
315
|
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
end
|
223
|
-
```
|
316
|
+
On Rubinius, some of tests fail and I haven't time to investigate it. If
|
317
|
+
somebody still uses Rubinius and wants TimeMath to be working properly
|
318
|
+
on it, please let me know.
|
224
319
|
|
225
320
|
## Alternatives
|
226
321
|
|
data/lib/time_math.rb
CHANGED
@@ -1,10 +1,5 @@
|
|
1
1
|
require 'time'
|
2
2
|
|
3
|
-
require_relative './time_math/units'
|
4
|
-
require_relative './time_math/sequence'
|
5
|
-
require_relative './time_math/measure'
|
6
|
-
require_relative './time_math/span'
|
7
|
-
|
8
3
|
# TimeMath is a small library for easy time units arithmetics (like "floor
|
9
4
|
# the timestamp to the nearest hour", "advance the time value by 3 days"
|
10
5
|
# and so on).
|
@@ -21,10 +16,18 @@ require_relative './time_math/span'
|
|
21
16
|
# time unit, which incapsulates most of the functionality. Refer to
|
22
17
|
# {Units::Base} to see what you can get of it.
|
23
18
|
#
|
19
|
+
# See also `TimeMath()` method in global namespace, it is lot of fun!
|
20
|
+
#
|
24
21
|
module TimeMath
|
25
|
-
|
26
|
-
|
27
|
-
|
22
|
+
require_relative './time_math/backports'
|
23
|
+
require_relative './time_math/units'
|
24
|
+
require_relative './time_math/op'
|
25
|
+
require_relative './time_math/sequence'
|
26
|
+
require_relative './time_math/measure'
|
27
|
+
require_relative './time_math/resamplers'
|
28
|
+
require_relative './time_math/util'
|
29
|
+
|
30
|
+
module_function
|
28
31
|
|
29
32
|
# List all unit names known.
|
30
33
|
#
|
@@ -71,7 +74,7 @@ module TimeMath
|
|
71
74
|
# @return [Units::Base]
|
72
75
|
#
|
73
76
|
Units.names.each do |unit|
|
74
|
-
|
77
|
+
define_singleton_method(unit) { Units.get(unit) }
|
75
78
|
end
|
76
79
|
|
77
80
|
# Measures distance between two time values in all units at once.
|
@@ -85,8 +88,8 @@ module TimeMath
|
|
85
88
|
# # => {:years=>33, :months=>3, :weeks=>2, :days=>0, :hours=>1, :minutes=>25, :seconds=>52}
|
86
89
|
# ```
|
87
90
|
#
|
88
|
-
# @param from [Time,DateTime]
|
89
|
-
# @param to [Time,DateTime]
|
91
|
+
# @param from [Time,Date,DateTime]
|
92
|
+
# @param to [Time,Date,DateTime]
|
90
93
|
# @param options [Hash] options
|
91
94
|
# @option options [Boolean] :weeks pass `false` to exclude weeks from calculation;
|
92
95
|
# @option options [Symbol] :upto pass max unit to use (e.g. if you'll
|
@@ -97,3 +100,39 @@ module TimeMath
|
|
97
100
|
Measure.measure(from, to, options)
|
98
101
|
end
|
99
102
|
end
|
103
|
+
|
104
|
+
# This method helps to create time arithmetics sequence as a value object.
|
105
|
+
# Some examples:
|
106
|
+
#
|
107
|
+
# ```ruby
|
108
|
+
# # 10 am at first weekday? Easy!
|
109
|
+
# TimeMath(Time.now).floor(:week).advance(:hour, 10).call
|
110
|
+
# # => 2016-06-20 10:00:00 +0300
|
111
|
+
#
|
112
|
+
# # For several time values? Nothing easier!
|
113
|
+
# TimeMath(Time.local(2016,1,1), Time.local(2016,2,1), Time.local(2016,3,1)).floor(:week).advance(:hour, 10).call
|
114
|
+
# # => [2015-12-28 10:00:00 +0200, 2016-02-01 10:00:00 +0200, 2016-02-29 10:00:00 +0200]
|
115
|
+
#
|
116
|
+
# # Or, the most fun, you can create complicated operation and call it
|
117
|
+
# # later:
|
118
|
+
# op = TimeMath().floor(:week).advance(:hour, 10)
|
119
|
+
# # => #<TimeMath::Op floor(:week).advance(:hour, 10)>
|
120
|
+
# op.call(Time.now)
|
121
|
+
# # => 2016-06-20 10:00:00 +0300
|
122
|
+
#
|
123
|
+
# # or even as a lambda:
|
124
|
+
# times = [Time.local(2016,1,1), Time.local(2016,2,1), Time.local(2016,3,1)]
|
125
|
+
# times.map(&op)
|
126
|
+
# # => [2015-12-28 10:00:00 +0200, 2016-02-01 10:00:00 +0200, 2016-02-29 10:00:00 +0200]
|
127
|
+
# ```
|
128
|
+
#
|
129
|
+
# See also {TimeMath::Op} for list of operations available, but basically
|
130
|
+
# they are all same you can call on {TimeMath::Unit}, just pass unit symbol
|
131
|
+
# as a first argument.
|
132
|
+
#
|
133
|
+
# @param arguments time-y value, or list of them, or nothing
|
134
|
+
#
|
135
|
+
# @return [TimeMath::Op]
|
136
|
+
def TimeMath(*arguments) # rubocop:disable Style/MethodName
|
137
|
+
TimeMath::Op.new(*arguments)
|
138
|
+
end
|
data/lib/time_math/op.rb
ADDED
@@ -0,0 +1,209 @@
|
|
1
|
+
module TimeMath
|
2
|
+
# `Op` is value object, incapsulating several operations performed on
|
3
|
+
# time unit. The names of operations are the same the single unit can
|
4
|
+
# perform, first parameter is always a unit.
|
5
|
+
#
|
6
|
+
# Ops can be created by `TimeMath::Op.new` or with pretty shortcut
|
7
|
+
# `TimeMath()`.
|
8
|
+
#
|
9
|
+
# Available usages:
|
10
|
+
#
|
11
|
+
# ```ruby
|
12
|
+
# # 1. chain operations:
|
13
|
+
# # without Op: 10:25 at first day of next week:
|
14
|
+
# TimeMath.min.advance(TimeMath.hour.advance(TimeMath.week.ceil(tm), 10), 25)
|
15
|
+
# # FOOOOOO
|
16
|
+
# # ...but with Op:
|
17
|
+
# TimeMath(tm).ceil(:week).advance(:hour, 10).advance(:min, 25).call
|
18
|
+
#
|
19
|
+
# # 2. chain operations on multiple objects:
|
20
|
+
# TimeMath(tm1, tm2, tm3).ceil(:week).advance(:hour, 10).advance(:min, 25).call
|
21
|
+
# # or
|
22
|
+
# TimeMath([array_of_times]).ceil(:week).advance(:hour, 10).advance(:min, 25).call
|
23
|
+
#
|
24
|
+
# # 3. preparing operation to be used on any objects:
|
25
|
+
# op = TimeMath().ceil(:week).advance(:hour, 10).advance(:min, 25)
|
26
|
+
# op.call(tm)
|
27
|
+
# op.call(tm1, tm2, tm3)
|
28
|
+
# op.call(array_of_times)
|
29
|
+
# # or even block-ish behavior:
|
30
|
+
# [tm1, tm2, tm3].map(&op)
|
31
|
+
# ```
|
32
|
+
#
|
33
|
+
# Note that Op also plays well with {Sequence} (see its docs for more).
|
34
|
+
class Op
|
35
|
+
# @private
|
36
|
+
OPERATIONS = [:floor, :ceil, :round, :next, :prev, :advance, :decrease].freeze
|
37
|
+
|
38
|
+
attr_reader :operations, :arguments
|
39
|
+
|
40
|
+
# Creates Op. Could (and recommended be also by its alias -- just
|
41
|
+
# `TimeMath(*arguments)`.
|
42
|
+
#
|
43
|
+
# @param arguments one, or several, or an array of time-y values
|
44
|
+
# (Time, Date, DateTime).
|
45
|
+
def initialize(*arguments)
|
46
|
+
@arguments = arguments
|
47
|
+
@operations = []
|
48
|
+
end
|
49
|
+
|
50
|
+
# @private
|
51
|
+
def initialize_copy(other)
|
52
|
+
@arguments = other.arguments.dup
|
53
|
+
@operations = other.operations.dup
|
54
|
+
end
|
55
|
+
|
56
|
+
# @method floor!(unit, span = 1)
|
57
|
+
# Adds {Units::Base#floor} to list of operations.
|
58
|
+
#
|
59
|
+
# @param unit [Symbol] One of {TimeMath.units}
|
60
|
+
# @param span [Numeric] how many units to floor to.
|
61
|
+
# @return [self]
|
62
|
+
#
|
63
|
+
# @method floor(unit, span = 1)
|
64
|
+
# Non-destructive version of {#floor!}.
|
65
|
+
# @param unit [Symbol] One of {TimeMath.units}
|
66
|
+
# @param span [Numeric] how many units to floor to.
|
67
|
+
# @return [Op]
|
68
|
+
#
|
69
|
+
# @method ceil!(unit, span = 1)
|
70
|
+
# Adds {Units::Base#ceil} to list of operations.
|
71
|
+
# @param unit [Symbol] One of {TimeMath.units}
|
72
|
+
# @param span [Numeric] how many units to ceil to.
|
73
|
+
# @return [self]
|
74
|
+
#
|
75
|
+
# @method ceil(unit, span = 1)
|
76
|
+
# Non-destructive version of {#ceil!}.
|
77
|
+
# @param unit [Symbol] One of {TimeMath.units}
|
78
|
+
# @param span [Numeric] how many units to ceil to.
|
79
|
+
# @return [Op]
|
80
|
+
#
|
81
|
+
# @method round!(unit, span = 1)
|
82
|
+
# Adds {Units::Base#round} to list of operations.
|
83
|
+
# @param unit [Symbol] One of {TimeMath.units}
|
84
|
+
# @param span [Numeric] how many units to round to.
|
85
|
+
# @return [self]
|
86
|
+
#
|
87
|
+
# @method round(unit, span = 1)
|
88
|
+
# Non-destructive version of {#round!}.
|
89
|
+
# @param unit [Symbol] One of {TimeMath.units}
|
90
|
+
# @param span [Numeric] how many units to round to.
|
91
|
+
# @return [Op]
|
92
|
+
#
|
93
|
+
# @method next!(unit, span = 1)
|
94
|
+
# Adds {Units::Base#next} to list of operations.
|
95
|
+
# @param unit [Symbol] One of {TimeMath.units}
|
96
|
+
# @param span [Numeric] how many units to ceil to.
|
97
|
+
# @return [self]
|
98
|
+
#
|
99
|
+
# @method next(unit, span = 1)
|
100
|
+
# Non-destructive version of {#next!}.
|
101
|
+
# @param unit [Symbol] One of {TimeMath.units}
|
102
|
+
# @param span [Numeric] how many units to ceil to.
|
103
|
+
# @return [Op]
|
104
|
+
#
|
105
|
+
# @method prev!(unit, span = 1)
|
106
|
+
# Adds {Units::Base#prev} to list of operations.
|
107
|
+
# @param unit [Symbol] One of {TimeMath.units}
|
108
|
+
# @param span [Numeric] how many units to floor to.
|
109
|
+
# @return [self]
|
110
|
+
#
|
111
|
+
# @method prev(unit, span = 1)
|
112
|
+
# Non-destructive version of {#prev!}.
|
113
|
+
# @param unit [Symbol] One of {TimeMath.units}
|
114
|
+
# @param span [Numeric] how many units to floor to.
|
115
|
+
# @return [Op]
|
116
|
+
#
|
117
|
+
# @method advance!(unit, amount = 1)
|
118
|
+
# Adds {Units::Base#advance} to list of operations.
|
119
|
+
# @param unit [Symbol] One of {TimeMath.units}
|
120
|
+
# @param amount [Numeric] how many units to advance.
|
121
|
+
# @return [self]
|
122
|
+
#
|
123
|
+
# @method advance(unit, amount = 1)
|
124
|
+
# Non-destructive version of {#advance!}.
|
125
|
+
# @param unit [Symbol] One of {TimeMath.units}
|
126
|
+
# @param amount [Numeric] how many units to advance.
|
127
|
+
# @return [Op]
|
128
|
+
#
|
129
|
+
# @method decrease!(unit, amount = 1)
|
130
|
+
# Adds {Units::Base#decrease} to list of operations.
|
131
|
+
# @param unit [Symbol] One of {TimeMath.units}
|
132
|
+
# @param amount [Numeric] how many units to decrease.
|
133
|
+
# @return [self]
|
134
|
+
#
|
135
|
+
# @method decrease(unit, amount = 1)
|
136
|
+
# Non-destructive version of {#decrease!}.
|
137
|
+
# @param unit [Symbol] One of {TimeMath.units}
|
138
|
+
# @param amount [Numeric] how many units to decrease.
|
139
|
+
# @return [Op]
|
140
|
+
#
|
141
|
+
|
142
|
+
OPERATIONS.each do |op|
|
143
|
+
define_method "#{op}!" do |unit, *args|
|
144
|
+
Units.names.include?(unit) or raise(ArgumentError, "Unknown unit #{unit}")
|
145
|
+
@operations << [op, unit, args]
|
146
|
+
self
|
147
|
+
end
|
148
|
+
|
149
|
+
define_method op do |unit, *args|
|
150
|
+
dup.send("#{op}!", unit, *args)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def inspect
|
155
|
+
"#<#{self.class}#{inspect_args}" + inspect_operations + '>'
|
156
|
+
end
|
157
|
+
|
158
|
+
# @private
|
159
|
+
def inspect_operations
|
160
|
+
operations.map { |op, unit, args|
|
161
|
+
"#{op}(#{[unit, *args].map(&:inspect).join(', ')})"
|
162
|
+
}.join('.')
|
163
|
+
end
|
164
|
+
|
165
|
+
def ==(other)
|
166
|
+
self.class == other.class && operations == other.operations &&
|
167
|
+
arguments == other.arguments
|
168
|
+
end
|
169
|
+
|
170
|
+
# Performs op. If an Op was created with arguments, just performs all
|
171
|
+
# operations on them and returns the result. If it was created without
|
172
|
+
# arguments, performs all operations on arguments provided to `call`.
|
173
|
+
#
|
174
|
+
# @param tms one, or several, or an array of time-y values; should not
|
175
|
+
# be passed if Op was created with arguments.
|
176
|
+
# @return [Time,Date,DateTime,Array] one, or an array of processed arguments
|
177
|
+
def call(*tms)
|
178
|
+
unless @arguments.empty?
|
179
|
+
tms.empty? or raise(ArgumentError, 'Op arguments is already set, use call()')
|
180
|
+
tms = @arguments
|
181
|
+
end
|
182
|
+
res = [*tms].flatten.map(&method(:perform))
|
183
|
+
tms.count == 1 && Util.timey?(tms.first) ? res.first : res
|
184
|
+
end
|
185
|
+
|
186
|
+
# Allows to use Op as a block:
|
187
|
+
#
|
188
|
+
# ```ruby
|
189
|
+
# timestamps.map(&TimeMath().ceil(:week).advance(:day, 1))
|
190
|
+
# ```
|
191
|
+
# @return [Proc]
|
192
|
+
def to_proc
|
193
|
+
method(:call).to_proc
|
194
|
+
end
|
195
|
+
|
196
|
+
private
|
197
|
+
|
198
|
+
def inspect_args
|
199
|
+
return ' ' if @arguments.empty?
|
200
|
+
'(' + [*@arguments].map(&:inspect).join(', ') + ').'
|
201
|
+
end
|
202
|
+
|
203
|
+
def perform(tm)
|
204
|
+
operations.inject(tm) { |memo, (op, unit, args)|
|
205
|
+
TimeMath::Units.get(unit).send(op, memo, *args)
|
206
|
+
}
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|