time_boots 0.0.1
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/README.md +214 -0
- data/lib/time_boots/boot/day.rb +28 -0
- data/lib/time_boots/boot/month.rb +45 -0
- data/lib/time_boots/boot/simple.rb +42 -0
- data/lib/time_boots/boot/week.rb +28 -0
- data/lib/time_boots/boot/year.rb +26 -0
- data/lib/time_boots/boot.rb +119 -0
- data/lib/time_boots/jump.rb +30 -0
- data/lib/time_boots/lace.rb +59 -0
- data/lib/time_boots/measure.rb +32 -0
- data/lib/time_boots/version.rb +4 -0
- data/lib/time_boots.rb +95 -0
- data/time_boots.gemspec +36 -0
- metadata +116 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2d915342173e04dfa56b06a071ce00895bfd1f7c
|
4
|
+
data.tar.gz: 224f704b9f2252a6d1915416972e935bb271ee0a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 75392ebf3e012f77a133ac50cc79bb18e510289574148fc47279c89a8df64264000c1a47e532c212bd1d6b8962a4a3d58c9da270a869372285a715efdd424e04
|
7
|
+
data.tar.gz: 3ab029a28565166df7c4581f447e0a0a127d4eaec7e6024391bcd39374fff88398ef6f7dac61058b45e8d68674a762612867686405bf000b9c3ec4f01d47faf1
|
data/README.md
ADDED
@@ -0,0 +1,214 @@
|
|
1
|
+
# Time Boots
|
2
|
+
|
3
|
+
[](https://gemnasium.com/zverok/time_boots)
|
4
|
+
[](https://codeclimate.com/github/zverok/time_boots)
|
5
|
+
[](https://travis-ci.org/zverok/time_boots)
|
6
|
+
[](https://coveralls.io/r/zverok/time_boots?branch=master)
|
7
|
+
|
8
|
+
**TimeBoots** is a small, no-dependencies library attemting to make time
|
9
|
+
steps easier. It provides you with simple, easy-to-remember API, without
|
10
|
+
any monkey-patching of core Ruby classes, so it can be used alongside
|
11
|
+
Rails or without it, for any purpose.
|
12
|
+
|
13
|
+
## Simple time math
|
14
|
+
|
15
|
+
### Floor, ceil and round:
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
TimeBoots.steps
|
19
|
+
# => [:sec, :min, :hour, :day, :week, :month, :year]
|
20
|
+
|
21
|
+
tm = Time.parse('2015-03-05 10:08')
|
22
|
+
# => 2015-03-05 10:08:00 +0200
|
23
|
+
|
24
|
+
TimeBoots.floor(:hour, tm)
|
25
|
+
# => 2015-03-05 10:00:00 +0200
|
26
|
+
|
27
|
+
TimeBoots.floor(:month, tm)
|
28
|
+
# => 2015-03-01 00:00:00 +0200
|
29
|
+
|
30
|
+
# or
|
31
|
+
TimeBoots.month.floor(tm)
|
32
|
+
# => 2015-03-01 00:00:00 +0200
|
33
|
+
|
34
|
+
TimeBoots.month.ceil(tm)
|
35
|
+
# => 2015-04-01 00:00:00 +0300
|
36
|
+
# Note the timezone change: our (Ukraine) DST was in March,
|
37
|
+
# and TimeBoots plays perfectly well with it.
|
38
|
+
|
39
|
+
TimeBoots.month.round(tm)
|
40
|
+
# => 2015-03-01 00:00:00 +0200
|
41
|
+
|
42
|
+
TimeBoots.month.round?(tm)
|
43
|
+
# => false
|
44
|
+
|
45
|
+
TimeBoots.min.round?(tm)
|
46
|
+
# => true
|
47
|
+
```
|
48
|
+
|
49
|
+
### Moving in time forwards and backwards
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
TimeBoots.month.advance(tm)
|
53
|
+
# => 2015-04-05 10:08:00 +0300
|
54
|
+
|
55
|
+
TimeBoots.month.advance(tm, 4)
|
56
|
+
# => 2015-07-05 10:08:00 +0300
|
57
|
+
|
58
|
+
TimeBoots.month.advance(tm, -4)
|
59
|
+
# => 2014-11-05 10:08:00 +0200
|
60
|
+
|
61
|
+
# or
|
62
|
+
TimeBoots.month.decrease(tm, 4)
|
63
|
+
# => 2014-11-05 10:08:00 +0200
|
64
|
+
```
|
65
|
+
|
66
|
+
Also abstracted as time **jump**, which you can store in variables and
|
67
|
+
pass to other methods:
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
jump = TimeBoots.month.jump(4)
|
71
|
+
# => #<TimeBoots::Jump(month): +4>
|
72
|
+
|
73
|
+
jump.before(tm)
|
74
|
+
# => 2014-11-05 10:08:00 +0200
|
75
|
+
|
76
|
+
jump.after(tm)
|
77
|
+
# => 2015-07-05 10:08:00 +0300
|
78
|
+
```
|
79
|
+
|
80
|
+
### Creating time ranges
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
TimeBoots.hour.range(tm, 5)
|
84
|
+
# => 2015-03-05 10:08:00 +0200...2015-03-05 15:08:00 +0200
|
85
|
+
TimeBoots.hour.range_back(tm, 5)
|
86
|
+
# => 2015-03-05 05:08:00 +0200...2015-03-05 10:08:00 +0200
|
87
|
+
```
|
88
|
+
|
89
|
+
## Measuring time periods
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
# My real birthday, in fact!
|
93
|
+
birthday = Time.parse('1983-02-14 13:30')
|
94
|
+
|
95
|
+
# How many days have I lived?
|
96
|
+
TimeBoots.day.measure(birthday, Time.now)
|
97
|
+
# => 11764
|
98
|
+
|
99
|
+
# And how many weeks (with reminder)?
|
100
|
+
TimeBoots.week.measure_rem(birthday, Time.now)
|
101
|
+
# => [1680, 2015-04-27 12:30:00 +0300]
|
102
|
+
# the thing is "birthday plus 1680 weeks == reminder"
|
103
|
+
|
104
|
+
# My full age
|
105
|
+
TimeBoots.measure(birthday, Time.now)
|
106
|
+
# => {:years=>32, :months=>2, :weeks=>2, :days=>3, :hours=>6, :minutes=>59, :seconds=>7}
|
107
|
+
|
108
|
+
# NB: you can use this output with String#format or String%:
|
109
|
+
puts "%{years}y %{months}m %{weeks}w %{days}d %{hours}h %{minutes}m %{seconds}s" % TimeBoots.measure(birthday, Time.now)
|
110
|
+
# "32y 2m 2w 3d 7h 2m 50s"
|
111
|
+
|
112
|
+
# Option: measure without weeks
|
113
|
+
TimeBoots.measure(birthday, Time.now, weeks: false)
|
114
|
+
# => {:years=>32, :months=>2, :days=>17, :hours=>7, :minutes=>5, :seconds=>11}
|
115
|
+
|
116
|
+
# My full age in days, hours, minutes
|
117
|
+
TimeBoots.measure(birthday, Time.now, max_step: :day)
|
118
|
+
# => {:days=>11764, :hours=>8, :minutes=>6, :seconds=>41}
|
119
|
+
```
|
120
|
+
|
121
|
+
## Time series generation: "laces"
|
122
|
+
|
123
|
+
I'm a real fan of funny names in gems. Here we have time **boots** for working
|
124
|
+
with time **steps**. So, something continuous will be called **lace**.
|
125
|
+
|
126
|
+
I hope, next examples are pretty self-explanatory.
|
127
|
+
|
128
|
+
```ruby
|
129
|
+
from = Time.parse('2015-03-05 10:08')
|
130
|
+
to = Time.parse('2015-03-09 11:07')
|
131
|
+
|
132
|
+
lace = TimeBoots.month.lace(from, to)
|
133
|
+
# => #<TimeBoots::Lace(2015-03-05 10:08:00 +0200 - 2015-03-09 11:07:00 +0200)>
|
134
|
+
|
135
|
+
# or
|
136
|
+
TimeBoots.lace(:month, from, to)
|
137
|
+
# => #<TimeBoots::Lace(2015-03-05 10:08:00 +0200 - 2015-03-09 11:07:00 +0200)>
|
138
|
+
|
139
|
+
lace.pull
|
140
|
+
# => [2015-03-05 10:08:00 +0200,
|
141
|
+
# 2015-03-06 10:08:00 +0200,
|
142
|
+
# 2015-03-07 10:08:00 +0200,
|
143
|
+
# 2015-03-08 10:08:00 +0200,
|
144
|
+
# 2015-03-09 10:08:00 +0200]
|
145
|
+
```
|
146
|
+
|
147
|
+
So, we have just a series of times, each day, from `from` until `to`.
|
148
|
+
Note, there is a same time of the day (10:08), as it was in `from`.
|
149
|
+
|
150
|
+
The `pull` method has an optional argument, when it is `true`, the
|
151
|
+
method returns `floor`-ed times (e.g. midnights for daily lace):
|
152
|
+
|
153
|
+
```ruby
|
154
|
+
lace.pull(true)
|
155
|
+
# => [2015-03-05 10:08:00 +0200,
|
156
|
+
# 2015-03-06 00:00:00 +0200,
|
157
|
+
# 2015-03-07 00:00:00 +0200,
|
158
|
+
# 2015-03-08 00:00:00 +0200,
|
159
|
+
# 2015-03-09 00:00:00 +0200]
|
160
|
+
```
|
161
|
+
|
162
|
+
Note the first value still at 10:08: we don't want to go before `from`.
|
163
|
+
Lace also can "expand" your period for you (`floor` the beginning and
|
164
|
+
`ceil` the end):
|
165
|
+
|
166
|
+
```ruby
|
167
|
+
lace.expand
|
168
|
+
# => #<TimeBoots::Lace(2015-03-05 00:00:00 +0200-2015-03-10 00:00:00 +0200)>
|
169
|
+
|
170
|
+
# or
|
171
|
+
lace.expand!
|
172
|
+
|
173
|
+
# or start with expanded:
|
174
|
+
TimeBoots.month.lace(from, to, expanded: true)
|
175
|
+
```
|
176
|
+
|
177
|
+
Another useful lace's functionality is generating periods.
|
178
|
+
It can be useful for filtering daily data from database, for example:
|
179
|
+
|
180
|
+
```ruby
|
181
|
+
lace.pull_ranges
|
182
|
+
# => [2015-03-05 10:08:00 +0200...2015-03-06 10:08:00 +0200,
|
183
|
+
# 2015-03-06 10:08:00 +0200...2015-03-07 10:08:00 +0200,
|
184
|
+
# 2015-03-07 10:08:00 +0200...2015-03-08 10:08:00 +0200,
|
185
|
+
# 2015-03-08 10:08:00 +0200...2015-03-09 10:08:00 +0200,
|
186
|
+
# 2015-03-09 10:08:00 +0200...2015-03-09 11:07:00 +0200]
|
187
|
+
|
188
|
+
# Now, you can do something like:
|
189
|
+
|
190
|
+
lace.pull_ranges.map{|range| dataset.where(timestamp: range).count}
|
191
|
+
# ...and have your daily stats with one line of code
|
192
|
+
|
193
|
+
```
|
194
|
+
|
195
|
+
## Got it, what else?
|
196
|
+
|
197
|
+
TimeBoots also play well when included into other classes or modules:
|
198
|
+
|
199
|
+
```ruby
|
200
|
+
class MyModel
|
201
|
+
include TimeBoots
|
202
|
+
|
203
|
+
def next_day
|
204
|
+
day.advance # Here!
|
205
|
+
end
|
206
|
+
end
|
207
|
+
```
|
208
|
+
|
209
|
+
And there are some plans for the future:
|
210
|
+
* `TimeBoots.resample`, which would do resampling (take daily data and
|
211
|
+
group it monthly, and so on) easy task;
|
212
|
+
* optional `core_ext`, providing methods like `4.months.ago` for the
|
213
|
+
(Rails-less) rest of us;
|
214
|
+
* your ideas?..
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module TimeBoots
|
3
|
+
class DayBoot < SimpleBoot
|
4
|
+
def initialize
|
5
|
+
super(:day)
|
6
|
+
end
|
7
|
+
|
8
|
+
protected
|
9
|
+
|
10
|
+
def _advance(tm, steps)
|
11
|
+
fix_dst(super(tm, steps), tm)
|
12
|
+
end
|
13
|
+
|
14
|
+
def _decrease(tm, steps)
|
15
|
+
fix_dst(super(tm, steps), tm)
|
16
|
+
end
|
17
|
+
|
18
|
+
def fix_dst(res, src)
|
19
|
+
if res.dst? && !src.dst?
|
20
|
+
hour.decrease(res)
|
21
|
+
elsif !res.dst? && src.dst?
|
22
|
+
hour.advance(res)
|
23
|
+
else
|
24
|
+
res
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module TimeBoots
|
3
|
+
class MonthBoot < Boot
|
4
|
+
def initialize
|
5
|
+
super(:month)
|
6
|
+
end
|
7
|
+
|
8
|
+
def measure(from, to)
|
9
|
+
ydiff = to.year - from.year
|
10
|
+
mdiff = to.month - from.month
|
11
|
+
|
12
|
+
to.day >= from.day ? (ydiff * 12 + mdiff) : (ydiff * 12 + mdiff - 1)
|
13
|
+
end
|
14
|
+
|
15
|
+
protected
|
16
|
+
|
17
|
+
def succ(tm)
|
18
|
+
return generate(tm, year: tm.year + 1, month: 1) if tm.month == 12
|
19
|
+
|
20
|
+
t = generate(tm, month: tm.month + 1)
|
21
|
+
fix_month(t, t.month + 1)
|
22
|
+
end
|
23
|
+
|
24
|
+
def prev(tm)
|
25
|
+
return generate(tm, year: tm.year - 1, month: 12) if tm.month == 1
|
26
|
+
|
27
|
+
t = generate(tm, month: tm.month - 1)
|
28
|
+
fix_month(t, t.month - 1)
|
29
|
+
end
|
30
|
+
|
31
|
+
def _advance(tm, steps)
|
32
|
+
steps.times.inject(tm){|t| succ(t)}
|
33
|
+
end
|
34
|
+
|
35
|
+
def _decrease(tm, steps)
|
36
|
+
steps.times.inject(tm){|t| prev(t)}
|
37
|
+
end
|
38
|
+
|
39
|
+
# fix for too far advance/insufficient decrease:
|
40
|
+
# Time.new(2013,2,31) #=> 2013-03-02 00:00:00 +0200
|
41
|
+
def fix_month(t, expected)
|
42
|
+
t.month == expected ? day.decrease(t, t.day) : t
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module TimeBoots
|
3
|
+
class SimpleBoot < Boot
|
4
|
+
def to_seconds(sz = 1)
|
5
|
+
sz * MULTIPLIERS[step_idx..-1].inject(:*)
|
6
|
+
end
|
7
|
+
|
8
|
+
def measure(from, to)
|
9
|
+
((to - from) / to_seconds).to_i
|
10
|
+
end
|
11
|
+
|
12
|
+
protected
|
13
|
+
|
14
|
+
def _advance(tm, steps)
|
15
|
+
tm + to_seconds(steps)
|
16
|
+
end
|
17
|
+
|
18
|
+
def _decrease(tm, steps)
|
19
|
+
tm - to_seconds(steps)
|
20
|
+
end
|
21
|
+
|
22
|
+
MULTIPLIERS = [12, 30, 24, 60, 60, 1]
|
23
|
+
end
|
24
|
+
|
25
|
+
class SecBoot < SimpleBoot
|
26
|
+
def initialize
|
27
|
+
super(:sec)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class MinBoot < SimpleBoot
|
32
|
+
def initialize
|
33
|
+
super(:min)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class HourBoot < SimpleBoot
|
38
|
+
def initialize
|
39
|
+
super(:hour)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module TimeBoots
|
3
|
+
class WeekBoot < SimpleBoot
|
4
|
+
def initialize
|
5
|
+
super(:week)
|
6
|
+
end
|
7
|
+
|
8
|
+
def floor(tm)
|
9
|
+
f = day.floor(tm)
|
10
|
+
extra_days = tm.wday == 0 ? 6 : tm.wday - 1
|
11
|
+
day.decrease(f, extra_days)
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_seconds(sz = 1)
|
15
|
+
day.to_seconds(sz * 7)
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
|
20
|
+
def _advance(tm, steps)
|
21
|
+
day.advance(tm, steps * 7)
|
22
|
+
end
|
23
|
+
|
24
|
+
def _decrease(tm, steps)
|
25
|
+
day.decrease(tm, steps * 7)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module TimeBoots
|
3
|
+
class YearBoot < Boot
|
4
|
+
def initialize
|
5
|
+
super(:year)
|
6
|
+
end
|
7
|
+
|
8
|
+
def measure(from, to)
|
9
|
+
if generate(from, year: to.year) < to
|
10
|
+
to.year - from.year
|
11
|
+
else
|
12
|
+
to.year - from.year - 1
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
protected
|
17
|
+
|
18
|
+
def _advance(tm, steps)
|
19
|
+
generate(tm, year: tm.year + steps)
|
20
|
+
end
|
21
|
+
|
22
|
+
def _decrease(tm, steps)
|
23
|
+
generate(tm, year: tm.year - steps)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module TimeBoots
|
3
|
+
class Boot
|
4
|
+
def initialize(step)
|
5
|
+
@step = step
|
6
|
+
end
|
7
|
+
|
8
|
+
attr_reader :step
|
9
|
+
|
10
|
+
def floor(tm)
|
11
|
+
components = [tm.year,
|
12
|
+
tm.month,
|
13
|
+
tm.day,
|
14
|
+
tm.hour,
|
15
|
+
tm.min,
|
16
|
+
tm.sec].first(step_idx + 1)
|
17
|
+
|
18
|
+
Time.new(*components)
|
19
|
+
end
|
20
|
+
|
21
|
+
def ceil(tm)
|
22
|
+
f = floor(tm)
|
23
|
+
|
24
|
+
f == tm ? f : advance(f)
|
25
|
+
end
|
26
|
+
|
27
|
+
def round(tm)
|
28
|
+
f, c = floor(tm), ceil(tm)
|
29
|
+
|
30
|
+
(tm - f).abs < (tm - c).abs ? f : c
|
31
|
+
end
|
32
|
+
|
33
|
+
def round?(tm)
|
34
|
+
floor(tm) == tm
|
35
|
+
end
|
36
|
+
|
37
|
+
def advance(tm, steps = 1)
|
38
|
+
return decrease(tm, -steps) if steps < 0
|
39
|
+
_advance(tm, steps)
|
40
|
+
end
|
41
|
+
|
42
|
+
def decrease(tm, steps = 1)
|
43
|
+
return advance(tm, -steps) if steps < 0
|
44
|
+
_decrease(tm, steps)
|
45
|
+
end
|
46
|
+
|
47
|
+
def range(tm, steps = 1)
|
48
|
+
(tm...advance(tm, steps))
|
49
|
+
end
|
50
|
+
|
51
|
+
def range_back(tm, steps = 1)
|
52
|
+
(decrease(tm, steps)...tm)
|
53
|
+
end
|
54
|
+
|
55
|
+
def measure(_from, _to)
|
56
|
+
fail NotImplementedError, '#measure should be implemented in subclasses'
|
57
|
+
end
|
58
|
+
|
59
|
+
def measure_rem(from, to)
|
60
|
+
m = measure(from, to)
|
61
|
+
[m, advance(from, m)]
|
62
|
+
end
|
63
|
+
|
64
|
+
def jump(steps)
|
65
|
+
Jump.new(step, steps)
|
66
|
+
end
|
67
|
+
|
68
|
+
def lace(from, to, options = {})
|
69
|
+
Lace.new(step, from, to, options)
|
70
|
+
end
|
71
|
+
|
72
|
+
protected
|
73
|
+
|
74
|
+
NATURAL_STEPS = [:year, :month, :day, :hour, :min, :sec]
|
75
|
+
|
76
|
+
def step_idx
|
77
|
+
NATURAL_STEPS.index(step) or
|
78
|
+
fail(NotImplementedError, "Can not be used for step #{step}")
|
79
|
+
end
|
80
|
+
|
81
|
+
def generate(tm, replacements = {})
|
82
|
+
hash_to_tm(tm_to_hash(tm).merge(replacements))
|
83
|
+
end
|
84
|
+
|
85
|
+
def tm_to_hash(tm)
|
86
|
+
Hash[*NATURAL_STEPS.flat_map{|s| [s, tm.send(s)]}]
|
87
|
+
end
|
88
|
+
|
89
|
+
def hash_to_tm(hash)
|
90
|
+
components = NATURAL_STEPS.map{|s| hash[s] || 0}
|
91
|
+
Time.new(*components)
|
92
|
+
end
|
93
|
+
|
94
|
+
include TimeBoots # now we can use something like #day inside boots
|
95
|
+
|
96
|
+
require_relative 'boot/simple'
|
97
|
+
require_relative 'boot/day'
|
98
|
+
require_relative 'boot/week'
|
99
|
+
require_relative 'boot/month'
|
100
|
+
require_relative 'boot/year'
|
101
|
+
|
102
|
+
BOOTS = {
|
103
|
+
sec: SecBoot.new, min: MinBoot.new, hour: HourBoot.new,
|
104
|
+
day: DayBoot.new, week: WeekBoot.new, month: MonthBoot.new,
|
105
|
+
year: YearBoot.new
|
106
|
+
}
|
107
|
+
|
108
|
+
class << self
|
109
|
+
def steps
|
110
|
+
BOOTS.keys
|
111
|
+
end
|
112
|
+
|
113
|
+
def get(step)
|
114
|
+
BOOTS[step] or
|
115
|
+
fail(ArgumentError, "Unsupported step: #{step}")
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module TimeBoots
|
3
|
+
class Jump
|
4
|
+
def initialize(step, amount)
|
5
|
+
@step, @amount = step, amount
|
6
|
+
@boot = Boot.get(step)
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :step, :amount
|
10
|
+
|
11
|
+
def before(tm = Time.now)
|
12
|
+
@boot.decrease(tm, amount)
|
13
|
+
end
|
14
|
+
|
15
|
+
def after(tm = Time.now)
|
16
|
+
@boot.advance(tm, amount)
|
17
|
+
end
|
18
|
+
|
19
|
+
alias_method :ago, :before
|
20
|
+
alias_method :from, :after
|
21
|
+
|
22
|
+
def ==(other)
|
23
|
+
step == other.step && amount == other.amount
|
24
|
+
end
|
25
|
+
|
26
|
+
def inspect
|
27
|
+
'#<%s(%s): %+i>' % [self.class, step, amount]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module TimeBoots
|
3
|
+
class Lace
|
4
|
+
def initialize(step, from, to, options = {})
|
5
|
+
@boot = Boot.get(step)
|
6
|
+
@from, @to = from, to
|
7
|
+
@options = options.dup
|
8
|
+
|
9
|
+
expand! if options[:expand]
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :from, :to
|
13
|
+
|
14
|
+
def expand!
|
15
|
+
@from = boot.floor(from)
|
16
|
+
@to = boot.ceil(to)
|
17
|
+
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
def expand
|
22
|
+
dup.tap(&:expand!)
|
23
|
+
end
|
24
|
+
|
25
|
+
def pull(beginnings = false)
|
26
|
+
seq = []
|
27
|
+
|
28
|
+
iter = from
|
29
|
+
while iter < to
|
30
|
+
seq << iter
|
31
|
+
|
32
|
+
iter = cond_floor(boot.advance(iter), beginnings)
|
33
|
+
end
|
34
|
+
|
35
|
+
seq
|
36
|
+
end
|
37
|
+
|
38
|
+
def pull_pairs(beginnings = false)
|
39
|
+
seq = pull(beginnings)
|
40
|
+
seq.zip(seq[1..-1] + [to])
|
41
|
+
end
|
42
|
+
|
43
|
+
def pull_ranges(beginnings = false)
|
44
|
+
pull_pairs(beginnings).map{|b, e| (b...e)}
|
45
|
+
end
|
46
|
+
|
47
|
+
def inspect
|
48
|
+
"#<#{self.class}(#{from} - #{to})>"
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def cond_floor(tm, should_floor)
|
54
|
+
should_floor ? boot.floor(tm) : tm
|
55
|
+
end
|
56
|
+
|
57
|
+
attr_reader :boot
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module TimeBoots
|
3
|
+
module Measure
|
4
|
+
PLURALS = {
|
5
|
+
year: :years,
|
6
|
+
month: :months,
|
7
|
+
week: :weeks,
|
8
|
+
day: :days,
|
9
|
+
hour: :hours,
|
10
|
+
min: :minutes,
|
11
|
+
sec: :seconds
|
12
|
+
}
|
13
|
+
|
14
|
+
def self.measure(from, to, options = {})
|
15
|
+
select_steps(options).reverse.inject({}) do |res, step|
|
16
|
+
span, from = Boot.get(step).measure_rem(from, to)
|
17
|
+
res.merge(PLURALS[step] => span)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.select_steps(options)
|
22
|
+
steps = Boot.steps
|
23
|
+
steps.delete(:week) if options[:weeks] == false
|
24
|
+
|
25
|
+
if (idx = steps.index(options[:max_step]))
|
26
|
+
steps = steps.first(idx + 1)
|
27
|
+
end
|
28
|
+
|
29
|
+
steps
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/time_boots.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'time'
|
3
|
+
|
4
|
+
module TimeBoots
|
5
|
+
# rubocop:disable Style/ModuleFunction
|
6
|
+
extend self
|
7
|
+
# rubocop:enable Style/ModuleFunction
|
8
|
+
|
9
|
+
def steps
|
10
|
+
Boot.steps
|
11
|
+
end
|
12
|
+
|
13
|
+
# NB: no fancy meta-programming here: we want YARD to be happy
|
14
|
+
|
15
|
+
# Boot shortcuts
|
16
|
+
|
17
|
+
def sec
|
18
|
+
Boot.get(:sec)
|
19
|
+
end
|
20
|
+
|
21
|
+
def min
|
22
|
+
Boot.get(:min)
|
23
|
+
end
|
24
|
+
|
25
|
+
def hour
|
26
|
+
Boot.get(:hour)
|
27
|
+
end
|
28
|
+
|
29
|
+
def day
|
30
|
+
Boot.get(:day)
|
31
|
+
end
|
32
|
+
|
33
|
+
def week
|
34
|
+
Boot.get(:week)
|
35
|
+
end
|
36
|
+
|
37
|
+
def month
|
38
|
+
Boot.get(:month)
|
39
|
+
end
|
40
|
+
|
41
|
+
def year
|
42
|
+
Boot.get(:year)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Boot-less method shortcuts
|
46
|
+
|
47
|
+
def floor(step, tm)
|
48
|
+
Boot.get(step).floor(tm)
|
49
|
+
end
|
50
|
+
|
51
|
+
def ceil(step, tm)
|
52
|
+
Boot.get(step).ceil(tm)
|
53
|
+
end
|
54
|
+
|
55
|
+
def round(step, tm)
|
56
|
+
Boot.get(step).round(tm)
|
57
|
+
end
|
58
|
+
|
59
|
+
def round?(step, tm)
|
60
|
+
Boot.get(step).round?(tm)
|
61
|
+
end
|
62
|
+
|
63
|
+
def advance(step, tm, steps = 1)
|
64
|
+
Boot.get(step).advance(step, tm, steps)
|
65
|
+
end
|
66
|
+
|
67
|
+
def decrease(step, tm, steps = 1)
|
68
|
+
Boot.get(step).decrease(step, tm, steps)
|
69
|
+
end
|
70
|
+
|
71
|
+
def range(step, tm, steps = 1)
|
72
|
+
Boot.get(step).range(tm, steps)
|
73
|
+
end
|
74
|
+
|
75
|
+
def range_back(step, tm, steps = 1)
|
76
|
+
Boot.get(step).range_back(tm, steps)
|
77
|
+
end
|
78
|
+
|
79
|
+
def jump(step, steps = 1)
|
80
|
+
Boot.get(step).jump(steps)
|
81
|
+
end
|
82
|
+
|
83
|
+
def measure(from, to, options = {})
|
84
|
+
Measure.measure(from, to, options)
|
85
|
+
end
|
86
|
+
|
87
|
+
def lace(step, from, to, options = {})
|
88
|
+
Boot.get(step).lace(from, to, options)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
require_relative './time_boots/boot'
|
93
|
+
require_relative './time_boots/lace'
|
94
|
+
require_relative './time_boots/measure'
|
95
|
+
require_relative './time_boots/jump'
|
data/time_boots.gemspec
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require './lib/time_boots/version'
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = 'time_boots'
|
5
|
+
s.version = TimeBoots::VERSION
|
6
|
+
s.authors = ['Victor Shepelev']
|
7
|
+
s.email = 'zverok.offline@gmail.com'
|
8
|
+
s.homepage = 'https://github.com/zverok/time_boots'
|
9
|
+
|
10
|
+
s.summary = 'Easy time steps math'
|
11
|
+
s.description = <<-EOF
|
12
|
+
TimeBoots is small, no-dependencies library attemting to make time
|
13
|
+
steps easier. It provides you with simple, easy remembered API, without
|
14
|
+
any monkey patching of core Ruby classes, so it can be used alongside
|
15
|
+
Rails or without it, for any purpose.
|
16
|
+
EOF
|
17
|
+
s.licenses = ['MIT']
|
18
|
+
|
19
|
+
s.files = `git ls-files`.split($RS).reject do |file|
|
20
|
+
file =~ /^(?:
|
21
|
+
spec\/.*
|
22
|
+
|Gemfile
|
23
|
+
|Rakefile
|
24
|
+
|\.rspec
|
25
|
+
|\.gitignore
|
26
|
+
|\.rubocop.yml
|
27
|
+
|\.travis.yml
|
28
|
+
)$/x
|
29
|
+
end
|
30
|
+
s.require_paths = ["lib"]
|
31
|
+
|
32
|
+
s.add_development_dependency 'rubocop', '~> 0.30'
|
33
|
+
s.add_development_dependency 'rspec', '~> 3'
|
34
|
+
s.add_development_dependency 'rspec-its', '~> 1'
|
35
|
+
s.add_development_dependency 'simplecov', '~> 0.9'
|
36
|
+
end
|
metadata
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: time_boots
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Victor Shepelev
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-05-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rubocop
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.30'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.30'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec-its
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: simplecov
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.9'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.9'
|
69
|
+
description: |2
|
70
|
+
TimeBoots is small, no-dependencies library attemting to make time
|
71
|
+
steps easier. It provides you with simple, easy remembered API, without
|
72
|
+
any monkey patching of core Ruby classes, so it can be used alongside
|
73
|
+
Rails or without it, for any purpose.
|
74
|
+
email: zverok.offline@gmail.com
|
75
|
+
executables: []
|
76
|
+
extensions: []
|
77
|
+
extra_rdoc_files: []
|
78
|
+
files:
|
79
|
+
- README.md
|
80
|
+
- lib/time_boots.rb
|
81
|
+
- lib/time_boots/boot.rb
|
82
|
+
- lib/time_boots/boot/day.rb
|
83
|
+
- lib/time_boots/boot/month.rb
|
84
|
+
- lib/time_boots/boot/simple.rb
|
85
|
+
- lib/time_boots/boot/week.rb
|
86
|
+
- lib/time_boots/boot/year.rb
|
87
|
+
- lib/time_boots/jump.rb
|
88
|
+
- lib/time_boots/lace.rb
|
89
|
+
- lib/time_boots/measure.rb
|
90
|
+
- lib/time_boots/version.rb
|
91
|
+
- time_boots.gemspec
|
92
|
+
homepage: https://github.com/zverok/time_boots
|
93
|
+
licenses:
|
94
|
+
- MIT
|
95
|
+
metadata: {}
|
96
|
+
post_install_message:
|
97
|
+
rdoc_options: []
|
98
|
+
require_paths:
|
99
|
+
- lib
|
100
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - ">="
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
requirements: []
|
111
|
+
rubyforge_project:
|
112
|
+
rubygems_version: 2.4.6
|
113
|
+
signing_key:
|
114
|
+
specification_version: 4
|
115
|
+
summary: Easy time steps math
|
116
|
+
test_files: []
|