time_boots 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Dependency Status](https://gemnasium.com/zverok/time_boots.svg)](https://gemnasium.com/zverok/time_boots)
|
4
|
+
[![Code Climate](https://codeclimate.com/github/zverok/time_boots/badges/gpa.svg)](https://codeclimate.com/github/zverok/time_boots)
|
5
|
+
[![Build Status](https://travis-ci.org/zverok/time_boots.svg?branch=master)](https://travis-ci.org/zverok/time_boots)
|
6
|
+
[![Coverage Status](https://coveralls.io/repos/zverok/time_boots/badge.svg?branch=master)](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: []
|