iso8601 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,28 @@
1
+ # ISO8601
2
+
3
+ ISO8601 is a simple implementation of the ISO 8601 (Data elements and
4
+ interchange formats — Information interchange — Representation of dates and
5
+ times) standard.
6
+
7
+ ## Comments
8
+
9
+ Because Durations and DateTime has substract method, Durations has sign to represent a negative value:
10
+
11
+ * `(ISO8601::Duration.new("PT10S") - ISO8601::Duration.new("PT12S")).to_s #=> "-PT2S"`
12
+ * `(ISO8601::Duration.new("-PT10S") + ISO8601::Duration.new("PT12S")).to_s #=> "PT2S"`
13
+
14
+
15
+ ## TODO
16
+
17
+ * Decimal fraction in dateTime and duration patterns
18
+ * Recurring time intervals
19
+ * Ordinal date pattern (YYYY-DDD)
20
+ * Week date pattern (YYYY-Www-D)
21
+
22
+ ## Contributors
23
+
24
+ * [Nick Lynch](https://github.com/njlynch)
25
+ * [Pelle Braendgaard](https://github.com/pelle)
26
+
27
+ ## Credits
28
+ Arnau Siches under [LGPL](http://www.gnu.org/licenses/lgpl.html) license. LICENSE file for details.
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "iso8601"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "iso8601"
7
+ s.version = ISO8601::VERSION
8
+ s.authors = ["Arnau Siches"]
9
+ s.email = ["arnau.siches@gmail.com"]
10
+ s.homepage = "https://github.com/arnau/ISO8601"
11
+ s.summary = %q{Ruby parser to work with ISO8601 dateTimes and durations - http://en.wikipedia.org/wiki/ISO_8601}
12
+ s.description = %q{ISO8601 is a simple implementation of the ISO 8601 (Data elements and
13
+ interchange formats - Information interchange - Representation of dates and
14
+ times) standard.}
15
+
16
+ s.rubyforge_project = "iso8601"
17
+
18
+ s.files = `git ls-files`.split("\n")
19
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
20
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
21
+ s.require_paths = ["lib"]
22
+
23
+ # specify any dependencies here; for example:
24
+ # s.add_development_dependency "rspec"
25
+ # s.add_runtime_dependency "rest-client"
26
+ end
@@ -0,0 +1,10 @@
1
+ module ISO8601
2
+ VERSION = "0.1.1"
3
+ end
4
+
5
+ require "time"
6
+
7
+ require "iso8601/errors"
8
+ require "iso8601/atoms"
9
+ require "iso8601/dateTime"
10
+ require "iso8601/duration"
@@ -0,0 +1,114 @@
1
+ module ISO8601
2
+
3
+ # Represents a generic atom in a +ISO8601::Duration+.
4
+ class Atom
5
+ def initialize(atom, base=nil)
6
+ is_number?(atom, "First argument for #{self.inspect} must be an Integer or a Float.")
7
+ @atom = atom
8
+ @base = base
9
+ end
10
+
11
+ def to_i
12
+ @atom
13
+ end
14
+ def to_seconds
15
+ @atom * self.factor
16
+ end
17
+
18
+ private
19
+ def is_number?(arg, error_message=nil)
20
+ raise TypeError, error_message unless (arg.is_a? Integer or arg.is_a? Float)
21
+ end
22
+ end
23
+
24
+ # A “calendar year” is the cyclic time interval in a calendar which is
25
+ # required for one revolution of the Earth around the Sun and approximated to
26
+ # an integral number of “calendar days”.
27
+
28
+ #A “duration year” is the duration of 365 or 366 “calendar days” depending on
29
+ # the start and/or the end of the corresponding time interval within the
30
+ # specific “calendar year”.
31
+ class Years < ISO8601::Atom
32
+
33
+ # The “duration year” average is calculated through time intervals of 400
34
+ # “duration years”. Each cycle of 400 “duration years” has 303 “common
35
+ # years” of 365 “calendar days” and 97 “leap years” of 366 “calendar days”.
36
+ def factor
37
+ if @base.nil?
38
+ ((365 * 303 + 366 * 97) / 400) * 86400
39
+ elsif @atom == 0
40
+ 0
41
+ else
42
+ year = (@base.year + @atom).to_i
43
+ (Time.utc(year) - Time.utc(@base.year)) / @atom
44
+ end
45
+ end
46
+ end
47
+
48
+ # A “calendar month” is the time interval resulting from the division of a
49
+ # “calendar year” in 12 time intervals.
50
+
51
+ # A “duration month” is the duration of 28, 29, 30 or 31 “calendar days”
52
+ # depending on the start and/or the end of the corresponding time interval
53
+ # within the specific “calendar month”.
54
+ class Months < ISO8601::Atom
55
+
56
+ # The “duration month” average is calculated through time intervals of 400
57
+ # “duration years”. Each cycle of 400 “duration years” has 303 “common
58
+ # years” of 365 “calendar days” and 97 “leap years” of 366 “calendar days”.
59
+ def factor
60
+ if @base.nil?
61
+ (((365 * 303 + 366 * 97) / 400) * 86400) / 12
62
+ elsif @atom == 0
63
+ 0
64
+ else
65
+ month = (@base.month + @atom <= 12) ? (@base.month + @atom) : ((@base.month + @atom) % 12)
66
+ year = @base.year + ((@base.month + @atom) / 12).to_i
67
+ (Time.utc(year, month) - Time.utc(@base.year, @base.month)) / @atom
68
+ end
69
+ end
70
+ end
71
+
72
+ class Weeks < ISO8601::Atom
73
+
74
+ # A week is equal to 604800 seconds.
75
+ def factor
76
+ 604800
77
+ end
78
+ end
79
+
80
+ # A “calendar day” is the time interval which starts at a certain time of day
81
+ # at a certain “calendar day” and ends at the same time of day at the next
82
+ # “calendar day”.
83
+ class Days < ISO8601::Atom
84
+
85
+ # A day is equal to 86400 seconds.
86
+ def factor
87
+ 86400
88
+ end
89
+ end
90
+
91
+ class Hours < ISO8601::Atom
92
+
93
+ # An hour is equal to 3600 seconds.
94
+ def factor
95
+ 3600
96
+ end
97
+ end
98
+ class Minutes < ISO8601::Atom
99
+
100
+ # A minute is equal to 60 seconds.
101
+ def factor
102
+ 60
103
+ end
104
+ end
105
+
106
+ # The second is the base unit of measurement of time in the International
107
+ # System of Units (SI) as defined by the International Committee of Weights
108
+ # and Measures (CIPM, i.e. Comité International des Poids et Mesures)
109
+ class Seconds < ISO8601::Atom
110
+ def factor
111
+ 1
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,104 @@
1
+ module ISO8601
2
+ class DateTime
3
+ attr_reader :date_time, :century, :year, :month, :day, :hour, :minute, :second, :timezone
4
+ def initialize(date_time)
5
+ @dt = /^(?:
6
+ (\d{2})(\d{2})?
7
+ (?:
8
+ (-)?(\d{2})
9
+ )?
10
+ (?:
11
+ (\3)?(\d{2})
12
+ )?
13
+ )?
14
+ (?:
15
+ T(\d{2})
16
+ (?:
17
+ (:)?(\d{2})
18
+ )?
19
+ (?:
20
+ (\8)?(\d{2})
21
+ )?
22
+ (
23
+ Z|([+-])
24
+ (\d{2})
25
+ (?:
26
+ (\8)?
27
+ (\d{2})
28
+ )?
29
+ )?
30
+ )?
31
+ $/x.match(date_time) or raise ISO8601::Errors::UnknownPattern.new(date_time)
32
+
33
+ @date_time = date_time
34
+ @time = @dt[7]
35
+ @date_separator = @dt[3] == @dt[5] ? @dt[3] : nil
36
+ @time_separator =
37
+ if (!@dt[8].nil? and (!@dt[10].nil? and !@dt[11].nil?) and (!@dt[15].nil? and !@dt[16].nil?)) or
38
+ (!@dt[8].nil? and (!@dt[10].nil? and !@dt[11].nil?) and @dt[16].nil?) or
39
+ (!@dt[8].nil? and @dt[11].nil? and @dt[16].nil?)
40
+ @dt[8]
41
+ else
42
+ nil
43
+ end
44
+ @century = @dt[1].to_i
45
+ @year = @dt[2].nil? ? nil : (@dt[1] + @dt[2]).to_i
46
+ @month = @dt[4].nil? ? nil : @dt[4].to_i
47
+ @day = @dt[6].nil? ? nil : @dt[6].to_i
48
+ @hour = @dt[7].nil? ? nil : @dt[7].to_i
49
+ @minute = @dt[9].nil? ? nil : @dt[9].to_i
50
+ @second = @dt[11].nil? ? nil : @dt[11].to_i
51
+ @timezone = {
52
+ :full => @dt[12].nil? ? (Time.now.gmt_offset / 3600) : (@dt[12] == "Z" ? 0 : @dt[12]),
53
+ :sign => @dt[13],
54
+ :hour => @dt[12].nil? ? (Time.now.gmt_offset / 3600) : (@dt[12] == "Z" ? 0 : @dt[14].to_i),
55
+ :minute => (@dt[12].nil? or @dt[12] == "Z") ? 0 : @dt[14].to_i
56
+ }
57
+
58
+ valid_pattern?
59
+ valid_range?
60
+ end
61
+ def to_time
62
+ raise RangeError if @year.nil?
63
+ if @month.nil?
64
+ Time.utc(@year)
65
+ elsif @day.nil?
66
+ date = [@year, @month, '01'].join('-')
67
+ Time.parse(date).getutc
68
+ else
69
+ Time.parse(@date_time).getutc
70
+ end
71
+ end
72
+ def +(d)
73
+ raise TypeError unless (d.is_a? Float or d.is_a? Integer)
74
+ Time.utc(@year, @month, @day, @hour, @minute, @second) + d
75
+ end
76
+ def -(d)
77
+ raise TypeError unless (d.is_a? Float or d.is_a? Integer or d.is_a? ISO8601::DateTime)
78
+ if (d.is_a? ISO8601::DateTime)
79
+ Time.utc(@year, @month, @day, @hour, @minute, @second) - Time.utc(d.year, d.month, d.day, d.hour, d.minute, d.second)
80
+ else
81
+ Time.utc(@year, @month, @day, @hour, @minute, @second) - d
82
+ end
83
+ end
84
+ private
85
+ def valid_pattern?
86
+ if (@date_separator.nil? and !@time_separator.nil?) or
87
+ (!@date_separator.nil? and !@time.nil? and @time_separator.nil? and !@minute.nil?) or
88
+ (@year.nil? and !@month.nil?)
89
+ raise ISO8601::Errors::UnknownPattern.new(@date_time)
90
+ elsif (@year.nil? and @month.nil?)
91
+ @year = (@century.to_s + "00").to_i
92
+ end
93
+ end
94
+ def valid_range?
95
+ if !month.nil? and (month < 1 or month > 12)
96
+ raise RangeError
97
+ elsif !day.nil? and (Time.parse(@date_time).month != month)
98
+ raise RangeError
99
+ end
100
+ rescue ArgumentError => e
101
+ raise RangeError
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,140 @@
1
+ module ISO8601
2
+
3
+ # Represents a duration in ISO 8601 format
4
+ class Duration
5
+ attr_reader :base, :atoms
6
+ def initialize(duration, base = nil)
7
+ @duration = /^(\+|-)?P(((\d+Y)?(\d+M)?(\d+D)?(T(\d+H)?(\d+M)?(\d+S)?)?)|(\d+W))$/.match(duration)
8
+ @base = base #date base for duration calculations
9
+ valid_pattern?
10
+ valid_base?
11
+ @atoms = {
12
+ :years => @duration[4].nil? ? 0 : @duration[4].chop.to_f * sign,
13
+ :months => @duration[5].nil? ? 0 : @duration[5].chop.to_f * sign,
14
+ :weeks => @duration[11].nil? ? 0 : @duration[11].chop.to_f * sign,
15
+ :days => @duration[6].nil? ? 0 : @duration[6].chop.to_f * sign,
16
+ :hours => @duration[8].nil? ? 0 : @duration[8].chop.to_f * sign,
17
+ :minutes => @duration[9].nil? ? 0 : @duration[9].chop.to_f * sign,
18
+ :seconds => @duration[10].nil? ? 0 : @duration[10].chop.to_f * sign
19
+ }
20
+ end
21
+
22
+ def base=(value)
23
+ @base = value
24
+ return @base
25
+ end
26
+
27
+ # Returns the original string of the duration
28
+ def to_s
29
+ @duration[0]
30
+ end
31
+
32
+ # Returns the years of the duration
33
+ def years
34
+ ISO8601::Years.new(@atoms[:years], @base)
35
+ end
36
+
37
+ # Returns the months of the duration
38
+ def months
39
+ base = @base.nil? ? nil : @base + self.years.to_seconds # prevent computing duplicated time
40
+ ISO8601::Months.new(@atoms[:months], base)
41
+ end
42
+
43
+ # Returns the weeks of the duration
44
+ def weeks
45
+ ISO8601::Weeks.new(@atoms[:weeks], @base)
46
+ end
47
+
48
+ # Returns the days of the duration
49
+ def days
50
+ ISO8601::Days.new(@atoms[:days], @base)
51
+ end
52
+
53
+ # Returns the hours of the duration
54
+ def hours
55
+ ISO8601::Hours.new(@atoms[:hours], @base)
56
+ end
57
+
58
+ # Returns the minutes of the duration
59
+ def minutes
60
+ ISO8601::Minutes.new(@atoms[:minutes], @base)
61
+ end
62
+
63
+ # Returns the seconds of the duration
64
+ def seconds
65
+ ISO8601::Seconds.new(@atoms[:seconds], @base)
66
+ end
67
+
68
+ # Returns the duration in seconds
69
+ def to_seconds
70
+ years, months, weeks, days, hours, minutes, seconds = self.years.to_seconds, self.months.to_seconds, self.weeks.to_seconds, self.days.to_seconds, self.hours.to_seconds, self.minutes.to_seconds, self.seconds.to_seconds
71
+ return years + months + weeks + days + hours + minutes + seconds
72
+ end
73
+
74
+ # Returns the absolute value of duration
75
+ def abs
76
+ return self.to_s.sub!(/^[-+]/, "")
77
+ end
78
+
79
+ def +(duration)
80
+ raise ISO8601::Errors::DurationBaseError.new(duration) if self.base != duration.base
81
+ d1 = self.to_seconds
82
+ d2 = duration.to_seconds
83
+ return self.seconds_to_iso(d1 + d2)
84
+ end
85
+ def -(duration)
86
+ raise ISO8601::Errors::DurationBaseError.new(duration) if self.base != duration.base
87
+ d1 = self.to_seconds
88
+ d2 = duration.to_seconds
89
+ return self.seconds_to_iso(d1 - d2)
90
+ # return d1 - d2
91
+ end
92
+
93
+ # Convenience method to turn instance method (which can take into
94
+ # account a base time or duration) into a simple class method.
95
+ def self.seconds_to_iso(duration)
96
+ return ISO8601::Duration.new('P0Y').seconds_to_iso(duration)
97
+ end
98
+
99
+ def seconds_to_iso(duration)
100
+ sign = "-" if (duration < 0)
101
+ duration = duration.abs
102
+ years, y_mod = (duration / self.years.factor).to_i, (duration % self.years.factor)
103
+ months, m_mod = (y_mod / self.months.factor).to_i, (y_mod % self.months.factor)
104
+ days, d_mod = (m_mod / self.days.factor).to_i, (m_mod % self.days.factor)
105
+ hours, h_mod = (d_mod / self.hours.factor).to_i, (d_mod % self.hours.factor)
106
+ minutes, mi_mod = (h_mod / self.minutes.factor).to_i, (h_mod % self.minutes.factor)
107
+ seconds = mi_mod.to_i
108
+
109
+ seconds = (seconds != 0 or (years == 0 and months == 0 and days == 0 and hours == 0 and minutes == 0)) ? "#{seconds}S" : ""
110
+ minutes = (minutes != 0) ? "#{minutes}M" : ""
111
+ hours = (hours != 0) ? "#{hours}H" : ""
112
+ days = (days != 0) ? "#{days}D" : ""
113
+ months = (months != 0) ? "#{months}M" : ""
114
+ years = (years != 0) ? "#{years}Y" : ""
115
+
116
+ date = %[#{sign}P#{years}#{months}#{days}]
117
+ time = (hours != "" or minutes != "" or seconds != "") ? %[T#{hours}#{minutes}#{seconds}] : ""
118
+ date_time = date + time
119
+ return ISO8601::Duration.new(date_time)
120
+ end
121
+
122
+ private
123
+ def sign
124
+ (@duration[1].nil? or @duration[1] == "+") ? 1 : -1
125
+ end
126
+ def valid_base?
127
+ if !(@base.is_a? NilClass or @base.is_a? ISO8601::DateTime)
128
+ raise TypeError
129
+ end
130
+ end
131
+ def valid_pattern?
132
+ if @duration.nil? or
133
+ (@duration[4].nil? and @duration[5].nil? and @duration[6].nil? and @duration[7].nil? and @duration[11].nil?) or
134
+ (!@duration[7].nil? and @duration[8].nil? and @duration[9].nil? and @duration[10].nil? and @duration[11].nil?)
135
+
136
+ raise ISO8601::Errors::UnknownPattern.new(@duration)
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ module ISO8601
3
+
4
+ # Contains all ISO8601-specific errors.
5
+ module Errors
6
+
7
+ # Error that is raised when unknown pattern is parsed.
8
+ class UnknownPattern < ::StandardError
9
+ def initialize(pattern)
10
+ super("The pattern “#{pattern}” is not allowed in this implementation of ISO8601.")
11
+ end
12
+ end
13
+ class DurationError < ::StandardError
14
+ def initialize(duration)
15
+ super("Unexpected type of duration “#{duration}”.")
16
+ end
17
+ end
18
+ class DurationBaseError < ISO8601::Errors::DurationError
19
+ def initialize(duration)
20
+ super("Wrong base for #{duration} duration.")
21
+ end
22
+ end
23
+ end
24
+ end