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.
@@ -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
@@ -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.
@@ -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)
@@ -0,0 +1 @@
1
+ require "as/duration"
@@ -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,5 @@
1
+ require "as/duration/core_ext/numeric"
2
+ require "as/duration/core_ext/integer"
3
+
4
+ require "as/duration/core_ext/time"
5
+ require "as/duration/core_ext/date"
@@ -0,0 +1,5 @@
1
+ require "as/duration/operations"
2
+
3
+ class Date
4
+ prepend AS::Duration::Operations::DateAndTime
5
+ end
@@ -0,0 +1,11 @@
1
+ class Integer
2
+ def months
3
+ AS::Duration.new(months: self)
4
+ end
5
+ alias month months
6
+
7
+ def years
8
+ AS::Duration.new(years: self)
9
+ end
10
+ alias year years
11
+ 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,5 @@
1
+ require "as/duration/operations"
2
+
3
+ class Time
4
+ prepend AS::Duration::Operations::DateAndTime
5
+ 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: