├── var ├── title ├── created ├── name ├── version ├── organization ├── summary ├── requirements ├── repositories ├── copyrights ├── authors ├── resources └── description ├── Gemfile ├── lib ├── rich_units.rb ├── richunits │ ├── version.rb │ ├── weekdays.rb │ ├── multipliers.rb │ ├── bytes.rb │ ├── duration.rb │ └── times.rb └── richunits.rb ├── .gitignore ├── .travis.yml ├── Assembly ├── MANIFEST ├── LICENSE.txt ├── spec ├── duration_spec.rb ├── bytes_spec.rb ├── multipliers_spec.rb └── times_spec.rb ├── .index ├── HISTORY.md ├── README.md ├── .gemspec └── work └── deprecated └── duration.rb /var/title: -------------------------------------------------------------------------------- 1 | RichUnits -------------------------------------------------------------------------------- /var/created: -------------------------------------------------------------------------------- 1 | 2008-02-21 -------------------------------------------------------------------------------- /var/name: -------------------------------------------------------------------------------- 1 | richunits 2 | -------------------------------------------------------------------------------- /var/version: -------------------------------------------------------------------------------- 1 | 0.6.2 2 | -------------------------------------------------------------------------------- /var/organization: -------------------------------------------------------------------------------- 1 | Rubyworks 2 | -------------------------------------------------------------------------------- /var/summary: -------------------------------------------------------------------------------- 1 | Simple LCD Unit System -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source :rubygems 2 | gemspec 3 | -------------------------------------------------------------------------------- /lib/rich_units.rb: -------------------------------------------------------------------------------- 1 | require 'richunits' 2 | -------------------------------------------------------------------------------- /var/requirements: -------------------------------------------------------------------------------- 1 | --- 2 | - detroit (build) 3 | - rspec (test) 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .reap/digest 2 | .yardoc 3 | log 4 | pkg 5 | doc 6 | tmp 7 | web 8 | -------------------------------------------------------------------------------- /lib/richunits/version.rb: -------------------------------------------------------------------------------- 1 | module RichUnits 2 | VERSION = "0.6.1" 3 | end 4 | 5 | -------------------------------------------------------------------------------- /var/repositories: -------------------------------------------------------------------------------- 1 | --- 2 | upstream: git://github.com/rubyworks/richunits.git 3 | -------------------------------------------------------------------------------- /var/copyrights: -------------------------------------------------------------------------------- 1 | --- 2 | - (c) 2008 Thomas Sawyer (MIT) 3 | - (c) 2005 Rich Kilmer (Ruby) 4 | -------------------------------------------------------------------------------- /var/authors: -------------------------------------------------------------------------------- 1 | --- 2 | - Thomas Sawyer 3 | - Rich Kilmer 4 | - Dave Hoover 5 | - Ryan Platte 6 | - George Moschovitis 7 | 8 | -------------------------------------------------------------------------------- /var/resources: -------------------------------------------------------------------------------- 1 | --- 2 | home: http://rubyworks.github.com/richunits 3 | code: http://github.com/rubyworks/richunits 4 | mail: http://groups.google.com/groups/rubyworks-mailinglist 5 | 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | script: "bundle exec rspec spec/" 3 | rvm: 4 | - 1.8.7 5 | - 1.9.2 6 | - 1.9.3 7 | - rbx 8 | - rbx-19mode 9 | - jruby 10 | - jruby-19mode 11 | 12 | -------------------------------------------------------------------------------- /lib/richunits.rb: -------------------------------------------------------------------------------- 1 | require 'richunits/version' 2 | require 'richunits/multipliers' 3 | require 'richunits/bytes' 4 | require 'richunits/times' 5 | require 'richunits/weekdays' 6 | require 'richunits/duration' 7 | -------------------------------------------------------------------------------- /Assembly: -------------------------------------------------------------------------------- 1 | --- 2 | github: 3 | gh_pages: web 4 | 5 | gem: 6 | active: true 7 | 8 | rspec: 9 | path: spec 10 | 11 | email: 12 | mailto: 13 | - ruby-talk@ruby-lang.org 14 | - rubyworks-mailinglist@googlegroups.com 15 | 16 | -------------------------------------------------------------------------------- /var/description: -------------------------------------------------------------------------------- 1 | A Unit system, based on Rich Kilmer's original time.rb work, which provides 2 | english-esque methods for working with common units, such as days and bytes 3 | and multiplers like kilo, or mega. It does so by reducing basic measures to 4 | a lower common denominator, such as seconds for time measures and bits for 5 | byte measures. 6 | -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | #!mast .index .yardopts bin lib man spec test *.txt *.md 2 | .index 3 | lib/rich_units.rb 4 | lib/richunits/bytes.rb 5 | lib/richunits/duration.rb 6 | lib/richunits/multipliers.rb 7 | lib/richunits/times.rb 8 | lib/richunits/version.rb 9 | lib/richunits/weekdays.rb 10 | lib/richunits.rb 11 | spec/bytes_spec.rb 12 | spec/duration_spec.rb 13 | spec/multipliers_spec.rb 14 | spec/times_spec.rb 15 | LICENSE.txt 16 | HISTORY.md 17 | README.md 18 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | (MIT License) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | 21 | -------------------------------------------------------------------------------- /spec/duration_spec.rb: -------------------------------------------------------------------------------- 1 | require 'richunits/duration' 2 | 3 | describe RichUnits::Duration do 4 | 5 | it "to_a" do 6 | d = RichUnits::Duration.new(24*60*60 + 1) 7 | r = d.to_a 8 | x = [1, 0, 0, 1] 9 | r.should == x 10 | 11 | d = RichUnits::Duration.new(10*24*60*60 + 5) 12 | r = d.to_a 13 | x = [10, 0, 0, 5] 14 | r.should == x 15 | end 16 | 17 | it "segmented" do 18 | d = RichUnits::Duration.new(8*24*60*60 + 1) 19 | r = d.segmented(:week, :day, :hour, :minute, :second) 20 | x = [1, 1, 0, 0, 1] 21 | r.to_a.should == x 22 | end 23 | 24 | it "to_h" do 25 | d = RichUnits::Duration.new(24*60*60) 26 | r = d.to_h 27 | x = { :days=>1, :hours=>0, :minutes=>0, :seconds=>0 } 28 | r.should == x 29 | end 30 | 31 | it "repeated_numeric_days" do 32 | a = 10.days 33 | r = 10.days.days 34 | x = 10 35 | r.should == x 36 | end 37 | 38 | it "repeated_numeric_years" do 39 | a = 10.years 40 | r = 10.years.years 41 | x = 10 42 | r.should == x 43 | end 44 | 45 | it "repeated_strftime" do 46 | a = RichUnits::Duration[24*60*60 + 1] 47 | r = a.strftime('%d:%h:%m:%s') 48 | x = "1:0:0:1" 49 | r.should == x 50 | end 51 | 52 | end 53 | 54 | -------------------------------------------------------------------------------- /spec/bytes_spec.rb: -------------------------------------------------------------------------------- 1 | require 'richunits/bytes' 2 | #require 'test/unit' 3 | 4 | describe Numeric do 5 | 6 | # bits 7 | 8 | it "bits" do 9 | 8.bits.should == 8 10 | end 11 | 12 | it "kilobits" do 13 | 1.kilobit.should == 1024**1 14 | end 15 | 16 | it "megabits" do 17 | 1.megabit.should == 1024**2 18 | end 19 | 20 | it "gigabits" do 21 | 1.gigabit.should == 1024**3 22 | end 23 | 24 | it "terabits" do 25 | 1.terabit.should == 1024**4 26 | end 27 | 28 | # bytes 29 | 30 | it "bytes" do 31 | 1024.bytes.should == 8192 32 | end 33 | 34 | it "kilobytes" do 35 | 1.kilobyte.should == 1024**1*8 36 | end 37 | 38 | it "megabytes" do 39 | 1.megabyte.should == 1024**2*8 40 | end 41 | 42 | it "gigabytes" do 43 | 1.gigabyte.should == 1024**3*8 44 | end 45 | 46 | it "terabytes" do 47 | 1.terabyte.should == 1024**4*8 48 | end 49 | 50 | # bits_to_s 51 | 52 | it "strfbits" do 53 | 1024.strfbits.should == "1.00 kb" 54 | 1048576.strfbits.should == "1.00 mb" 55 | 1073741824.strfbits.should == "1.00 gb" 56 | 1099511627776.strfbits .should == "1.00 tb" 57 | end 58 | 59 | # bytes_to_s 60 | 61 | it "strfbytes" do 62 | 1024.strfbytes.should == "1.00 KB" 63 | 1048576.strfbytes.should == "1.00 MB" 64 | 1073741824.strfbytes.should == "1.00 GB" 65 | 1099511627776.strfbytes.should == "1.00 TB" 66 | end 67 | 68 | end 69 | -------------------------------------------------------------------------------- /lib/richunits/weekdays.rb: -------------------------------------------------------------------------------- 1 | module RichUnits 2 | 3 | # = Weekdays 4 | # 5 | # The Weekdays class provides useful weekday terminology. 6 | # 7 | # Thanks to Dave Hoover and Ryan Platte for the Weekdays implementation. 8 | # 9 | class Weekdays 10 | 11 | WEEKDAYS = 1..5 # Monday is wday 1 12 | ONE_DAY = 60 * 60 * 24 13 | 14 | def initialize(n) 15 | @n = n 16 | end 17 | 18 | def ago(time = ::Time.now) 19 | step :down, time 20 | end 21 | alias_method :until, :ago 22 | alias_method :before, :ago 23 | 24 | def since(time = ::Time.now) 25 | step :up, time 26 | end 27 | alias_method :from_now, :since 28 | alias_method :after, :since 29 | 30 | private 31 | 32 | def step(direction, original_time) 33 | result = original_time 34 | time = ONE_DAY 35 | 36 | compare = direction == :up ? ">" : "<" 37 | time *= -1 if direction == :down 38 | 39 | @n.times do 40 | result += time until result.send(compare, original_time) && WEEKDAYS.member?(result.wday) 41 | original_time = result 42 | end 43 | result 44 | end 45 | 46 | # = Numeric Weekday Extensions 47 | # 48 | module Numeric 49 | 50 | # Works with day in terms of weekdays. 51 | def weekdays 52 | Weekdays.new(self) 53 | end 54 | 55 | alias_method :weekday, :weekdays 56 | 57 | end#module Numeric 58 | 59 | end#class Weekdays 60 | 61 | end#module RichUnits 62 | 63 | 64 | class Numeric #:nodoc: 65 | include RichUnits::Weekdays::Numeric 66 | end 67 | 68 | -------------------------------------------------------------------------------- /.index: -------------------------------------------------------------------------------- 1 | --- 2 | revision: 2013 3 | type: ruby 4 | sources: 5 | - var 6 | authors: 7 | - name: Thomas Sawyer 8 | email: transfire@gmail.com 9 | - name: Rich Kilmer 10 | - name: Dave Hoover 11 | - name: Ryan Platte 12 | - name: George Moschovitis 13 | organizations: [] 14 | requirements: 15 | - groups: 16 | - build 17 | development: true 18 | name: detroit 19 | - groups: 20 | - test 21 | development: true 22 | name: rspec 23 | conflicts: [] 24 | alternatives: [] 25 | resources: 26 | - type: home 27 | uri: http://rubyworks.github.com/richunits 28 | label: Homepage 29 | - type: code 30 | uri: http://github.com/rubyworks/richunits 31 | label: Source Code 32 | - type: mail 33 | uri: http://groups.google.com/groups/rubyworks-mailinglist 34 | label: Mailing List 35 | repositories: 36 | - name: upstream 37 | scm: git 38 | uri: git://github.com/rubyworks/richunits.git 39 | categories: [] 40 | copyrights: 41 | - holder: Thomas Sawyer 42 | year: '2008' 43 | license: MIT 44 | - holder: Rich Kilmer 45 | year: '2005' 46 | license: Ruby 47 | customs: [] 48 | paths: 49 | lib: 50 | - lib 51 | created: '2008-02-21' 52 | summary: Simple LCD Unit System 53 | title: RichUnits 54 | version: 0.6.2 55 | name: richunits 56 | description: ! 'A Unit system, based on Rich Kilmer''s original time.rb work, which 57 | provides 58 | 59 | english-esque methods for working with common units, such as days and bytes 60 | 61 | and multiplers like kilo, or mega. It does so by reducing basic measures to 62 | 63 | a lower common denominator, such as seconds for time measures and bits for 64 | 65 | byte measures.' 66 | date: '2013-03-09' 67 | -------------------------------------------------------------------------------- /spec/multipliers_spec.rb: -------------------------------------------------------------------------------- 1 | require 'richunits/multipliers' 2 | 3 | describe "Multipliers" do 4 | 5 | it "deka" do 6 | 1.deka.should == 10 7 | end 8 | 9 | it "hecto" do 10 | 1.hecto.should == 100 11 | end 12 | 13 | it "kilo" do 14 | 1.kilo.should == 1000 15 | end 16 | 17 | it "mega" do 18 | 1.mega.should == 1000000 19 | end 20 | 21 | it "giga" do 22 | 1.giga.should == 1000000000 23 | end 24 | 25 | it "tera" do 26 | 1.tera.should == 1000000000000 27 | end 28 | 29 | it "peta" do 30 | 1.peta.should == 1000000000000000 31 | end 32 | 33 | it "exa" do 34 | 1.exa.should == 1000000000000000000 35 | end 36 | 37 | # Fractional 38 | 39 | it "deci" do 40 | 1.deci.should == 0.1 41 | end 42 | 43 | it "centi" do 44 | 1.centi.should == 0.01 45 | end 46 | 47 | it "milli" do 48 | 1.milli.should == 0.001 49 | end 50 | 51 | it "milli" do 52 | 1.micro.should == 0.000001 53 | end 54 | 55 | it "nano" do 56 | 1.nano.should == 0.000000001 57 | end 58 | 59 | it "pico" do 60 | 1.pico.should == 0.000000000001 61 | end 62 | 63 | it "femto" do 64 | 1.femto.should == 0.000000000000001 65 | end 66 | 67 | it "atto" do 68 | 1.atto.should == 0.000000000000000001 69 | end 70 | 71 | # SI Binary 72 | 73 | it "kibi" do 74 | 1.kibi.should == 1024 75 | end 76 | 77 | it "mebi" do 78 | 1.mebi.should == 1024**2 79 | end 80 | 81 | it "gibi" do 82 | 1.gibi.should == 1024**3 83 | end 84 | 85 | it "tebi" do 86 | 1.tebi.should == 1024**4 87 | end 88 | 89 | it "pebi" do 90 | 1.pebi.should == 1024**5 91 | end 92 | 93 | it "exbi" do 94 | 1.exbi.should == 1024**6 95 | end 96 | 97 | end 98 | 99 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | # Release History 2 | 3 | ## 0.6.2 / 2011-10-24 4 | 5 | A simple maintenance release to bring the project's build configuration 6 | up to date with more modern tools. 7 | 8 | Changes: 9 | 10 | * Modernize build configuration. 11 | 12 | 13 | ## 0.6.1 / 2010-10-04 14 | 15 | Minor release simply add #to_int method to Duration so Time#+ 16 | and Time#- will work with Durations in Ruby 1.9.2 (Ruby 1.8.x 17 | uses #to_f). 18 | 19 | Changes: 20 | 21 | * Add Duration#to_int for Ruby 1.9.2. 22 | 23 | 24 | ## 0.6.0 / 2009-10-31 25 | 26 | This release tightens up the Duration interface a bit and fixed the 27 | `Duration#*` method which was accidentally named `#+`. 28 | 29 | Changes: 30 | 31 | * More robust interface for Duration class. 32 | * Rename #+ method to #* (typo). 33 | * Update metadata for Gem Do POM. 34 | 35 | 36 | ## 0.5.0 / 2009-05-30 37 | 38 | This release updates some extensions to Time for use with the 39 | Duration class, and standardizes the library on the name 'richunits', 40 | rather than using the underscored "rich_units". This means installation 41 | is now: 42 | 43 | gem install richunits 44 | 45 | and loading the library is now: 46 | 47 | require 'richunits' 48 | 49 | Though a backward compatible require is still present. 50 | 51 | The previous release contains a complete rewrite of the Duration 52 | class and integrates it into the rest of the system. 53 | 54 | Changes: 55 | 56 | * Rewrote Duration class and integrated into rest of system. 57 | * Name of gem and main require will be 'richunits' (no underscore). 58 | 59 | 60 | ## 0.4.0 / 2008-09-09 61 | 62 | This release includes a completely rewritten Duration class and integrates 63 | into the rest of the system. 64 | 65 | Changes: 66 | 67 | * Wrote new Duration class. 68 | 69 | 70 | ## 0.3.0 / 2008-09-08 71 | 72 | This release adds RichUnits toplevel namespace. 73 | 74 | Changes: 75 | 76 | * Encapsulated entire library in RichUnits module. 77 | 78 | 79 | ## 0.2.0 / 2008-08-01 80 | 81 | Continued work on RichUnits. 82 | 83 | Changes: 84 | 85 | * Reorganized repository. 86 | 87 | 88 | ## 0.1.0 / 2008-03-27 89 | 90 | First release of RichUnits. 91 | 92 | Changes: 93 | 94 | * Initial version. 95 | 96 | -------------------------------------------------------------------------------- /lib/richunits/multipliers.rb: -------------------------------------------------------------------------------- 1 | module RichUnits 2 | 3 | # = Multipliers 4 | # 5 | # Adds methods to Numeric to make working with magnitudes 6 | # such as (kilo, mega, giga, milli, micro, etc.). 7 | # 8 | module Multiplers 9 | 10 | # = Numeric Multipliers 11 | # 12 | # Adds methods to Numeric to make working with 13 | # magnitudes (kilo, mega, giga, milli, micro, etc.) 14 | # as well as bits and bytes easier. 15 | # 16 | # 1.kilo #=> 1000 17 | # 1.milli #=> 0.001 18 | # 1.kibi #=> 1024 19 | # 20 | # To display a value in a certain denomination, simply 21 | # perform the inverse operation by placing the 22 | # multiplier called on unit (1) in the denominator. 23 | # 24 | # 1000 / 1.kilo #=> 1 25 | # 1024 / 1.kibi #=> 1 26 | # 27 | module Numeric 28 | 29 | # SI Multipliers 30 | 31 | def deka ; self * 10 ; end 32 | def hecto ; self * 100 ; end 33 | def kilo ; self * 1000 ; end 34 | def mega ; self * 1000000 ; end 35 | def giga ; self * 1000000000 ; end 36 | def tera ; self * 1000000000000 ; end 37 | def peta ; self * 1000000000000000 ; end 38 | def exa ; self * 1000000000000000000 ; end 39 | 40 | # SI Fractional 41 | 42 | def deci ; self.to_f / 10 ; end 43 | def centi ; self.to_f / 100 ; end 44 | def milli ; self.to_f / 1000 ; end 45 | def micro ; self.to_f / 1000000 ; end 46 | def nano ; self.to_f / 1000000000 ; end 47 | def pico ; self.to_f / 1000000000000 ; end 48 | def femto ; self.to_f / 1000000000000000 ; end 49 | def atto ; self.to_f / 1000000000000000000 ; end 50 | 51 | # SI Binary 52 | 53 | def kibi ; self * 1024 ; end 54 | def mebi ; self * 1024**2 ; end 55 | def gibi ; self * 1024**3 ; end 56 | def tebi ; self * 1024**4 ; end 57 | def pebi ; self * 1024**5 ; end 58 | def exbi ; self * 1024**6 ; end 59 | 60 | # Bits and Bytes 61 | 62 | def bit ; self ; end 63 | def bits ; self ; end 64 | def byte ; self * 8 ; end 65 | def bytes ; self * 8 ; end 66 | 67 | end#module Numeric 68 | 69 | end#module Multipliers 70 | 71 | end#module RichUnits 72 | 73 | 74 | class Numeric #:nodoc: 75 | include RichUnits::Multiplers::Numeric 76 | end 77 | -------------------------------------------------------------------------------- /spec/times_spec.rb: -------------------------------------------------------------------------------- 1 | require 'richunits/times' 2 | 3 | describe "RichUnits::Times" do 4 | 5 | #it "micro_seconds 6 | # 1.microsecond.should == 0.000001 7 | #end 8 | 9 | #it "milli_seconds 10 | # 1.millisecond.should == 0.001 11 | #end 12 | 13 | it "seconds" do 14 | 1.seconds.to_i.should == 60**0 15 | end 16 | 17 | it "minutes" do 18 | 1.minutes.to_i.should == 60**1 19 | end 20 | 21 | it "hours" do 22 | 1.hours.to_i.should == 60**2 23 | end 24 | 25 | it "days" do 26 | 1.days.to_i.should == 24*(60**2) 27 | end 28 | 29 | it "weeks" do 30 | 1.weeks.to_i.should == 7*24*(60**2) 31 | end 32 | 33 | it "fortnights" do 34 | 1.fortnights.to_i .should == 14*24*(60**2) 35 | end 36 | 37 | it "months" do 38 | 1.months.to_i.should == 30*24*(60**2) 39 | end 40 | 41 | it "years" do 42 | 1.years.to_i.should == 365*24*(60**2) 43 | end 44 | 45 | 46 | it "before" do 47 | t = Time.now 48 | 1.day.before(t).should == t - 1.day 49 | end 50 | 51 | it "after" do 52 | t = Time.now 53 | 1.day.after(t).should == t + 1.day 54 | end 55 | 56 | # 57 | 58 | it "addition" do 59 | (10.minutes + 1.minute).should == 11.minutes 60 | (10.minutes + 1.minute).to_i.should == 11.minutes.to_i 61 | 62 | (10.minutes + 60.seconds).should == 11.minutes 63 | (10.minutes + 60.seconds).to_i.should == 11.minutes.to_i 64 | end 65 | 66 | it "multiplication" do 67 | (10.minutes * 2).should == 20.minutes 68 | (10.minutes * 2).to_i.should ==20.minutes.to_i 69 | end 70 | 71 | end 72 | 73 | describe "Weekdays Test" do 74 | 75 | MONDAY = Time.at(1165250000) 76 | THURSDAY = Time.at(1165500000) 77 | FRIDAY = Time.at(1165606025) 78 | 79 | it "weekday after monday" do 80 | 1.weekday.since(MONDAY).wday.should == 2 81 | end 82 | 83 | it "weekday after friday" do 84 | 1.weekday.after(FRIDAY).wday.should == 1 85 | end 86 | 87 | it "weekdays before friday" do 88 | 3.weekdays.before(FRIDAY).wday.should == 2 89 | end 90 | 91 | #it "weekday before today" do 92 | # Time.expects(:now).returns(THURSDAY) 93 | # 1.weekday.ago.wday.should == 3 94 | #end 95 | 96 | #it "weekdays after today" do 97 | # Time.expects(:now).returns(MONDAY) 98 | # 2.weekday.from_now.wday.should == 3 99 | #end 100 | 101 | end 102 | 103 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RichUnits 2 | 3 | [Website](http://rubyworks.github.com/richunits) / 4 | [Documentation](http://rubydoc.info/gems/richunits/frames) / 5 | [Report Issue](http://github.com/rubyworks/richunits/issues) / 6 | [Source Code](http://github.com/rubyworks/richunits) 7 | 8 | [![Gem Version](https://badge.fury.io/rb/richunits.png)](http://badge.fury.io/rb/richunits) 9 | [![Build Status](https://travis-ci.org/rubyworks/richunits.png)](https://travis-ci.org/rubyworks/richunits)     10 | [![Flattr Me](http://api.flattr.com/button/flattr-badge-large.png)](http://flattr.com/thing/324911/Rubyworks-Ruby-Development-Fund) 11 | 12 | 13 | ## About 14 | 15 | RichUnits is simple units system based on Rich Kilmer's original 16 | time.rb script, in which different measures of time are represented 17 | as seconds via extensions to the Numeric class. Later, Ruby on Rails' 18 | ActiveSupport library adopted this script and expanded upon it. 19 | RichUnits borrows from their work and a few other renditions this code 20 | that have made the rounds to create a robust stand-alone version. 21 | 22 | Unique to RichUnits' rendition is the Duration class (inspired by Matthew 23 | Harris' version by the same name). Originally time measures were stored as 24 | integers representing seconds. The Duration class instead stores the seconds 25 | as an attribute and adds an optional segements property which can be used to 26 | select exactly how to segment up the time period (years, week, days, etc.). 27 | 28 | 29 | ## Usage 30 | 31 | You only need to require 'richunits' and all of RichUnit's functionality 32 | becomes available. 33 | 34 | require 'richunits' 35 | 36 | 2.hours #=> 7200 37 | 38 | See RDocs for complete API documentation. 39 | 40 | 41 | ## Development 42 | 43 | RichUnits utilizes GitHub for development. You will find the git 44 | repo under the rubyworks account. 45 | 46 | git://github.com/rubyworks/richunits.git 47 | 48 | RichUnits utilizes the Syckle build system. 49 | 50 | 51 | ## Copyrights 52 | 53 | * Copyright (c) 2008 Rubyworks 54 | * Copyright (c) 2006 Matthew Harris 55 | * Copyright (c) 2004 Rich Kilmer 56 | 57 | If you have contributed to this code and feel you deserve mention 58 | in the copyright notice, please let me know. Since this code has 59 | been through a number of hands and other projects (Facets and 60 | ActiveSupport among them) it is hard to identify exactly who is 61 | responsible for what. Nonetheless, Rich Kilmer is the initiator 62 | of the whole idea (AFAIK), so I figure much of the rights belong 63 | to him. 64 | 65 | RichUnits is distributed under the terms of the MIT license. 66 | 67 | See LICENSE.txt file. 68 | -------------------------------------------------------------------------------- /lib/richunits/bytes.rb: -------------------------------------------------------------------------------- 1 | module RichUnits 2 | 3 | # TODO: Currently kilo, mega, etc. are all powers of two and not ten, 4 | # which technically isn't corrent even though it is common usage. 5 | # 6 | # TODO: The in_* notation is weak. If a better nomentclature is thought 7 | # of then consider changing this. 8 | 9 | # = Binary Multipliers 10 | # 11 | # Additional methods for Numeric class to make working with 12 | # bits and bytes easier. 13 | # 14 | # Special thanks to Richard Kilmer for the orignal work. 15 | # This library is based on the original library bytes.rb 16 | # Copyright (c) 2004 by Rich Kilmer. 17 | # 18 | module Bytes 19 | 20 | # = Binary Multipliers for Numeric 21 | # 22 | # Additional methods for Numeric class to make working with 23 | # bits and bytes easier. Bits are used as the base value and 24 | # these methods can be used to convert between different 25 | # magnitudes. 26 | # 27 | # == Synopisis 28 | # 29 | # 1.byte #=> 8 30 | # 2.bytes #=> 16 31 | # 1.kilobit #=> 1024 32 | # 1.kilobyte #=> 8192 33 | # 34 | # Use the in_* methods to perform the inverse operations. 35 | # 36 | # 8192.in_kilobytes #=> 1 37 | # 1024.in_kilobits #=> 1 38 | # 39 | module Numeric 40 | 41 | def bit ; self ; end 42 | def bits ; self ; end 43 | def byte ; self * 8 ; end 44 | def bytes ; self * 8 ; end 45 | 46 | [ 'kilo', 'mega', 'giga', 'tera', 'peta', 'exa' ].each_with_index do |m, i| 47 | j = i + 1 48 | class_eval %{ 49 | def #{m}bit ; self * #{1024**j} ; end 50 | def #{m}byte ; self * #{1024**j*8} ; end 51 | def in_#{m}bits ; self / #{1024**j} ; end 52 | def in_#{m}bytes ; self / #{1024**j*8} ; end 53 | alias_method :#{m}bits, :#{m}bit 54 | alias_method :#{m}bytes, :#{m}byte 55 | } 56 | end 57 | 58 | [ 'kibi', 'mebi', 'gibi', 'tebi', 'pebi', 'exbi' ].each_with_index do |m, i| 59 | j = i + 1 60 | class_eval %{ 61 | def #{m}bit ; self * #{1024**j} ; end 62 | def #{m}byte ; self * #{1024**j*8} ; end 63 | def in_#{m}bits ; self / #{1024**j} ; end 64 | def in_#{m}bytes ; self / #{1024**j*8} ; end 65 | alias_method :#{m}bits, :#{m}bit 66 | alias_method :#{m}bytes, :#{m}byte 67 | } 68 | end 69 | 70 | # Formated string of bits proportial to size. 71 | # 72 | # 1024.bits_to_s #=> "1.00 kb" 73 | # 1048576.bits_to_s #=> "1.00 mb" 74 | # 1073741824.bits_to_s #=> "1.00 gb" 75 | # 1099511627776.bits_to_s #=> "1.00 tb" 76 | # 77 | # Takes a format string to adjust output. 78 | # 79 | # 1024.bits_to_s('%.0f') #=> "1 kb" 80 | # 81 | def strfbits(fmt='%.2f') 82 | case 83 | when self < 1024 84 | "#{self} bits" 85 | when self < 1024**2 86 | "#{fmt % (self.to_f / 1024)} kb" 87 | when self < 1024**3 88 | "#{fmt % (self.to_f / 1024**2)} mb" 89 | when self < 1024**4 90 | "#{fmt % (self.to_f / 1024**3)} gb" 91 | when self < 1024**5 92 | "#{fmt % (self.to_f / 1024**4)} tb" 93 | else 94 | "#{self} bits" 95 | end 96 | end 97 | 98 | # Formated string of bytes proportial to size. 99 | # 100 | # 1024.bytes_to_s #=> "1.00 KB" 101 | # 1048576.bytes_to_s #=> "1.00 MB" 102 | # 1073741824.bytes_to_s #=> "1.00 GB" 103 | # 1099511627776.bytes_to_s #=> "1.00 TB" 104 | # 105 | # Takes a format string to adjust output. 106 | # 107 | # 1024.bytes_to_s('%.0f') #=> "1 KB" 108 | # 109 | def strfbytes(fmt='%.2f') 110 | case 111 | when self < 1024 112 | "#{self} bytes" 113 | when self < 1024**2 114 | "#{fmt % (self.to_f / 1024)} KB" 115 | when self < 1024**3 116 | "#{fmt % (self.to_f / 1024**2)} MB" 117 | when self < 1024**4 118 | "#{fmt % (self.to_f / 1024**3)} GB" 119 | when self < 1024**5 120 | "#{fmt % (self.to_f / 1024**4)} TB" 121 | else 122 | "#{self} bytes" 123 | end 124 | end 125 | 126 | # TODO: deprecate octet_units (?) 127 | alias_method :octet_units, :strfbytes 128 | 129 | end#module Numeric 130 | 131 | end#module Bytes 132 | 133 | end#module RichUnits 134 | 135 | 136 | class Numeric #:nodoc: 137 | include RichUnits::Bytes::Numeric 138 | end 139 | -------------------------------------------------------------------------------- /.gemspec: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'yaml' 4 | require 'pathname' 5 | 6 | module Indexer 7 | 8 | # Convert index data into a gemspec. 9 | # 10 | # Notes: 11 | # * Assumes all executables are in bin/. 12 | # * Does not yet handle default_executable setting. 13 | # * Does not yet handle platform setting. 14 | # * Does not yet handle required_ruby_version. 15 | # * Support for rdoc entries is weak. 16 | # 17 | class GemspecExporter 18 | 19 | # File globs to include in package --unless a manifest file exists. 20 | FILES = ".index .yardopts alt bin data demo ext features lib man spec test try* [A-Z]*.*" unless defined?(FILES) 21 | 22 | # File globs to omit from FILES. 23 | OMIT = "Config.rb" unless defined?(OMIT) 24 | 25 | # Standard file patterns. 26 | PATTERNS = { 27 | :root => '{.index,Gemfile}', 28 | :bin => 'bin/*', 29 | :lib => 'lib/{**/}*', #.rb', 30 | :ext => 'ext/{**/}extconf.rb', 31 | :doc => '*.{txt,rdoc,md,markdown,tt,textile}', 32 | :test => '{test,spec}/{**/}*.rb' 33 | } unless defined?(PATTERNS) 34 | 35 | # For which revision of indexer spec is this converter intended? 36 | REVISION = 2013 unless defined?(REVISION) 37 | 38 | # 39 | def self.gemspec 40 | new.to_gemspec 41 | end 42 | 43 | # 44 | attr :metadata 45 | 46 | # 47 | def initialize(metadata=nil) 48 | @root_check = false 49 | 50 | if metadata 51 | root_dir = metadata.delete(:root) 52 | if root_dir 53 | @root = root_dir 54 | @root_check = true 55 | end 56 | metadata = nil if metadata.empty? 57 | end 58 | 59 | @metadata = metadata || YAML.load_file(root + '.index') 60 | 61 | if @metadata['revision'].to_i != REVISION 62 | warn "This gemspec exporter was not designed for this revision of index metadata." 63 | end 64 | end 65 | 66 | # 67 | def has_root? 68 | root ? true : false 69 | end 70 | 71 | # 72 | def root 73 | return @root if @root || @root_check 74 | @root_check = true 75 | @root = find_root 76 | end 77 | 78 | # 79 | def manifest 80 | return nil unless root 81 | @manifest ||= Dir.glob(root + 'manifest{,.txt}', File::FNM_CASEFOLD).first 82 | end 83 | 84 | # 85 | def scm 86 | return nil unless root 87 | @scm ||= %w{git hg}.find{ |m| (root + ".#{m}").directory? }.to_sym 88 | end 89 | 90 | # 91 | def files 92 | return [] unless root 93 | @files ||= \ 94 | if manifest 95 | File.readlines(manifest). 96 | map{ |line| line.strip }. 97 | reject{ |line| line.empty? || line[0,1] == '#' } 98 | else 99 | list = [] 100 | Dir.chdir(root) do 101 | FILES.split(/\s+/).each do |pattern| 102 | list.concat(glob(pattern)) 103 | end 104 | OMIT.split(/\s+/).each do |pattern| 105 | list = list - glob(pattern) 106 | end 107 | end 108 | list 109 | end.select{ |path| File.file?(path) }.uniq 110 | end 111 | 112 | # 113 | def glob_files(pattern) 114 | return [] unless root 115 | Dir.chdir(root) do 116 | Dir.glob(pattern).select do |path| 117 | File.file?(path) && files.include?(path) 118 | end 119 | end 120 | end 121 | 122 | def patterns 123 | PATTERNS 124 | end 125 | 126 | def executables 127 | @executables ||= \ 128 | glob_files(patterns[:bin]).map do |path| 129 | File.basename(path) 130 | end 131 | end 132 | 133 | def extensions 134 | @extensions ||= \ 135 | glob_files(patterns[:ext]).map do |path| 136 | File.basename(path) 137 | end 138 | end 139 | 140 | def name 141 | metadata['name'] || metadata['title'].downcase.gsub(/\W+/,'_') 142 | end 143 | 144 | def homepage 145 | page = ( 146 | metadata['resources'].find{ |r| r['type'] =~ /^home/i } || 147 | metadata['resources'].find{ |r| r['name'] =~ /^home/i } || 148 | metadata['resources'].find{ |r| r['name'] =~ /^web/i } 149 | ) 150 | page ? page['uri'] : false 151 | end 152 | 153 | def licenses 154 | metadata['copyrights'].map{ |c| c['license'] }.compact 155 | end 156 | 157 | def require_paths 158 | paths = metadata['paths'] || {} 159 | paths['load'] || ['lib'] 160 | end 161 | 162 | # 163 | # Convert to gemnspec. 164 | # 165 | def to_gemspec 166 | if has_root? 167 | Gem::Specification.new do |gemspec| 168 | to_gemspec_data(gemspec) 169 | to_gemspec_paths(gemspec) 170 | end 171 | else 172 | Gem::Specification.new do |gemspec| 173 | to_gemspec_data(gemspec) 174 | to_gemspec_paths(gemspec) 175 | end 176 | end 177 | end 178 | 179 | # 180 | # Convert pure data settings. 181 | # 182 | def to_gemspec_data(gemspec) 183 | gemspec.name = name 184 | gemspec.version = metadata['version'] 185 | gemspec.summary = metadata['summary'] 186 | gemspec.description = metadata['description'] 187 | 188 | metadata['authors'].each do |author| 189 | gemspec.authors << author['name'] 190 | 191 | if author.has_key?('email') 192 | if gemspec.email 193 | gemspec.email << author['email'] 194 | else 195 | gemspec.email = [author['email']] 196 | end 197 | end 198 | end 199 | 200 | gemspec.licenses = licenses 201 | 202 | requirements = metadata['requirements'] || [] 203 | requirements.each do |req| 204 | next if req['optional'] 205 | next if req['external'] 206 | 207 | name = req['name'] 208 | groups = req['groups'] || [] 209 | 210 | version = gemify_version(req['version']) 211 | 212 | if groups.empty? or groups.include?('runtime') 213 | # populate runtime dependencies 214 | if gemspec.respond_to?(:add_runtime_dependency) 215 | gemspec.add_runtime_dependency(name,*version) 216 | else 217 | gemspec.add_dependency(name,*version) 218 | end 219 | else 220 | # populate development dependencies 221 | if gemspec.respond_to?(:add_development_dependency) 222 | gemspec.add_development_dependency(name,*version) 223 | else 224 | gemspec.add_dependency(name,*version) 225 | end 226 | end 227 | end 228 | 229 | # convert external dependencies into gemspec requirements 230 | requirements.each do |req| 231 | next unless req['external'] 232 | gemspec.requirements << ("%s-%s" % req.values_at('name', 'version')) 233 | end 234 | 235 | gemspec.homepage = homepage 236 | gemspec.require_paths = require_paths 237 | gemspec.post_install_message = metadata['install_message'] 238 | end 239 | 240 | # 241 | # Set gemspec settings that require a root directory path. 242 | # 243 | def to_gemspec_paths(gemspec) 244 | gemspec.files = files 245 | gemspec.extensions = extensions 246 | gemspec.executables = executables 247 | 248 | if Gem::VERSION < '1.7.' 249 | gemspec.default_executable = gemspec.executables.first 250 | end 251 | 252 | gemspec.test_files = glob_files(patterns[:test]) 253 | 254 | unless gemspec.files.include?('.document') 255 | gemspec.extra_rdoc_files = glob_files(patterns[:doc]) 256 | end 257 | end 258 | 259 | # 260 | # Return a copy of this file. This is used to generate a local 261 | # .gemspec file that can automatically read the index file. 262 | # 263 | def self.source_code 264 | File.read(__FILE__) 265 | end 266 | 267 | private 268 | 269 | def find_root 270 | root_files = patterns[:root] 271 | if Dir.glob(root_files).first 272 | Pathname.new(Dir.pwd) 273 | elsif Dir.glob("../#{root_files}").first 274 | Pathname.new(Dir.pwd).parent 275 | else 276 | #raise "Can't find root of project containing `#{root_files}'." 277 | warn "Can't find root of project containing `#{root_files}'." 278 | nil 279 | end 280 | end 281 | 282 | def glob(pattern) 283 | if File.directory?(pattern) 284 | Dir.glob(File.join(pattern, '**', '*')) 285 | else 286 | Dir.glob(pattern) 287 | end 288 | end 289 | 290 | def gemify_version(version) 291 | case version 292 | when /^(.*?)\+$/ 293 | ">= #{$1}" 294 | when /^(.*?)\-$/ 295 | "< #{$1}" 296 | when /^(.*?)\~$/ 297 | "~> #{$1}" 298 | else 299 | version 300 | end 301 | end 302 | 303 | end 304 | 305 | end 306 | 307 | Indexer::GemspecExporter.gemspec -------------------------------------------------------------------------------- /lib/richunits/duration.rb: -------------------------------------------------------------------------------- 1 | module RichUnits 2 | 3 | # Durations 4 | # 5 | class Duration 6 | include Comparable 7 | 8 | SECOND = 1 9 | MINUTE = 60 * SECOND 10 | HOUR = 60 * MINUTE 11 | DAY = 24 * HOUR 12 | WEEK = 7 * DAY 13 | YEAR = 365 * DAY 14 | 15 | SEGMENTS = %w{years weeks days hours minutes seconds}.collect{ |s| s.to_sym } 16 | 17 | # Same as #new 18 | # 19 | def self.[](seconds, segmentA=nil, segmentB=nil) 20 | new(seconds, segmentA, segmentB) 21 | end 22 | 23 | # New duration. 24 | # 25 | # call-seq: 26 | # new(seconds) 27 | # new(seconds, max-period) 28 | # new(seconds, max-period, min-period) 29 | # new(seconds, [period1, period2, ...]) 30 | # 31 | def initialize(seconds=0, segmentA=nil, segmentB=nil) 32 | @seconds = seconds.to_i 33 | reset_segments(segmentA, segmentB) 34 | end 35 | 36 | # List of period segments. 37 | def segments; @segments; end 38 | 39 | # Reset segments. 40 | # 41 | # call-seq: 42 | # reset_segments(max-period) 43 | # reset_segments(max-period, min-period) 44 | # reset_segments([period1, period2, ...]) 45 | # 46 | def reset_segments(segmentA=nil, segmentB=nil) 47 | if !segmentA 48 | @segments = [:days, :hours, :minutes, :seconds] 49 | elsif !segmentB 50 | case segmentA 51 | when Array 52 | @segments = segmentA.map{ |p| (p.to_s.downcase.chomp('s') + 's').to_sym } 53 | raise ArgumentError unless @segments.all?{ |s| SEGMENTS.include?(s) } 54 | else 55 | f = SEGMENTS.index(segmentA) 56 | @segments = SEGMENTS[f..0] 57 | end 58 | else # segmentA && segmentB 59 | f = SEGMENTS.index(segmentA) 60 | t = SEGMENTS.index(segmentB) 61 | @segments = SEGMENTS[f..t] 62 | end 63 | end 64 | 65 | def inspect 66 | h = to_h 67 | segments.reverse.collect do |l| 68 | "#{h[l.to_sym]} #{l}" 69 | end.join(' ') 70 | end 71 | 72 | def to_i ; @seconds.to_i ; end 73 | def to_f ; @seconds.to_f ; end 74 | 75 | alias_method :to_int, :to_i 76 | 77 | public 78 | 79 | def to_a 80 | a, s = [], @seconds 81 | a[5], s = *s.divmod(YEAR) if @segments.include?(:years) 82 | a[4], s = *s.divmod(WEEK) if @segments.include?(:weeks) 83 | a[3], s = *s.divmod(DAY) if @segments.include?(:days) 84 | a[2], s = *s.divmod(HOUR) if @segments.include?(:hours) 85 | a[1], s = *s.divmod(MINUTE) if @segments.include?(:minutes) 86 | a[0], s = *s.divmod(SECOND) if @segments.include?(:seconds) 87 | a.compact.reverse 88 | end 89 | 90 | # 91 | def to_h 92 | h, s = {}, @seconds 93 | h[:years], s = *s.divmod(YEAR) if @segments.include?(:years) 94 | h[:weeks], s = *s.divmod(WEEK) if @segments.include?(:weeks) 95 | h[:days], s = *s.divmod(DAY) if @segments.include?(:days) 96 | h[:hours], s = *s.divmod(HOUR) if @segments.include?(:hours) 97 | h[:minutes], s = *s.divmod(MINUTE) if @segments.include?(:minutes) 98 | h[:seconds], s = *s.divmod(SECOND) if @segments.include?(:seconds) 99 | h 100 | end 101 | 102 | def to_s 103 | h = to_h 104 | segments.reverse.collect do |l| 105 | "#{h[l.to_sym]} #{l}" 106 | end.join(' ') 107 | end 108 | 109 | # Returns true if other is also a Duration instance with the 110 | # same value, or if other == value. 111 | def ==(other) 112 | if Duration === other 113 | other.seconds == seconds 114 | else 115 | other == seconds 116 | end 117 | end 118 | 119 | def <=>(other) 120 | @seconds <=> other.to_i 121 | end 122 | 123 | #def is_a?(klass) #:nodoc: 124 | # klass == self.class 125 | #end 126 | 127 | #def self.===(other) #:nodoc: 128 | # other.is_a?(Duration) rescue super 129 | #end 130 | 131 | def years ; to_h[:years] ; end 132 | def weeks ; to_h[:weeks] ; end 133 | def days ; to_h[:days] ; end 134 | def hours ; to_h[:hours] ; end 135 | def minutes ; to_h[:minutes] ; end 136 | def seconds ; to_h[:seconds] ; end 137 | 138 | def total ; seconds ; end 139 | 140 | def +(other) 141 | self.class.new(@seconds + other.to_i, segments) 142 | end 143 | 144 | def -(other) 145 | self.class.new(@seconds - other.to_i, segments) 146 | end 147 | 148 | def *(other) 149 | self.class.new(@seconds * other.to_i, segments) 150 | end 151 | 152 | def /(other) 153 | self.class.new(@seconds / other.to_i, segments) 154 | end 155 | 156 | # 157 | def segmented(*segments) 158 | self.class.new(@seconds, segments) 159 | #segments = segments.collect{ |p| p.to_s.downcase.chomp('s') } 160 | #y,w,d,h,m,s = nil,nil,nil,nil,nil,nil 161 | #x = @seconds 162 | #y, x = *x.divmod(YEAR) if segments.include?('year') 163 | #w, x = *x.divmod(WEEK) if segments.include?('week') 164 | #d, x = *x.divmod(DAY) if segments.include?('day') 165 | #h, x = *x.divmod(HOUR) if segments.include?('hour') 166 | #m, x = *x.divmod(MINUTE) if segments.include?('minute') 167 | #s = x if segments.include?('second') 168 | #[y, w, d, h, m, s].compact 169 | end 170 | 171 | # Format duration. 172 | # 173 | # *Identifiers* 174 | # 175 | # %w -- Number of weeks 176 | # %d -- Number of days 177 | # %h -- Number of hours 178 | # %m -- Number of minutes 179 | # %s -- Number of seconds 180 | # %t -- Total number of seconds 181 | # %x -- Duration#to_s 182 | # %% -- Literal `%' character 183 | # 184 | # *Example* 185 | # 186 | # d = Duration.new(:weeks => 10, :days => 7) 187 | # => # 188 | # d.strftime("It's been %w weeks!") 189 | # => "It's been 11 weeks!" 190 | # 191 | def strftime(fmt) 192 | h = to_h 193 | hx = { 194 | 'y' => h[:years] , 195 | 'w' => h[:weeks] , 196 | 'd' => h[:days] , 197 | 'h' => h[:hours] , 198 | 'm' => h[:minutes], 199 | 's' => h[:seconds], 200 | 't' => total, 201 | 'x' => to_s 202 | } 203 | fmt.gsub(/%?%(w|d|h|m|s|t|x)/) do |match| 204 | hx[match[1..1]] 205 | end.gsub('%%', '%') 206 | end 207 | 208 | # 209 | def -@ #:nodoc: 210 | self.class.new(-@seconds) 211 | end 212 | 213 | # 214 | def +@ #:nodoc: 215 | self.class.new(+@seconds) 216 | end 217 | 218 | # 219 | # Need to wrap back to numeric methods, maybe use method_missing? 220 | # 221 | 222 | # 223 | def before(time) 224 | @seconds.before(time) 225 | end 226 | 227 | # 228 | def after(time) 229 | @seconds.after(time) 230 | end 231 | 232 | 233 | # = Numeric Extensions for Durations 234 | # 235 | module Numeric 236 | 237 | # Enables the use of time calculations and declarations, 238 | # like 45.minutes + 2.hours + 4.years. The base unit for 239 | # all of these Numeric time methods is seconds. 240 | def seconds ; Duration[self] ; end 241 | alias_method :second, :seconds 242 | 243 | # Converts minutes into seconds. 244 | def minutes ; Duration[self * 60] ; end 245 | alias_method :minute, :minutes 246 | 247 | # Converts hours into seconds. 248 | def hours ; Duration[self * 3600] ; end 249 | alias_method :hour, :hours 250 | #def as_hours ; self / 60.minutes ; end 251 | 252 | # Converts days into seconds. 253 | def days ; Duration[self * 86400] ; end 254 | alias_method :day, :days 255 | 256 | # Converts weeks into seconds. 257 | def weeks ; Duration[self * 604800] ; end 258 | alias_method :week, :weeks 259 | 260 | # Converts fortnights into seconds. 261 | # (A fortnight is 2 weeks) 262 | def fortnights ; Duration[self * 1209600] ; end 263 | alias_method :fortnight, :fortnights 264 | 265 | # Converts months into seconds. 266 | # WARNING: This is not exact as it assumes 30 days to a month. 267 | def months ; Duration[self * 30 * 86400] ; end 268 | alias_method :month, :months 269 | 270 | # Converts years into seconds. 271 | # WARNING: This is not exact as it assumes 365 days to a year. 272 | # ie. It doesn not account for leap years. 273 | def years ; Duration[self * 365 * 86400, :years] ; end 274 | alias_method :year, :years 275 | 276 | end 277 | 278 | # Time#duration has been added to convert the UNIX timestamp into a Duration. 279 | # See Time#duration for an example. 280 | # 281 | module Time 282 | # Create a Duration object from the UNIX timestamp. 283 | # 284 | # *Example* 285 | # 286 | # Time.now.duration 287 | # => # 288 | # 289 | def duration 290 | Duration[to_i] 291 | end 292 | end 293 | 294 | end 295 | 296 | end 297 | 298 | class Numeric #:nodoc: 299 | include RichUnits::Duration::Numeric 300 | end 301 | 302 | class Time #:nodoc: 303 | include RichUnits::Duration::Time 304 | end 305 | 306 | -------------------------------------------------------------------------------- /lib/richunits/times.rb: -------------------------------------------------------------------------------- 1 | module RichUnits 2 | require 'richunits/weekdays' 3 | require 'richunits/duration' 4 | 5 | # TODO: Extra Add in_* methods, like in_days, in_hours, etc. ? 6 | 7 | # = Times 8 | # 9 | # Plain-English convenience methods for dealing with dates and times. 10 | # 11 | # Thanks to Richard Kilmer for the orignal work version this work is based upon. 12 | # 13 | module Times 14 | 15 | NEVER = ::Time.mktime(2038) 16 | ZERO = ::Time.mktime(1972) 17 | 18 | # = Time Extensions 19 | # 20 | module Time 21 | 22 | # Like change but does not reset earlier times. 23 | # 24 | # NOTE: It would be better, probably if this were called "change". 25 | # and that #change were called "reset". 26 | # 27 | def set(options) 28 | opts={}; options.each_pair{ |k,v| opts[k] = v.to_i } 29 | self.class.send( self.utc? ? :utc : :local, 30 | opts[:year] || self.year, 31 | opts[:month] || self.month, 32 | opts[:day] || self.day, 33 | opts[:hour] || self.hour, 34 | opts[:min] || self.min, 35 | opts[:sec] || self.sec, 36 | opts[:usec] || self.usec 37 | ) 38 | end 39 | 40 | # Returns a new Time where one or more of the elements 41 | # have been changed according to the +options+ parameter. 42 | # The time options (hour, minute, sec, usec) reset 43 | # cascadingly, so if only the hour is passed, then 44 | # minute, sec, and usec is set to 0. If the hour and 45 | # minute is passed, then sec and usec is set to 0. 46 | # 47 | # t = Time.now #=> Sat Dec 01 14:10:15 -0500 2007 48 | # t.change(:hour => 11) #=> Sat Dec 01 11:00:00 -0500 2007 49 | # 50 | def change(options) 51 | opts=options; #{}; options.each_pair{ |k,v| opts[k] = v.to_i } 52 | self.class.send( 53 | self.utc? ? :utc : :local, 54 | opts[:year] || self.year, 55 | opts[:month] || self.month, 56 | opts[:day] || self.day, 57 | opts[:hour] || self.hour, 58 | opts[:min] || (opts[:hour] ? 0 : self.min), 59 | opts[:sec] || ((opts[:hour] || opts[:min]) ? 0 : self.sec), 60 | opts[:usec] || ((opts[:hour] || opts[:min] || opts[:sec]) ? 0 : self.usec) 61 | ) 62 | end 63 | 64 | # Returns a new Time representing the time 65 | # a number of time-units ago. 66 | # 67 | def ago(number, units=:seconds) 68 | time =( 69 | case units.to_s.downcase.to_sym 70 | when :years 71 | set(:year => (year - number)) 72 | when :months 73 | #years = ((month - number) / 12).to_i 74 | y = ((month - number) / 12).to_i 75 | m = ((month - number - 1) % 12) + 1 76 | set(:year => (year - y), :month => m) 77 | when :weeks 78 | self - (number * 604800) 79 | when :days 80 | self - (number * 86400) 81 | when :hours 82 | self - (number * 3600) 83 | when :minutes 84 | self - (number * 60) 85 | when :seconds, nil 86 | self - number 87 | else 88 | raise ArgumentError, "unrecognized time units -- #{units}" 89 | end 90 | ) 91 | dst_adjustment(time) 92 | end 93 | 94 | # 95 | # Returns a new Time representing the time 96 | # a number of time-units hence. 97 | 98 | def hence(number, units=:seconds) 99 | time =( 100 | case units.to_s.downcase.to_sym 101 | when :years 102 | set( :year=>(year + number) ) 103 | when :months 104 | y = ((month + number) / 12).to_i 105 | m = ((month + number - 1) % 12) + 1 106 | set(:year => (year + y), :month => m) 107 | when :weeks 108 | self + (number * 604800) 109 | when :days 110 | self + (number * 86400) 111 | when :hours 112 | self + (number * 3600) 113 | when :minutes 114 | self + (number * 60) 115 | when :seconds 116 | self + number 117 | else 118 | raise ArgumentError, "unrecognized time units -- #{units}" 119 | end 120 | ) 121 | dst_adjustment(time) 122 | end 123 | 124 | alias_method :in, :hence 125 | 126 | # This is a Railism. 127 | alias_method :since, :hence 128 | 129 | # This is a Railism. 130 | alias_method :from_now, :hence 131 | 132 | # Adjust DST 133 | # 134 | # TODO: Can't seem to get this to pass ActiveSupport tests. 135 | # Even though it is essentially identical to the 136 | # ActiveSupport code (see Time#since in time/calculations.rb). 137 | # It handels all but 4 tests. 138 | def dst_adjustment(time) 139 | self_dst = self.dst? ? 1 : 0 140 | time_dst = time.dst? ? 1 : 0 141 | seconds = (self - time).abs 142 | if (seconds >= 86400 && self_dst != time_dst) 143 | time + ((self_dst - time_dst) * 60 * 60) 144 | else 145 | time 146 | end 147 | end 148 | 149 | # Seconds since midnight: Time.now.seconds_since_midnight 150 | 151 | def seconds_since_midnight 152 | self.hour.hours + self.min.minutes + self.sec + (self.usec/1.0e+6) 153 | end 154 | 155 | # Returns a new Time representing the time a number of seconds ago. 156 | # Do not use this method in combination with x.months, use months_ago instead! 157 | #def ago(seconds) 158 | # # This is basically a wrapper around the Numeric extension. 159 | # #seconds.until(self) 160 | # self - seconds 161 | #end 162 | 163 | # Returns a new Time representing the time 164 | # a number of minutes ago. 165 | 166 | def minutes_ago(minutes) 167 | self - (minutes * 60) 168 | end 169 | 170 | # Returns a new Time representing the time 171 | # a number of hours ago. 172 | 173 | def hours_ago(hours) 174 | self - (hours * 3600) 175 | end 176 | 177 | # Returns a new Time representing the time 178 | # a number of days ago. 179 | 180 | def days_ago(days) 181 | self - (days * 86400) 182 | end 183 | 184 | # Returns a new Time representing the time 185 | # a number of weeks ago. 186 | 187 | def weeks_ago(weeks) 188 | self - (weeks * 604800) 189 | end 190 | 191 | # Returns a new Time representing the time 192 | # a number of months ago. 193 | 194 | def months_ago(months) 195 | years = (month - months / 12).to_i 196 | set(:year=>(year - years), :month=>(month - months) % 12) 197 | end 198 | 199 | # Returns a new Time representing the time a number of specified 200 | # months ago. 201 | #def months_ago(months) 202 | # if months >= self.month 203 | # change(:year => self.year - 1, :month => 12).months_ago(months - self.month) 204 | # else 205 | # change(:year => self.year, :month => self.month - months) 206 | # end 207 | #end 208 | 209 | # Returns a new Time representing the time 210 | # a number of years ago. 211 | 212 | def years_ago(years) 213 | set(:year=>(year - years)) 214 | end 215 | 216 | # Returns a new Time representing the time a number of specified 217 | # years ago. 218 | #def years_ago(years) 219 | # change(:year => self.year - years) 220 | #end 221 | 222 | # Returns a new Time representing the time 223 | # a number of minutes hence. 224 | 225 | def minutes_hence(minutes) 226 | self + (minutes * 60) 227 | end 228 | 229 | # Returns a new Time representing the time 230 | # a number of hours hence. 231 | 232 | def hours_hence(hours) 233 | self + (hours * 3600) 234 | end 235 | 236 | # Returns a new Time representing the time 237 | # a number of days hence. 238 | 239 | def days_hence(days) 240 | self + (days * 86400) 241 | end 242 | 243 | # Returns a new Time representing the time 244 | # a number of weeks hence. 245 | 246 | def weeks_hence(weeks) 247 | self + (weeks * 604800) 248 | end 249 | 250 | # Returns a new Time representing the time 251 | # a number of months hence. 252 | 253 | def months_hence(months) 254 | years = (month + months / 12).to_i 255 | set(:year=>(year + years), :month=>(month + months) % 12) 256 | end 257 | 258 | #def months_hence(months) 259 | # if months + self.month > 12 260 | # change(:year => self.year + 1, :month => 1).months_since(months - (self.month == 1 ? 12 : (self.month + 1))) 261 | # else 262 | # change(:year => self.year, :month => self.month + months) 263 | # end 264 | #end 265 | 266 | # Returns a new Time representing the time 267 | # a number of years hence. 268 | 269 | def years_hence(years) 270 | set(:year=>(year + years)) 271 | end 272 | 273 | # Returns a new Time representing the time a number of seconds 274 | # since the instance time. Do not use this method in combination 275 | # with x.months, use months_since instead! 276 | alias_method :since, :hence 277 | 278 | alias_method :minutes_since, :minutes_hence 279 | alias_method :days_since, :days_hence 280 | alias_method :weeks_since, :weeks_hence 281 | alias_method :months_since, :months_hence 282 | alias_method :years_since, :years_hence 283 | 284 | #def years_since(years) 285 | # change(:year => self.year + years) 286 | #end 287 | 288 | # Short-hand for years_ago(1) 289 | def last_year 290 | years_ago(1) 291 | end 292 | 293 | # Short-hand for years_since(1) 294 | def next_year 295 | years_since(1) 296 | end 297 | 298 | # Short-hand for months_ago(1) 299 | def last_month 300 | months_ago(1) 301 | end 302 | 303 | # Short-hand for months_since(1) 304 | def next_month 305 | months_since(1) 306 | end 307 | 308 | # Returns a new Time representing the "start" of this week (Monday, 0:00) 309 | def beginning_of_week 310 | (self - self.wday.days).midnight + 1.day 311 | end 312 | alias :monday :beginning_of_week 313 | alias :at_beginning_of_week :beginning_of_week 314 | 315 | # Returns a new Time representing the start of the given 316 | # day in next week (default is Monday). 317 | def next_week(day = :monday) 318 | days_into_week = { :monday => 0, :tuesday => 1, :wednesday => 2, 319 | :thursday => 3, :friday => 4, :saturday => 5, 320 | :sunday => 6 } 321 | since(1.week).beginning_of_week.since(days_into_week[day].day).change(:hour => 0) 322 | end 323 | 324 | # Set time to end of day 325 | #def end_of_day 326 | # return Time.mktime(year, month, day, 23, 59, 59, 999) 327 | #end 328 | 329 | # Returns a new Time representing the start of the day (0:00) 330 | def beginning_of_day 331 | self - self.seconds_since_midnight 332 | end 333 | alias :midnight :beginning_of_day 334 | alias :at_midnight :beginning_of_day 335 | alias :at_beginning_of_day :beginning_of_day 336 | alias :start_of_day :beginning_of_day 337 | 338 | # Returns a new Time representing the start of the month 339 | # (1st of the month, 0:00) 340 | def beginning_of_month 341 | #self - ((self.mday-1).days + self.seconds_since_midnight) 342 | change(:day => 1,:hour => 0, :min => 0, :sec => 0, :usec => 0) 343 | end 344 | alias_method :at_beginning_of_month, :beginning_of_month 345 | 346 | # Returns a new Time representing the start of the year (1st of january, 0:00) 347 | def beginning_of_year 348 | change(:month => 1,:day => 1,:hour => 0, :min => 0, :sec => 0, :usec => 0) 349 | end 350 | alias :at_beginning_of_year :beginning_of_year 351 | 352 | # Convenience method which returns a new Time representing 353 | # the time 1 day ago 354 | def yesterday 355 | self.ago(1.day) 356 | end 357 | 358 | # Convenience method which returns a new Time representing 359 | # the time 1 day since the instance time 360 | def tomorrow 361 | self.since(1.day) 362 | end 363 | 364 | # Returns a new time at start of day 365 | def start_of_day 366 | return Time.mktime(year, month, day, 0, 0, 0, 0) 367 | end 368 | alias_method :to_start_of_day, :start_of_day 369 | 370 | # Returns a new time at end of day 371 | def end_of_day 372 | Time.mktime(year, month, day, 23, 59, 59, 999) 373 | end 374 | alias_method :to_end_of_day, :end_of_day 375 | 376 | # Returns true only if day of time is included in the 377 | # range (stime..etime). Only year days are checked. 378 | def in_day_range?(stime=ZERO, etime=NEVER) 379 | if (etime <= stime) 380 | warn "invalid end time (#{etime} < #{stime})" if $DEBUG 381 | etime = NEVER 382 | end 383 | 384 | stime = stime.to_start_of_day 385 | etime = etime.to_end_of_day 386 | 387 | return (stime..etime).include?(time) 388 | end 389 | 390 | end#module Time 391 | 392 | # = Time Metaclass Extensions 393 | # 394 | module TimeClass 395 | 396 | # This method calculates the days extrema given two time objects. 397 | # start time is the given time1 at 00:00:00 398 | # end time is the given time2 at 23:59:59:999 399 | # 400 | # Input: 401 | # - the two times (if only time1 is provided then you get an extrema 402 | # of exactly one day extrema. 403 | # 404 | # Output: 405 | # - the time range. you can get the start/end times using 406 | # range methods. 407 | # 408 | # CREDIT George Moschovitis 409 | 410 | def days_extrema(time1, time2=nil) 411 | time2 = time1 if (not time2.valid? Time) 412 | time2 = NEVER if (time2 <= time1) 413 | start_time = time1.start_of_day #start_of_day(time1) 414 | end_time = time2.end_of_day #end_of_day(time2) 415 | return (start_time..end_time) 416 | end 417 | 418 | end 419 | 420 | # = Numeric Times 421 | # 422 | module Numeric 423 | 424 | # Calculates time _before_ a given time. Default time is now. 425 | # Reads best with arguments: 10.days.before( Time.now - 1.day ) 426 | def before(time = ::Time.now) 427 | time - self 428 | end 429 | alias_method :until, :before # Reads best with argument: 10.minutes.until(time) 430 | alias_method :ago, :before # Reads best without arguments: 10.minutes.ago 431 | 432 | # Calculates time _after_ a given time. Default time is now. 433 | # Reads best with argument: 10.minutes.after(time) 434 | def after(time = ::Time.now) 435 | time + self 436 | end 437 | alias_method :since, :after # Reads best with argument: 10.minutes.since(time) 438 | alias_method :hence, :after # Reads best with argument: 10.minutes.since(time) 439 | alias_method :from_now, :after # Reads best without arguments: 10.minutes.from_now 440 | alias_method :later, :after # Reads best without arguments: 10.minutes.later 441 | 442 | end#module Numeric 443 | 444 | end#module Times 445 | 446 | end#module RichUnits 447 | 448 | class Numeric #:nodoc: 449 | include RichUnits::Times::Numeric 450 | end 451 | 452 | class Time #:nodoc: 453 | extend RichUnits::Times::TimeClass 454 | include RichUnits::Times::Time 455 | 456 | NEVER = Time.mktime(2038) 457 | ZERO = Time.mktime(1972) 458 | end 459 | 460 | -------------------------------------------------------------------------------- /work/deprecated/duration.rb: -------------------------------------------------------------------------------- 1 | module RichUnits 2 | 3 | # = Duration 4 | # 5 | # Duration is a simple class that provides ways of easily manipulating durations 6 | # (timespans) and formatting them as well. 7 | # 8 | # d = Duration.new(60 * 60 * 24 * 10 + 120 + 30) 9 | # => # 10 | # 11 | # d.to_s 12 | # => "1 week, 3 days, 2 minutes and 30 seconds" 13 | # 14 | # [d.weeks, d.days] 15 | # => [1, 3] 16 | # 17 | # d.days = 7; d 18 | # => # 19 | # 20 | # d.strftime('%w w, %d d, %h h, %m m, %s s') 21 | # => "2 w, 0 d, 0 h, 2 m, 30 s" 22 | # 23 | # == Author 24 | # 25 | # * Matthew Harris (mailto:shugotenshi@gmail.com) 26 | # 27 | # == Notes 28 | # 29 | # * This library comes from the orginal Duration project. 30 | # See: http://www.rubyforge.org/projects/duration 31 | # 32 | # == Copying 33 | # 34 | # Copyright (c) 2006 Matthew Harris 35 | # 36 | class Duration 37 | include Comparable 38 | include Enumerable 39 | 40 | attr_reader :total, :weeks, :days, :hours, :minutes 41 | 42 | WEEK = 60 * 60 * 24 * 7 43 | DAY = 60 * 60 * 24 44 | HOUR = 60 * 60 45 | MINUTE = 60 46 | SECOND = 1 47 | 48 | # Initialize Duration class. 49 | # 50 | # *Example* 51 | # 52 | # d = Duration.new(60 * 60 * 24 * 10 + 120 + 30) 53 | # => # 54 | # d = Duration.new(:weeks => 1, :days => 3, :minutes => 2, :seconds => 30) 55 | # => # 56 | # 57 | def initialize(seconds_or_attr = 0) 58 | if seconds_or_attr.kind_of? Hash 59 | # Part->time map table. 60 | h = {:weeks => WEEK, :days => DAY, :hours => HOUR, :minutes => MINUTE, :seconds => SECOND} 61 | 62 | # Loop through each valid part, ignore all others. 63 | seconds = seconds_or_attr.inject(0) do |sec, args| 64 | # Grab the part of the duration (week, day, whatever) and the number of seconds for it. 65 | part, time = args 66 | 67 | # Map each part to their number of seconds and the given value. 68 | # {:weeks => 2} maps to h[:weeks] -- so... weeks = WEEK * 2 69 | if h.key?(prt = part.to_s.to_sym) then sec + time * h[prt] else 0 end 70 | end 71 | else 72 | seconds = seconds_or_attr 73 | end 74 | 75 | @total, array = seconds.to_f.round, [] 76 | @seconds = [WEEK, DAY, HOUR, MINUTE].inject(@total) do |left, part| 77 | array << left / part; left % part 78 | end 79 | 80 | @weeks, @days, @hours, @minutes = array 81 | end 82 | 83 | # Format duration. 84 | # 85 | # *Identifiers* 86 | # 87 | # %w -- Number of weeks 88 | # %d -- Number of days 89 | # %h -- Number of hours 90 | # %m -- Number of minutes 91 | # %s -- Number of seconds 92 | # %t -- Total number of seconds 93 | # %x -- Duration#to_s 94 | # %% -- Literal `%' character 95 | # 96 | # *Example* 97 | # 98 | # d = Duration.new(:weeks => 10, :days => 7) 99 | # => # 100 | # d.strftime("It's been %w weeks!") 101 | # => "It's been 11 weeks!" 102 | # 103 | def strftime(fmt) 104 | h =\ 105 | {'w' => @weeks , 106 | 'd' => @days , 107 | 'h' => @hours , 108 | 'm' => @minutes, 109 | 's' => @seconds, 110 | 't' => @total , 111 | 'x' => to_s} 112 | 113 | fmt.gsub(/%?%(w|d|h|m|s|t|x)/) do |match| 114 | match.size == 3 ? match : h[match[1..1]] 115 | end.gsub('%%', '%') 116 | end 117 | 118 | # Get the number of seconds of a given part, or simply just get the number of 119 | # seconds. 120 | # 121 | # *Example* 122 | # 123 | # d = Duration.new(:weeks => 1, :days => 1, :hours => 1, :seconds => 30) 124 | # => # 125 | # d.seconds(:weeks) 126 | # => 604800 127 | # d.seconds(:days) 128 | # => 86400 129 | # d.seconds(:hours) 130 | # => 3600 131 | # d.seconds 132 | # => 30 133 | # 134 | def seconds(part = nil) 135 | # Table mapping 136 | h = {:weeks => WEEK, :days => DAY, :hours => HOUR, :minutes => MINUTE} 137 | 138 | if [:weeks, :days, :hours, :minutes].include? part 139 | __send__(part) * h[part] 140 | else 141 | @seconds 142 | end 143 | end 144 | 145 | # For iterating through the duration set of weeks, days, hours, minutes, and 146 | # seconds. 147 | # 148 | # *Example* 149 | # 150 | # Duration.new(:weeks => 1, :seconds => 30).each do |part, time| 151 | # puts "part: #{part}, time: #{time}" 152 | # end 153 | # 154 | # _Output_ 155 | # 156 | # part: weeks, time: 1 157 | # part: days, time: 0 158 | # part: hours, time: 0 159 | # part: minutes, time: 0 160 | # part: seconds, time: 30 161 | # 162 | def each 163 | [['weeks' , @weeks ], 164 | ['days' , @days ], 165 | ['hours' , @hours ], 166 | ['minutes' , @minutes], 167 | ['seconds' , @seconds]].each do |part, time| 168 | # Yield to block 169 | yield part, time 170 | end 171 | end 172 | 173 | # Calls `<=>' on Duration#total. 174 | # 175 | # *Example* 176 | # 177 | # 5.days == 24.hours * 5 178 | # => true 179 | # 180 | def <=>(other) 181 | @total <=> other.to_i 182 | end 183 | 184 | # Set the number of weeks. 185 | # 186 | # *Example* 187 | # 188 | # d = Duration.new(0) 189 | # => # 190 | # d.weeks = 2; d 191 | # => # 192 | # 193 | def weeks=(n) 194 | initialize(:weeks => n, :seconds => @total - seconds(:weeks)) 195 | end 196 | 197 | # Set the number of days. 198 | # 199 | # *Example* 200 | # 201 | # d = Duration.new(0) 202 | # => # 203 | # d.days = 5; d 204 | # => # 205 | # 206 | def days=(n) 207 | initialize(:days => n, :seconds => @total - seconds(:days)) 208 | end 209 | 210 | # Set the number of hours. 211 | # 212 | # *Example* 213 | # 214 | # d = Duration.new(0) 215 | # => # 216 | # d.hours = 5; d 217 | # => # 218 | # 219 | def hours=(n) 220 | initialize(:hours => n, :seconds => @total - seconds(:hours)) 221 | end 222 | 223 | # Set the number of minutes. 224 | # 225 | # *Example* 226 | # 227 | # d = Duration.new(0) 228 | # => # 229 | # d.minutes = 30; d 230 | # => # 231 | # 232 | def minutes=(n) 233 | initialize(:minutes => n, :seconds => @total - seconds(:minutes)) 234 | end 235 | 236 | # Set the number of minutes. 237 | # 238 | # *Example* 239 | # 240 | # d = Duration.new(0) 241 | # => # 242 | # d.seconds = 30; d 243 | # => # 244 | # 245 | def seconds=(n) 246 | initialize(:seconds => (@total + n) - @seconds) 247 | end 248 | 249 | # Friendly, human-readable string representation of the duration. 250 | # 251 | # *Example* 252 | # 253 | # d = Duration.new(:seconds => 140) 254 | # => # 255 | # d.to_s 256 | # => "2 minutes and 20 seconds" 257 | # 258 | def to_s 259 | str = '' 260 | 261 | each do |part, time| 262 | # Skip any zero times. 263 | next if time.zero? 264 | 265 | # Concatenate the part of the time and the time itself. 266 | str << "#{time} #{time == 1 ? part[0..-2] : part}, " 267 | end 268 | 269 | str.chomp(', ').sub(/(.+), (.+)/, '\1 and \2') 270 | end 271 | 272 | # Inspection string--Similar to #to_s except that it has the class name. 273 | # 274 | # *Example* 275 | # 276 | # Duration.new(:seconds => 140) 277 | # => # 278 | # 279 | def inspect 280 | "#<#{self.class}: #{(s = to_s).empty? ? '...' : s}>" 281 | end 282 | 283 | # Add to Duration. 284 | # 285 | # *Example* 286 | # 287 | # d = Duration.new(30) 288 | # => # 289 | # d + 30 290 | # => # 291 | # 292 | def +(other) 293 | self.class.new(@total + other.to_i) 294 | end 295 | 296 | # Subtract from Duration. 297 | # 298 | # *Example* 299 | # 300 | # d = Duration.new(30) 301 | # => # 302 | # d - 15 303 | # => # 304 | # 305 | def -(other) 306 | self.class.new(@total - other.to_i) 307 | end 308 | 309 | # Multiply two Durations. 310 | # 311 | # *Example* 312 | # 313 | # d = Duration.new(30) 314 | # => # 315 | # d * 2 316 | # => # 317 | # 318 | def *(other) 319 | self.class.new(@total * other.to_i) 320 | end 321 | 322 | # Divide two Durations. 323 | # 324 | # *Example* 325 | # 326 | # d = Duration.new(30) 327 | # => # 328 | # d / 2 329 | # => # 330 | # 331 | def /(other) 332 | self.class.new(@total / other.to_i) 333 | end 334 | 335 | alias to_i total 336 | end 337 | 338 | # = BigDuration 339 | # 340 | # BigDuration is a variant of Duration that supports years and months. Support 341 | # for months is not accurate, as a month is assumed to be 30 days so use at your 342 | # own risk. 343 | # 344 | class BigDuration < Duration 345 | attr_reader :years, :months 346 | 347 | YEAR = 60 * 60 * 24 * 30 * 12 348 | MONTH = 60 * 60 * 24 * 30 349 | 350 | # Similar to Duration.new except that BigDuration.new supports `:years' and 351 | # `:months' and will also handle years and months correctly when breaking down 352 | # the seconds. 353 | # 354 | def initialize(seconds_or_attr = 0) 355 | if seconds_or_attr.kind_of? Hash 356 | # Part->time map table. 357 | h =\ 358 | {:years => YEAR , 359 | :months => MONTH , 360 | :weeks => WEEK , 361 | :days => DAY , 362 | :hours => HOUR , 363 | :minutes => MINUTE, 364 | :seconds => SECOND} 365 | 366 | # Loop through each valid part, ignore all others. 367 | seconds = seconds_or_attr.inject(0) do |sec, args| 368 | # Grab the part of the duration (week, day, whatever) and the number of seconds for it. 369 | part, time = args 370 | 371 | # Map each part to their number of seconds and the given value. 372 | # {:weeks => 2} maps to h[:weeks] -- so... weeks = WEEK * 2 373 | if h.key?(prt = part.to_s.to_sym) then sec + time * h[prt] else 0 end 374 | end 375 | else 376 | seconds = seconds_or_attr 377 | end 378 | 379 | @total, array = seconds.to_f.round, [] 380 | @seconds = [YEAR, MONTH, WEEK, DAY, HOUR, MINUTE].inject(@total) do |left, part| 381 | array << left / part; left % part 382 | end 383 | 384 | @years, @months, @weeks, @days, @hours, @minutes = array 385 | end 386 | 387 | # BigDuration variant of Duration#strftime. 388 | # 389 | # *Identifiers: BigDuration* 390 | # 391 | # %y -- Number of years 392 | # %m -- Number of months 393 | # 394 | def strftime(fmt) 395 | h = {'y' => @years, 'M' => @months} 396 | super(fmt.gsub(/%?%(y|M)/) { |match| match.size == 3 ? match : h[match[1..1]] }) 397 | end 398 | 399 | # Similar to Duration#each except includes years and months in the interation. 400 | # 401 | def each 402 | [['years' , @years ], 403 | ['months' , @months ], 404 | ['weeks' , @weeks ], 405 | ['days' , @days ], 406 | ['hours' , @hours ], 407 | ['minutes' , @minutes], 408 | ['seconds' , @seconds]].each do |part, time| 409 | # Yield to block 410 | yield part, time 411 | end 412 | end 413 | 414 | # Derived from Duration#seconds, but supports `:years' and `:months' as well. 415 | # 416 | def seconds(part = nil) 417 | h = {:years => YEAR, :months => MONTH} 418 | if [:years, :months].include? part 419 | __send__(part) * h[part] 420 | else 421 | super(part) 422 | end 423 | end 424 | 425 | # Set the number of years in the BigDuration. 426 | # 427 | def years=(n) 428 | initialize(:years => n, :seconds => @total - seconds(:years)) 429 | end 430 | 431 | # Set the number of months in the BigDuration. 432 | # 433 | def months=(n) 434 | initialize(:months => n, :seconds => @total - seconds(:months)) 435 | end 436 | end 437 | 438 | # The following important additions are made to Numeric: 439 | # 440 | # Numeric#weeks -- Create a Duration object with given weeks 441 | # Numeric#days -- Create a Duration object with given days 442 | # Numeric#hours -- Create a Duration object with given hours 443 | # Numeric#minutes -- Create a Duration object with given minutes 444 | # Numeric#seconds -- Create a Duration object with given seconds 445 | # 446 | # BigDuration support: 447 | # 448 | # Numeric#years -- Create a BigDuration object with given years 449 | # Numeric#months -- Create a BigDuration object with given months 450 | # 451 | # BigDuration objects can be created from regular weeks, days, hours, etc by 452 | # providing `:big' as an argument to the above Numeric methods. 453 | 454 | # 455 | module Numeric 456 | # Create a Duration object using self where self could represent weeks, days, 457 | # hours, minutes, and seconds. 458 | # 459 | # *Example* 460 | # 461 | # 10.duration(:weeks) 462 | # => # 463 | # 10.duration 464 | # => # 465 | # 466 | def duration(part = nil, klass = Duration) 467 | if [:years, :months, :weeks, :days, :hours, :minutes, :seconds].include? part 468 | klass.new(part => self) 469 | else 470 | klass.new(self) 471 | end 472 | end 473 | 474 | # TODO: IF WE WANT TO DO THIS IT NEEDS TO BE ADDED TO times.rb ??? 475 | # 476 | # alias __numeric_old_method_missing method_missing 477 | # 478 | # # Intercept calls to .weeks, .days, .hours, .minutes and .seconds because 479 | # # Rails defines its own methods, so I'd like to prevent any redefining of 480 | # # Rails' methods. If these methods don't get captured, then alternatively 481 | # # Numeric#duration can be used. 482 | # # 483 | # # BigDuration methods include .years and .months, also BigDuration objects 484 | # # can be created from any time such as weeks or minutes and even seconds. 485 | # # 486 | # # *Example: BigDuration* 487 | # # 488 | # # 5.years 489 | # # => # 490 | # # 10.minutes(:big) 491 | # # => # 492 | # # 493 | # # *Example* 494 | # # 495 | # # 140.seconds 496 | # # => # 497 | # # 498 | # def method_missing(method, *args) 499 | # if [:weeks, :days, :hours, :minutes, :seconds].include? method 500 | # if args.size > 0 && args[0] == :big 501 | # duration(method, BigDuration) 502 | # else 503 | # duration(method) 504 | # end 505 | # elsif [:years, :months].include? method 506 | # duration(method, BigDuration) 507 | # else 508 | # __numeric_old_method_missing(method, *args) 509 | # end 510 | # end 511 | 512 | end 513 | 514 | # Time#duration has been added to convert the UNIX timestamp into a Duration. 515 | # See Time#duration for an example. 516 | # 517 | module Time 518 | # Create a Duration object from the UNIX timestamp. 519 | # 520 | # *Example* 521 | # 522 | # Time.now.duration 523 | # => # 524 | # 525 | def duration(type = nil) 526 | if type == :big then BigDuration.new(to_i) else Duration.new(to_i) end 527 | end 528 | end 529 | 530 | end 531 | 532 | 533 | class Numeric #:nodoc: 534 | include RichUnits::Duration::Numeric 535 | end 536 | 537 | class Time #:nodoc: 538 | include RichUnits::Duration::Time 539 | end 540 | 541 | --------------------------------------------------------------------------------