iso8601 0.8.7 → 0.9.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 +17 -0
- data/CONTRIBUTING.md +58 -0
- data/Gemfile +2 -2
- data/README.md +28 -102
- data/docs/date-time.md +86 -0
- data/docs/duration.md +77 -0
- data/docs/time-interval.md +120 -0
- data/iso8601.gemspec +43 -6
- data/lib/iso8601.rb +15 -7
- data/lib/iso8601/atomic.rb +78 -0
- data/lib/iso8601/date.rb +35 -10
- data/lib/iso8601/date_time.rb +14 -3
- data/lib/iso8601/days.rb +47 -0
- data/lib/iso8601/duration.rb +115 -99
- data/lib/iso8601/errors.rb +13 -1
- data/lib/iso8601/hours.rb +43 -0
- data/lib/iso8601/minutes.rb +43 -0
- data/lib/iso8601/months.rb +98 -0
- data/lib/iso8601/seconds.rb +47 -0
- data/lib/iso8601/time.rb +43 -15
- data/lib/iso8601/time_interval.rb +392 -0
- data/lib/iso8601/version.rb +1 -1
- data/lib/iso8601/weeks.rb +43 -0
- data/lib/iso8601/years.rb +80 -0
- data/spec/iso8601/date_spec.rb +0 -6
- data/spec/iso8601/date_time_spec.rb +0 -8
- data/spec/iso8601/days_spec.rb +44 -0
- data/spec/iso8601/duration_spec.rb +103 -99
- data/spec/iso8601/hours_spec.rb +44 -0
- data/spec/iso8601/minutes_spec.rb +44 -0
- data/spec/iso8601/months_spec.rb +86 -0
- data/spec/iso8601/seconds_spec.rb +44 -0
- data/spec/iso8601/time_interval_spec.rb +416 -0
- data/spec/iso8601/time_spec.rb +0 -6
- data/spec/iso8601/weeks_spec.rb +46 -0
- data/spec/iso8601/years_spec.rb +69 -0
- metadata +37 -19
- data/.dockerignore +0 -7
- data/.editorconfig +0 -9
- data/.gitignore +0 -19
- data/.rubocop.yml +0 -38
- data/.travis.yml +0 -19
- data/Dockerfile +0 -10
- data/Makefile +0 -19
- data/circle.yml +0 -13
- data/lib/iso8601/atoms.rb +0 -279
- data/spec/iso8601/atoms_spec.rb +0 -329
data/iso8601.gemspec
CHANGED
@@ -18,13 +18,50 @@ Gem::Specification.new do |s|
|
|
18
18
|
EOD
|
19
19
|
s.license = 'MIT'
|
20
20
|
s.rubyforge_project = 'iso8601'
|
21
|
-
|
22
|
-
|
23
|
-
|
21
|
+
s.files = %W(CHANGELOG.md
|
22
|
+
CONTRIBUTING.md
|
23
|
+
Gemfile
|
24
|
+
LICENSE
|
25
|
+
README.md
|
26
|
+
Rakefile
|
27
|
+
iso8601.gemspec
|
28
|
+
docs/date-time.md
|
29
|
+
docs/duration.md
|
30
|
+
docs/time-interval.md
|
31
|
+
lib/iso8601.rb
|
32
|
+
lib/iso8601/atomic.rb
|
33
|
+
lib/iso8601/date.rb
|
34
|
+
lib/iso8601/date_time.rb
|
35
|
+
lib/iso8601/days.rb
|
36
|
+
lib/iso8601/duration.rb
|
37
|
+
lib/iso8601/errors.rb
|
38
|
+
lib/iso8601/hours.rb
|
39
|
+
lib/iso8601/minutes.rb
|
40
|
+
lib/iso8601/months.rb
|
41
|
+
lib/iso8601/seconds.rb
|
42
|
+
lib/iso8601/time.rb
|
43
|
+
lib/iso8601/time_interval.rb
|
44
|
+
lib/iso8601/version.rb
|
45
|
+
lib/iso8601/weeks.rb
|
46
|
+
lib/iso8601/years.rb
|
47
|
+
spec/iso8601/date_spec.rb
|
48
|
+
spec/iso8601/date_time_spec.rb
|
49
|
+
spec/iso8601/days_spec.rb
|
50
|
+
spec/iso8601/duration_spec.rb
|
51
|
+
spec/iso8601/hours_spec.rb
|
52
|
+
spec/iso8601/minutes_spec.rb
|
53
|
+
spec/iso8601/months_spec.rb
|
54
|
+
spec/iso8601/seconds_spec.rb
|
55
|
+
spec/iso8601/time_interval_spec.rb
|
56
|
+
spec/iso8601/time_spec.rb
|
57
|
+
spec/iso8601/weeks_spec.rb
|
58
|
+
spec/iso8601/years_spec.rb
|
59
|
+
spec/spec_helper.rb)
|
60
|
+
s.test_files = s.files.grep(%r{^spec/})
|
24
61
|
s.require_paths = ['lib']
|
25
62
|
|
26
63
|
s.has_rdoc = 'yard'
|
27
|
-
s.required_ruby_version = '>=
|
28
|
-
s.add_development_dependency 'rspec', '~> 3.
|
29
|
-
s.add_development_dependency 'rubocop', '~> 0.
|
64
|
+
s.required_ruby_version = '>= 2.0.0'
|
65
|
+
s.add_development_dependency 'rspec', '~> 3.4'
|
66
|
+
s.add_development_dependency 'rubocop', '~> 0.35'
|
30
67
|
end
|
data/lib/iso8601.rb
CHANGED
@@ -3,10 +3,18 @@
|
|
3
3
|
require 'time'
|
4
4
|
require 'forwardable'
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
6
|
+
require_relative 'iso8601/version'
|
7
|
+
require_relative 'iso8601/errors'
|
8
|
+
require_relative 'iso8601/atomic'
|
9
|
+
require_relative 'iso8601/years'
|
10
|
+
require_relative 'iso8601/months'
|
11
|
+
require_relative 'iso8601/weeks'
|
12
|
+
require_relative 'iso8601/days'
|
13
|
+
require_relative 'iso8601/hours'
|
14
|
+
require_relative 'iso8601/minutes'
|
15
|
+
require_relative 'iso8601/seconds'
|
16
|
+
require_relative 'iso8601/date'
|
17
|
+
require_relative 'iso8601/time'
|
18
|
+
require_relative 'iso8601/date_time'
|
19
|
+
require_relative 'iso8601/duration'
|
20
|
+
require_relative 'iso8601/time_interval'
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module ISO8601
|
4
|
+
module Atomic
|
5
|
+
include Comparable
|
6
|
+
|
7
|
+
attr_reader :atom
|
8
|
+
|
9
|
+
##
|
10
|
+
# The integer representation
|
11
|
+
#
|
12
|
+
# @return [Integer]
|
13
|
+
def to_i
|
14
|
+
atom.to_i
|
15
|
+
end
|
16
|
+
|
17
|
+
##
|
18
|
+
# The float representation
|
19
|
+
#
|
20
|
+
# @return [Float]
|
21
|
+
def to_f
|
22
|
+
atom.to_f
|
23
|
+
end
|
24
|
+
|
25
|
+
##
|
26
|
+
# Returns the ISO 8601 representation for the atom
|
27
|
+
#
|
28
|
+
# @return [String]
|
29
|
+
def to_s
|
30
|
+
(value.zero?) ? '' : "#{value}#{symbol}"
|
31
|
+
end
|
32
|
+
|
33
|
+
##
|
34
|
+
# The simplest numeric representation. If modulo equals 0 returns an
|
35
|
+
# integer else a float.
|
36
|
+
#
|
37
|
+
# @return [Numeric]
|
38
|
+
def value
|
39
|
+
(atom % 1).zero? ? atom.to_i : atom
|
40
|
+
end
|
41
|
+
|
42
|
+
##
|
43
|
+
# @param [Atom] other The contrast to compare against
|
44
|
+
#
|
45
|
+
# @return [-1, 0, 1]
|
46
|
+
def <=>(other)
|
47
|
+
return nil unless other.is_a?(self.class)
|
48
|
+
|
49
|
+
to_f <=> other.to_f
|
50
|
+
end
|
51
|
+
|
52
|
+
##
|
53
|
+
# @param [#hash] other The contrast to compare against
|
54
|
+
#
|
55
|
+
# @return [Boolean]
|
56
|
+
def eql?(other)
|
57
|
+
(hash == other.hash)
|
58
|
+
end
|
59
|
+
|
60
|
+
##
|
61
|
+
# @return [Fixnum]
|
62
|
+
def hash
|
63
|
+
[atom, self.class].hash
|
64
|
+
end
|
65
|
+
|
66
|
+
##
|
67
|
+
# Validates the atom is a Numeric
|
68
|
+
def valid_atom?(atom)
|
69
|
+
fail ISO8601::Errors::TypeError,
|
70
|
+
"The atom argument for #{self.class} should be a Numeric value." unless atom.is_a?(Numeric)
|
71
|
+
end
|
72
|
+
|
73
|
+
def valid_base?(base)
|
74
|
+
fail ISO8601::Errors::TypeError,
|
75
|
+
"The base argument for #{self.class} should be a ISO8601::DateTime instance or nil." unless base.is_a?(ISO8601::DateTime) || base.nil?
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/lib/iso8601/date.rb
CHANGED
@@ -20,12 +20,15 @@ module ISO8601
|
|
20
20
|
:to_s, :to_time, :to_date, :to_datetime,
|
21
21
|
:year, :month, :day, :wday
|
22
22
|
)
|
23
|
+
|
23
24
|
##
|
24
25
|
# The original atoms
|
25
26
|
attr_reader :atoms
|
27
|
+
|
26
28
|
##
|
27
29
|
# The separator used in the original ISO 8601 string.
|
28
30
|
attr_reader :separator
|
31
|
+
|
29
32
|
##
|
30
33
|
# @param [String] input The date pattern
|
31
34
|
def initialize(input)
|
@@ -33,6 +36,7 @@ module ISO8601
|
|
33
36
|
@atoms = atomize(input)
|
34
37
|
@date = compose(@atoms)
|
35
38
|
end
|
39
|
+
|
36
40
|
##
|
37
41
|
# The calendar week number (1-53)
|
38
42
|
#
|
@@ -40,6 +44,7 @@ module ISO8601
|
|
40
44
|
def week
|
41
45
|
@date.cweek
|
42
46
|
end
|
47
|
+
|
43
48
|
##
|
44
49
|
# Forwards the date the given amount of days.
|
45
50
|
#
|
@@ -50,6 +55,7 @@ module ISO8601
|
|
50
55
|
other = other.to_days if other.respond_to?(:to_days)
|
51
56
|
ISO8601::Date.new((@date + other).iso8601)
|
52
57
|
end
|
58
|
+
|
53
59
|
##
|
54
60
|
# Backwards the date the given amount of days.
|
55
61
|
#
|
@@ -60,11 +66,13 @@ module ISO8601
|
|
60
66
|
other = other.to_days if other.respond_to?(:to_days)
|
61
67
|
ISO8601::Date.new((@date - other).iso8601)
|
62
68
|
end
|
69
|
+
|
63
70
|
##
|
64
71
|
# Converts self to an array of atoms.
|
65
72
|
def to_a
|
66
73
|
[year, month, day]
|
67
74
|
end
|
75
|
+
|
68
76
|
##
|
69
77
|
# @param [#hash] other The contrast to compare against
|
70
78
|
#
|
@@ -72,6 +80,7 @@ module ISO8601
|
|
72
80
|
def ==(other)
|
73
81
|
(hash == other.hash)
|
74
82
|
end
|
83
|
+
|
75
84
|
##
|
76
85
|
# @param [#hash] other The contrast to compare against
|
77
86
|
#
|
@@ -79,6 +88,7 @@ module ISO8601
|
|
79
88
|
def eql?(other)
|
80
89
|
(hash == other.hash)
|
81
90
|
end
|
91
|
+
|
82
92
|
##
|
83
93
|
# @return [Fixnum]
|
84
94
|
def hash
|
@@ -103,14 +113,31 @@ module ISO8601
|
|
103
113
|
#
|
104
114
|
# @return [Array<Integer>]
|
105
115
|
def atomize(input)
|
106
|
-
week_date =
|
116
|
+
week_date = parse_weekdate(input)
|
107
117
|
return atomize_week_date(input, week_date[2], week_date[1]) unless week_date.nil?
|
108
118
|
|
109
|
-
|
110
|
-
_, sign, year, separator, day = ordinal_regexp.match(input).to_a.compact
|
119
|
+
_, sign, year, separator, day = parse_ordinal(input)
|
111
120
|
return atomize_ordinal(year, day, separator, sign) unless year.nil?
|
112
121
|
|
113
|
-
_, year, separator, month, day =
|
122
|
+
_, year, separator, month, day = parse_date(input)
|
123
|
+
|
124
|
+
fail ISO8601::Errors::UnknownPattern, @original if year.nil?
|
125
|
+
|
126
|
+
@separator = separator
|
127
|
+
|
128
|
+
[year, month, day].compact.map(&:to_i)
|
129
|
+
end
|
130
|
+
|
131
|
+
def parse_weekdate(input)
|
132
|
+
/^([+-]?)\d{4}(-?)W\d{2}(?:\2\d)?$/.match(input)
|
133
|
+
end
|
134
|
+
|
135
|
+
def parse_ordinal(input)
|
136
|
+
/^([+-]?)(\d{4})(-?)(\d{3})$/.match(input).to_a.compact
|
137
|
+
end
|
138
|
+
|
139
|
+
def parse_date(input)
|
140
|
+
/^
|
114
141
|
([+-]?\d{4}) # YYYY
|
115
142
|
(?:
|
116
143
|
(-?)(\d{2}) # YYYY-MM
|
@@ -119,13 +146,8 @@ module ISO8601
|
|
119
146
|
)?
|
120
147
|
)?
|
121
148
|
$/x.match(input).to_a.compact
|
122
|
-
|
123
|
-
fail ISO8601::Errors::UnknownPattern, @original if year.nil?
|
124
|
-
|
125
|
-
@separator = separator
|
126
|
-
|
127
|
-
[year, month, day].compact.map(&:to_i)
|
128
149
|
end
|
150
|
+
|
129
151
|
##
|
130
152
|
# Parses a week date (YYYY-Www-D, YYYY-Www) and returns its atoms.
|
131
153
|
#
|
@@ -141,6 +163,7 @@ module ISO8601
|
|
141
163
|
|
142
164
|
[sign * date.year, date.month, date.day]
|
143
165
|
end
|
166
|
+
|
144
167
|
##
|
145
168
|
# Parses an ordinal date (YYYY-DDD) and returns its atoms.
|
146
169
|
#
|
@@ -157,6 +180,7 @@ module ISO8601
|
|
157
180
|
|
158
181
|
[sign * date.year, date.month, date.day]
|
159
182
|
end
|
183
|
+
|
160
184
|
##
|
161
185
|
# Wraps ::Date.parse to play nice with ArgumentError.
|
162
186
|
#
|
@@ -168,6 +192,7 @@ module ISO8601
|
|
168
192
|
rescue ArgumentError
|
169
193
|
raise ISO8601::Errors::RangeError, @original
|
170
194
|
end
|
195
|
+
|
171
196
|
##
|
172
197
|
# Wraps ::Date.new to play nice with ArgumentError.
|
173
198
|
#
|
data/lib/iso8601/date_time.rb
CHANGED
@@ -25,6 +25,7 @@ module ISO8601
|
|
25
25
|
@date_time = parse(date_time)
|
26
26
|
@second = @date_time.second + @date_time.second_fraction.to_f.round(1)
|
27
27
|
end
|
28
|
+
|
28
29
|
##
|
29
30
|
# Addition
|
30
31
|
#
|
@@ -34,6 +35,7 @@ module ISO8601
|
|
34
35
|
|
35
36
|
self.class.new(moment.strftime('%Y-%m-%dT%H:%M:%S.%N%:z'))
|
36
37
|
end
|
38
|
+
|
37
39
|
##
|
38
40
|
# Substraction
|
39
41
|
#
|
@@ -43,24 +45,28 @@ module ISO8601
|
|
43
45
|
|
44
46
|
self.class.new(moment.strftime('%Y-%m-%dT%H:%M:%S.%N%:z'))
|
45
47
|
end
|
48
|
+
|
46
49
|
##
|
47
50
|
# Converts DateTime to a formated string
|
48
51
|
def to_s
|
49
|
-
second_format = (second % 1).zero? ? '%02d'
|
52
|
+
second_format = format((second % 1).zero? ? '%02d' : '%04.1f', second)
|
50
53
|
|
51
|
-
"%04d-%02d-%02dT%02d:%02d:#{second_format}#{zone}"
|
54
|
+
format("%04d-%02d-%02dT%02d:%02d:#{second_format}#{zone}", *atoms)
|
52
55
|
end
|
56
|
+
|
53
57
|
##
|
54
58
|
# Converts DateTime to an array of atoms.
|
55
59
|
def to_a
|
56
60
|
[year, month, day, hour, minute, second, zone]
|
57
61
|
end
|
58
62
|
alias_method :atoms, :to_a
|
63
|
+
|
59
64
|
##
|
60
65
|
# Converts DateTime to a floating point number of seconds since the Epoch.
|
61
66
|
def to_f
|
62
67
|
to_time.to_f
|
63
68
|
end
|
69
|
+
|
64
70
|
##
|
65
71
|
# @param [#hash] other The contrast to compare against
|
66
72
|
#
|
@@ -68,6 +74,7 @@ module ISO8601
|
|
68
74
|
def ==(other)
|
69
75
|
(hash == other.hash)
|
70
76
|
end
|
77
|
+
|
71
78
|
##
|
72
79
|
# @param [#hash] other The contrast to compare against
|
73
80
|
#
|
@@ -75,6 +82,7 @@ module ISO8601
|
|
75
82
|
def eql?(other)
|
76
83
|
(hash == other.hash)
|
77
84
|
end
|
85
|
+
|
78
86
|
##
|
79
87
|
# @return [Fixnum]
|
80
88
|
def hash
|
@@ -106,6 +114,7 @@ module ISO8601
|
|
106
114
|
|
107
115
|
::DateTime.new(*(date_atoms + time_atoms).compact)
|
108
116
|
end
|
117
|
+
|
109
118
|
##
|
110
119
|
# Validates the date has the right pattern.
|
111
120
|
#
|
@@ -122,6 +131,7 @@ module ISO8601
|
|
122
131
|
|
123
132
|
date.atoms << date.separator
|
124
133
|
end
|
134
|
+
|
125
135
|
##
|
126
136
|
# @return [Array<String, nil>]
|
127
137
|
def parse_time(input)
|
@@ -141,11 +151,12 @@ module ISO8601
|
|
141
151
|
|
142
152
|
true
|
143
153
|
end
|
154
|
+
|
144
155
|
##
|
145
156
|
# If time is provided date must use a complete representation
|
146
157
|
def valid_representation?(date, time)
|
147
158
|
year, month, day = date
|
148
|
-
hour
|
159
|
+
hour = time.first
|
149
160
|
|
150
161
|
date.nil? || !(!year.nil? && (month.nil? || day.nil?) && !hour.nil?)
|
151
162
|
end
|
data/lib/iso8601/days.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module ISO8601
|
4
|
+
##
|
5
|
+
# The Days atom in a {ISO8601::Duration}
|
6
|
+
#
|
7
|
+
# A "calendar day" is the time interval which starts at a certain time of day
|
8
|
+
# at a certain "calendar day" and ends at the same time of day at the next
|
9
|
+
# "calendar day".
|
10
|
+
class Days
|
11
|
+
include Atomic
|
12
|
+
|
13
|
+
AVERAGE_FACTOR = 86400
|
14
|
+
|
15
|
+
##
|
16
|
+
# @param [Numeric] atom The atom value
|
17
|
+
def initialize(atom)
|
18
|
+
valid_atom?(atom)
|
19
|
+
|
20
|
+
@atom = atom
|
21
|
+
end
|
22
|
+
|
23
|
+
##
|
24
|
+
# The Day factor
|
25
|
+
#
|
26
|
+
# @return [Numeric]
|
27
|
+
def factor
|
28
|
+
AVERAGE_FACTOR
|
29
|
+
end
|
30
|
+
|
31
|
+
##
|
32
|
+
# The amount of seconds
|
33
|
+
#
|
34
|
+
# @return [Numeric]
|
35
|
+
def to_seconds
|
36
|
+
AVERAGE_FACTOR * atom
|
37
|
+
end
|
38
|
+
|
39
|
+
##
|
40
|
+
# The atom symbol.
|
41
|
+
#
|
42
|
+
# @return [Symbol]
|
43
|
+
def symbol
|
44
|
+
:D
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/iso8601/duration.rb
CHANGED
@@ -3,148 +3,129 @@
|
|
3
3
|
module ISO8601
|
4
4
|
##
|
5
5
|
# A duration representation. When no base is provided, all atoms use an
|
6
|
-
# average factor
|
7
|
-
# `#to_seconds`.
|
6
|
+
# average factor to compute the amount of seconds.
|
8
7
|
#
|
9
8
|
# @example
|
10
9
|
# d = ISO8601::Duration.new('P2Y1MT2H')
|
11
|
-
# d.years # => #<ISO8601::Years:0x000000051adee8 @atom=2.0
|
12
|
-
# d.months # => #<ISO8601::Months:0x00000004f230b0 @atom=1.0
|
13
|
-
# d.days # => #<ISO8601::Days:0x00000005205468 @atom=0
|
14
|
-
# d.hours # => #<ISO8601::Hours:0x000000051e02a8 @atom=2.0
|
10
|
+
# d.years # => #<ISO8601::Years:0x000000051adee8 @atom=2.0>
|
11
|
+
# d.months # => #<ISO8601::Months:0x00000004f230b0 @atom=1.0>
|
12
|
+
# d.days # => #<ISO8601::Days:0x00000005205468 @atom=0>
|
13
|
+
# d.hours # => #<ISO8601::Hours:0x000000051e02a8 @atom=2.0>
|
15
14
|
# d.to_seconds # => 65707200.0
|
16
15
|
#
|
17
16
|
# @example Explicit base date time
|
18
17
|
# base = ISO8601::DateTime.new('2014-08017')
|
19
|
-
# d
|
20
|
-
# d.years # => #<ISO8601::Years:0x000000051adee8 @atom=2.0,
|
21
|
-
# @base=#<ISO8601::DateTime...>>
|
22
|
-
# d.months # => #<ISO8601::Months:0x00000004f230b0 @atom=1.0,
|
23
|
-
# @base=#<ISO8601::DateTime...>>
|
24
|
-
# d.days # => #<ISO8601::Days:0x00000005205468 @atom=0,
|
25
|
-
# @base=#<ISO8601::DateTime...>>
|
26
|
-
# d.hours # => #<ISO8601::Hours:0x000000051e02a8 @atom=2.0,
|
27
|
-
# @base=#<ISO8601::DateTime...>>
|
28
|
-
# d.to_seconds # => 65757600.0
|
18
|
+
# d.to_seconds(base) # => 65757600.0
|
29
19
|
#
|
30
20
|
# @example Number of seconds versus patterns
|
31
21
|
# di = ISO8601::Duration.new(65707200)
|
32
|
-
# dp = ISO8601::Duration.new('P2Y1MT2H')
|
33
22
|
# ds = ISO8601::Duration.new('P65707200S')
|
23
|
+
# dp = ISO8601::Duration.new('P2Y1MT2H')
|
34
24
|
# di == dp # => true
|
35
25
|
# di == ds # => true
|
36
26
|
#
|
37
27
|
class Duration
|
38
28
|
##
|
39
29
|
# @param [String, Numeric] input The duration pattern
|
40
|
-
|
41
|
-
# calculate the duration against an specific point in time.
|
42
|
-
def initialize(input, base = nil)
|
30
|
+
def initialize(input)
|
43
31
|
@original = input
|
44
|
-
@pattern = to_pattern
|
32
|
+
@pattern = to_pattern(input)
|
45
33
|
@atoms = atomize(@pattern)
|
46
|
-
@base = validate_base(base)
|
47
34
|
end
|
35
|
+
|
48
36
|
##
|
49
37
|
# Raw atoms result of parsing the given pattern.
|
50
38
|
#
|
51
39
|
# @return [Hash<Float>]
|
52
40
|
attr_reader :atoms
|
53
|
-
|
54
|
-
# Datetime base.
|
55
|
-
#
|
56
|
-
# @return [ISO8601::DateTime, nil]
|
57
|
-
attr_reader :base
|
58
|
-
##
|
59
|
-
# Assigns a new base datetime
|
60
|
-
#
|
61
|
-
# @return [ISO8601::DateTime, nil]
|
62
|
-
def base=(value)
|
63
|
-
@base = validate_base(value)
|
64
|
-
@base
|
65
|
-
end
|
41
|
+
|
66
42
|
##
|
67
43
|
# @return [String] The string representation of the duration
|
68
44
|
attr_reader :pattern
|
69
45
|
alias_method :to_s, :pattern
|
46
|
+
|
70
47
|
##
|
71
48
|
# @return [ISO8601::Years] The years of the duration
|
72
49
|
def years
|
73
|
-
ISO8601::Years.new(atoms[:years]
|
50
|
+
ISO8601::Years.new(atoms[:years])
|
74
51
|
end
|
52
|
+
|
75
53
|
##
|
76
54
|
# @return [ISO8601::Months] The months of the duration
|
77
55
|
def months
|
78
|
-
|
79
|
-
month_base = base.nil? ? nil : base + years.to_seconds
|
80
|
-
ISO8601::Months.new(atoms[:months], month_base)
|
56
|
+
ISO8601::Months.new(atoms[:months])
|
81
57
|
end
|
58
|
+
|
82
59
|
##
|
83
60
|
# @return [ISO8601::Weeks] The weeks of the duration
|
84
61
|
def weeks
|
85
|
-
ISO8601::Weeks.new(atoms[:weeks]
|
62
|
+
ISO8601::Weeks.new(atoms[:weeks])
|
86
63
|
end
|
64
|
+
|
87
65
|
##
|
88
66
|
# @return [ISO8601::Days] The days of the duration
|
89
67
|
def days
|
90
|
-
ISO8601::Days.new(atoms[:days]
|
68
|
+
ISO8601::Days.new(atoms[:days])
|
91
69
|
end
|
70
|
+
|
92
71
|
##
|
93
72
|
# @return [ISO8601::Hours] The hours of the duration
|
94
73
|
def hours
|
95
|
-
ISO8601::Hours.new(atoms[:hours]
|
74
|
+
ISO8601::Hours.new(atoms[:hours])
|
96
75
|
end
|
76
|
+
|
97
77
|
##
|
98
78
|
# @return [ISO8601::Minutes] The minutes of the duration
|
99
79
|
def minutes
|
100
|
-
ISO8601::Minutes.new(atoms[:minutes]
|
80
|
+
ISO8601::Minutes.new(atoms[:minutes])
|
101
81
|
end
|
82
|
+
|
102
83
|
##
|
103
84
|
# @return [ISO8601::Seconds] The seconds of the duration
|
104
85
|
def seconds
|
105
|
-
ISO8601::Seconds.new(atoms[:seconds]
|
86
|
+
ISO8601::Seconds.new(atoms[:seconds])
|
106
87
|
end
|
88
|
+
|
107
89
|
##
|
108
90
|
# The Integer representation of the duration sign.
|
109
91
|
#
|
110
92
|
# @return [Integer]
|
111
93
|
attr_reader :sign
|
94
|
+
|
112
95
|
##
|
113
96
|
# @return [ISO8601::Duration] The absolute representation of the duration
|
114
97
|
def abs
|
115
|
-
self.class.new(pattern.sub(/^[-+]/, '')
|
98
|
+
self.class.new(pattern.sub(/^[-+]/, ''))
|
116
99
|
end
|
100
|
+
|
117
101
|
##
|
118
102
|
# Addition
|
119
103
|
#
|
120
104
|
# @param [ISO8601::Duration] other The duration to add
|
121
105
|
#
|
122
|
-
# @raise [ISO8601::Errors::DurationBaseError] If bases doesn't match
|
123
106
|
# @return [ISO8601::Duration]
|
124
107
|
def +(other)
|
125
|
-
|
126
|
-
seconds_to_iso(to_seconds + other.to_seconds)
|
108
|
+
seconds_to_iso(to_seconds + fetch_seconds(other))
|
127
109
|
end
|
110
|
+
|
128
111
|
##
|
129
112
|
# Substraction
|
130
113
|
#
|
131
114
|
# @param [ISO8601::Duration] other The duration to substract
|
132
115
|
#
|
133
|
-
# @raise [ISO8601::Errors::DurationBaseError] If bases doesn't match
|
134
116
|
# @return [ISO8601::Duration]
|
135
117
|
def -(other)
|
136
|
-
|
137
|
-
seconds_to_iso(to_seconds - other.to_seconds)
|
118
|
+
seconds_to_iso(to_seconds - fetch_seconds(other))
|
138
119
|
end
|
120
|
+
|
139
121
|
##
|
140
122
|
# @param [ISO8601::Duration] other The duration to compare
|
141
123
|
#
|
142
|
-
# @raise [ISO8601::Errors::DurationBaseError] If bases doesn't match
|
143
124
|
# @return [Boolean]
|
144
125
|
def ==(other)
|
145
|
-
|
146
|
-
(to_seconds == other.to_seconds)
|
126
|
+
(to_seconds == fetch_seconds(other))
|
147
127
|
end
|
128
|
+
|
148
129
|
##
|
149
130
|
# @param [ISO8601::Duration] other The duration to compare
|
150
131
|
#
|
@@ -152,42 +133,40 @@ module ISO8601
|
|
152
133
|
def eql?(other)
|
153
134
|
(hash == other.hash)
|
154
135
|
end
|
136
|
+
|
155
137
|
##
|
156
138
|
# @return [Fixnum]
|
157
139
|
def hash
|
158
140
|
[atoms.values, self.class].hash
|
159
141
|
end
|
142
|
+
|
160
143
|
##
|
161
144
|
# Converts original input into a valid ISO 8601 duration pattern.
|
162
145
|
#
|
163
146
|
# @return [String]
|
164
|
-
def to_pattern
|
165
|
-
(
|
147
|
+
def to_pattern(original)
|
148
|
+
(original.is_a? Numeric) ? "PT#{original}S" : original
|
166
149
|
end
|
150
|
+
|
167
151
|
##
|
152
|
+
# @param [ISO8601::DateTime, nil] base (nil) The base datetime to
|
153
|
+
# calculate the duration against an specific point in time.
|
154
|
+
#
|
168
155
|
# @return [Numeric] The duration in seconds
|
169
|
-
def to_seconds
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
##
|
174
|
-
# @return [Numeric] The duration in days
|
175
|
-
def to_days
|
176
|
-
(to_seconds / 86400)
|
177
|
-
end
|
178
|
-
##
|
179
|
-
# @return [Integer] The integer part of the duration in seconds
|
180
|
-
def to_i
|
181
|
-
to_seconds.to_i
|
182
|
-
end
|
183
|
-
##
|
184
|
-
# @return [Float] The duration in seconds coerced to float
|
185
|
-
def to_f
|
186
|
-
to_seconds.to_f
|
156
|
+
def to_seconds(base = nil)
|
157
|
+
rest = [weeks, days, hours, minutes, seconds].map(&:to_seconds)
|
158
|
+
|
159
|
+
years.to_seconds(base) + months_to_seconds(base) + rest.reduce(&:+)
|
187
160
|
end
|
188
161
|
|
189
162
|
private
|
190
163
|
|
164
|
+
# Changes the base to compute the months for the right base year
|
165
|
+
def months_to_seconds(base)
|
166
|
+
month_base = base.nil? ? nil : base + years.to_seconds(base)
|
167
|
+
months.to_seconds(month_base)
|
168
|
+
end
|
169
|
+
|
191
170
|
##
|
192
171
|
# Splits a duration pattern into valid atoms.
|
193
172
|
#
|
@@ -205,7 +184,35 @@ module ISO8601
|
|
205
184
|
#
|
206
185
|
# @return [Hash<Float>]
|
207
186
|
def atomize(input)
|
208
|
-
duration = input
|
187
|
+
duration = parse(input) || fail(ISO8601::Errors::UnknownPattern, input)
|
188
|
+
|
189
|
+
valid_pattern?(duration)
|
190
|
+
|
191
|
+
@sign = sign_to_i(duration[:sign])
|
192
|
+
|
193
|
+
components = parse_tokens(duration)
|
194
|
+
components.delete(:time) # clean time capture
|
195
|
+
|
196
|
+
valid_fractions?(components.values)
|
197
|
+
|
198
|
+
components
|
199
|
+
end
|
200
|
+
|
201
|
+
def parse_tokens(tokens)
|
202
|
+
components = tokens.names.zip(tokens.captures).map! do |k, v|
|
203
|
+
value = v.nil? ? v : v.tr(',', '.')
|
204
|
+
[k.to_sym, sign * value.to_f]
|
205
|
+
end
|
206
|
+
|
207
|
+
Hash[components]
|
208
|
+
end
|
209
|
+
|
210
|
+
def sign_to_i(sign)
|
211
|
+
(sign == '-') ? -1 : 1
|
212
|
+
end
|
213
|
+
|
214
|
+
def parse(input)
|
215
|
+
input.match(/^
|
209
216
|
(?<sign>\+|-)?
|
210
217
|
P(?:
|
211
218
|
(?:
|
@@ -220,23 +227,9 @@ module ISO8601
|
|
220
227
|
) |
|
221
228
|
(?<weeks>\d+(?:[.,]\d+)?W)
|
222
229
|
) # Duration
|
223
|
-
$/x)
|
224
|
-
|
225
|
-
valid_pattern?(duration)
|
226
|
-
|
227
|
-
@sign = (duration[:sign] == '-') ? -1 : 1
|
228
|
-
|
229
|
-
components = duration.names.zip(duration.captures).map! do |k, v|
|
230
|
-
value = v.nil? ? v : v.tr(',', '.')
|
231
|
-
[k.to_sym, sign * value.to_f]
|
232
|
-
end
|
233
|
-
components = Hash[components]
|
234
|
-
components.delete(:time) # clean time capture
|
235
|
-
|
236
|
-
valid_fractions?(components.values)
|
237
|
-
|
238
|
-
components
|
230
|
+
$/x)
|
239
231
|
end
|
232
|
+
|
240
233
|
##
|
241
234
|
# @param [Numeric] value The seconds to promote
|
242
235
|
#
|
@@ -279,18 +272,20 @@ module ISO8601
|
|
279
272
|
end
|
280
273
|
|
281
274
|
def valid_pattern?(components)
|
282
|
-
date = [
|
283
|
-
|
284
|
-
|
285
|
-
time = [
|
286
|
-
|
287
|
-
|
275
|
+
date = [components[:years],
|
276
|
+
components[:months],
|
277
|
+
components[:days]]
|
278
|
+
time = [components[:hours],
|
279
|
+
components[:minutes],
|
280
|
+
components[:seconds]].compact
|
288
281
|
weeks = components[:weeks]
|
289
282
|
all = [date, time, weeks].flatten.compact
|
290
283
|
|
291
284
|
missing_time = (weeks.nil? && !components[:time].nil? && time.empty?)
|
292
285
|
empty = missing_time || all.empty?
|
293
|
-
|
286
|
+
|
287
|
+
fail ISO8601::Errors::UnknownPattern,
|
288
|
+
@pattern if empty
|
294
289
|
end
|
295
290
|
|
296
291
|
def valid_fractions?(values)
|
@@ -301,8 +296,29 @@ module ISO8601
|
|
301
296
|
fail ISO8601::Errors::InvalidFractions if fractions.size > 1 || consistent
|
302
297
|
end
|
303
298
|
|
304
|
-
def compare_bases(other)
|
299
|
+
def compare_bases(other, base)
|
305
300
|
fail ISO8601::Errors::DurationBaseError, other if base != other.base
|
306
301
|
end
|
302
|
+
|
303
|
+
##
|
304
|
+
# Fetch the number of seconds of another element.
|
305
|
+
#
|
306
|
+
# @param [ISO8601::Duration, Numeric] other Instance of a class to fetch
|
307
|
+
# seconds.
|
308
|
+
#
|
309
|
+
# @raise [ISO8601::Errors::TypeError] If other param is not an instance of
|
310
|
+
# ISO8601::Duration or Numeric classes
|
311
|
+
#
|
312
|
+
# @return [Float] Number of seconds of other param Object
|
313
|
+
def fetch_seconds(other, base = nil)
|
314
|
+
case other
|
315
|
+
when ISO8601::Duration
|
316
|
+
other.to_seconds(base)
|
317
|
+
when Numeric
|
318
|
+
other.to_f
|
319
|
+
else
|
320
|
+
fail(ISO8601::Errors::TypeError, other)
|
321
|
+
end
|
322
|
+
end
|
307
323
|
end
|
308
324
|
end
|