Permalink
Browse files

Keeping things simple

  • Loading branch information...
1 parent 06b6bc3 commit 0aafc2ae1c76bfe2fc0ba044b0d8aacec71e74c0 @azisaka azisaka committed Aug 4, 2011
View
@@ -0,0 +1,191 @@
+# -*- encoding: utf-8 -*-
+require 'i18n'
+require 'active_support/core_ext'
+
+# Duration objects are simple mechanisms that allow you to operate on durations
+# of time. They allow you to know how much time has passed since a certain
+# point in time, or they can tell you how much time something is (when given as
+# seconds) in different units of time measurement. Durations would particularly
+# be useful for those scripts or applications that allow you to know the uptime
+# of themselves or perhaps provide a countdown until a certain event.
+class Duration
+ include Comparable
+
+ UNITS = [:seconds, :minutes, :hours, :days, :weeks]
+
+ MULTIPLES = {:seconds => 1,
+ :minutes => 60,
+ :hours => 3600,
+ :days => 86400,
+ :weeks => 604800,
+ :second => 1,
+ :minute => 60,
+ :hour => 3600,
+ :day => 86400,
+ :week => 604800}
+
+ attr_reader :weeks, :days, :hours, :minutes, :seconds, :total
+
+ # Initialize a duration. 'args' can be a hash or anything else. If a hash is
+ # passed, it will be scanned for a key=>value pair of time units such as those
+ # listed in the Duration::UNITS array or Duration::MULTIPLES hash.
+ #
+ # If anything else except a hash is passed, #to_i is invoked on that object
+ # and expects that it return the number of seconds desired for the duration.
+ def initialize(args = 0)
+ if args.kind_of?(Hash)
+ @seconds = 0
+ MULTIPLES.each do |unit, multiple|
+ unit = unit.to_sym
+ @seconds += args[unit].to_i * multiple if args.key?(unit)
+ end
+ else
+ @seconds = args.to_i
+ end
+
+ calculate!
+ end
+
+ # Compare this duration to another (or objects that respond to #to_i)
+ def <=>(other)
+ return false unless other.is_a?(Duration)
+ @total <=> other.to_i
+ end
+
+ def +(other)
+ Duration.new(@total + other.to_i)
+ end
+
+ def -(other)
+ Duration.new(@total - other.to_i)
+ end
+
+ def *(other)
+ Duration.new(@total * other.to_i)
+ end
+
+ def /(other)
+ Duration.new(@total / other.to_i)
+ end
+
+ def %(other)
+ Duration.new(@total % other.to_i)
+ end
+
+ # Formats a duration in ISO8601.
+ # @see http://en.wikipedia.org/wiki/ISO_8601#Durations
+ def iso8601
+ output = 'P'
+
+ output << "#{weeks}W" if weeks > 0
+ output << "#{days}D" if days > 0
+ if seconds > 0 || minutes > 0 || hours > 0
+ output << 'T'
+ output << "#{hours}H" if hours > 0
+ output << "#{minutes}M" if minutes > 0
+ output << "#{seconds}S" if seconds > 0
+ end
+
+ output
+ end
+
+ # @return true if total is 0
+ def blank?
+ @total == 0
+ end
+
+ # @return true if total different than 0
+ def present?
+ !blank?
+ end
+
+ # Format a duration into a human-readable string.
+ #
+ # %w => weeks
+ # %d => days
+ # %h => hours
+ # %m => minutes
+ # %s => seconds
+ # %t => total seconds
+ # %H => zero-padded hours
+ # %M => zero-padded minutes
+ # %S => zero-padded seconds
+ # %~s => locale-dependent "seconds" terminology
+ # %~m => locale-dependent "minutes" terminology
+ # %~h => locale-dependent "hours" terminology
+ # %~d => locale-dependent "days" terminology
+ # %~w => locale-dependent "weeks" terminology
+ #
+ # You can also use the I18n support.
+ # The %~s, %~m, %~h, %~d and %~w can be translated with I18n.
+ # If you are using Ruby on Rails, the support is ready out of the box, so just change your locale file. Otherwise you can try:
+ #
+ # I18n.load_path << "path/to/your/locale"
+ # I18n.locale = :your_locale
+ #
+ # And you must use the following structure (example) for your locale file:
+ # pt:
+ # ruby_duration:
+ # second: segundo
+ # seconds: segundos
+ # minute: minuto
+ # minutes: minutos
+ # hour: hora
+ # hours: horas
+ # day: dia
+ # days: dias
+ # week: semana
+ # weeks: semanas
+ #
+ def format(format_str)
+ identifiers = {
+ 'w' => @weeks,
+ 'd' => @days,
+ 'h' => @hours,
+ 'm' => @minutes,
+ 's' => @seconds,
+ 't' => @total,
+ 'H' => @hours.to_s.rjust(2, '0'),
+ 'M' => @minutes.to_s.rjust(2, '0'),
+ 'S' => @seconds.to_s.rjust(2, '0'),
+ '~s' => i18n_for(:second),
+ '~m' => i18n_for(:minute),
+ '~h' => i18n_for(:hour),
+ '~d' => i18n_for(:day),
+ '~w' => i18n_for(:week)
+ }
+
+ format_str.gsub(/%?%(w|d|h|m|s|t|H|M|S|~(?:s|m|h|d|w))/) do |match|
+ match['%%'] ? match : identifiers[match[1..-1]]
+ end.gsub('%%', '%')
+ end
+
+ alias_method :to_i, :total
+ alias_method :strftime, :format
+
+private
+
+ # Calculates the duration from seconds and figures out what the actual
+ # durations are in specific units. This method is called internally, and
+ # does not need to be called by user code.
+ def calculate!
+ multiples = [MULTIPLES[:weeks], MULTIPLES[:days], MULTIPLES[:hours], MULTIPLES[:minutes], MULTIPLES[:seconds]]
+ units = []
+ @total = @seconds.to_f.round
+ multiples.inject(@total) do |total, multiple|
+ # Divide into largest unit
+ units << total / multiple
+ total % multiple # The remainder will be divided as the next largest
+ end
+
+ # Gather the divided units
+ @weeks, @days, @hours, @minutes, @seconds = units
+ end
+
+ def i18n_for(singular)
+ plural = "#{singular}s"
+ label = send(plural) == 1 ? singular : plural
+
+ I18n.t(label, :scope => :ruby_duration, :default => label.to_s)
+ end
+end
View
@@ -0,0 +1,37 @@
+require "#{File.dirname(__FILE__)}/../duration"
+
+# Mongoid serialization support for Duration type.
+module Mongoid
+ module Fields
+ module Serializable
+ class Duration
+ include Serializable
+
+ # Deserialize a Duration given the amount of seconds stored by Mongodb
+ #
+ # @param [Integer, nil] duration in seconds
+ # @return [Duration] deserialized Duration
+ def deserialize(seconds)
+ return if !seconds
+ ::Duration.new(seconds)
+ end
+
+ # Serialize a Duration or a Hash (with duration units) or a amount of seconds to
+ # a BSON serializable type.
+ #
+ # @param [Duration, Hash, Integer] value
+ # @return [Integer] duration in seconds
+ def serialize(value)
+ return if value.blank?
+ if value.is_a?(Hash)
+ value.delete_if{|k, v| v.blank? || !::Duration::UNITS.include?(k.to_sym)}
+ return if value.blank?
+ ::Duration.new(value).to_i
+ elsif value.respond_to?(:to_i)
+ value.to_i
+ end
+ end
+ end
+ end
+ end
+end
View
@@ -0,0 +1,3 @@
+class Duration
+ VERSION = "2.0.0"
+end
Oops, something went wrong.

0 comments on commit 0aafc2a

Please sign in to comment.