iso8601 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|