time_math2 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 +7 -0
- data/.yardopts +2 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +22 -0
- data/README.md +316 -0
- data/lib/time_math/core_ext.rb +52 -0
- data/lib/time_math/measure.rb +33 -0
- data/lib/time_math/sequence.rb +175 -0
- data/lib/time_math/span.rb +53 -0
- data/lib/time_math/units/base.rb +264 -0
- data/lib/time_math/units/day.rb +34 -0
- data/lib/time_math/units/month.rb +48 -0
- data/lib/time_math/units/simple.rb +58 -0
- data/lib/time_math/units/week.rb +30 -0
- data/lib/time_math/units/year.rb +28 -0
- data/lib/time_math/units.rb +29 -0
- data/lib/time_math/version.rb +4 -0
- data/lib/time_math.rb +99 -0
- data/time_math2.gemspec +39 -0
- metadata +163 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 006dfa63a22116ad083fd98e2aca4390734bce26
|
4
|
+
data.tar.gz: 028d5e1ca0a36f80840b95aadb675f60497385f5
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 261cbba1b37663d18b456edfb0f0ea7a18dbd7c78c72d4c77d6d20feb664a90f4ca2baaffcaf7493a04181cf37c40a4c3480e2493dd767d4dece3dcdf0271e37
|
7
|
+
data.tar.gz: 2d9beccf3cd5e3a3b4f7a35366ec20576dc770c7570768cc6784b8cfb0bcc139df713ac88ac764ba2eb2eb44b6c4aa070d6c3b1e20626bb5c58540a38eff991b
|
data/.yardopts
ADDED
data/CHANGELOG.md
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014-15 Victor 'Zverok' Shepelev
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,316 @@
|
|
1
|
+
# Time Math
|
2
|
+
|
3
|
+
[](http://badge.fury.io/rb/time_math2)
|
4
|
+
[](https://gemnasium.com/zverok/time_math2)
|
5
|
+
[](https://codeclimate.com/github/zverok/time_math2)
|
6
|
+
[](https://travis-ci.org/zverok/time_math2)
|
7
|
+
[](https://coveralls.io/r/zverok/time_math2?branch=master)
|
8
|
+
|
9
|
+
**TimeMath2** is a small, no-dependencies library attempting to make time
|
10
|
+
arithmetics easier. It provides you with simple, easy-to-remember API, without
|
11
|
+
any monkey-patching of core Ruby classes, so it can be used alongside
|
12
|
+
Rails or without it, for any purpose.
|
13
|
+
|
14
|
+
## Features
|
15
|
+
|
16
|
+
* No monkey-patching of core classes (opt-in patching of Time and DateTime
|
17
|
+
provided, though);
|
18
|
+
* Works with Time and DateTime;
|
19
|
+
* Accurately preserves timezone info;
|
20
|
+
* Simple arithmetics: floor/ceil/round to any time unit (second, hour, year
|
21
|
+
or whatnot), advance/decrease by any unit;
|
22
|
+
* Simple time span abstraction (like "5 years" object you can store and
|
23
|
+
pass to other methods);
|
24
|
+
* Easy generation of time sequences (like "each day from _this_ to _that_
|
25
|
+
date");
|
26
|
+
* Measuring of time distances between two timestamps in any units.
|
27
|
+
|
28
|
+
## Naming
|
29
|
+
|
30
|
+
`TimeMath` is the better name I know for the task library does, but
|
31
|
+
it is [already taken](https://rubygems.org/gems/time_math). So, with no
|
32
|
+
other thoughts I came with the ugly solution.
|
33
|
+
|
34
|
+
(BTW, the [previous version](https://github.com/zverok/time_math/blob/e997d7ddd52fc5bce3c77dc3c8022adfc9fe7028/README.md)
|
35
|
+
had some dumb "funny" name for gem and all helper classes, and nobody liked it.)
|
36
|
+
|
37
|
+
## Reasons
|
38
|
+
|
39
|
+
You frequently need to calculate things like "exact midnight of the next
|
40
|
+
day", but you don't want to monkey-patch all of your integers, tug in
|
41
|
+
5K LOC of ActiveSupport and you like to have things clean and readable.
|
42
|
+
|
43
|
+
## Installation
|
44
|
+
|
45
|
+
Install it like always:
|
46
|
+
|
47
|
+
```
|
48
|
+
$ gem install time_math2
|
49
|
+
```
|
50
|
+
|
51
|
+
or add to your Gemfile
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
gem 'time_math2'
|
55
|
+
```
|
56
|
+
|
57
|
+
and `bundle install` it.
|
58
|
+
|
59
|
+
## Usage
|
60
|
+
|
61
|
+
First, you take time unit you want:
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
TimeMath[:day] # => #<TimeMath::Units::Day>
|
65
|
+
# or
|
66
|
+
TimeMath.day # => #<TimeMath::Units::Day>
|
67
|
+
|
68
|
+
# List of units supported:
|
69
|
+
TimeMath.units
|
70
|
+
# => [:sec, :min, :hour, :day, :week, :month, :year]
|
71
|
+
```
|
72
|
+
|
73
|
+
Then you use this unit for any math you want:
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
TimeMath.day.floor(Time.now) # => 2016-05-28 00:00:00 +0300
|
77
|
+
TimeMath.day.ceil(Time.now) # => 2016-05-29 00:00:00 +0300
|
78
|
+
TimeMath.day.advance(Time.now, +10) # => 2016-06-07 14:06:57 +0300
|
79
|
+
# ...and so on
|
80
|
+
```
|
81
|
+
|
82
|
+
### Full list of simple arithmetic methods
|
83
|
+
|
84
|
+
* `<unit>.floor(tm)` -- rounds down to nearest `<unit>`;
|
85
|
+
* `<unit>.ceil(tm)` -- rounds up to nearest `<unit>`;
|
86
|
+
* `<unit>.round(tm)` -- rounds to nearest `<unit>` (up or down);
|
87
|
+
* `<unit>.round?(tm)` -- checks if `tm` is already round to `<unit>`;
|
88
|
+
* `<unit>.prev(tm)` -- like `floor`, but always decreases:
|
89
|
+
- `2015-06-27 13:30` would be converted to `2015-06-27 00:00` by both
|
90
|
+
`floor` and `prev`, but
|
91
|
+
- `2015-06-27 00:00` would be left intact on `floor`, but would be
|
92
|
+
decreased to `2015-06-26 00:00` by `prev`;
|
93
|
+
* `<unit>.next(tm)` -- like `ceil`, but always increases;
|
94
|
+
* `<unit>.advance(tm, amount)` -- increases tm by integer amount of `<unit>`s;
|
95
|
+
* `<unit>.decrease(tm, amount)` -- decreases tm by integer amount of `<unit>`s;
|
96
|
+
* `<unit>.range(tm, amount)` -- creates range of `tm ... tm + amount <units>`;
|
97
|
+
* `<unit>.range_back(tm, amount)` -- creates range of `tm - amount <units> ... tm`.
|
98
|
+
|
99
|
+
See also [Units::Base](http://www.rubydoc.info/gems/time_math2/TimeMath/Units/Base).
|
100
|
+
|
101
|
+
### Time span abstraction
|
102
|
+
|
103
|
+
`TimeMath::Span` is a simple abstraction of "N units of time", which you
|
104
|
+
can store in variable and then apply to some time value:
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
span = TimeMath.day.span(5)
|
108
|
+
# => #<TimeMath::Span(day): +5>
|
109
|
+
span.before(Time.now)
|
110
|
+
# => 2016-05-23 17:46:13 +0300
|
111
|
+
```
|
112
|
+
|
113
|
+
See also [Span YARD docs](http://www.rubydoc.info/gems/time_math2/TimeMath/Span).
|
114
|
+
|
115
|
+
### Time sequence abstraction
|
116
|
+
|
117
|
+
Time sequence allows you to generate an array of time values between some
|
118
|
+
points:
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
to = Time.now
|
122
|
+
# => 2016-05-28 17:47:30 +0300
|
123
|
+
from = TimeMath.day.floor(to)
|
124
|
+
# => 2016-05-28 00:00:00 +0300
|
125
|
+
seq = TimeMath.hour.sequence(from, to)
|
126
|
+
# => #<TimeMath::Sequence(2016-05-28 00:00:00 +0300 - 2016-05-28 17:47:30 +0300)>
|
127
|
+
p(*seq)
|
128
|
+
# 2016-05-28 00:00:00 +0300
|
129
|
+
# 2016-05-28 01:00:00 +0300
|
130
|
+
# 2016-05-28 02:00:00 +0300
|
131
|
+
# 2016-05-28 03:00:00 +0300
|
132
|
+
# 2016-05-28 04:00:00 +0300
|
133
|
+
# 2016-05-28 05:00:00 +0300
|
134
|
+
# 2016-05-28 06:00:00 +0300
|
135
|
+
# 2016-05-28 07:00:00 +0300
|
136
|
+
# ...and so on
|
137
|
+
```
|
138
|
+
|
139
|
+
See also [Sequence YARD docs](http://www.rubydoc.info/gems/time_math2/TimeMath/Sequence).
|
140
|
+
|
141
|
+
### Measuring time periods
|
142
|
+
|
143
|
+
Simple measure: just "how many `<unit>`s from date A to date B:
|
144
|
+
|
145
|
+
```ruby
|
146
|
+
TimeMath.week.measure(Time.parse('2016-05-01'), Time.parse('2016-06-01'))
|
147
|
+
# => 4
|
148
|
+
```
|
149
|
+
|
150
|
+
Measure with remaineder: returns number of `<unit>`s between dates and
|
151
|
+
the date when this number would be exact:
|
152
|
+
|
153
|
+
```ruby
|
154
|
+
TimeMath.week.measure_rem(Time.parse('2016-05-01'), Time.parse('2016-06-01'))
|
155
|
+
# => [4, 2016-05-29 00:00:00 +0300]
|
156
|
+
```
|
157
|
+
|
158
|
+
(on May 29 there would be exactly 4 weeks since May 1).
|
159
|
+
|
160
|
+
Multi-unit measuring:
|
161
|
+
|
162
|
+
```ruby
|
163
|
+
# My real birthday, in fact!
|
164
|
+
birthday = Time.parse('1983-02-14 13:30')
|
165
|
+
|
166
|
+
# My full age
|
167
|
+
TimeMath.measure(birthday, Time.now)
|
168
|
+
# => {:years=>33, :months=>3, :weeks=>2, :days=>0, :hours=>1, :minutes=>25, :seconds=>52}
|
169
|
+
|
170
|
+
# NB: you can use this output with String#format or String%:
|
171
|
+
puts "%{years}y %{months}m %{weeks}w %{days}d %{hours}h %{minutes}m %{seconds}s" %
|
172
|
+
TimeMath.measure(birthday, Time.now)
|
173
|
+
# 33y 3m 2w 0d 1h 26m 15s
|
174
|
+
|
175
|
+
# Option: measure without weeks
|
176
|
+
TimeMath.measure(birthday, Time.now, weeks: false)
|
177
|
+
# => {:years=>33, :months=>3, :days=>14, :hours=>1, :minutes=>26, :seconds=>31}
|
178
|
+
|
179
|
+
# My full age in days, hours, minutes
|
180
|
+
TimeMath.measure(birthday, Time.now, upto: :day)
|
181
|
+
# => {:days=>12157, :hours=>2, :minutes=>26, :seconds=>55}
|
182
|
+
```
|
183
|
+
|
184
|
+
### Optional `Time` and `DateTime` patches
|
185
|
+
|
186
|
+
This core classes extension is optional and should be required explicitly.
|
187
|
+
TimeMath doesn't change behavior of any existing methods (like `#+`),
|
188
|
+
it just adds a couple of new ones:
|
189
|
+
|
190
|
+
```ruby
|
191
|
+
require 'time_math/core_ext'
|
192
|
+
|
193
|
+
Time.now.decrease_by(:day, 10).floor_to(:month)
|
194
|
+
Time.now.sequence_to(:month, Time.now.advance_by(:year, 5))
|
195
|
+
```
|
196
|
+
|
197
|
+
See [CoreExt](http://www.rubydoc.info/gems/time_math2/TimeMath/CoreExt)
|
198
|
+
documentation for full lists of methods added.
|
199
|
+
|
200
|
+
### Notes on timezones
|
201
|
+
|
202
|
+
TimeMath tries its best to preserve timezones of original values. Currently,
|
203
|
+
it means:
|
204
|
+
|
205
|
+
* For `Time` instances, symbolic timezone is preserved; when jumping over
|
206
|
+
DST border, UTC offset will change and everything remains as expected;
|
207
|
+
* For `DateTime` Ruby not provides symbolic timezone, only numeric offset;
|
208
|
+
it is preserved by TimeMath (but be careful about jumping around DST,
|
209
|
+
offset would not change).
|
210
|
+
|
211
|
+
|
212
|
+
## Time series generation: "laces"
|
213
|
+
|
214
|
+
I'm a real fan of funny names in gems. Here we have time **boots** for working
|
215
|
+
with time **steps**. So, something continuous will be called **lace**.
|
216
|
+
|
217
|
+
I hope, next examples are pretty self-explanatory.
|
218
|
+
|
219
|
+
```ruby
|
220
|
+
from = Time.parse('2015-03-05 10:08')
|
221
|
+
to = Time.parse('2015-03-09 11:07')
|
222
|
+
|
223
|
+
lace = TimeBoots.month.lace(from, to)
|
224
|
+
# => #<TimeBoots::Lace(2015-03-05 10:08:00 +0200 - 2015-03-09 11:07:00 +0200)>
|
225
|
+
|
226
|
+
# or
|
227
|
+
TimeBoots.lace(:month, from, to)
|
228
|
+
# => #<TimeBoots::Lace(2015-03-05 10:08:00 +0200 - 2015-03-09 11:07:00 +0200)>
|
229
|
+
|
230
|
+
lace.pull
|
231
|
+
# => [2015-03-05 10:08:00 +0200,
|
232
|
+
# 2015-03-06 10:08:00 +0200,
|
233
|
+
# 2015-03-07 10:08:00 +0200,
|
234
|
+
# 2015-03-08 10:08:00 +0200,
|
235
|
+
# 2015-03-09 10:08:00 +0200]
|
236
|
+
```
|
237
|
+
|
238
|
+
So, we have just a series of times, each day, from `from` until `to`.
|
239
|
+
Note, there is a same time of the day (10:08), as it was in `from`.
|
240
|
+
|
241
|
+
The `pull` method has an optional argument, when it is `true`, the
|
242
|
+
method returns `floor`-ed times (e.g. midnights for daily lace):
|
243
|
+
|
244
|
+
```ruby
|
245
|
+
lace.pull(true)
|
246
|
+
# => [2015-03-05 10:08:00 +0200,
|
247
|
+
# 2015-03-06 00:00:00 +0200,
|
248
|
+
# 2015-03-07 00:00:00 +0200,
|
249
|
+
# 2015-03-08 00:00:00 +0200,
|
250
|
+
# 2015-03-09 00:00:00 +0200]
|
251
|
+
```
|
252
|
+
|
253
|
+
Note the first value still at 10:08: we don't want to go before `from`.
|
254
|
+
Lace also can "expand" your period for you (`floor` the beginning and
|
255
|
+
`ceil` the end):
|
256
|
+
|
257
|
+
```ruby
|
258
|
+
lace.expand
|
259
|
+
# => #<TimeBoots::Lace(2015-03-05 00:00:00 +0200-2015-03-10 00:00:00 +0200)>
|
260
|
+
|
261
|
+
# or
|
262
|
+
lace.expand!
|
263
|
+
|
264
|
+
# or start with expanded:
|
265
|
+
TimeBoots.month.lace(from, to, expanded: true)
|
266
|
+
```
|
267
|
+
|
268
|
+
Another useful lace's functionality is generating periods.
|
269
|
+
It can be useful for filtering daily data from database, for example:
|
270
|
+
|
271
|
+
```ruby
|
272
|
+
lace.pull_ranges
|
273
|
+
# => [2015-03-05 10:08:00 +0200...2015-03-06 10:08:00 +0200,
|
274
|
+
# 2015-03-06 10:08:00 +0200...2015-03-07 10:08:00 +0200,
|
275
|
+
# 2015-03-07 10:08:00 +0200...2015-03-08 10:08:00 +0200,
|
276
|
+
# 2015-03-08 10:08:00 +0200...2015-03-09 10:08:00 +0200,
|
277
|
+
# 2015-03-09 10:08:00 +0200...2015-03-09 11:07:00 +0200]
|
278
|
+
|
279
|
+
# Now, you can do something like:
|
280
|
+
|
281
|
+
lace.pull_ranges.map{|range| dataset.where(timestamp: range).count}
|
282
|
+
# ...and have your daily stats with one line of code
|
283
|
+
|
284
|
+
```
|
285
|
+
|
286
|
+
## Got it, what else?
|
287
|
+
|
288
|
+
TimeMath also play well when included into other classes or modules:
|
289
|
+
|
290
|
+
```ruby
|
291
|
+
class MyModel
|
292
|
+
include TimeMath
|
293
|
+
|
294
|
+
def next_day
|
295
|
+
day.advance # Here!
|
296
|
+
end
|
297
|
+
end
|
298
|
+
```
|
299
|
+
|
300
|
+
## Alternatives
|
301
|
+
|
302
|
+
There's pretty small and useful [AS::Duration](https://github.com/janko-m/as-duration)
|
303
|
+
by Janko Marohnić, which is time durations, extracted from ActiveSupport,
|
304
|
+
but without any ActiveSupport bloat.
|
305
|
+
|
306
|
+
## Links
|
307
|
+
|
308
|
+
* [API Docs](http://www.rubydoc.info/gems/time_math2)
|
309
|
+
|
310
|
+
## Author
|
311
|
+
|
312
|
+
[Victor Shepelev](http://zverok.github.io/)
|
313
|
+
|
314
|
+
## License
|
315
|
+
|
316
|
+
[MIT](https://github.com/zverok/time_math2/blob/master/LICENSE.txt).
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module TimeMath
|
2
|
+
# This module is included into `Time` and `DateTime`. It is optional
|
3
|
+
# and only included by explicit `require "time_math/core_ext"`.
|
4
|
+
module CoreExt
|
5
|
+
# @!method floor_to(unit)
|
6
|
+
# Shortcut to {Units::Base#floor}
|
7
|
+
# @!method ceil_to(unit)
|
8
|
+
# Shortcut to {Units::Base#ceil}
|
9
|
+
# @!method round_to(unit)
|
10
|
+
# Shortcut to {Units::Base#round}
|
11
|
+
# @!method next_to(unit)
|
12
|
+
# Shortcut to {Units::Base#next}
|
13
|
+
# @!method prev_to(unit)
|
14
|
+
# Shortcut to {Units::Base#prev}
|
15
|
+
[:floor, :ceil, :round, :next, :prev].each do |sym|
|
16
|
+
define_method("#{sym}_to") { |unit| TimeMath[unit].send(sym, self) }
|
17
|
+
end
|
18
|
+
|
19
|
+
# Shortcut to {Units::Base#round?}
|
20
|
+
def round_to?(unit)
|
21
|
+
TimeMath[unit].round?(self)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Shortcut to {Units::Base#advance}
|
25
|
+
def advance_by(unit, amount = 1)
|
26
|
+
TimeMath[unit].advance(self, amount)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Shortcut to {Units::Base#decrease}
|
30
|
+
def decrease_by(unit, amount = 1)
|
31
|
+
TimeMath[unit].decrease(self, amount)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Shortcut to {Units::Base#range}
|
35
|
+
def range_to(unit, amount = 1)
|
36
|
+
TimeMath[unit].range(self, amount)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Shortcut to {Units::Base#range_back}
|
40
|
+
def range_from(unit, amount = 1)
|
41
|
+
TimeMath[unit].range_back(self, amount)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Shortcut to {Units::Base#sequence}. See its docs for possible options.
|
45
|
+
def sequence_to(unit, other, options = {})
|
46
|
+
TimeMath[unit].sequence(self, other, options)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
Time.send :include, CoreExt
|
51
|
+
DateTime.send :include, CoreExt if ::Kernel.const_defined?(:DateTime)
|
52
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module TimeMath
|
3
|
+
# @private
|
4
|
+
module Measure
|
5
|
+
PLURALS = {
|
6
|
+
year: :years,
|
7
|
+
month: :months,
|
8
|
+
week: :weeks,
|
9
|
+
day: :days,
|
10
|
+
hour: :hours,
|
11
|
+
min: :minutes,
|
12
|
+
sec: :seconds
|
13
|
+
}.freeze
|
14
|
+
|
15
|
+
def self.measure(from, to, options = {})
|
16
|
+
select_units(options).reverse.inject({}) do |res, unit|
|
17
|
+
span, from = Units.get(unit).measure_rem(from, to)
|
18
|
+
res.merge(PLURALS[unit] => span)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.select_units(options)
|
23
|
+
units = Units.names
|
24
|
+
units.delete(:week) if options[:weeks] == false
|
25
|
+
|
26
|
+
if (idx = units.index(options[:upto]))
|
27
|
+
units = units.first(idx + 1)
|
28
|
+
end
|
29
|
+
|
30
|
+
units
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
module TimeMath
|
2
|
+
# Sequence represents a sequential units of time between two points.
|
3
|
+
# It has several options and convenience methods for creating arrays of
|
4
|
+
# data.
|
5
|
+
#
|
6
|
+
# Basic usage example:
|
7
|
+
#
|
8
|
+
# ```ruby
|
9
|
+
# from = Time.parse('2016-05-01 13:30')
|
10
|
+
# to = Time.parse('2016-05-04 18:20')
|
11
|
+
# seq = TimeMath.day.sequence(from, to)
|
12
|
+
# # => #<TimeMath::Sequence(2016-05-01 13:30:00 +0300 - 2016-05-04 18:20:00 +0300)>
|
13
|
+
# ```
|
14
|
+
#
|
15
|
+
# Now, you can use it:
|
16
|
+
# ```ruby
|
17
|
+
# seq.to_a
|
18
|
+
# # => [2016-05-01 13:30:00 +0300, 2016-05-02 13:30:00 +0300, 2016-05-03 13:30:00 +0300, 2016-05-04 13:30:00 +0300]
|
19
|
+
# ```
|
20
|
+
# -- it's an "each day start between from and to". As you can see,
|
21
|
+
# the period start is the same as in `from`. You can request to floor
|
22
|
+
# them to beginning of day with {#floor} method or `:floor` option:
|
23
|
+
#
|
24
|
+
# ```ruby
|
25
|
+
# seq.floor.to_a
|
26
|
+
# # => [2016-05-01 13:30:00 +0300, 2016-05-02 00:00:00 +0300, 2016-05-03 00:00:00 +0300, 2016-05-04 00:00:00 +0300]
|
27
|
+
# # or:
|
28
|
+
# seq = TimeMath.day.sequence(from, to, floor: true)
|
29
|
+
# seq.to_a
|
30
|
+
# ```
|
31
|
+
# -- it floors all day starts except of `from`, which is preserved.
|
32
|
+
#
|
33
|
+
# You can expand from and to to nearest round unit by {#expand} method
|
34
|
+
# or `:expand` option:
|
35
|
+
#
|
36
|
+
# ```ruby
|
37
|
+
# seq.expand.to_a
|
38
|
+
# # => [2016-05-01 00:00:00 +0300, 2016-05-02 00:00:00 +0300, 2016-05-03 00:00:00 +0300, 2016-05-04 00:00:00 +0300]
|
39
|
+
# # or:
|
40
|
+
# seq = TimeMath.day.sequence(from, to, expand: true)
|
41
|
+
# # => #<TimeMath::Sequence(2016-05-01 00:00:00 +0300 - 2016-05-05 00:00:00 +0300)>
|
42
|
+
# seq.to_a
|
43
|
+
# ```
|
44
|
+
#
|
45
|
+
# Besides each period beginning, you can also request pairs of begin/end
|
46
|
+
# of a period, either as an array of arrays, or array of ranges:
|
47
|
+
#
|
48
|
+
# ```ruby
|
49
|
+
# seq = TimeMath.day.sequence(from, to)
|
50
|
+
# seq.pairs
|
51
|
+
# # => [[2016-05-01 13:30:00 +0300, 2016-05-02 13:30:00 +0300], [2016-05-02 13:30:00 +0300, 2016-05-03 13:30:00 +0300], [2016-05-03 13:30:00 +0300, 2016-05-04 13:30:00 +0300], [2016-05-04 13:30:00 +0300, 2016-05-04 18:20:00 +0300]]
|
52
|
+
# seq.ranges
|
53
|
+
# # => [2016-05-01 13:30:00 +0300...2016-05-02 13:30:00 +0300, 2016-05-02 13:30:00 +0300...2016-05-03 13:30:00 +0300, 2016-05-03 13:30:00 +0300...2016-05-04 13:30:00 +0300, 2016-05-04 13:30:00 +0300...2016-05-04 18:20:00 +0300]
|
54
|
+
# ```
|
55
|
+
#
|
56
|
+
# It is pretty convenient for filtering data from databases or APIs,
|
57
|
+
# TimeMath creates list of filtering ranges in a blink.
|
58
|
+
#
|
59
|
+
class Sequence
|
60
|
+
# Creates a sequence. Typically, it is easier to to it with {Units::Base#sequence},
|
61
|
+
# like this:
|
62
|
+
#
|
63
|
+
# ```ruby
|
64
|
+
# TimeMath.day.sequence(from, to)
|
65
|
+
# ```
|
66
|
+
#
|
67
|
+
# @param unit [Symbol] one of {TimeMath.units};
|
68
|
+
# @param from [Time,DateTime] start of sequence;
|
69
|
+
# @param to [Time,DateTime] upper limit of sequence;
|
70
|
+
# @param options [Hash]
|
71
|
+
# @option options [Boolean] :expand round sequence ends on creation
|
72
|
+
# (from is floored and to is ceiled);
|
73
|
+
# @option options [Boolean] :floor sequence will be rounding'ing all
|
74
|
+
# the intermediate values.
|
75
|
+
#
|
76
|
+
def initialize(unit, from, to, options = {})
|
77
|
+
@unit = Units.get(unit)
|
78
|
+
@from, @to = from, to
|
79
|
+
@options = options.dup
|
80
|
+
|
81
|
+
expand! if options[:expand]
|
82
|
+
@floor = options[:floor]
|
83
|
+
end
|
84
|
+
|
85
|
+
attr_reader :from, :to, :unit
|
86
|
+
|
87
|
+
def ==(other)
|
88
|
+
self.class == other.class && unit == other.unit &&
|
89
|
+
from == other.from && to == other.to
|
90
|
+
end
|
91
|
+
|
92
|
+
# If `:floor` option is set for sequence.
|
93
|
+
def floor?
|
94
|
+
@floor
|
95
|
+
end
|
96
|
+
|
97
|
+
# Expand sequence ends to nearest round unit.
|
98
|
+
#
|
99
|
+
# @return self
|
100
|
+
def expand!
|
101
|
+
@from = unit.floor(from)
|
102
|
+
@to = unit.ceil(to)
|
103
|
+
|
104
|
+
self
|
105
|
+
end
|
106
|
+
|
107
|
+
# Creates new sequence with ends rounded to nearest unit.
|
108
|
+
#
|
109
|
+
# @return [Sequence]
|
110
|
+
def expand
|
111
|
+
dup.tap(&:expand!)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Sets sequence to floor all the intermediate values.
|
115
|
+
#
|
116
|
+
# @return self
|
117
|
+
def floor!
|
118
|
+
@floor = true
|
119
|
+
end
|
120
|
+
|
121
|
+
# Creates new sequence with setting to floor all the intermediate
|
122
|
+
# values.
|
123
|
+
#
|
124
|
+
# @return [Sequence]
|
125
|
+
def floor
|
126
|
+
dup.tap(&:floor!)
|
127
|
+
end
|
128
|
+
|
129
|
+
# Creates an array of time unit starts between from and to. They will
|
130
|
+
# have same granularity as from (e.g. if unit is day and from is
|
131
|
+
# 2016-05-01 13:30, each of return values will be next day at 13:30),
|
132
|
+
# unless sequence is not set to floor values.
|
133
|
+
#
|
134
|
+
# @return [Array<Time or DateTime>]
|
135
|
+
def to_a
|
136
|
+
seq = []
|
137
|
+
|
138
|
+
iter = from
|
139
|
+
while iter < to
|
140
|
+
seq << iter
|
141
|
+
|
142
|
+
iter = cond_floor(unit.advance(iter))
|
143
|
+
end
|
144
|
+
|
145
|
+
seq
|
146
|
+
end
|
147
|
+
|
148
|
+
# Creates an array of pairs (time unit start, time unit end) between
|
149
|
+
# from and to.
|
150
|
+
#
|
151
|
+
# @return [Array<Array>]
|
152
|
+
def pairs
|
153
|
+
seq = to_a
|
154
|
+
seq.zip(seq[1..-1] + [to])
|
155
|
+
end
|
156
|
+
|
157
|
+
# Creates an array of Ranges (time unit start...time unit end) between
|
158
|
+
# from and to.
|
159
|
+
#
|
160
|
+
# @return [Array<Range>]
|
161
|
+
def ranges
|
162
|
+
pairs.map { |b, e| (b...e) }
|
163
|
+
end
|
164
|
+
|
165
|
+
def inspect
|
166
|
+
"#<#{self.class}(#{from} - #{to})>"
|
167
|
+
end
|
168
|
+
|
169
|
+
private
|
170
|
+
|
171
|
+
def cond_floor(tm)
|
172
|
+
@floor ? unit.floor(tm) : tm
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module TimeMath
|
2
|
+
# Represents time span (amount of units), like "4 years".
|
3
|
+
# Allows to advance or decrease Time or DateTime.
|
4
|
+
#
|
5
|
+
# Use it like this:
|
6
|
+
#
|
7
|
+
# ```ruby
|
8
|
+
# TimeMath.year.span(4).before(Time.now)
|
9
|
+
# ```
|
10
|
+
#
|
11
|
+
class Span
|
12
|
+
attr_reader :unit, :amount
|
13
|
+
|
14
|
+
# Creates Span instance.
|
15
|
+
# Typically, it is easire to use {Units::Base#span} than create
|
16
|
+
# spans directly.
|
17
|
+
#
|
18
|
+
# @param unit [Symbol] one of {Units.names}, unit of span;
|
19
|
+
# @param amount [Integer] amount of units in span.
|
20
|
+
def initialize(unit, amount)
|
21
|
+
@unit, @amount = unit, amount
|
22
|
+
@unit_impl = Units.get(unit)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Decreases `tm` by `amount` of `unit`.
|
26
|
+
#
|
27
|
+
# @param tm [Time,DateTime] time value to decrease;
|
28
|
+
# @return [Time,DateTime] decreased time.
|
29
|
+
def before(tm = Time.now)
|
30
|
+
@unit_impl.decrease(tm, amount)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Increases `tm` by `amount` of `unit`.
|
34
|
+
#
|
35
|
+
# @param tm [Time,DateTime] time value to increase;
|
36
|
+
# @return [Time,DateTime] increased time.
|
37
|
+
def after(tm = Time.now)
|
38
|
+
@unit_impl.advance(tm, amount)
|
39
|
+
end
|
40
|
+
|
41
|
+
alias ago before
|
42
|
+
alias from after
|
43
|
+
|
44
|
+
def ==(other)
|
45
|
+
self.class == other.class &&
|
46
|
+
unit == other.unit && amount == other.amount
|
47
|
+
end
|
48
|
+
|
49
|
+
def inspect
|
50
|
+
'#<%s(%s): %+i>' % [self.class, unit, amount]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|