pravigo-duration 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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