time_math2 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|