aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport/lib/active_support/duration/iso8601_serializer.rb
diff options
context:
space:
mode:
authorArnau Siches, Andrey Novikov <envek@envek.name>2014-09-14 17:16:51 +0400
committerJeremy Daer <jeremydaer@gmail.com>2016-04-18 16:27:30 -0700
commit04c512da1247a54474cfd8bef17a9e9019c34004 (patch)
tree995e1bae1ce60a217da23b3ff52f7da5e290556a /activesupport/lib/active_support/duration/iso8601_serializer.rb
parentfe685c62929fd3c67df041f1ccb0def2ff051f35 (diff)
downloadrails-04c512da1247a54474cfd8bef17a9e9019c34004.tar.gz
rails-04c512da1247a54474cfd8bef17a9e9019c34004.tar.bz2
rails-04c512da1247a54474cfd8bef17a9e9019c34004.zip
`ActiveSupport::Duration` supports ISO8601 formatting and parsing.
```ruby ActiveSupport::Duration.parse('P3Y6M4DT12H30M5S') (3.years + 3.days).iso8601 ``` Inspired by Arnau Siches' [ISO8601 gem](https://github.com/arnau/ISO8601/) and rewritten by Andrey Novikov with suggestions from Andrew White. Test data from the ISO8601 gem redistributed under MIT license. (Will be used to support the PostgreSQL interval data type.)
Diffstat (limited to 'activesupport/lib/active_support/duration/iso8601_serializer.rb')
-rw-r--r--activesupport/lib/active_support/duration/iso8601_serializer.rb51
1 files changed, 51 insertions, 0 deletions
diff --git a/activesupport/lib/active_support/duration/iso8601_serializer.rb b/activesupport/lib/active_support/duration/iso8601_serializer.rb
new file mode 100644
index 0000000000..05c6a083a9
--- /dev/null
+++ b/activesupport/lib/active_support/duration/iso8601_serializer.rb
@@ -0,0 +1,51 @@
+require 'active_support/core_ext/object/blank'
+require 'active_support/core_ext/hash/transform_values'
+
+module ActiveSupport
+ class Duration
+ # Serializes duration to string according to ISO 8601 Duration format.
+ class ISO8601Serializer
+ def initialize(duration, precision: nil)
+ @duration = duration
+ @precision = precision
+ end
+
+ # Builds and returns output string.
+ def serialize
+ output = 'P'
+ parts, sign = normalize
+ output << "#{parts[:years]}Y" if parts.key?(:years)
+ output << "#{parts[:months]}M" if parts.key?(:months)
+ output << "#{parts[:weeks]}W" if parts.key?(:weeks)
+ output << "#{parts[:days]}D" if parts.key?(:days)
+ time = ''
+ time << "#{parts[:hours]}H" if parts.key?(:hours)
+ time << "#{parts[:minutes]}M" if parts.key?(:minutes)
+ if parts.key?(:seconds)
+ time << "#{sprintf(@precision ? "%0.0#{@precision}f" : '%g', parts[:seconds])}S"
+ end
+ output << "T#{time}" if time.present?
+ "#{sign}#{output}"
+ end
+
+ private
+
+ # Return pair of duration's parts and whole duration sign.
+ # Parts are summarized (as they can become repetitive due to addition, etc).
+ # Zero parts are removed as not significant.
+ # If all parts are negative it will negate all of them and return minus as a sign.
+ def normalize
+ parts = @duration.parts.each_with_object(Hash.new(0)) do |(k,v),p|
+ p[k] += v unless v.zero?
+ end
+ # If all parts are negative - let's make a negative duration
+ sign = ''
+ if parts.values.all? { |v| v < 0 }
+ sign = '-'
+ parts.transform_values!(&:-@)
+ end
+ [parts, sign]
+ end
+ end
+ end
+end