Skip to content
Browse files

Initial release

  • Loading branch information...
0 parents commit ad5a1a685f5c95f92894204c23a29493faa117d5 @peleteiro committed Sep 14, 2010
Showing with 321 additions and 0 deletions.
  1. +22 −0 .gitignore
  2. +20 −0 LICENSE
  3. +17 −0 README.rdoc
  4. +53 −0 Rakefile
  5. +133 −0 lib/duration.rb
  6. +14 −0 lib/duration/mongoid.rb
  7. +1 −0 lib/ruby-duration.rb
  8. +22 −0 test/duration/test_mongoid.rb
  9. +13 −0 test/helper.rb
  10. +26 −0 test/test_duration.rb
22 .gitignore
@@ -0,0 +1,22 @@
+## MAC OS
+.DS_Store
+
+## TEXTMATE
+*.tmproj
+tmtags
+
+## EMACS
+*~
+\#*
+.\#*
+
+## VIM
+*.swp
+
+## PROJECT::GENERAL
+coverage
+rdoc
+pkg
+
+## PROJECT::SPECIFIC
+.document
20 LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2009 Jose Peleteiro
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17 README.rdoc
@@ -0,0 +1,17 @@
+= duration
+
+Description goes here.
+
+== Note on Patches/Pull Requests
+
+* Fork the project.
+* Make your feature addition or bug fix.
+* Add tests for it. This is important so I don't break it in a
+ future version unintentionally.
+* Commit, do not mess with rakefile, version, or history.
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
+* Send me a pull request. Bonus points for topic branches.
+
+== Copyright
+
+Copyright (c) 2010 Jose Peleteiro. See LICENSE for details.
53 Rakefile
@@ -0,0 +1,53 @@
+require 'rubygems'
+require 'rake'
+
+begin
+ require 'jeweler'
+ Jeweler::Tasks.new do |gem|
+ gem.name = "ruby-duration"
+ gem.summary = %Q{Duration }
+ gem.description = %Q{TODO: longer description of your gem}
+ gem.email = "jose@peleteiro.net"
+ gem.homepage = "http://github.com/peleteiro/duration"
+ gem.authors = ["Jose Peleteiro"]
+ gem.add_development_dependency "minitest", ">= 0"
+ gem.add_development_dependency "yard", ">= 0"
+ end
+ Jeweler::GemcutterTasks.new
+rescue LoadError
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
+end
+
+require 'rake/testtask'
+Rake::TestTask.new(:test) do |test|
+ test.libs << 'lib' << 'test'
+ test.pattern = 'test/**/test_*.rb'
+ test.verbose = true
+end
+
+begin
+ require 'rcov/rcovtask'
+ Rcov::RcovTask.new do |test|
+ test.libs << 'test'
+ test.pattern = 'test/**/test_*.rb'
+ test.rcov_opts << %w{--exclude /Library,.bundle,test}
+ test.verbose = true
+ end
+rescue LoadError
+ task :rcov do
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
+ end
+end
+
+task :test => :check_dependencies
+
+task :default => :test
+
+begin
+ require 'yard'
+ YARD::Rake::YardocTask.new
+rescue LoadError
+ task :yardoc do
+ abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
+ end
+end
133 lib/duration.rb
@@ -0,0 +1,133 @@
+# -*- encoding: utf-8 -*-
+
+# 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
+ include Enumerable
+
+ # Unit names
+ UNITS = [:seconds, :minutes, :hours, :days, :weeks]
+
+ # Unit labels
+ UNIT_LABELS = {:second => 'seconds',
+ :seconds => 'seconds',
+ :minute => 'minute',
+ :minutes => 'minutes',
+ :hour => 'hour',
+ :hours => 'hours',
+ :day => 'day',
+ :days => 'days',
+ :week => 'week',
+ :weeks => 'weeks'}
+
+ # Unit multiples
+ MULTIPLES = {:seconds => 1,
+ :minutes => 60,
+ :hours => 3600,
+ :days => 86400,
+ :weeks => 604800,
+ :second => 1,
+ :minute => 60,
+ :hour => 3600,
+ :day => 86400,
+ :week => 604800}
+
+
+ attr_reader :total, *UNITS
+
+ # 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)
+ # Two types of arguments are accepted. If it isn't a hash, it's converted
+ # to an integer.
+ if args.kind_of?(Hash)
+ @seconds = 0
+ MULTIPLES.each do |unit, multiple|
+ unit = unit.to_sym
+ @seconds += args[unit] * multiple if args.key?(unit)
+ end
+ else
+ @seconds = args.to_i
+ end
+
+ # Calculate duration
+ calculate!
+ end
+
+ # Compare this duration to another (or objects that respond to #to_i)
+ def <=>(other)
+ @total <=> other.to_i
+ 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
+ #
+ 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' => @seconds == 1 ? UNIT_LABELS[:second] : UNIT_LABELS[:seconds],
+ '~m' => @minutes == 1 ? UNIT_LABELS[:minute] : UNIT_LABELS[:minutes],
+ '~h' => @hours == 1 ? UNIT_LABELS[:hour] : UNIT_LABELS[:hours],
+ '~d' => @days == 1 ? UNIT_LABELS[:day] : UNIT_LABELS[:days],
+ '~w' => @weeks == 1 ? UNIT_LABELS[:week] : UNIT_LABELS[:weeks]
+ }
+
+ 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
+
+end
14 lib/duration/mongoid.rb
@@ -0,0 +1,14 @@
+# -*- encoding: utf-8 -*-
+require 'duration'
+
+class Duration
+ def get
+ total
+ end
+
+ def self.set(seconds)
+ return Duration.new(0) unless seconds.respond_to?(:to_i)
+ Duration.new(seconds.to_i)
+ end
+end
+
1 lib/ruby-duration.rb
@@ -0,0 +1 @@
+require 'duration'
22 test/duration/test_mongoid.rb
@@ -0,0 +1,22 @@
+# -*- encoding: utf-8 -*-
+require 'helper'
+
+class Duration::TestMongoid < MiniTest::Unit::TestCase
+
+ # Returns seconds to serialization
+ def test_get
+ assert_equal 90, Duration.new(90).get
+ end
+
+ def test_set_default
+ zero = Duration.new(0)
+ assert_equal zero, Duration.set("string")
+ assert_equal zero, Duration.set(nil)
+ end
+
+ def test_set_default
+ assert_equal Duration.new(10), Duration.set("10")
+ assert_equal Duration.new(10), Duration.set(10)
+ end
+
+end
13 test/helper.rb
@@ -0,0 +1,13 @@
+# -*- encoding: utf-8 -*-
+require 'rubygems'
+require 'minitest/unit'
+
+$LOAD_PATH.unshift(File.dirname(__FILE__))
+$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
+require 'duration'
+require 'duration/mongoid'
+
+class MiniTest::Unit::TestCase
+end
+
+MiniTest::Unit.autorun
26 test/test_duration.rb
@@ -0,0 +1,26 @@
+# -*- encoding: utf-8 -*-
+require 'helper'
+
+class TestDuration < MiniTest::Unit::TestCase
+
+ def test_initialization_with_seconds
+ d = Duration.new(90)
+ assert_equal 0, d.weeks
+ assert_equal 0, d.days
+ assert_equal 0, d.hours
+ assert_equal 1, d.minutes
+ assert_equal 30, d.seconds
+ assert_equal 90, d.total
+ end
+
+ def test_initialization_with_hash
+ d = Duration.new(:minutes => 1, :seconds => 30)
+ assert_equal 0, d.weeks
+ assert_equal 0, d.days
+ assert_equal 0, d.hours
+ assert_equal 1, d.minutes
+ assert_equal 30, d.seconds
+ assert_equal 90, d.total
+ end
+
+end

0 comments on commit ad5a1a6

Please sign in to comment.
Something went wrong with that request. Please try again.