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 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
@@ -0,0 +1,2 @@
1
+ --markup=markdown
2
+ --no-private
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # TimeBoots changelog
2
+
3
+ # 0.0.2 (2016-05-27)
4
+
5
+ * Add support for `DateTime`.
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
+ [![Gem Version](https://badge.fury.io/rb/time_math2.svg)](http://badge.fury.io/rb/time_math2)
4
+ [![Dependency Status](https://gemnasium.com/zverok/time_math2.svg)](https://gemnasium.com/zverok/time_math2)
5
+ [![Code Climate](https://codeclimate.com/github/zverok/time_math2/badges/gpa.svg)](https://codeclimate.com/github/zverok/time_math2)
6
+ [![Build Status](https://travis-ci.org/zverok/time_math2.svg?branch=master)](https://travis-ci.org/zverok/time_math2)
7
+ [![Coverage Status](https://coveralls.io/repos/zverok/time_math2/badge.svg?branch=master)](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