as-duration 0.0.2
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/LICENSE.txt +21 -0
- data/README.md +145 -0
- data/lib/as-duration.rb +1 -0
- data/lib/as/duration.rb +133 -0
- data/lib/as/duration/core_ext.rb +5 -0
- data/lib/as/duration/core_ext/date.rb +5 -0
- data/lib/as/duration/core_ext/integer.rb +11 -0
- data/lib/as/duration/core_ext/numeric.rb +31 -0
- data/lib/as/duration/core_ext/time.rb +5 -0
- data/lib/as/duration/operations.rb +23 -0
- metadata +83 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: efd76209348cb343f2ef4b2d5039cb1dd033640c
|
4
|
+
data.tar.gz: 3110aaa5b289824b2d18291a2acf675c1b8cffa0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: bc6cf946e1a327453b91a30b3282a0478085039b909a538bb0bd9aecfcda44e9aa2a5e8cf06121668fc3867f3a9bd418c40a021d22d65938acfd8899ef764f82
|
7
|
+
data.tar.gz: 6f38f2966c070df139baca861f030c62d1456331a5cd5538190fbf63e1fe43c91c0bc421f261a490be168ec81f40e41ec1803ede3c9dcd6e66ec527746ad68ea
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Janko Marohnić
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
# AS::Duration
|
2
|
+
|
3
|
+
This gem is an extraction of `ActiveSupport::Duration` from Rails, along with the
|
4
|
+
related core extensions.
|
5
|
+
|
6
|
+
Ruby 2.0 or greater is required.
|
7
|
+
|
8
|
+
## Why not simply use ActiveSupport?
|
9
|
+
|
10
|
+
If you're in a Rails project, then you should use `ActiveSupport::Duration`.
|
11
|
+
Otherwise there are several reason why you might prefer `as-duration`:
|
12
|
+
|
13
|
+
* You simply don't want to have ActiveSupport as a dependency
|
14
|
+
* You want to control what you require. You may think that requiring
|
15
|
+
`active_support/core_ext/integer/time` will only require what you want, but
|
16
|
+
in fact it will require a total of **5000 LOC** (a lot of those are additional
|
17
|
+
core extensions which you may not have wanted). `as-duration` has only
|
18
|
+
under **200 LOC**, and only gives you what you've asked for.
|
19
|
+
|
20
|
+
## Is it well tested?
|
21
|
+
|
22
|
+
It sure is! I copied all the related tests from Rails, and modified them
|
23
|
+
so that they work standalone. So, `as-duration` passes all of Rails' tests.
|
24
|
+
|
25
|
+
## Installation
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
gem 'as-duration'
|
29
|
+
```
|
30
|
+
|
31
|
+
## Features
|
32
|
+
|
33
|
+
*DISCLAIMER: In most cases `as-duration` should work exactly like
|
34
|
+
`ActiveSupport::Duration`. However, there are a few modifications made, mostly
|
35
|
+
removing some of the magic, see [Modifications](#modifications-to-activesupportduration).*
|
36
|
+
|
37
|
+
### Numeric methods
|
38
|
+
|
39
|
+
The following methods are added on `Numeric` (`Float` and `Integer`):
|
40
|
+
|
41
|
+
```rb
|
42
|
+
# plural versions
|
43
|
+
2.seconds
|
44
|
+
3.minutes
|
45
|
+
4.hours
|
46
|
+
5.days
|
47
|
+
6.weeks
|
48
|
+
7.fortnights
|
49
|
+
8.months
|
50
|
+
9.years
|
51
|
+
|
52
|
+
# singular versions
|
53
|
+
1.second
|
54
|
+
1.minute
|
55
|
+
1.hour
|
56
|
+
1.day
|
57
|
+
1.week
|
58
|
+
1.fortnight
|
59
|
+
1.month
|
60
|
+
1.year
|
61
|
+
```
|
62
|
+
|
63
|
+
The only exception is `#months` and `#years` which are only added to `Integer`
|
64
|
+
(to maintain precision in calculations).
|
65
|
+
|
66
|
+
### Duration/Time arithmetics
|
67
|
+
|
68
|
+
You can add and subtract durations from `Time` or `Date` objects.
|
69
|
+
|
70
|
+
```rb
|
71
|
+
Time.now + 2.hours
|
72
|
+
Date.today + 1.year
|
73
|
+
```
|
74
|
+
|
75
|
+
When you add seconds/minutes/hours to a Date, the Date is automatically
|
76
|
+
converted to a `Time` object.
|
77
|
+
|
78
|
+
```rb
|
79
|
+
(Date.today + 1.minute).class #=> Time
|
80
|
+
```
|
81
|
+
|
82
|
+
As syntax sugar, you can also call time methods on the duration object:
|
83
|
+
|
84
|
+
```rb
|
85
|
+
# forward in time
|
86
|
+
1.year.from_now
|
87
|
+
2.months.since(Date.new(2015,4,27))
|
88
|
+
2.months.after(Date.new(2015,4,27))
|
89
|
+
2.months.from(Date.new(2015,4,27))
|
90
|
+
|
91
|
+
# back in time
|
92
|
+
2.hours.ago
|
93
|
+
20.minutes.until(Time.now)
|
94
|
+
20.minutes.before(Time.now)
|
95
|
+
20.minutes.to(Time.now)
|
96
|
+
```
|
97
|
+
|
98
|
+
### Duration/Duration arithmetics
|
99
|
+
|
100
|
+
You can add and subtract durations:
|
101
|
+
|
102
|
+
```rb
|
103
|
+
1.week + 1.day
|
104
|
+
2.minutes - 1.second
|
105
|
+
```
|
106
|
+
|
107
|
+
Unlike `ActiveSupport::Duration`, you can't add durations to integers and vice
|
108
|
+
versa. You either have to convert the integer to a duration, or
|
109
|
+
the duration to an integer with `AS::Duration#to_i`. This is to help you
|
110
|
+
not to mix different time units.
|
111
|
+
|
112
|
+
```rb
|
113
|
+
# Bad
|
114
|
+
10 + 1.minute # TypeError
|
115
|
+
1.minute + 10 # TypeError
|
116
|
+
|
117
|
+
# Good
|
118
|
+
10.seconds + 1.minute # AS::Duration
|
119
|
+
1.minute.to_i + 10 # Integer
|
120
|
+
```
|
121
|
+
|
122
|
+
## Modifications to `ActiveSupport::Duration`
|
123
|
+
|
124
|
+
The behaviour of `ActiveSupport::Duration` has been slightly modified, mostly
|
125
|
+
to remove some magic:
|
126
|
+
|
127
|
+
* Added `#from`, `#after`, `#before` and `#to` to `AS::Duration`
|
128
|
+
* `#from_now` and `#ago` cannot take any arguments, they always use the current
|
129
|
+
time (passing an argument doesn't read well, better to use `#from` and
|
130
|
+
`#until`)
|
131
|
+
* Removed support for `DateTime`
|
132
|
+
- `DateTime` was first introduced in Ruby so that you can represent time
|
133
|
+
that the `Time` class at the moment wasn't able to. However, the `Time`
|
134
|
+
class improved over time and removed those limitations, so there is no more
|
135
|
+
need to use `DateTime`
|
136
|
+
* Year lasts 365 days instead of 365.25
|
137
|
+
* `AS::Duration` doesn't act like an Integer
|
138
|
+
- to compare it with an integer you have to either convert the integer to
|
139
|
+
a duration or convert the duration to an integer (with `#to_i`)
|
140
|
+
- you can only add and subtract two duration objects
|
141
|
+
* Removed hash equality
|
142
|
+
|
143
|
+
## License
|
144
|
+
|
145
|
+
[MIT](LICENSE.txt)
|
data/lib/as-duration.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "as/duration"
|
data/lib/as/duration.rb
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
require "as/duration/core_ext"
|
2
|
+
require "time"
|
3
|
+
|
4
|
+
module AS
|
5
|
+
class Duration
|
6
|
+
include Comparable
|
7
|
+
|
8
|
+
attr_reader :parts
|
9
|
+
|
10
|
+
def initialize(parts)
|
11
|
+
parts = parts.dup
|
12
|
+
|
13
|
+
# Remove partial weeks and days for accurate date behaviour
|
14
|
+
if Float === parts[:weeks]
|
15
|
+
parts[:weeks], partial_weeks = parts[:weeks].divmod(1)
|
16
|
+
parts[:days] = parts.fetch(:days, 0) + 7 * partial_weeks
|
17
|
+
end
|
18
|
+
if Float === parts[:days]
|
19
|
+
parts[:days], partial_days = parts[:days].divmod(1)
|
20
|
+
parts[:hours] = parts.fetch(:hours, 0) + 24 * partial_days
|
21
|
+
end
|
22
|
+
|
23
|
+
@parts = parts.freeze
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_i
|
27
|
+
@parts.inject(0) do |sum, (type, value)|
|
28
|
+
case type
|
29
|
+
when :seconds then sum + value
|
30
|
+
when :minutes then sum + value * 60
|
31
|
+
when :hours then sum + value * 60 * 60
|
32
|
+
when :days then sum + value * 60 * 60 * 24
|
33
|
+
when :weeks then sum + value * 60 * 60 * 24 * 7
|
34
|
+
when :fortnights then sum + value * 60 * 60 * 24 * 14
|
35
|
+
when :months then sum + value * 60 * 60 * 24 * 30
|
36
|
+
when :years then sum + value * 60 * 60 * 24 * 365
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def <=>(other)
|
42
|
+
return nil if not Duration === other
|
43
|
+
self.to_i <=> other.to_i
|
44
|
+
end
|
45
|
+
|
46
|
+
def +(other)
|
47
|
+
raise TypeError, "can only add Duration objects" if not Duration === other
|
48
|
+
added_parts = parts.merge(other.parts) { |key, old, new| old + new }
|
49
|
+
Duration.new(added_parts)
|
50
|
+
end
|
51
|
+
|
52
|
+
def -(other)
|
53
|
+
raise TypeError, "can only subtract Duration objects" if not Duration === other
|
54
|
+
self + (-other)
|
55
|
+
end
|
56
|
+
|
57
|
+
def -@
|
58
|
+
negated_parts = parts.inject({}) { |h, (k, v)| h.update(k => -v) }
|
59
|
+
Duration.new(negated_parts)
|
60
|
+
end
|
61
|
+
|
62
|
+
def from(time)
|
63
|
+
advance(time)
|
64
|
+
end
|
65
|
+
alias since from
|
66
|
+
alias after from
|
67
|
+
|
68
|
+
def from_now
|
69
|
+
from(Time.now)
|
70
|
+
end
|
71
|
+
|
72
|
+
def until(time)
|
73
|
+
(-self).advance(time)
|
74
|
+
end
|
75
|
+
alias to until
|
76
|
+
alias before until
|
77
|
+
|
78
|
+
def ago
|
79
|
+
self.until(Time.now)
|
80
|
+
end
|
81
|
+
|
82
|
+
protected
|
83
|
+
|
84
|
+
def advance(time)
|
85
|
+
case time
|
86
|
+
when Time then advance_time(time)
|
87
|
+
when Date then advance_date(time)
|
88
|
+
else
|
89
|
+
raise ArgumentError, "expected Time or Date, got #{time.inspect}"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def advance_time(time)
|
94
|
+
date = advance_date_part(time.to_date)
|
95
|
+
|
96
|
+
time_advanced_by_date_args =
|
97
|
+
if time.utc?
|
98
|
+
Time.utc(date.year, date.month, date.day, time.hour, time.min, time.sec)
|
99
|
+
elsif time.zone
|
100
|
+
Time.local(date.year, date.month, date.day, time.hour, time.min, time.sec)
|
101
|
+
else
|
102
|
+
Time.new(date.year, date.month, date.day, time.hour, time.min, time.sec, time.utc_offset)
|
103
|
+
end
|
104
|
+
|
105
|
+
time_advanced_by_date_args + seconds_to_advance
|
106
|
+
end
|
107
|
+
|
108
|
+
def advance_date(date)
|
109
|
+
date = advance_date_part(date)
|
110
|
+
|
111
|
+
if seconds_to_advance == 0
|
112
|
+
date
|
113
|
+
else
|
114
|
+
date.to_time + seconds_to_advance
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def advance_date_part(date)
|
119
|
+
date = date >> parts.fetch(:years, 0) * 12
|
120
|
+
date = date >> parts.fetch(:months, 0)
|
121
|
+
date = date + parts.fetch(:weeks, 0) * 7
|
122
|
+
date = date + parts.fetch(:days, 0)
|
123
|
+
date = date.gregorian if date.julian?
|
124
|
+
date
|
125
|
+
end
|
126
|
+
|
127
|
+
def seconds_to_advance
|
128
|
+
parts.fetch(:seconds, 0) +
|
129
|
+
parts.fetch(:minutes, 0) * 60 +
|
130
|
+
parts.fetch(:hours, 0) * 3600
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class Numeric
|
2
|
+
def seconds
|
3
|
+
AS::Duration.new(seconds: self)
|
4
|
+
end
|
5
|
+
alias second seconds
|
6
|
+
|
7
|
+
def minutes
|
8
|
+
AS::Duration.new(minutes: self)
|
9
|
+
end
|
10
|
+
alias minute minutes
|
11
|
+
|
12
|
+
def hours
|
13
|
+
AS::Duration.new(hours: self)
|
14
|
+
end
|
15
|
+
alias hour hours
|
16
|
+
|
17
|
+
def days
|
18
|
+
AS::Duration.new(days: self)
|
19
|
+
end
|
20
|
+
alias day days
|
21
|
+
|
22
|
+
def weeks
|
23
|
+
AS::Duration.new(weeks: self)
|
24
|
+
end
|
25
|
+
alias week weeks
|
26
|
+
|
27
|
+
def fortnights
|
28
|
+
AS::Duration.new(weeks: self * 2)
|
29
|
+
end
|
30
|
+
alias fortnight fortnights
|
31
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module AS
|
2
|
+
class Duration
|
3
|
+
module Operations
|
4
|
+
module DateAndTime
|
5
|
+
def +(other)
|
6
|
+
if Duration === other
|
7
|
+
other.since(self)
|
8
|
+
else
|
9
|
+
super
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def -(other)
|
14
|
+
if Duration === other
|
15
|
+
other.until(self)
|
16
|
+
else
|
17
|
+
super
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: as-duration
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Janko Marohnić
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-04-27 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: minitest
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 5.6.0
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 5.6.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description: Extraction of ActiveSupport::Duration and the related core extensions.
|
42
|
+
email:
|
43
|
+
- janko.marohnic@gmail.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- LICENSE.txt
|
49
|
+
- README.md
|
50
|
+
- lib/as-duration.rb
|
51
|
+
- lib/as/duration.rb
|
52
|
+
- lib/as/duration/core_ext.rb
|
53
|
+
- lib/as/duration/core_ext/date.rb
|
54
|
+
- lib/as/duration/core_ext/integer.rb
|
55
|
+
- lib/as/duration/core_ext/numeric.rb
|
56
|
+
- lib/as/duration/core_ext/time.rb
|
57
|
+
- lib/as/duration/operations.rb
|
58
|
+
homepage: https://github.com/janko-m/as-duration
|
59
|
+
licenses:
|
60
|
+
- MIT
|
61
|
+
metadata: {}
|
62
|
+
post_install_message:
|
63
|
+
rdoc_options: []
|
64
|
+
require_paths:
|
65
|
+
- lib
|
66
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: 2.0.0
|
71
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
requirements: []
|
77
|
+
rubyforge_project:
|
78
|
+
rubygems_version: 2.4.5
|
79
|
+
signing_key:
|
80
|
+
specification_version: 4
|
81
|
+
summary: Extraction of ActiveSupport::Duration and the related core extensions.
|
82
|
+
test_files: []
|
83
|
+
has_rdoc:
|