iso8601 0.7.0 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -5
- data/README.md +7 -7
- data/iso8601.gemspec +1 -1
- data/lib/iso8601.rb +1 -1
- data/lib/iso8601/atoms.rb +126 -28
- data/lib/iso8601/date.rb +23 -4
- data/lib/iso8601/{dateTime.rb → date_time.rb} +21 -1
- data/lib/iso8601/duration.rb +187 -110
- data/lib/iso8601/errors.rb +4 -0
- data/lib/iso8601/time.rb +20 -1
- data/lib/iso8601/version.rb +1 -1
- data/spec/iso8601/atoms_spec.rb +182 -46
- data/spec/iso8601/date_spec.rb +13 -16
- data/spec/iso8601/{dateTime_spec.rb → date_time_spec.rb} +30 -31
- data/spec/iso8601/duration_spec.rb +113 -84
- data/spec/iso8601/time_spec.rb +25 -21
- data/spec/spec_helper.rb +1 -1
- metadata +7 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a046b00234ecd1ead9531234435f3b28bb1e89d3
|
4
|
+
data.tar.gz: 53e15b12514e255b2a5aaeb451e041047b2d6ef0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4be163f63f8e4d488ecb8d6c997e17031445ab0b124644f8c0b6d33ac60cbc9b9b21f5009ba0a185f8cb08cbc7e4a3de9900c9fdccf3589fd75ea5a6cb8e2d99
|
7
|
+
data.tar.gz: a10fa5e91adbd1717954b81809cdf206e34f676f07f27add1cceb1530d782c7a3df5b5e8052265f10f915578538f3417c8f7890d97fe4bceedf11d2b0061ae22
|
data/CHANGELOG.md
CHANGED
@@ -1,22 +1,32 @@
|
|
1
|
-
##
|
1
|
+
## 0.8.0
|
2
|
+
|
3
|
+
* `DateTime` has hash identity by value.
|
4
|
+
* `Time` has hash identity by value.
|
5
|
+
* `Date` has hash identity by value.
|
6
|
+
* `Duration` has hash identity by value.
|
7
|
+
* `Atom` has hash identity by value.
|
8
|
+
* `Atom#value` returns either an integer or a float.
|
9
|
+
* `Atom#to_s` returns a valid ISO8601 subpattern.
|
10
|
+
|
11
|
+
## 0.7.0
|
2
12
|
|
3
13
|
* Add decimal fractions for any component in a duration.
|
4
14
|
* Add a catch all `ISO8601::Errors::StandardError`.
|
5
15
|
* Add support for comma (`,`) as a separator for duration decimal fractions.
|
6
16
|
|
7
|
-
##
|
17
|
+
## 0.6.0
|
8
18
|
|
9
19
|
* Add `#hash` to `Duration`, `Date`, `Time` and `DateTime`.
|
10
20
|
|
11
|
-
##
|
21
|
+
## 0.5.2
|
12
22
|
|
13
23
|
* Fix `DateTime` when handling empty strings.
|
14
24
|
|
15
|
-
##
|
25
|
+
## 0.5.1
|
16
26
|
|
17
27
|
* Fix durations with sign.
|
18
28
|
|
19
|
-
##
|
29
|
+
## 0.5.0
|
20
30
|
|
21
31
|
* Drop support for Ruby 1.8.7.
|
22
32
|
* Add support for Rubinius 2.
|
data/README.md
CHANGED
@@ -84,16 +84,16 @@ Week dates raise an error when two digit days provied instead of return monday:
|
|
84
84
|
DateTime.new('2014-W15-02') # => #<Date: 2014-04-07 ((2456755j,0s,0n),+0s,2299161j)>
|
85
85
|
|
86
86
|
|
87
|
-
|
88
|
-
## TODO
|
89
|
-
|
90
|
-
* Recurring time intervals
|
91
|
-
|
92
|
-
|
93
|
-
## Contributors
|
87
|
+
## Contributing
|
94
88
|
|
95
89
|
[Contributors](https://github.com/arnau/ISO8601/graphs/contributors)
|
96
90
|
|
91
|
+
1. Fork it (http://github.com/arnau/ISO8601/fork)
|
92
|
+
2. Create your feature branch (git checkout -b features/xyz)
|
93
|
+
3. Commit your changes (git commit -am 'Add XYZ')
|
94
|
+
4. Push to the branch (git push origin features/xyz)
|
95
|
+
5. Create new Pull Request
|
96
|
+
|
97
97
|
|
98
98
|
## License
|
99
99
|
|
data/iso8601.gemspec
CHANGED
data/lib/iso8601.rb
CHANGED
data/lib/iso8601/atoms.rb
CHANGED
@@ -8,28 +8,75 @@ module ISO8601
|
|
8
8
|
class Atom
|
9
9
|
##
|
10
10
|
# @param [Numeric] atom The atom value
|
11
|
-
# @param [ISO8601::DateTime, nil] base (nil) The base datetime to
|
12
|
-
#
|
11
|
+
# @param [ISO8601::DateTime, nil] base (nil) The base datetime to compute
|
12
|
+
# the atom factor.
|
13
13
|
def initialize(atom, base=nil)
|
14
|
-
raise TypeError, "The atom argument for #{self.inspect} should be a Numeric value." unless atom.kind_of?
|
15
|
-
raise TypeError, "The base argument for #{self.inspect} should be a ISO8601::DateTime instance or nil." unless base.kind_of?
|
14
|
+
raise TypeError, "The atom argument for #{self.inspect} should be a Numeric value." unless atom.kind_of?(Numeric)
|
15
|
+
raise TypeError, "The base argument for #{self.inspect} should be a ISO8601::DateTime instance or nil." unless base.kind_of?(ISO8601::DateTime) || base.nil?
|
16
16
|
@atom = atom
|
17
17
|
@base = base
|
18
18
|
end
|
19
|
+
attr_reader :atom
|
20
|
+
attr_reader :base
|
19
21
|
##
|
20
|
-
# The integer representation
|
22
|
+
# The integer representation
|
23
|
+
#
|
24
|
+
# @return [Integer]
|
21
25
|
def to_i
|
22
|
-
|
26
|
+
atom.to_i
|
27
|
+
end
|
28
|
+
##
|
29
|
+
# The float representation
|
30
|
+
#
|
31
|
+
# @return [Float]
|
32
|
+
def to_f
|
33
|
+
atom.to_f
|
23
34
|
end
|
24
35
|
##
|
25
|
-
#
|
36
|
+
# Returns the ISO 8601 representation for the atom
|
37
|
+
#
|
38
|
+
# @return [String]
|
39
|
+
def to_s
|
40
|
+
(value.zero?) ? '' : "#{value}#{symbol}"
|
41
|
+
end
|
42
|
+
##
|
43
|
+
# The simplest numeric representation. If modulo equals 0 returns an
|
44
|
+
# integer else a float.
|
45
|
+
#
|
46
|
+
# @return [Numeric]
|
47
|
+
def value
|
48
|
+
(atom % 1).zero? ? atom.to_i : atom
|
49
|
+
end
|
50
|
+
##
|
51
|
+
# The amount of seconds
|
52
|
+
#
|
53
|
+
# @return [Numeric]
|
26
54
|
def to_seconds
|
27
|
-
|
55
|
+
atom * factor
|
56
|
+
end
|
57
|
+
##
|
58
|
+
# @param [#hash] contrast The contrast to compare against
|
59
|
+
#
|
60
|
+
# @return [Boolean]
|
61
|
+
def ==(contrast)
|
62
|
+
(hash == contrast.hash)
|
63
|
+
end
|
64
|
+
##
|
65
|
+
# @param [#hash] contrast The contrast to compare against
|
66
|
+
#
|
67
|
+
# @return [Boolean]
|
68
|
+
def eql?(contrast)
|
69
|
+
(hash == contrast.hash)
|
70
|
+
end
|
71
|
+
##
|
72
|
+
# @return [Fixnum]
|
73
|
+
def hash
|
74
|
+
[atom, self.class].hash
|
28
75
|
end
|
29
76
|
##
|
30
77
|
# The atom factor to compute the amount of seconds for the atom
|
31
78
|
def factor
|
32
|
-
raise NotImplementedError, "The #factor method should be implemented
|
79
|
+
raise NotImplementedError, "The #factor method should be implemented by each subclass"
|
33
80
|
end
|
34
81
|
end
|
35
82
|
##
|
@@ -49,17 +96,26 @@ module ISO8601
|
|
49
96
|
# The “duration year” average is calculated through time intervals of 400
|
50
97
|
# “duration years”. Each cycle of 400 “duration years” has 303 “common
|
51
98
|
# years” of 365 “calendar days” and 97 “leap years” of 366 “calendar days”.
|
99
|
+
#
|
100
|
+
# @return [Integer]
|
52
101
|
def factor
|
53
|
-
if
|
102
|
+
if base.nil?
|
54
103
|
((365 * 303 + 366 * 97) / 400) * 86400
|
55
|
-
elsif
|
56
|
-
year = (
|
57
|
-
(::Time.utc(year) - ::Time.utc(
|
104
|
+
elsif atom.zero?
|
105
|
+
year = (base.year).to_i
|
106
|
+
(::Time.utc(year) - ::Time.utc(base.year))
|
58
107
|
else
|
59
|
-
year = (
|
60
|
-
(::Time.utc(year) - ::Time.utc(
|
108
|
+
year = (base.year + atom).to_i
|
109
|
+
(::Time.utc(year) - ::Time.utc(base.year)) / atom
|
61
110
|
end
|
62
111
|
end
|
112
|
+
##
|
113
|
+
# The atom symbol.
|
114
|
+
#
|
115
|
+
# @return [Symbol]
|
116
|
+
def symbol
|
117
|
+
:Y
|
118
|
+
end
|
63
119
|
end
|
64
120
|
##
|
65
121
|
# A Months atom in a {ISO8601::Duration}
|
@@ -78,14 +134,21 @@ module ISO8601
|
|
78
134
|
# “duration years”. Each cycle of 400 “duration years” has 303 “common
|
79
135
|
# years” of 365 “calendar days” and 97 “leap years” of 366 “calendar days”.
|
80
136
|
def factor
|
81
|
-
if
|
137
|
+
if base.nil?
|
82
138
|
nobase_calculation
|
83
|
-
elsif
|
139
|
+
elsif atom.zero?
|
84
140
|
zero_calculation
|
85
141
|
else
|
86
142
|
calculation
|
87
143
|
end
|
88
144
|
end
|
145
|
+
##
|
146
|
+
# The atom symbol.
|
147
|
+
#
|
148
|
+
# @return [Symbol]
|
149
|
+
def symbol
|
150
|
+
:M
|
151
|
+
end
|
89
152
|
|
90
153
|
private
|
91
154
|
|
@@ -94,30 +157,30 @@ module ISO8601
|
|
94
157
|
end
|
95
158
|
|
96
159
|
def zero_calculation
|
97
|
-
month = (
|
98
|
-
year =
|
160
|
+
month = (base.month <= 12) ? (base.month) : ((base.month) % 12)
|
161
|
+
year = base.year + ((base.month) / 12).to_i
|
99
162
|
|
100
|
-
(::Time.utc(year, month) - ::Time.utc(
|
163
|
+
(::Time.utc(year, month) - ::Time.utc(base.year, base.month))
|
101
164
|
end
|
102
165
|
|
103
166
|
def calculation
|
104
|
-
if
|
105
|
-
month =
|
167
|
+
if base.month + atom <= 0
|
168
|
+
month = base.month + atom
|
106
169
|
|
107
170
|
if month % 12 == 0
|
108
|
-
year =
|
171
|
+
year = base.year + (month / 12) - 1
|
109
172
|
month = 12
|
110
173
|
else
|
111
|
-
year =
|
174
|
+
year = base.year + (month / 12).floor
|
112
175
|
month = (12 + month > 0) ? (12 + month) : (12 + (month % -12))
|
113
176
|
end
|
114
177
|
else
|
115
|
-
month = (
|
116
|
-
month = 12 if month
|
117
|
-
year =
|
178
|
+
month = (base.month + atom <= 12) ? (base.month + atom) : ((base.month + atom) % 12)
|
179
|
+
month = 12 if month.zero?
|
180
|
+
year = base.year + ((base.month + atom) / 12).to_i
|
118
181
|
end
|
119
182
|
|
120
|
-
(::Time.utc(year, month) - ::Time.utc(
|
183
|
+
(::Time.utc(year, month) - ::Time.utc(base.year, base.month)) / atom
|
121
184
|
end
|
122
185
|
end
|
123
186
|
##
|
@@ -128,6 +191,13 @@ module ISO8601
|
|
128
191
|
def factor
|
129
192
|
604800
|
130
193
|
end
|
194
|
+
##
|
195
|
+
# The atom symbol.
|
196
|
+
#
|
197
|
+
# @return [Symbol]
|
198
|
+
def symbol
|
199
|
+
:W
|
200
|
+
end
|
131
201
|
end
|
132
202
|
##
|
133
203
|
# The Days atom in a {ISO8601::Duration}
|
@@ -141,6 +211,13 @@ module ISO8601
|
|
141
211
|
def factor
|
142
212
|
86400
|
143
213
|
end
|
214
|
+
##
|
215
|
+
# The atom symbol.
|
216
|
+
#
|
217
|
+
# @return [Symbol]
|
218
|
+
def symbol
|
219
|
+
:D
|
220
|
+
end
|
144
221
|
end
|
145
222
|
##
|
146
223
|
# The Hours atom in a {ISO8601::Duration}
|
@@ -150,6 +227,13 @@ module ISO8601
|
|
150
227
|
def factor
|
151
228
|
3600
|
152
229
|
end
|
230
|
+
##
|
231
|
+
# The atom symbol.
|
232
|
+
#
|
233
|
+
# @return [Symbol]
|
234
|
+
def symbol
|
235
|
+
:H
|
236
|
+
end
|
153
237
|
end
|
154
238
|
##
|
155
239
|
# The Minutes atom in a {ISO8601::Duration}
|
@@ -159,6 +243,13 @@ module ISO8601
|
|
159
243
|
def factor
|
160
244
|
60
|
161
245
|
end
|
246
|
+
##
|
247
|
+
# The atom symbol.
|
248
|
+
#
|
249
|
+
# @return [Symbol]
|
250
|
+
def symbol
|
251
|
+
:M
|
252
|
+
end
|
162
253
|
end
|
163
254
|
##
|
164
255
|
# The Seconds atom in a {ISO8601::Duration}
|
@@ -172,5 +263,12 @@ module ISO8601
|
|
172
263
|
def factor
|
173
264
|
1
|
174
265
|
end
|
266
|
+
##
|
267
|
+
# The atom symbol.
|
268
|
+
#
|
269
|
+
# @return [Symbol]
|
270
|
+
def symbol
|
271
|
+
:S
|
272
|
+
end
|
175
273
|
end
|
176
274
|
end
|
data/lib/iso8601/date.rb
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
module ISO8601
|
2
2
|
##
|
3
|
-
# A Date representation
|
3
|
+
# A Date representation.
|
4
4
|
#
|
5
5
|
# @example
|
6
|
-
# d = Date.new('2014-05-28')
|
6
|
+
# d = ISO8601::Date.new('2014-05-28')
|
7
7
|
# d.year # => 2014
|
8
8
|
# d.month # => 5
|
9
9
|
#
|
10
10
|
# @example Week dates
|
11
|
-
# d = Date.new('2014-W15-2')
|
11
|
+
# d = ISO8601::Date.new('2014-W15-2')
|
12
12
|
# d.day # => 27
|
13
13
|
# d.wday # => 2
|
14
14
|
# d.week # => 15
|
@@ -17,7 +17,7 @@ module ISO8601
|
|
17
17
|
|
18
18
|
def_delegators(:@date,
|
19
19
|
:to_s, :to_time, :to_date, :to_datetime,
|
20
|
-
:year, :month, :day, :wday
|
20
|
+
:year, :month, :day, :wday)
|
21
21
|
##
|
22
22
|
# The original atoms
|
23
23
|
attr_reader :atoms
|
@@ -61,6 +61,25 @@ module ISO8601
|
|
61
61
|
def to_a
|
62
62
|
[year, month, day]
|
63
63
|
end
|
64
|
+
##
|
65
|
+
# @param [#hash] contrast The contrast to compare against
|
66
|
+
#
|
67
|
+
# @return [Boolean]
|
68
|
+
def ==(contrast)
|
69
|
+
(hash == contrast.hash)
|
70
|
+
end
|
71
|
+
##
|
72
|
+
# @param [#hash] contrast The contrast to compare against
|
73
|
+
#
|
74
|
+
# @return [Boolean]
|
75
|
+
def eql?(contrast)
|
76
|
+
(hash == contrast.hash)
|
77
|
+
end
|
78
|
+
##
|
79
|
+
# @return [Fixnum]
|
80
|
+
def hash
|
81
|
+
[atoms, self.class].hash
|
82
|
+
end
|
64
83
|
|
65
84
|
private
|
66
85
|
##
|
@@ -12,7 +12,7 @@ module ISO8601
|
|
12
12
|
|
13
13
|
def_delegators(:@date_time,
|
14
14
|
:strftime, :to_time, :to_date, :to_datetime,
|
15
|
-
:year, :month, :day, :hour, :minute, :zone
|
15
|
+
:year, :month, :day, :hour, :minute, :zone)
|
16
16
|
|
17
17
|
attr_reader :second
|
18
18
|
|
@@ -57,6 +57,26 @@ module ISO8601
|
|
57
57
|
def to_a
|
58
58
|
[year, month, day, hour, minute, second, zone]
|
59
59
|
end
|
60
|
+
##
|
61
|
+
# @param [#hash] contrast The contrast to compare against
|
62
|
+
#
|
63
|
+
# @return [Boolean]
|
64
|
+
def ==(contrast)
|
65
|
+
(hash == contrast.hash)
|
66
|
+
end
|
67
|
+
##
|
68
|
+
# @param [#hash] contrast The contrast to compare against
|
69
|
+
#
|
70
|
+
# @return [Boolean]
|
71
|
+
def eql?(contrast)
|
72
|
+
(hash == contrast.hash)
|
73
|
+
end
|
74
|
+
##
|
75
|
+
# @return [Fixnum]
|
76
|
+
def hash
|
77
|
+
[second, self.class].hash
|
78
|
+
end
|
79
|
+
|
60
80
|
|
61
81
|
private
|
62
82
|
##
|
data/lib/iso8601/duration.rb
CHANGED
@@ -2,111 +2,111 @@
|
|
2
2
|
|
3
3
|
module ISO8601
|
4
4
|
##
|
5
|
-
#
|
5
|
+
# A duration representation. When no base is provided, all atoms use an
|
6
|
+
# average factor which affects the result of any computation like `#to_seconds`.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# d = ISO8601::Duration.new('P2Y1MT2H')
|
10
|
+
# d.years # => #<ISO8601::Years:0x000000051adee8 @atom=2.0, @base=nil>
|
11
|
+
# d.months # => #<ISO8601::Months:0x00000004f230b0 @atom=1.0, @base=nil>
|
12
|
+
# d.days # => #<ISO8601::Days:0x00000005205468 @atom=0, @base=nil>
|
13
|
+
# d.hours # => #<ISO8601::Hours:0x000000051e02a8 @atom=2.0, @base=nil>
|
14
|
+
# d.to_seconds # => 65707200.0
|
15
|
+
#
|
16
|
+
# @example Explicit base date time
|
17
|
+
# d = ISO8601::Duration.new('P2Y1MT2H', ISO8601::DateTime.new('2014-08017'))
|
18
|
+
# d.years # => #<ISO8601::Years:0x000000051adee8 @atom=2.0, @base=#<ISO8601::DateTime...>>
|
19
|
+
# d.months # => #<ISO8601::Months:0x00000004f230b0 @atom=1.0, @base=#<ISO8601::DateTime...>>
|
20
|
+
# d.days # => #<ISO8601::Days:0x00000005205468 @atom=0, @base=#<ISO8601::DateTime...>>
|
21
|
+
# d.hours # => #<ISO8601::Hours:0x000000051e02a8 @atom=2.0, @base=#<ISO8601::DateTime...>>
|
22
|
+
# d.to_seconds # => 65757600.0
|
23
|
+
#
|
24
|
+
# @example Number of seconds versus patterns
|
25
|
+
# di = ISO8601::Duration.new(65707200)
|
26
|
+
# dp = ISO8601::Duration.new('P2Y1MT2H')
|
27
|
+
# ds = ISO8601::Duration.new('P65707200S')
|
28
|
+
# di == dp # => true
|
29
|
+
# di == ds # => true
|
6
30
|
#
|
7
|
-
# @todo Support fraction values for years, months, days, weeks, hours
|
8
|
-
# and minutes
|
9
31
|
class Duration
|
10
|
-
attr_reader :base, :atoms
|
11
32
|
##
|
12
|
-
# @param [String, Numeric]
|
33
|
+
# @param [String, Numeric] input The duration pattern
|
13
34
|
# @param [ISO8601::DateTime, nil] base (nil) The base datetime to
|
14
|
-
# calculate the duration
|
15
|
-
def initialize(
|
16
|
-
|
17
|
-
pattern =
|
18
|
-
@
|
19
|
-
|
20
|
-
(
|
21
|
-
(\d+(?:[,.]\d+)?Y)? # Years
|
22
|
-
(\d+(?:[.,]\d+)?M)? # Months
|
23
|
-
(\d+(?:[.,]\d+)?D)? # Days
|
24
|
-
(T
|
25
|
-
(\d+(?:[.,]\d+)?H)? # Hours
|
26
|
-
(\d+(?:[.,]\d+)?M)? # Minutes
|
27
|
-
(\d+(?:[.,]\d+)?S)? # Seconds
|
28
|
-
)? # Time
|
29
|
-
)
|
30
|
-
|(\d+(?:[.,]\d+)?W) # Weeks
|
31
|
-
) # Duration
|
32
|
-
$/x.match(pattern) or raise ISO8601::Errors::UnknownPattern.new(pattern)
|
33
|
-
|
34
|
-
@base = base
|
35
|
-
valid_pattern?
|
36
|
-
valid_base?
|
37
|
-
@atoms = {
|
38
|
-
:years => @duration[4].nil? ? 0 : @duration[4].chop.to_f * sign,
|
39
|
-
:months => @duration[5].nil? ? 0 : @duration[5].chop.to_f * sign,
|
40
|
-
:weeks => @duration[11].nil? ? 0 : @duration[11].chop.to_f * sign,
|
41
|
-
:days => @duration[6].nil? ? 0 : @duration[6].chop.to_f * sign,
|
42
|
-
:hours => @duration[8].nil? ? 0 : @duration[8].chop.to_f * sign,
|
43
|
-
:minutes => @duration[9].nil? ? 0 : @duration[9].chop.to_f * sign,
|
44
|
-
:seconds => @duration[10].nil? ? 0 : @duration[10].chop.to_f * sign
|
45
|
-
}
|
46
|
-
valid_fractions?
|
35
|
+
# calculate the duration against an specific point in time.
|
36
|
+
def initialize(input, base = nil)
|
37
|
+
@original = input
|
38
|
+
@pattern = to_pattern
|
39
|
+
@atoms = atomize(@pattern)
|
40
|
+
@base = validate_base(base)
|
47
41
|
end
|
48
42
|
##
|
43
|
+
# Raw atoms result of parsing the given pattern.
|
44
|
+
#
|
45
|
+
# @return [Hash<Float>]
|
46
|
+
attr_reader :atoms
|
47
|
+
##
|
48
|
+
# Datetime base.
|
49
|
+
#
|
50
|
+
# @return [ISO8601::DateTime, nil]
|
51
|
+
attr_reader :base
|
52
|
+
##
|
49
53
|
# Assigns a new base datetime
|
50
54
|
#
|
51
55
|
# @return [ISO8601::DateTime, nil]
|
52
56
|
def base=(value)
|
53
|
-
@base = value
|
54
|
-
|
55
|
-
return @base
|
57
|
+
@base = validate_base(value)
|
58
|
+
@base
|
56
59
|
end
|
57
60
|
##
|
58
61
|
# @return [String] The string representation of the duration
|
59
|
-
|
60
|
-
|
61
|
-
end
|
62
|
+
attr_reader :pattern
|
63
|
+
alias_method :to_s, :pattern
|
62
64
|
##
|
63
65
|
# @return [ISO8601::Years] The years of the duration
|
64
66
|
def years
|
65
|
-
ISO8601::Years.new(
|
67
|
+
ISO8601::Years.new(atoms[:years], base)
|
66
68
|
end
|
67
69
|
##
|
68
70
|
# @return [ISO8601::Months] The months of the duration
|
69
71
|
def months
|
70
72
|
# Changes the base to compute the months for the right base year
|
71
|
-
|
72
|
-
ISO8601::Months.new(
|
73
|
+
month_base = base.nil? ? nil : base + years.to_seconds
|
74
|
+
ISO8601::Months.new(atoms[:months], month_base)
|
73
75
|
end
|
74
76
|
##
|
75
77
|
# @return [ISO8601::Weeks] The weeks of the duration
|
76
78
|
def weeks
|
77
|
-
ISO8601::Weeks.new(
|
79
|
+
ISO8601::Weeks.new(atoms[:weeks], base)
|
78
80
|
end
|
79
81
|
##
|
80
82
|
# @return [ISO8601::Days] The days of the duration
|
81
83
|
def days
|
82
|
-
ISO8601::Days.new(
|
84
|
+
ISO8601::Days.new(atoms[:days], base)
|
83
85
|
end
|
84
86
|
##
|
85
87
|
# @return [ISO8601::Hours] The hours of the duration
|
86
88
|
def hours
|
87
|
-
ISO8601::Hours.new(
|
89
|
+
ISO8601::Hours.new(atoms[:hours], base)
|
88
90
|
end
|
89
91
|
##
|
90
92
|
# @return [ISO8601::Minutes] The minutes of the duration
|
91
93
|
def minutes
|
92
|
-
ISO8601::Minutes.new(
|
94
|
+
ISO8601::Minutes.new(atoms[:minutes], base)
|
93
95
|
end
|
94
96
|
##
|
95
97
|
# @return [ISO8601::Seconds] The seconds of the duration
|
96
98
|
def seconds
|
97
|
-
ISO8601::Seconds.new(
|
99
|
+
ISO8601::Seconds.new(atoms[:seconds], base)
|
98
100
|
end
|
99
101
|
##
|
100
|
-
#
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
end
|
102
|
+
# The Integer representation of the duration sign.
|
103
|
+
#
|
104
|
+
# @return [Integer]
|
105
|
+
attr_reader :sign
|
105
106
|
##
|
106
107
|
# @return [ISO8601::Duration] The absolute representation of the duration
|
107
108
|
def abs
|
108
|
-
|
109
|
-
return ISO8601::Duration.new(absolute)
|
109
|
+
self.class.new(pattern.sub(/^[-+]/, ''), base)
|
110
110
|
end
|
111
111
|
##
|
112
112
|
# Addition
|
@@ -116,10 +116,12 @@ module ISO8601
|
|
116
116
|
# @raise [ISO8601::Errors::DurationBaseError] If bases doesn't match
|
117
117
|
# @return [ISO8601::Duration]
|
118
118
|
def +(duration)
|
119
|
-
|
119
|
+
compare_bases(duration)
|
120
|
+
|
120
121
|
d1 = to_seconds
|
121
122
|
d2 = duration.to_seconds
|
122
|
-
|
123
|
+
|
124
|
+
seconds_to_iso(d1 + d2)
|
123
125
|
end
|
124
126
|
##
|
125
127
|
# Substraction
|
@@ -129,15 +131,12 @@ module ISO8601
|
|
129
131
|
# @raise [ISO8601::Errors::DurationBaseError] If bases doesn't match
|
130
132
|
# @return [ISO8601::Duration]
|
131
133
|
def -(duration)
|
132
|
-
|
134
|
+
compare_bases(duration)
|
135
|
+
|
133
136
|
d1 = to_seconds
|
134
137
|
d2 = duration.to_seconds
|
135
|
-
|
136
|
-
|
137
|
-
return ISO8601::Duration.new('PT0S')
|
138
|
-
else
|
139
|
-
return seconds_to_iso(duration)
|
140
|
-
end
|
138
|
+
|
139
|
+
seconds_to_iso(d1 - d2)
|
141
140
|
end
|
142
141
|
##
|
143
142
|
# @param [ISO8601::Duration] duration The duration to compare
|
@@ -145,67 +144,145 @@ module ISO8601
|
|
145
144
|
# @raise [ISO8601::Errors::DurationBaseError] If bases doesn't match
|
146
145
|
# @return [Boolean]
|
147
146
|
def ==(duration)
|
148
|
-
|
149
|
-
|
147
|
+
compare_bases(duration)
|
148
|
+
|
149
|
+
(to_seconds == duration.to_seconds)
|
150
|
+
end
|
151
|
+
##
|
152
|
+
# @param [ISO8601::Duration] duration The duration to compare
|
153
|
+
#
|
154
|
+
# @return [Boolean]
|
155
|
+
def eql?(duration)
|
156
|
+
(hash == duration.hash)
|
150
157
|
end
|
151
158
|
##
|
152
159
|
# @return [Fixnum]
|
153
160
|
def hash
|
154
|
-
|
161
|
+
[atoms.values, self.class].hash
|
162
|
+
end
|
163
|
+
##
|
164
|
+
# Converts original input into a valid ISO 8601 duration pattern.
|
165
|
+
#
|
166
|
+
# @return [String]
|
167
|
+
def to_pattern
|
168
|
+
(@original.kind_of? Numeric) ? "PT#{@original}S" : @original
|
169
|
+
end
|
170
|
+
##
|
171
|
+
# @return [Numeric] The duration in seconds
|
172
|
+
def to_seconds
|
173
|
+
[years, months, weeks, days, hours, minutes, seconds].map(&:to_seconds).reduce(&:+)
|
155
174
|
end
|
156
175
|
|
157
176
|
|
158
177
|
private
|
159
178
|
##
|
160
|
-
#
|
179
|
+
# Splits a duration pattern into valid atoms.
|
180
|
+
#
|
181
|
+
# Acceptable patterns:
|
182
|
+
#
|
183
|
+
# * PnYnMnD
|
184
|
+
# * PTnHnMnS
|
185
|
+
# * PnYnMnDTnHnMnS
|
186
|
+
# * PnW
|
187
|
+
#
|
188
|
+
# Where `n` is any number. If it contains a decimal fraction, a dot (`.`) or
|
189
|
+
# comma (`,`) can be used.
|
190
|
+
#
|
191
|
+
# @param [String] input
|
192
|
+
#
|
193
|
+
# @return [Hash<Float>]
|
194
|
+
def atomize(input)
|
195
|
+
duration = input.match(/^
|
196
|
+
(?<sign>\+|-)?
|
197
|
+
P(?:
|
198
|
+
(?:
|
199
|
+
(?:(?<years>\d+(?:[,.]\d+)?)Y)?
|
200
|
+
(?:(?<months>\d+(?:[.,]\d+)?)M)?
|
201
|
+
(?:(?<days>\d+(?:[.,]\d+)?)D)?
|
202
|
+
(?<time>T
|
203
|
+
(?:(?<hours>\d+(?:[.,]\d+)?)H)?
|
204
|
+
(?:(?<minutes>\d+(?:[.,]\d+)?)M)?
|
205
|
+
(?:(?<seconds>\d+(?:[.,]\d+)?)S)?
|
206
|
+
)?
|
207
|
+
) |
|
208
|
+
(?<weeks>\d+(?:[.,]\d+)?W)
|
209
|
+
) # Duration
|
210
|
+
$/x) or raise ISO8601::Errors::UnknownPattern.new(input)
|
211
|
+
|
212
|
+
valid_pattern?(duration)
|
213
|
+
|
214
|
+
@sign = (duration[:sign].nil? || duration[:sign] == '+') ? 1 : -1
|
215
|
+
|
216
|
+
keys = duration.names.map(&:to_sym)
|
217
|
+
values = duration.captures.map { |v| v.to_f * sign }
|
218
|
+
components = Hash[keys.zip(values)]
|
219
|
+
components.delete(:time) # clean time capture
|
220
|
+
|
221
|
+
valid_fractions?(components.values)
|
222
|
+
|
223
|
+
components
|
224
|
+
end
|
225
|
+
##
|
226
|
+
# @param [Numeric] value The seconds to promote
|
161
227
|
#
|
162
228
|
# @return [ISO8601::Duration]
|
163
|
-
def seconds_to_iso(
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
end
|
185
|
-
|
186
|
-
def sign
|
187
|
-
(
|
188
|
-
end
|
189
|
-
def valid_base?
|
190
|
-
if !(@base.nil? or @base.kind_of? ISO8601::DateTime)
|
191
|
-
raise TypeError
|
192
|
-
end
|
229
|
+
def seconds_to_iso(value)
|
230
|
+
return self.class.new('PT0S') if value.zero?
|
231
|
+
|
232
|
+
sign_str = (value < 0) ? '-' : ''
|
233
|
+
value = value.abs
|
234
|
+
|
235
|
+
y, y_mod = decompose_atom(value, years)
|
236
|
+
m, m_mod = decompose_atom(y_mod, months)
|
237
|
+
d, d_mod = decompose_atom(m_mod, days)
|
238
|
+
h, h_mod = decompose_atom(d_mod, hours)
|
239
|
+
mi, mi_mod = decompose_atom(h_mod, minutes)
|
240
|
+
s = Seconds.new(mi_mod)
|
241
|
+
|
242
|
+
date = to_date_s(sign_str, y, m, d)
|
243
|
+
time = to_time_s(h, mi, s)
|
244
|
+
|
245
|
+
self.class.new(date + time)
|
246
|
+
end
|
247
|
+
|
248
|
+
def decompose_atom(value, atom)
|
249
|
+
[atom.class.new((value / atom.factor).to_i), (value % atom.factor)]
|
250
|
+
end
|
251
|
+
|
252
|
+
def to_date_s(sign, *args)
|
253
|
+
"#{sign}P#{args.map(&:to_s).join('')}"
|
193
254
|
end
|
194
|
-
def valid_pattern?
|
195
|
-
if @duration.nil? or
|
196
|
-
(@duration[4].nil? and @duration[5].nil? and @duration[6].nil? and @duration[7].nil? and @duration[11].nil?) or
|
197
|
-
(!@duration[7].nil? and @duration[8].nil? and @duration[9].nil? and @duration[10].nil? and @duration[11].nil?)
|
198
255
|
|
199
|
-
|
256
|
+
def to_time_s(*args)
|
257
|
+
(args.map(&:value).reduce(&:+) > 0) ? "T#{args.map(&:to_s).join('')}" : ''
|
258
|
+
end
|
259
|
+
|
260
|
+
def validate_base(input)
|
261
|
+
raise ISO8601::Errors::TypeError if !(input.nil? or input.kind_of? ISO8601::DateTime)
|
262
|
+
|
263
|
+
input
|
264
|
+
end
|
265
|
+
def valid_pattern?(components)
|
266
|
+
date = [components[:years], components[:months], components[:days]].compact
|
267
|
+
time = [components[:hours], components[:minutes], components[:seconds]].compact
|
268
|
+
weeks = components[:weeks]
|
269
|
+
all = [date, time, weeks].flatten.compact
|
270
|
+
|
271
|
+
if all.empty? || (!components[:time].nil? && time.empty? && weeks.nil?)
|
272
|
+
raise ISO8601::Errors::UnknownPattern.new(@pattern)
|
200
273
|
end
|
201
274
|
end
|
202
275
|
|
203
|
-
def valid_fractions?
|
204
|
-
values =
|
276
|
+
def valid_fractions?(values)
|
277
|
+
values = values.reject(&:zero?)
|
205
278
|
fractions = values.select { |a| (a % 1) != 0 }
|
206
279
|
if fractions.size > 1 || (fractions.size == 1 && fractions.last != values.last)
|
207
|
-
raise ISO8601::Errors::InvalidFractions.new(@
|
280
|
+
raise ISO8601::Errors::InvalidFractions.new(@pattern)
|
208
281
|
end
|
209
282
|
end
|
283
|
+
|
284
|
+
def compare_bases(duration)
|
285
|
+
raise ISO8601::Errors::DurationBaseError.new(duration) if base.to_s != duration.base.to_s
|
286
|
+
end
|
210
287
|
end
|
211
288
|
end
|