pravigo-duration 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.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in pravigo-duration.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Rich Hall
2
+
3
+ MIT License
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,53 @@
1
+ # Pravigo::Duration
2
+
3
+ extends ActiveSupport::Duration class to add iso8601 support
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'pravigo-duration'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install pravigo-duration
18
+
19
+ ## Usage
20
+
21
+ ###Example 1: subtract 2 time objects to get a duration
22
+
23
+ irb
24
+ irb(main):001:0> require 'pravigo/duration'
25
+ => true
26
+ irb(main):002:0> t2 = Time.parse("2015-07-09T09:56:33Z")
27
+ => 2015-07-09 09:56:33 UTC
28
+ irb(main):003:0> t = Time.parse("2015-07-09T09:37:33Z")
29
+ => 2015-07-09 09:37:33 UTC
30
+ irb(main):004:0> d = Duration.new(t2-t)
31
+ => 19 minutes
32
+
33
+ ### Running RSPEC tests
34
+
35
+ From the root directory:
36
+
37
+ rspec ./spec/duration_spec.rb -f documentation
38
+
39
+ or
40
+
41
+ rake test
42
+
43
+ or
44
+
45
+ rake spec
46
+
47
+ ## Contributing
48
+
49
+ 1. Fork it ( http://github.com/richhall/pravigo-duration/fork )
50
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
51
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
52
+ 4. Push to the branch (`git push origin my-new-feature`)
53
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new
5
+
6
+ task :default => :spec
7
+ task :test => :spec
@@ -0,0 +1,235 @@
1
+ require "pravigo/duration/version"
2
+ require 'active_support/time'
3
+
4
+ class BasicObject
5
+ def __realclass__; (class << self; self end).superclass end
6
+ end
7
+
8
+ class Object; alias __realclass__ class end
9
+
10
+ class Pravigo
11
+ #module Duration
12
+
13
+
14
+ #class Duration < ActiveSupport::Duration
15
+ class ActiveSupport::Duration
16
+
17
+ @@MONTHS_IN_A_YEAR = 12
18
+ @@SECONDS_IN_A_WEEK = 604800
19
+ @@SECONDS_IN_A_DAY = 86400
20
+ @@SECONDS_IN_AN_HOUR = 3600
21
+ @@SECONDS_IN_A_MINUTE = 60
22
+ #Approximate number of seconds in a month and year
23
+ @@SECONDS_IN_A_YEAR = @@SECONDS_IN_A_DAY * 365
24
+ @@SECONDS_IN_A_MONTH = @@SECONDS_IN_A_DAY * 30
25
+
26
+ def initialize(*args)
27
+ case args.size
28
+ when 1
29
+ if args[0].is_a? ::ActiveSupport::Duration
30
+ @value = args[0].value
31
+ @parts = args[0].parts
32
+ else
33
+ if args[0].is_a? ::String
34
+ iso8601Duration = args[0]
35
+ iso8601Duration = iso8601Duration.gsub(',','.')
36
+
37
+ if /\AP(?:(-?\d+)Y|Y)?
38
+ (?:(-?\d+)M|M)?
39
+ (?:(-?\d+)D|D)?
40
+ (?:T(?:(-?\d+)H|H)?
41
+ (?:(-?\d+)M|M)?
42
+ (?:(-?\d+(?:(\.|\,)\d{1,4})?)S|S)?
43
+ )?
44
+ \Z/ix =~iso8601Duration
45
+
46
+ @years = $1.to_i
47
+ @months = $2.to_i
48
+ @days = $3.to_i
49
+ @hours = $4.to_i
50
+ @minutes = $5.to_i
51
+ @seconds = $6.to_f
52
+
53
+ else
54
+ if /\A(?:(\d+):?)?
55
+ (?:(\d+):?)?
56
+ (\d+)?
57
+ \Z/ix =~iso8601Duration
58
+
59
+ @years = 0
60
+ @months = 0
61
+ @days = 0
62
+ @hours = $1.to_i
63
+ @minutes = $2.to_i
64
+ @seconds = $3.to_f
65
+ else
66
+ return ::NilClass
67
+ @years = 0
68
+ @months = 0
69
+ @days = 0
70
+ @hours = 0
71
+ @minutes = 0
72
+ @seconds = 0
73
+ end
74
+ end
75
+
76
+ @value = (@years*@@SECONDS_IN_A_YEAR)+
77
+ (@months*@@SECONDS_IN_A_MONTH)+
78
+ (@days*@@SECONDS_IN_A_DAY)+
79
+ (@hours*@@SECONDS_IN_AN_HOUR)+
80
+ (@minutes*@@SECONDS_IN_A_MINUTE)+
81
+ (@seconds)
82
+ @parts = []
83
+
84
+ @parts << [:years, @years] if @years!=0
85
+ @parts << [:months, @months] if @months!=0
86
+ @parts << [:days, @days] if @days!=0
87
+ @parts << [:hours, @hours] if @hours !=0
88
+ @parts << [:minutes, @minutes] if @minutes !=0
89
+ @parts << [:seconds,@seconds] if @seconds!=0
90
+
91
+ #else raise an error
92
+ else if args[0].is_a? ::Numeric
93
+ @value = args[0]
94
+ days_c = @value.divmod(@@SECONDS_IN_A_DAY)
95
+ hours_c = days_c[1].divmod(@@SECONDS_IN_AN_HOUR)
96
+ minutes_c = hours_c[1].divmod(@@SECONDS_IN_A_MINUTE)
97
+ seconds_c = minutes_c[1]
98
+ @parts = []
99
+ @parts << [:days, days_c[0]] if days_c[0]>0
100
+ @parts << [:hours, hours_c[0]] if hours_c[0] >0
101
+ @parts << [:minutes, minutes_c[0]] if minutes_c[0] >0
102
+ @parts << [:seconds,seconds_c] if seconds_c >0
103
+
104
+ end
105
+ end
106
+ end
107
+ when 2
108
+ @value = args[0]
109
+ @parts = args[1]
110
+ end
111
+ end
112
+
113
+ def iso8601
114
+ grouped_parts = parts.group_by(&:first).map { |k, v| [k, v.map(&:last).inject(:+)] }
115
+ hash_of_parts = ::Hash[grouped_parts]
116
+ hash_of_parts.default = 0
117
+
118
+ total_months = (hash_of_parts[:years]*@@MONTHS_IN_A_YEAR)+(hash_of_parts[:months])
119
+ totalSeconds = (hash_of_parts[:days]*@@SECONDS_IN_A_DAY)+(hash_of_parts[:hours]*@@SECONDS_IN_AN_HOUR)+(hash_of_parts[:minutes]*@@SECONDS_IN_A_MINUTE)+(hash_of_parts[:seconds])
120
+ if (total_months < 0)
121
+ negative_months = "-"
122
+ total_months = 0-total_months
123
+ else
124
+ negative_months = ""
125
+ end
126
+ if (totalSeconds < 0)
127
+ negative_seconds = "-"
128
+ totalSeconds = 0-totalSeconds
129
+ else
130
+ negative_seconds=""
131
+ end
132
+
133
+ years_c = total_months.divmod(@@MONTHS_IN_A_YEAR)
134
+ days_c = totalSeconds.divmod(@@SECONDS_IN_A_DAY)
135
+ hours_c = days_c[1].divmod(@@SECONDS_IN_AN_HOUR)
136
+ minutes_c = hours_c[1].divmod(@@SECONDS_IN_A_MINUTE)
137
+ seconds_c = minutes_c[1]
138
+
139
+ result = "P"
140
+ result << (years_c[0] != 0 ? "#{negative_months}#{years_c[0]}Y" : "")
141
+ result << (years_c[1] != 0 ? "#{negative_months}#{years_c[1]}M" : "")
142
+ result << (days_c[0] != 0 ? "#{negative_months}#{days_c[0]}D" : "")
143
+
144
+ result << (days_c[1] != 0 ? "T" : "")
145
+ result << (hours_c[0] != 0 ? "#{negative_seconds}#{hours_c[0]}H" : "")
146
+ result << (minutes_c[0] != 0 ? "#{negative_seconds}#{minutes_c[0]}M" : "")
147
+ # result << (seconds_c > 0 ? "#{seconds_c}S" : "")
148
+ result << (seconds_c != 0 ? ((seconds_c % 1 ==0) ? "#{negative_seconds}#{seconds_c.to_i}S" : "#{negative_seconds}#{seconds_c.round(4)}S") : "")
149
+
150
+ end
151
+
152
+ ##need to DRY this out
153
+ def as_24h_clock
154
+ grouped_parts = parts.group_by(&:first).map { |k, v| [k, v.map(&:last).inject(:+)] }
155
+ hash_of_parts = ::Hash[grouped_parts]
156
+ hash_of_parts.default = 0
157
+
158
+ total_months = (hash_of_parts[:years]*@@MONTHS_IN_A_YEAR)+(hash_of_parts[:months])
159
+ totalSeconds = (hash_of_parts[:days]*@@SECONDS_IN_A_DAY)+(hash_of_parts[:hours]*@@SECONDS_IN_AN_HOUR)+(hash_of_parts[:minutes]*@@SECONDS_IN_A_MINUTE)+(hash_of_parts[:seconds])
160
+ if (total_months < 0)
161
+ negative_months = "-"
162
+ total_months = 0-total_months
163
+ else
164
+ negative_months = ""
165
+ end
166
+ if (totalSeconds < 0)
167
+ negative_seconds = "-"
168
+ totalSeconds = 0-totalSeconds
169
+ else
170
+ negative_seconds=""
171
+ end
172
+
173
+ years_c = total_months.divmod(@@MONTHS_IN_A_YEAR)
174
+ days_c = totalSeconds.divmod(@@SECONDS_IN_A_DAY)
175
+ hours_c = days_c[1].divmod(@@SECONDS_IN_AN_HOUR)
176
+ minutes_c = hours_c[1].divmod(@@SECONDS_IN_A_MINUTE)
177
+ seconds_c = minutes_c[1]
178
+
179
+ result = ""
180
+
181
+ result << (hours_c[0] != 0 ? "#{negative_seconds}#{ "%02d" % hours_c[0]}:" : "00:")
182
+ result << (minutes_c[0] != 0 ? "#{negative_seconds}#{ "%02d" % minutes_c[0]}:" : "00:")
183
+ result << (seconds_c != 0 ? ((seconds_c % 1 ==0) ? "#{negative_seconds}#{ "%02d" % seconds_c.to_i}" : "#{negative_seconds}#{seconds_c.round(4)}") : "00")
184
+ result << (years_c[0] != 0 ? "+ #{negative_months}#{years_c[0]} years" : "")
185
+ result << (years_c[1] != 0 ? "+ #{negative_months}#{years_c[1]} months": "")
186
+ result << (days_c[0] != 0 ? "+ #{negative_months}#{days_c[0]} days" : "")
187
+
188
+ end
189
+
190
+ #include :hours in inspect
191
+ def inspect #:nodoc:
192
+ consolidated = parts.inject(::Hash.new(0)) { |h,(l,r)| h[l] += r; h }
193
+ parts = [:years, :months, :days, :hours, :minutes, :seconds].map do |length|
194
+ n = consolidated[length]
195
+ "#{n} #{n == 1 ? length.to_s.singularize : length.to_s}" if n.nonzero?
196
+ end.compact
197
+ parts = ["0 seconds"] if parts.empty?
198
+ parts.to_sentence(:locale => :en)
199
+ end
200
+
201
+ # Adds another Duration or a Numeric to this Duration. Numeric values
202
+ # are treated as seconds.
203
+ def +(other)
204
+ if ::Duration === other
205
+ ::Duration.new(value + other.value, @parts + other.parts)
206
+ else
207
+ if ::Time === other
208
+ other + self
209
+ else
210
+ ::Duration.new(value + other, @parts + [[:seconds, other]])
211
+ end
212
+ end
213
+ end
214
+
215
+ # Subtracts another Duration or a Numeric from this Duration. Numeric
216
+ # values are treated as seconds.
217
+ def -(other)
218
+ if ::Duration === other
219
+
220
+ ::Duration.new(value - other.value, @parts +( other.parts.dup.map! { |k,v| [k,-v] } ))
221
+ else
222
+ if ::Time === other
223
+ # minus operator is not canonical
224
+ else
225
+ ::Duration.new(value - other, @parts + [[:seconds, -other]])
226
+ end
227
+ end
228
+ end
229
+
230
+ end
231
+ end
232
+
233
+ Duration = ActiveSupport::Duration
234
+ d= (Duration.new("PT0S")+Duration.new("PT3M"))
235
+ puts d.as_24h_clock
@@ -0,0 +1,5 @@
1
+ #module Pravigo
2
+ # module Duration
3
+ # VERSION = "0.0.1"
4
+ # end
5
+ #end
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'pravigo/duration/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "pravigo-duration"
8
+ spec.version = "0.0.1"
9
+ spec.authors = ["Rich Hall"]
10
+ spec.email = ["rich@pravigo.org"]
11
+ spec.summary = %q{manipulate iso8601 durations}
12
+ spec.description = %q{Pravigo::Duration extends ActiveSupport::Duration class to add iso8601 support}
13
+ spec.homepage = "https://github.com/pravigo/pravigo-duration"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.5"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "rspec"
24
+ spec.add_runtime_dependency "activesupport"
25
+
26
+ end
27
+
@@ -0,0 +1,455 @@
1
+ # duration_spec.rb
2
+ require "spec_helper"
3
+ #require 'active_support/time'
4
+ #require File.expand_path(File.dirname(__FILE__) + '/duration')
5
+
6
+ Duration = ActiveSupport::Duration
7
+
8
+ describe Duration do
9
+
10
+ MONTHS_IN_A_YEAR = 12
11
+ SECONDS_IN_A_WEEK=604800
12
+ SECONDS_IN_A_DAY=86400
13
+ SECONDS_IN_AN_HOUR=3600
14
+ SECONDS_IN_A_MINUTE=60
15
+ #Approximate number of seconds in a month and year
16
+ SECONDS_IN_A_YEAR=(SECONDS_IN_A_DAY*365)
17
+ SECONDS_IN_A_MONTH=(SECONDS_IN_A_DAY * 30)
18
+
19
+ let(:duration_of_0_iso8601_string) { Duration.new('PT0s') }
20
+ let(:duration_of_5_minutes_and_30_seconds_initialised_from_iso8601_string) { Duration.new('PT5m30s') }
21
+ let(:duration_of_5_minutes_and_30_seconds_initialised_from_hhmmss_string) { Duration.new('00:05:30') }
22
+ let(:duration_of_4_minutes_initialised_by_value_and_parts) { Duration.new(240,[[:minutes, 4]]) }
23
+ let(:duration_of_4_minutes_initialised_by_numeric) { Duration.new(240) }
24
+ let(:activeSupport_Duration_of_4_minutes) { ActiveSupport::Duration.new(240,[[:minutes, 4]]) }
25
+ let(:activeSupport_Duration_of_5_minutes) { ActiveSupport::Duration.new(300,[[:minutes, 5]]) }
26
+ let(:activeSupport_Duration_of_1_year_3_months_and_3_hours) { ActiveSupport::Duration.new((SECONDS_IN_A_YEAR+(SECONDS_IN_A_MONTH*3)+(SECONDS_IN_AN_HOUR*3)),[[:years, 1],[:months,3],[:hours,3]]) }
27
+ let(:duration_of_5_minutes_initialised_by_ActiveSupport_Duration) { Duration.new(activeSupport_Duration_of_5_minutes) }
28
+ let(:time_2014_02_28_1558_28) { Time.new(2014, 02, 28, 15, 58, 28) }
29
+ let(:duration_of_1_year_5_months_and_3_hours) { Duration.new('P1y5mT3h') }
30
+ let(:invalid_Duration_missing_T) { Duration.new('P5m30s') }
31
+
32
+ #seconds as a float
33
+ let(:y____s_float) { Duration.new('P1YT1.234S')}
34
+ let(:ym___s_float) { Duration.new('P1Y1MT1.234S')}
35
+ let(:ymd__s_float) { Duration.new('P1Y1M1DT1.234S')}
36
+ let(:ymdh_s_float) { Duration.new('P1Y1M1DT1H1.234S')}
37
+ let(:ymdhms_float) { Duration.new('P1Y1M1DT1H1M1.234S')}
38
+ let(:_m___s_float) { Duration.new('P1MT1.234S')}
39
+ let(:_md__s_float) { Duration.new('P1M1DT1.234S')}
40
+ let(:_mdh_s_float) { Duration.new('P1M1DT1H1.234S')}
41
+ let(:_mdhms_float) { Duration.new('P1M1DT1H1M1.234S')}
42
+ let(:__d__s_float) { Duration.new('P1DT1.234S')}
43
+ let(:__dh_s_float) { Duration.new('P1DT1H1.234S')}
44
+ let(:__dhms_float) { Duration.new('P1DT1H1M1.234S')}
45
+ let(:___h_s_float) { Duration.new('PT1H1.234S')}
46
+ let(:___hms_float) { Duration.new('PT1H1M1.234S')}
47
+ let(:____ms_float) { Duration.new('PT1M1.234S')}
48
+ let(:_____s_float) { Duration.new('PT1.234S')}
49
+
50
+ describe '.initialise' do
51
+
52
+ context 'given valid iso8601 string' do
53
+
54
+ it 'creates an instance of Duration' do
55
+ duration_of_5_minutes_and_30_seconds_initialised_from_iso8601_string.__realclass__.should eq(Duration)
56
+ end
57
+
58
+ it 'creates a new parts array' do
59
+ duration_of_5_minutes_and_30_seconds_initialised_from_iso8601_string.parts.should eq([[:minutes,5],[:seconds,30.0]])
60
+
61
+ end
62
+
63
+ it 'creates a new value' do
64
+ duration_of_5_minutes_and_30_seconds_initialised_from_iso8601_string.value.should eq(330.0)
65
+ end
66
+
67
+ it 'outputs a valid iso8601 representation' do
68
+ duration_of_5_minutes_and_30_seconds_initialised_from_iso8601_string.iso8601.should eq('PT5M30S')
69
+ end
70
+
71
+ it 'creates a subclass of ActiveSupport::Duration' do
72
+ (duration_of_5_minutes_and_30_seconds_initialised_from_iso8601_string.is_a? ActiveSupport::Duration).should eq(true)
73
+ end
74
+
75
+ end
76
+
77
+ context 'given an invalid iso8601 string' do
78
+
79
+ it 'returns a NilClass' do
80
+ expect(invalid_Duration_missing_T).to be_a NilClass
81
+ end
82
+
83
+ end
84
+
85
+ context 'given valid hh:mm:ss string' do
86
+
87
+ it 'creates an instance of Duration' do
88
+ duration_of_5_minutes_and_30_seconds_initialised_from_hhmmss_string.__realclass__.should eq(Duration)
89
+ end
90
+
91
+ it 'creates a new parts array' do
92
+ duration_of_5_minutes_and_30_seconds_initialised_from_hhmmss_string.parts.should eq([[:minutes,5],[:seconds,30.0]])
93
+
94
+ end
95
+
96
+ it 'creates a new value' do
97
+ duration_of_5_minutes_and_30_seconds_initialised_from_hhmmss_string.value.should eq(330.0)
98
+ end
99
+
100
+ it 'outputs a valid iso8601 representation' do
101
+ duration_of_5_minutes_and_30_seconds_initialised_from_hhmmss_string.iso8601.should eq('PT5M30S')
102
+ end
103
+
104
+ it 'creates a subclass of ActiveSupport::Duration' do
105
+ (duration_of_5_minutes_and_30_seconds_initialised_from_hhmmss_string.is_a? ActiveSupport::Duration).should eq(true)
106
+ end
107
+
108
+ end
109
+
110
+
111
+ context 'given a value and parts array' do
112
+
113
+ it 'creates an instance of Duration' do
114
+ duration_of_4_minutes_initialised_by_value_and_parts.__realclass__.should eq(Duration)
115
+ end
116
+
117
+ it 'creates a new parts array' do
118
+ duration_of_4_minutes_initialised_by_value_and_parts.parts.should eq([[:minutes,4]])
119
+ end
120
+
121
+ it 'creates a new value' do
122
+ duration_of_4_minutes_initialised_by_value_and_parts.value.should eq(240)
123
+ end
124
+
125
+ it 'outputs a valid iso8601 representation' do
126
+ duration_of_4_minutes_initialised_by_value_and_parts.iso8601.should eq('PT4M')
127
+ end
128
+
129
+ it 'creates a subclass of ActiveSupport::Duration' do
130
+ (duration_of_4_minutes_initialised_by_value_and_parts.is_a? ActiveSupport::Duration).should eq(true)
131
+ end
132
+
133
+ it 'creates a Duration equal to ActiveSupport::Duration initialised the same way' do
134
+ duration_of_4_minutes_initialised_by_value_and_parts.should eq(activeSupport_Duration_of_4_minutes)
135
+ end
136
+
137
+ end
138
+
139
+ context 'given a numeric' do
140
+
141
+ it 'creates an instance of Duration' do
142
+ duration_of_4_minutes_initialised_by_numeric.__realclass__.should eq(Duration)
143
+ end
144
+
145
+ it 'assumes the numeric to be seconds and creates a new parts array' do
146
+ duration_of_4_minutes_initialised_by_numeric.parts.should eq([[:minutes,4]])
147
+ end
148
+
149
+ it 'assumes the numeric to be seconds and creates a new value' do
150
+ duration_of_4_minutes_initialised_by_numeric.value.should eq(240)
151
+ end
152
+
153
+ it 'outputs a valid iso8601 representation' do
154
+ duration_of_4_minutes_initialised_by_numeric.iso8601.should eq('PT4M')
155
+ end
156
+
157
+ it 'creates a subclass of ActiveSupport::Duration' do
158
+ (duration_of_4_minutes_initialised_by_numeric.is_a? ActiveSupport::Duration).should eq(true)
159
+ end
160
+
161
+ end
162
+
163
+ context 'given an ActiveSupport::Duration' do
164
+
165
+ it 'creates an instance of Duration' do
166
+ duration_of_5_minutes_initialised_by_ActiveSupport_Duration.__realclass__.should eq(Duration)
167
+ end
168
+
169
+ it 'creates a new parts array' do
170
+ duration_of_5_minutes_initialised_by_ActiveSupport_Duration.parts.should eq([[:minutes,5]])
171
+ end
172
+
173
+ it 'creates a new value' do
174
+ duration_of_5_minutes_initialised_by_ActiveSupport_Duration.value.should eq(300.0)
175
+ end
176
+
177
+ it 'outputs a valid iso8601 representation' do
178
+ duration_of_5_minutes_initialised_by_ActiveSupport_Duration.iso8601.should eq('PT5M')
179
+ end
180
+
181
+ it 'creates a subclass of ActiveSupport::Duration' do
182
+ (duration_of_5_minutes_initialised_by_ActiveSupport_Duration.is_a? ActiveSupport::Duration).should eq(true)
183
+ end
184
+
185
+ it 'creates a Duration equal to ActiveSupport::Duration initialised the same way' do
186
+ duration_of_5_minutes_initialised_by_ActiveSupport_Duration.should eq(activeSupport_Duration_of_5_minutes)
187
+ end
188
+
189
+ end
190
+
191
+ end
192
+
193
+ describe "iso8604 testing" do
194
+ {"PT0S" => [[],0,"P"],
195
+ "P1Y" => [[[:years,1]],(SECONDS_IN_A_DAY*365),"P1Y"],
196
+ "P1Y1M" => [[[:years,1],[:months,1]],(SECONDS_IN_A_DAY*365)+SECONDS_IN_A_MONTH, "P1Y1M"],
197
+ "P1YT1H" => [[[:years,1],[:hours,1]], (SECONDS_IN_A_DAY*365)+SECONDS_IN_AN_HOUR, "P1YT1H"],
198
+ "P1YT1M" => [[[:years,1],[:minutes,1]],(SECONDS_IN_A_DAY*365)+60,"P1YT1M"],
199
+ "P1YT1S" => [[[:years,1],[:seconds,1.0]],(SECONDS_IN_A_DAY*365)+1,"P1YT1S"],
200
+ "P1Y1M1D" => [[[:years,1],[:months,1],[:days,1]],(SECONDS_IN_A_DAY*365)+(SECONDS_IN_A_DAY*31),"P1Y1M1D"],
201
+ "P1Y1MT1H" => [[[:years,1],[:months,1],[:hours,1]],(SECONDS_IN_A_DAY*365)+(SECONDS_IN_A_DAY*30)+SECONDS_IN_AN_HOUR,"P1Y1MT1H"],
202
+ "P1Y1MT1M" => [[[:years,1],[:months,1],[:minutes,1]],(SECONDS_IN_A_DAY*365)+(SECONDS_IN_A_DAY*30)+60,"P1Y1MT1M" ],
203
+ "P1Y1MT1S" => [[[:years,1],[:months,1],[:seconds,1.0]],(SECONDS_IN_A_DAY*365)+(SECONDS_IN_A_DAY*30)+1 ,"P1Y1MT1S"],
204
+ "P1Y1M1DT1H" => [[[:years,1],[:months,1],[:days,1],[:hours,1]],(SECONDS_IN_A_DAY*365)+(SECONDS_IN_A_DAY*31)+SECONDS_IN_AN_HOUR,"P1Y1M1DT1H"],
205
+ "P1Y1M1DT1M" => [[[:years,1],[:months,1],[:days,1],[:minutes,1]],(SECONDS_IN_A_DAY*365)+(SECONDS_IN_A_DAY*31)+60,"P1Y1M1DT1M"],
206
+ "P1Y1M1DT1S" => [[[:years,1],[:months,1],[:days,1],[:seconds,1.0]],(SECONDS_IN_A_DAY*365)+(SECONDS_IN_A_DAY*31)+1,"P1Y1M1DT1S"],
207
+ "P1Y1M1DT1H1M" => [[[:years,1],[:months,1],[:days,1],[:hours,1],[:minutes,1]],(SECONDS_IN_A_DAY*365)+(SECONDS_IN_A_DAY*31)+SECONDS_IN_AN_HOUR+60,"P1Y1M1DT1H1M"],
208
+ "P1Y1M1DT1H1S" => [[[:years,1],[:months,1],[:days,1],[:hours,1],[:seconds,1.0]],(SECONDS_IN_A_DAY*365)+(SECONDS_IN_A_DAY*31)+SECONDS_IN_AN_HOUR+1,"P1Y1M1DT1H1S"],
209
+ "P1Y1M1DT1H1M1S" => [[[:years,1],[:months,1],[:days,1],[:hours,1],[:minutes,1],[:seconds,1.0]],(SECONDS_IN_A_DAY*365)+(SECONDS_IN_A_DAY*31)+SECONDS_IN_AN_HOUR+61,"P1Y1M1DT1H1M1S"],
210
+ "P1M" => [[[:months,1]],(SECONDS_IN_A_DAY*30),"P1M"],
211
+ "P1M1D" => [[[:months,1],[:days,1]],(SECONDS_IN_A_DAY*31),"P1M1D"],
212
+ "P1MT1H" => [[[:months,1],[:hours,1]],(SECONDS_IN_A_DAY*30)+SECONDS_IN_AN_HOUR,"P1MT1H"],
213
+ "P1MT1M" => [[[:months,1],[:minutes,1]],(SECONDS_IN_A_DAY*30)+60,"P1MT1M"],
214
+ "P1MT1S" => [[[:months,1],[:seconds,1.0]],(SECONDS_IN_A_DAY*30)+1,"P1MT1S"],
215
+ "P1M1DT1H" => [[[:months,1],[:days,1],[:hours,1]],(SECONDS_IN_A_DAY*31)+SECONDS_IN_AN_HOUR,"P1M1DT1H"],
216
+ "P1M1DT1M" => [[[:months,1],[:days,1],[:minutes,1]],(SECONDS_IN_A_DAY*31)+60,"P1M1DT1M"],
217
+ "P1M1DT1S" => [[[:months,1],[:days,1],[:seconds,1.0]],(SECONDS_IN_A_DAY*31)+1,"P1M1DT1S"],
218
+ "P1M1DT1H1M" => [[[:months,1],[:days,1],[:hours,1],[:minutes,1]],(SECONDS_IN_A_DAY*31)+SECONDS_IN_AN_HOUR+60,"P1M1DT1H1M"],
219
+ "P1M1DT1H1S" => [[[:months,1],[:days,1],[:hours,1],[:seconds,1.0]],(SECONDS_IN_A_DAY*31)+SECONDS_IN_AN_HOUR+1,"P1M1DT1H1S"],
220
+ "P1M1DT1H1M1S" => [[[:months,1],[:days,1],[:hours,1],[:minutes,1],[:seconds,1.0]],(SECONDS_IN_A_DAY*31)+SECONDS_IN_AN_HOUR+61,"P1M1DT1H1M1S"],
221
+ "P1D" => [[[:days,1]],SECONDS_IN_A_DAY,"P1D"],
222
+ "P1DT1H" => [[[:days,1],[:hours,1]],SECONDS_IN_A_DAY+SECONDS_IN_AN_HOUR,"P1DT1H"],
223
+ "P1DT1M" => [[[:days,1],[:minutes,1]],SECONDS_IN_A_DAY+60,"P1DT1M"],
224
+ "P1DT1S" => [[[:days,1],[:seconds,1.0]],SECONDS_IN_A_DAY+1,"P1DT1S"],
225
+ "P1DT1H1M" => [[[:days,1],[:hours,1],[:minutes,1]],SECONDS_IN_A_DAY+SECONDS_IN_AN_HOUR+60,"P1DT1H1M"],
226
+ "P1DT1H1S" => [[[:days,1],[:hours,1],[:seconds,1.0]],SECONDS_IN_A_DAY+SECONDS_IN_AN_HOUR+1,"P1DT1H1S"],
227
+ "P1DT1H1M1S" => [[[:days,1],[:hours,1],[:minutes,1],[:seconds,1.0]],SECONDS_IN_A_DAY+SECONDS_IN_AN_HOUR+61,"P1DT1H1M1S"],
228
+ "PT1H" => [[[:hours,1]],SECONDS_IN_AN_HOUR,"PT1H"],
229
+ "PT1H1M" => [[[:hours,1],[:minutes,1]],SECONDS_IN_AN_HOUR+60,"PT1H1M"],
230
+ "PT1H1S" => [[[:hours,1],[:seconds,1.0]],SECONDS_IN_AN_HOUR+1,"PT1H1S"],
231
+ "PT1H1M1S" => [[[:hours,1],[:minutes,1],[:seconds,1.0]],SECONDS_IN_AN_HOUR+61,"PT1H1M1S"],
232
+ "PT1M" => [[[:minutes,1]],60,"PT1M"],
233
+ "PT1M1S" => [[[:minutes,1],[:seconds,1.0]],61,"PT1M1S"],
234
+ "PT1S" => [[[:seconds,1.0]],1,"PT1S"],
235
+ "PT1.2345S" => [[[:seconds,1.2345]],1.2345,"PT1.2345S"],
236
+ "PT1.234S" => [[[:seconds,1.234]],1.234,"PT1.234S"],
237
+ "PT1.23S" => [[[:seconds,1.23]],1.23,"PT1.23S"],
238
+ "PT1.2S" => [[[:seconds,1.2]],1.2,"PT1.2S"],
239
+ "PT1,2345S" => [[[:seconds,1.2345]],1.2345,"PT1.2345S"],
240
+ "PT1,234S" => [[[:seconds,1.234]],1.234,"PT1.234S"],
241
+ "PT1,23S" => [[[:seconds,1.23]],1.23,"PT1.23S"],
242
+ "PT1,2S" => [[[:seconds,1.2]],1.2,"PT1.2S"]
243
+
244
+ }.each do |input, output|
245
+ durationToTest = Duration.new(input)
246
+ context "given a valid iso8601 duration string: #{input}, the initialised Duration" do
247
+
248
+ it "has parts = #{output[0].to_s}" do
249
+ durationToTest.parts.should eq(output[0])
250
+ end
251
+
252
+ it "has value = #{output[1].to_s} seconds" do
253
+ durationToTest.value.should eq(output[1])
254
+ end
255
+
256
+ it "returns #{output[2]} as iso8601" do
257
+ durationToTest.iso8601.should eq(output[2])
258
+ end
259
+ end
260
+ end
261
+ end
262
+
263
+ describe '#+ (add)' do
264
+ context 'given a Time' do
265
+
266
+ it 'returns a time' do
267
+ (duration_of_5_minutes_and_30_seconds_initialised_from_iso8601_string+time_2014_02_28_1558_28).should be_an_instance_of Time
268
+ end
269
+
270
+ it 'increases the Time by the Duration' do
271
+ (duration_of_5_minutes_and_30_seconds_initialised_from_iso8601_string+time_2014_02_28_1558_28).should eq(Time.new(2014, 02, 28, 16, 03, 58))
272
+ end
273
+
274
+ end
275
+
276
+
277
+ context 'given a Duration' do
278
+
279
+ it 'returns a Duration' do
280
+ (duration_of_5_minutes_and_30_seconds_initialised_from_iso8601_string+duration_of_5_minutes_and_30_seconds_initialised_from_iso8601_string).__realclass__.should eq(Duration)
281
+ end
282
+
283
+ it 'returns the sum of the two Durations' do
284
+ (duration_of_5_minutes_and_30_seconds_initialised_from_iso8601_string+duration_of_5_minutes_and_30_seconds_initialised_from_iso8601_string).should eq(Duration.new('PT11m'))
285
+ end
286
+
287
+ end
288
+
289
+ context 'given an ActiveSupport::Duration' do
290
+ it 'returns a Duration' do
291
+ (duration_of_5_minutes_and_30_seconds_initialised_from_iso8601_string+activeSupport_Duration_of_4_minutes).__realclass__.should eq(Duration)
292
+ end
293
+
294
+ it 'returns the sum of the two Durations' do
295
+ (duration_of_5_minutes_and_30_seconds_initialised_from_iso8601_string+activeSupport_Duration_of_4_minutes).should eq(Duration.new('PT9m30s'))
296
+ end
297
+ end
298
+
299
+ context 'given a Numeric' do
300
+ it 'returns a Duration' do
301
+ expect((duration_of_5_minutes_and_30_seconds_initialised_from_iso8601_string+30).__realclass__).to eq(Duration)
302
+ end
303
+
304
+ it 'assumes the Numeric is seconds and adds to the Duration' do
305
+ expect(duration_of_5_minutes_and_30_seconds_initialised_from_iso8601_string+30).to eq(360)
306
+ end
307
+
308
+ end
309
+
310
+ end
311
+
312
+ describe '#- (subtract)' do
313
+
314
+ context 'given a Duration' do
315
+
316
+ it 'returns a Duration' do
317
+ (duration_of_5_minutes_and_30_seconds_initialised_from_iso8601_string-duration_of_5_minutes_and_30_seconds_initialised_from_iso8601_string).__realclass__.should eq(Duration)
318
+ end
319
+
320
+ it 'returns the Duration minus the second duration' do
321
+ (duration_of_5_minutes_and_30_seconds_initialised_from_iso8601_string-duration_of_5_minutes_and_30_seconds_initialised_from_iso8601_string).should eq(Duration.new('P'))
322
+ end
323
+
324
+ it 'returns the Duration minus the second duration' do
325
+ (duration_of_4_minutes_initialised_by_value_and_parts-duration_of_5_minutes_and_30_seconds_initialised_from_iso8601_string).should eq(Duration.new('PT-1M-30S'))
326
+ end
327
+
328
+ it 'returns the Duration minus the second duration' do
329
+ ((Duration.new("PT40M")-Duration.new("PT38M")).should eq(Duration.new('PT2M')))
330
+ end
331
+
332
+ it 'returns the Duration minus the second duration' do
333
+ ((Duration.new("PT40M")-Duration.new("PT38M")).parts.should eq([[:minutes, 40], [:minutes, -38]]))
334
+ end
335
+
336
+
337
+ end
338
+
339
+ end
340
+
341
+ describe '#iso80601' do
342
+
343
+ let(:duration_with_15_months) { Duration.new('P15M') }
344
+ let(:duration_with_75_seconds) { Duration.new('PT75S') }
345
+ let(:duration_with_70_minutes) { Duration.new('PT70M') }
346
+ let(:duration_with_27_hours) { Duration.new('PT27H') }
347
+ let(:duration_with_9_days) { Duration.new('P9D') }
348
+ let(:duration_with_2_months_and_45_days) { Duration.new('P2M45D') }
349
+ let(:duration_with_second_to_6_decimal_places) { Duration.new(1.234567,[[:seconds,1.234567]]) }
350
+
351
+
352
+ it 'returns iso8601 representation in years and months when months is 12 or over' do
353
+ duration_with_15_months.iso8601.should eq('P1Y3M')
354
+
355
+ end
356
+
357
+ it 'returns iso8601 representation in minutes and seconds when seconds is 60 or over' do
358
+ duration_with_75_seconds.iso8601.should eq('PT1M15S')
359
+ end
360
+
361
+ it 'returns iso8601 representation in hours and minutes when minutes is 60 or over' do
362
+ duration_with_70_minutes.iso8601.should eq('PT1H10M')
363
+ end
364
+
365
+ it 'returns iso8601 representation in days and hours when hours is 24 or over' do
366
+ duration_with_27_hours.iso8601.should eq('P1DT3H')
367
+ end
368
+
369
+ it 'returns iso8601 representation in days when number of days exceeds 7 (weeks are ignored)' do
370
+ duration_with_9_days.iso8601.should eq('P9D')
371
+ end
372
+
373
+ it 'returns iso8601 representation in days when days clearly exceeds number of possible days in a month' do
374
+ duration_with_2_months_and_45_days.iso8601.should eq('P2M45D')
375
+ end
376
+
377
+ it 'returns iso8601 representation with seconds rounded to 4 decimal places' do
378
+ duration_with_second_to_6_decimal_places.iso8601.should eq('PT1.2346S')
379
+ end
380
+
381
+ end
382
+
383
+ describe 'Test adding a Duration to an ActiveSupport::Duration' do
384
+ it 'returns an ActiveSupport:Duration' do
385
+ (activeSupport_Duration_of_4_minutes + duration_of_4_minutes_initialised_by_value_and_parts).__realclass__.should eq(ActiveSupport::Duration)
386
+ end
387
+
388
+ it 'returns the sum of the two durations' do
389
+ expect(activeSupport_Duration_of_4_minutes + duration_of_4_minutes_initialised_by_value_and_parts).to eq(ActiveSupport::Duration.new(480,[[:minutes,4],[:minutes,4]]))
390
+ end
391
+
392
+ it 'confirms ActiveSupport::Duration behaviour combines parts when matching i.e. [[:minutes,4],[:minutes,4]] is equal to [[:minutes,8]]' do
393
+ expect(activeSupport_Duration_of_4_minutes + duration_of_4_minutes_initialised_by_value_and_parts).to eq(ActiveSupport::Duration.new(480,[[:minutes,8]]))
394
+ end
395
+
396
+ it 'preserves months and years (does not convert to seconds)' do
397
+ expect(Duration.new(activeSupport_Duration_of_1_year_3_months_and_3_hours+duration_of_1_year_5_months_and_3_hours).iso8601).to eq("P2Y8MT6H")
398
+ end
399
+
400
+ end
401
+
402
+ describe 'Test adding a Duration to a Time' do
403
+
404
+ it 'returns a time' do
405
+ (time_2014_02_28_1558_28+duration_of_5_minutes_and_30_seconds_initialised_from_iso8601_string).should be_an_instance_of Time
406
+ end
407
+
408
+ it 'increases the Time by the Duration' do
409
+ (time_2014_02_28_1558_28+duration_of_5_minutes_and_30_seconds_initialised_from_iso8601_string).should eq(Time.new(2014, 02, 28, 16, 03, 58))
410
+ end
411
+
412
+ it 'correctly handles months and years (does not convert to seconds)' do
413
+ (time_2014_02_28_1558_28+duration_of_1_year_5_months_and_3_hours).should eq(Time.new(2015, 07, 28, 18, 58, 28))
414
+ end
415
+
416
+ end
417
+
418
+ describe 'Test adding a Duration to a Numeric' do
419
+ it 'returns a Numeric' do
420
+ expect((100+duration_of_5_minutes_and_30_seconds_initialised_from_iso8601_string).is_a? Numeric).to eq(true)
421
+ end
422
+
423
+ it 'returns the Numeric plus the Duration in seconds' do
424
+ (100+duration_of_5_minutes_and_30_seconds_initialised_from_iso8601_string).should eq(430)
425
+ end
426
+
427
+ it 'assumes a year is 365 days and a month is 30 days' do
428
+ expect(100+duration_of_1_year_5_months_and_3_hours).to eq(SECONDS_IN_A_YEAR+(SECONDS_IN_A_MONTH*5)+(SECONDS_IN_AN_HOUR*3)+100)
429
+ end
430
+
431
+ end
432
+
433
+ # TODO
434
+ # .years
435
+ # .months
436
+ # .hours
437
+ # .minutes
438
+ # .seconds
439
+ # .tenths
440
+ # .hundredths
441
+ # .thousandths
442
+ # should return normalised part
443
+ # .in_years
444
+ # .in_months
445
+ # .in_hours
446
+ # .in_minutes
447
+ # .in_seconds
448
+ # .in_tenths
449
+ # .in_hundredths
450
+ # .in_thousandths
451
+ # should return decimal representation
452
+ #
453
+ # mathematical operations should normalise the internal representation (as far as days)
454
+
455
+ end
@@ -0,0 +1 @@
1
+ require "pravigo/duration"
metadata ADDED
@@ -0,0 +1,124 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pravigo-duration
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Rich Hall
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2015-07-16 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.5'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.5'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: activesupport
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ description: Pravigo::Duration extends ActiveSupport::Duration class to add iso8601
79
+ support
80
+ email:
81
+ - rich@pravigo.org
82
+ executables: []
83
+ extensions: []
84
+ extra_rdoc_files: []
85
+ files:
86
+ - .gitignore
87
+ - .rspec
88
+ - Gemfile
89
+ - LICENSE.txt
90
+ - README.md
91
+ - Rakefile
92
+ - lib/pravigo/duration.rb
93
+ - lib/pravigo/duration/version.rb
94
+ - pravigo-duration.gemspec
95
+ - spec/duration_spec.rb
96
+ - spec/spec_helper.rb
97
+ homepage: https://github.com/pravigo/pravigo-duration
98
+ licenses:
99
+ - MIT
100
+ post_install_message:
101
+ rdoc_options: []
102
+ require_paths:
103
+ - lib
104
+ required_ruby_version: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ required_rubygems_version: !ruby/object:Gem::Requirement
111
+ none: false
112
+ requirements:
113
+ - - ! '>='
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ requirements: []
117
+ rubyforge_project:
118
+ rubygems_version: 1.8.28
119
+ signing_key:
120
+ specification_version: 3
121
+ summary: manipulate iso8601 durations
122
+ test_files:
123
+ - spec/duration_spec.rb
124
+ - spec/spec_helper.rb