├── Gemfile ├── .gitignore ├── .travis.yml ├── lib ├── dotiw │ ├── version.rb │ ├── locale │ │ └── en.yml │ ├── time_hash.rb │ └── action_view_ext │ │ └── helpers │ │ └── date_helper.rb └── dotiw.rb ├── Rakefile ├── spec ├── spec_helper.rb ├── translations │ └── es.yml └── lib │ └── dotiw_spec.rb ├── dotiw.gemspec ├── MIT-LICENSE └── README.markdown /Gemfile: -------------------------------------------------------------------------------- 1 | source 'http://rubygems.org' 2 | 3 | gemspec -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | tmp 2 | pkg 3 | spec/fixtures/database.yml 4 | .rvmrc 5 | Gemfile.lock -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | rvm: 2 | - 2.0.0 3 | - 1.9.3 4 | - rbx-19mode 5 | - jruby-19mode 6 | -------------------------------------------------------------------------------- /lib/dotiw/version.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module DOTIW 4 | VERSION = "1.2" 5 | end 6 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'rake' 3 | require 'bundler' 4 | 5 | require 'rspec/core' 6 | require 'rspec/core/rake_task' 7 | 8 | Bundler::GemHelper.install_tasks 9 | 10 | begin 11 | [:spec, :rcov].each { |task| 12 | RSpec::Core::RakeTask.new(task) do |t| 13 | t.rspec_opts = %w(-fs --color) 14 | end 15 | } 16 | task :default => :spec 17 | rescue LoadError 18 | raise 'RSpec could not be loaded. Run `bundle install` to get all development dependencies.' 19 | end 20 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | ROOT_PATH = File.join(File.dirname(__FILE__), '..') 4 | $:.unshift ROOT_PATH unless $:.include? ROOT_PATH 5 | 6 | # Files that are usually required by Rails, but in a testing context will not be. 7 | require 'erb' 8 | 9 | require 'active_support/all' 10 | require 'action_view' 11 | 12 | require 'dotiw' # require dotiw through init (like a plugin would) 13 | 14 | Time.zone = 'UTC' 15 | # I18n.load_path.clear 16 | I18n.load_path << Dir[File.join(File.dirname(__FILE__), "translations", "*")] 17 | I18n.locale = :en 18 | -------------------------------------------------------------------------------- /lib/dotiw/locale/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | datetime: 3 | dotiw: 4 | seconds: 5 | one: 1 second 6 | other: "%{count} seconds" 7 | minutes: 8 | one: 1 minute 9 | other: "%{count} minutes" 10 | hours: 11 | one: 1 hour 12 | other: "%{count} hours" 13 | days: 14 | one: 1 day 15 | other: "%{count} days" 16 | weeks: 17 | one: 1 week 18 | other: "%{count} weeks" 19 | months: 20 | one: 1 month 21 | other: "%{count} months" 22 | years: 23 | one: 1 year 24 | other: "%{count} years" 25 | -------------------------------------------------------------------------------- /spec/translations/es.yml: -------------------------------------------------------------------------------- 1 | es: 2 | datetime: 3 | dotiw: 4 | seconds: 5 | one: uno segundo 6 | other: "%{count} segundos" 7 | minutes: 8 | one: uno minuto 9 | other: "%{count} minutos" 10 | hours: 11 | one: una hora 12 | other: "%{count} horas" 13 | days: 14 | one: un día 15 | other: "%{count} días" 16 | weeks: 17 | one: una semana 18 | other: "%{count} semanas" 19 | months: 20 | one: un mes 21 | other: "%{count} meses" 22 | years: 23 | one: un año 24 | other: "%{count} años" 25 | -------------------------------------------------------------------------------- /lib/dotiw.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'i18n' 4 | 5 | # Rails hacks 6 | if defined?(ActionView::Helpers) 7 | require 'dotiw/action_view_ext/helpers/date_helper' 8 | end 9 | 10 | module DOTIW 11 | extend self 12 | 13 | autoload :VERSION, 'dotiw/version' 14 | autoload :TimeHash, 'dotiw/time_hash' 15 | 16 | DEFAULT_I18N_SCOPE = :'datetime.dotiw' 17 | 18 | def init_i18n 19 | I18n.load_path.unshift(*locale_files) 20 | I18n.reload! 21 | end 22 | 23 | protected 24 | # Returns all locale files shipped with library 25 | def locale_files 26 | Dir[File.join(File.dirname(__FILE__), 'dotiw', 'locale', '**/*')] 27 | end 28 | end # DOTIW 29 | 30 | DOTIW.init_i18n 31 | -------------------------------------------------------------------------------- /dotiw.gemspec: -------------------------------------------------------------------------------- 1 | # dotiw.gemspec 2 | # -*- encoding: utf-8 -*- 3 | 4 | $:.push File.expand_path("../lib", __FILE__) 5 | require 'dotiw/version' 6 | 7 | Gem::Specification.new do |s| 8 | s.name = 'dotiw' 9 | s.version = DOTIW::VERSION 10 | 11 | s.authors = ["Ryan Bigg"] 12 | s.date = %q{2010-12-23} 13 | s.description = "Better distance_of_time_in_words for Rails" 14 | s.summary = "Better distance_of_time_in_words for Rails" 15 | s.email = "radarlistener@gmail.com" 16 | 17 | s.add_dependency "actionpack", ">= 3" 18 | s.add_dependency "i18n" 19 | 20 | s.add_development_dependency "rake" 21 | s.add_development_dependency "bundler" 22 | s.add_development_dependency "rspec", "~> 2.0" 23 | s.add_development_dependency "tzinfo" 24 | 25 | s.files = `git ls-files`.split("\n") 26 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 27 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 28 | s.require_paths = ["lib"] 29 | end 30 | 31 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 Ryan Bigg 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /lib/dotiw/time_hash.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module DOTIW 4 | class TimeHash 5 | TIME_FRACTIONS = [:seconds, :minutes, :hours, :days, :months, :years] 6 | 7 | attr_accessor :distance, :smallest, :largest, :from_time, :to_time 8 | 9 | def initialize(distance, from_time = nil, to_time = nil, options = {}) 10 | self.output = ActiveSupport::OrderedHash.new 11 | self.options = options 12 | self.distance = distance 13 | self.from_time = from_time || Time.now 14 | self.to_time = to_time || (self.from_time + self.distance.seconds) 15 | self.smallest, self.largest = [self.from_time, self.to_time].minmax 16 | 17 | build_time_hash 18 | end 19 | 20 | def to_hash 21 | output 22 | end 23 | 24 | private 25 | attr_accessor :options, :output 26 | 27 | def build_time_hash 28 | if accumulate_on = options.delete(:accumulate_on) 29 | accumulate_on = accumulate_on.to_sym 30 | if accumulate_on == :years 31 | return build_time_hash 32 | end 33 | TIME_FRACTIONS.index(accumulate_on).downto(0) { |i| self.send("build_#{TIME_FRACTIONS[i]}") } 34 | else 35 | while distance > 0 36 | if distance < 1.minute 37 | build_seconds 38 | elsif distance < 1.hour 39 | build_minutes 40 | elsif distance < 1.day 41 | build_hours 42 | elsif distance < 28.days 43 | build_days 44 | else # greater than a month 45 | build_years_months_days 46 | end 47 | end 48 | end 49 | 50 | output 51 | end 52 | 53 | def build_seconds 54 | output[:seconds] = distance.to_i 55 | self.distance = 0 56 | end 57 | 58 | def build_minutes 59 | output[:minutes], self.distance = distance.divmod(1.minute) 60 | end 61 | 62 | def build_hours 63 | output[:hours], self.distance = distance.divmod(1.hour) 64 | end 65 | 66 | def build_days 67 | output[:days], self.distance = distance.divmod(1.day) 68 | end 69 | 70 | def build_months 71 | build_years_months_days 72 | 73 | if (years = output.delete(:years)) > 0 74 | output[:months] += (years * 12) 75 | end 76 | end 77 | 78 | def build_years_months_days 79 | months = (largest.year - smallest.year) * 12 + (largest.month - smallest.month) 80 | years, months = months.divmod(12) 81 | 82 | days = largest.day - smallest.day 83 | 84 | # Will otherwise incorrectly say one more day if our range goes over a day. 85 | days -= 1 if largest.hour < smallest.hour 86 | 87 | if days < 0 88 | # Convert the last month to days and add to total 89 | months -= 1 90 | last_month = largest.advance(:months => -1) 91 | days += Time.days_in_month(last_month.month, last_month.year) 92 | end 93 | 94 | if months < 0 95 | # Convert a year to months 96 | years -= 1 97 | months += 12 98 | end 99 | 100 | output[:years] = years 101 | output[:months] = months 102 | output[:days] = days 103 | 104 | total_days, self.distance = (from_time - to_time).abs.divmod(1.day) 105 | end 106 | end # TimeHash 107 | end # DOTIW -------------------------------------------------------------------------------- /lib/dotiw/action_view_ext/helpers/date_helper.rb: -------------------------------------------------------------------------------- 1 | module ActionView 2 | module Helpers 3 | module DateHelper 4 | alias_method :old_distance_of_time_in_words, :distance_of_time_in_words 5 | 6 | def distance_of_time_in_words_hash(from_time, to_time, options = {}) 7 | from_time = from_time.to_time if !from_time.is_a?(Time) && from_time.respond_to?(:to_time) 8 | to_time = to_time.to_time if !to_time.is_a?(Time) && to_time.respond_to?(:to_time) 9 | 10 | DOTIW::TimeHash.new((from_time - to_time).abs, from_time, to_time, options).to_hash 11 | end 12 | 13 | def distance_of_time(seconds, options = {}) 14 | options[:include_seconds] ||= true 15 | display_time_in_words DOTIW::TimeHash.new(seconds, nil, nil, options).to_hash, options 16 | end 17 | 18 | def distance_of_time_in_words(from_time, to_time = 0, include_seconds_or_options = {}, options = {}) 19 | if include_seconds_or_options.is_a?(Hash) 20 | options = include_seconds_or_options 21 | else 22 | options[:include_seconds] ||= !!include_seconds_or_options 23 | end 24 | return distance_of_time(from_time, options) if to_time == 0 25 | return old_distance_of_time_in_words(from_time, to_time, options) if options.delete(:vague) 26 | hash = distance_of_time_in_words_hash(from_time, to_time, options) 27 | display_time_in_words(hash, options) 28 | end 29 | 30 | def distance_of_time_in_percent(from_time, current_time, to_time, options = {}) 31 | options[:precision] ||= 0 32 | distance = to_time - from_time 33 | result = ((current_time - from_time) / distance) * 100 34 | number_with_precision(result, options).to_s + "%" 35 | end 36 | 37 | alias_method :old_time_ago_in_words, :time_ago_in_words 38 | 39 | def time_ago_in_words(from_time, include_seconds_or_options = {}) 40 | distance_of_time_in_words(from_time, Time.now, include_seconds_or_options) 41 | end 42 | 43 | private 44 | def display_time_in_words(hash, options = {}) 45 | options.reverse_merge!( 46 | :include_seconds => false 47 | ).symbolize_keys! 48 | 49 | include_seconds = options.delete(:include_seconds) 50 | hash.delete(:seconds) if !include_seconds && hash[:minutes] 51 | 52 | options[:except] = Array.wrap(options[:except]).map!(&:to_s) if options[:except] 53 | options[:only] = Array.wrap(options[:only]).map!(&:to_s) if options[:only] 54 | 55 | # Remove all the values that are nil or excluded. Keep the required ones. 56 | hash.delete_if do |key, value| 57 | value.nil? || value.zero? || 58 | (options[:except] && options[:except].include?(key.to_s)) || 59 | (options[:only] && !options[:only].include?(key.to_s)) 60 | end 61 | return I18n.t('datetime.distance_in_words.less_than_x_seconds', :count => 1, :locale => options[:locale]) if hash.empty? 62 | 63 | options.delete(:except) 64 | options.delete(:only) 65 | 66 | i18n_scope = options.delete(:scope) || DOTIW::DEFAULT_I18N_SCOPE 67 | output = [] 68 | I18n.with_options :locale => options[:locale], :scope => i18n_scope do |locale| 69 | output = hash.map { |key, value| locale.t(key, :count => value) } 70 | end 71 | 72 | highest_measures = options.delete(:highest_measures) 73 | highest_measures = 1 if options.delete(:highest_measure_only) 74 | if highest_measures 75 | output = output[0...highest_measures] 76 | end 77 | 78 | options[:words_connector] ||= I18n.translate :'datetime.dotiw.words_connector', 79 | :default => :'support.array.words_connector', 80 | :locale => options[:locale] 81 | options[:two_words_connector] ||= I18n.translate :'datetime.dotiw.two_words_connector', 82 | :default => :'support.array.two_words_connector', 83 | :locale => options[:locale] 84 | options[:last_word_connector] ||= I18n.translate :'datetime.dotiw.last_word_connector', 85 | :default => :'support.array.last_word_connector', 86 | :locale => options[:locale] 87 | 88 | output.to_sentence(options) 89 | end 90 | end # DateHelper 91 | end # Helpers 92 | end # ActionView 93 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # dotiw [![](https://travis-ci.org/Sija/dotiw.png)](https://travis-ci.org/Sija/dotiw) 2 | 3 | dotiw is a plugin for Rails that overrides the default `distance_of_time_in_words` and provides a more accurate output. Do you crave accuracy down to the second? So do I. That's why I made this plugin. Take this for a totally kickass example: 4 | 5 | >> distance_of_time_in_words(Time.now, Time.now + 1.year + 2.months + 3.days + 4.hours + 5.minutes + 6.seconds, true) 6 | => "1 year, 2 months, 3 days, 4 hours, 5 minutes, and 6 seconds" 7 | 8 | Also if one of the measurement is zero it will not output it: 9 | 10 | >> distance_of_time_in_words(Time.now, Time.now + 1.year + 2.months + 4.hours + 5.minutes + 6.seconds, true) 11 | => "1 year, 2 months, 4 hours, 5 minutes, and 6 seconds" 12 | 13 | Better than "about 1 year", am I right? Of course I am. 14 | 15 | "But Ryan!", you say, "What happens if the time is only in seconds but because of the default the seconds aren't shown? Won't it be blank?" 16 | "No!" I triumphantly reply: 17 | 18 | >> distance_of_time_in_words(Time.now, Time.now + 1.second, false) 19 | => "1 second" 20 | 21 | The third argument for this method is whether or not to include seconds. By default this is `false` (because in Rails' `distance_of_time_in_words` it is), you can turn it on though by passing `true` as the third argument: 22 | 23 | >> distance_of_time_in_words(Time.now, Time.now + 1.year + 1.second, true) 24 | => "1 year, and 1 second" 25 | 26 | Yes this could just be merged into the options hash but I'm leaving it here to ensure "backwards-compatibility", 27 | because that's just an insanely radical thing to do. \m/ 28 | 29 | The last argument is an optional options hash that can be used to manipulate behavior and (which uses `to_sentence`). 30 | 31 | Don't like having to pass in `Time.now` all the time? Then use `time_ago_in_words` which also will *rock your 32 | world*: 33 | 34 | >> time_ago_in_words(Time.now + 3.days + 1.second) 35 | => "3 days, and 1 second" 36 | 37 | Oh, and did I mention it supports I18n? Oh yeah. Rock on! 38 | 39 | ### Options 40 | 41 | #### :locale 42 | 43 | You can pass in a locale and it'll output it in whatever language you want (provided you have translations, otherwise it'll default to English): 44 | 45 | >> distance_of_time_in_words(Time.now, Time.now + 1.minute, false, :locale => :es) 46 | => "1 minuto" 47 | 48 | This will also be passed to `to_sentence` 49 | 50 | #### :vague 51 | 52 | Specify this if you want it to use the old `distance_of_time_in_words`. The value can be anything except `nil` or `false`. 53 | 54 | #### :accumulate_on 55 | 56 | Specifies the maximum output unit which will accumulate all the surplus. Say you set it to seconds and your time difference is of 2 minutes then the output would be 120 seconds. Here's a code example: 57 | 58 | >> distance_of_time_in_words(Time.now, Time.now + 2.hours + 70.seconds, true, :accumulate_on => :minutes) 59 | => "121 minutes and 10 seconds" 60 | 61 | #### :only 62 | 63 | Only want a specific measurement of time? No problem! 64 | 65 | >> distance_of_time_in_words(Time.now, Time.now + 1.hour + 1.minute, false, :only => :minutes) 66 | => "1 minute" 67 | 68 | You only want some? No problem too! 69 | 70 | >> distance_of_time_in_words(Time.now, Time.now + 1.hour + 1.day + 1.minute, false, :only => [:minutes, :hours]) 71 | => "1 hour and 1 minute" 72 | 73 | #### :except 74 | 75 | Don't want a measurement of time? No problem! 76 | 77 | >> distance_of_time_in_words(Time.now, Time.now + 1.hour + 1.minute, false, :except => :minutes) 78 | => "1 hour" 79 | 80 | Culling a whole group of measurements of time: 81 | 82 | >> distance_of_time_in_words(Time.now, Time.now + 1.hour + 1.day + 1.minute, false, :except => [:minutes, :hours]) 83 | => "1 day" 84 | 85 | #### :highest\_measure\_only 86 | 87 | For times when Rails `distance_of_time_in_words` is not precise enough and `DOTIW` is too precise. For instance, if you only want to know the highest time part (measure) that elapsed between two dates. 88 | 89 | >> distance_of_time_in_words(Time.now, Time.now + 1.hour + 1.minute + 1.second, true, :highest_measure_only => true) 90 | => "1 hour" 91 | 92 | Notice how minutes and seconds were removed from the output. Another example: 93 | 94 | >> distance_of_time_in_words(Time.now, Time.now + 1.minute + 1.second, true, :highest_measure_only => true) 95 | => "1 minute" 96 | 97 | Minutes are the highest measure, so seconds were discarded from the output. 98 | 99 | #### :highest\_measuress 100 | 101 | When you want variable precision from `DOTIW`: 102 | 103 | >> distance_of_time_in_words(Time.now, Time.now + 1.hour + 1.minute + 1.second, true, :highest_measures => 2) 104 | => "1 hour and 1 minute" 105 | 106 | #### :words_connector 107 | 108 | **This is an option for `to_sentence`, defaults to ', '** 109 | 110 | Using something other than a comma: 111 | 112 | >> distance_of_time_in_words(Time.now, Time.now + 1.hour + 1.minute + 1.second, true, :words_connector => ' - ') 113 | => "1 hour - 1 minute, and 1 second" 114 | 115 | #### :two\_words\_connector 116 | 117 | **This is an option for `to_sentence`, defaults to ' and '** 118 | 119 | Using something other than 'and': 120 | 121 | >> distance_of_time_in_words(Time.now, Time.now + 1.hour + 1.minute, true, :two_words_connector => ' plus ') 122 | => "1 hour plus 1 minute" 123 | 124 | #### :last\_word\_connector 125 | 126 | **This is an option for `to_sentence`, defaults to ', and '** 127 | 128 | Using something other than ', and': 129 | 130 | >> distance_of_time_in_words(Time.now, Time.now + 1.hour + 1.minute + 1.second, true, :last_word_connector => ', finally ') 131 | => "1 hour, 1 minute, finally 1 second" 132 | 133 | ## distance\_of\_time 134 | 135 | If you have simply a number of seconds you can get the "stringified" version of this by using `distance_of_time`: 136 | 137 | >> distance_of_time(300) 138 | => "5 minutes" 139 | 140 | ## distance\_of\_time\_in\_words\_hash 141 | 142 | Don't like any format you're given? That's cool too! Here, have an indifferent hash version: 143 | 144 | >> distance_of_time_in_words_hash(Time.now, Time.now + 1.year + 2.months + 3.days + 4.hours + 5.minutes + 6.seconds) 145 | => {:days => 3, :seconds => 6, :minutes => 5, :years => 1, :hours => 4, :months => 2} 146 | 147 | Indifferent means that you can access all keys by their `String` or `Symbol` version. 148 | 149 | ## distance\_of\_time\_in\_percent 150 | 151 | If you want to calculate a distance of time in percent, use `distance_of_time_in_percent`. The first argument is the beginning time, the second argument the "current" time and the third argument is the end time. This method takes the same options as [`number_with_precision`](http://api.rubyonrails.org/classes/ActionView/Helpers/NumberHelper.html#method-i-number_with_precision). 152 | 153 | distance_of_time_in_percent("04-12-2009".to_time, "29-01-2010".to_time, "04-12-2010".to_time, options) 154 | 155 | 156 | ## Contributors 157 | 158 | * [chendo](http://github.com/chendo) - for talking through it with me and drawing on the whiteboard 159 | * [Derander](http://github.com/derander) - correct Spanish translations 160 | * [DBA](http://github.com/dba) - Commits leading up to the 0.7 release. 161 | -------------------------------------------------------------------------------- /spec/lib/dotiw_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe "A better distance_of_time_in_words" do 6 | include ActionView::Helpers::DateHelper 7 | include ActionView::Helpers::TextHelper 8 | include ActionView::Helpers::NumberHelper 9 | 10 | before do 11 | I18n.locale = :en 12 | time = "01-08-2009".to_time 13 | Time.stub(:now).and_return(time) 14 | Time.zone.stub(:now).and_return(time) 15 | end 16 | 17 | describe "distance of time" do 18 | fragments = [ 19 | [0.5.minutes, "30 seconds"], 20 | [4.5.minutes, "4 minutes and 30 seconds"], 21 | [5.minutes.to_i, "5 minutes"], 22 | [10.minutes.to_i, "10 minutes"], 23 | [1.hour.to_i, "1 hour"], 24 | [1.hour + 30.seconds, "1 hour and 30 seconds"], 25 | [4.weeks.to_i, "28 days"], 26 | [24.weeks.to_i, "5 months and 15 days"] 27 | ] 28 | fragments.each do |number, result| 29 | it "#{number} == #{result}" do 30 | distance_of_time(number).should eql(result) 31 | end 32 | end 33 | 34 | describe "with options" do 35 | it "except:seconds should skip seconds" do 36 | distance_of_time(1.2.minute, except: 'seconds').should eq("1 minute") 37 | distance_of_time(2.5.hours + 30.seconds, except: 'seconds').should eq("2 hours and 30 minutes") 38 | end 39 | 40 | it "except:seconds har higher presedence than include_seconds:true" do 41 | distance_of_time(1.2.minute, include_seconds: true, except: 'seconds').should eq('1 minute') 42 | end 43 | end 44 | 45 | end 46 | 47 | describe "hash version" do 48 | describe "giving correct numbers of" do 49 | 50 | [:years, :months, :days, :minutes, :seconds].each do |name| 51 | describe name do 52 | it "exactly" do 53 | hash = distance_of_time_in_words_hash(Time.now, Time.now + 1.send(name)) 54 | hash[name].should eql(1) 55 | end 56 | 57 | it "two" do 58 | hash = distance_of_time_in_words_hash(Time.now, Time.now + 2.send(name)) 59 | hash[name].should eql(2) 60 | end 61 | end 62 | end 63 | 64 | it "should be happy with lots of measurements" do 65 | hash = distance_of_time_in_words_hash(Time.now, 66 | Time.now + 1.year + 2.months + 3.days + 4.hours + 5.minutes + 6.seconds) 67 | hash[:years].should eql(1) 68 | hash[:months].should eql(2) 69 | hash[:days].should eql(3) 70 | hash[:hours].should eql(4) 71 | hash[:minutes].should eql(5) 72 | hash[:seconds].should eql(6) 73 | end 74 | end 75 | end 76 | 77 | describe "real version" do 78 | it "debe hablar español" do 79 | distance_of_time_in_words(Time.now, Time.now + 1.days, true, :locale => :es).should eql("un día") 80 | distance_of_time_in_words(Time.now, Time.now + 5.days, true, :locale => :es).should eql("5 días") 81 | end 82 | 83 | fragments = [ 84 | [Time.now, Time.now + 5.days + 3.minutes, "5 days and 3 minutes"], 85 | [Time.now, Time.now + 1.minute, "1 minute"], 86 | [Time.now, Time.now + 3.years, "3 years"], 87 | [Time.now, Time.now + 10.years, "10 years"], 88 | [Time.now, Time.now + 10.years, "10 years"], 89 | [Time.now, Time.now + 3.hour, "3 hours"], 90 | [Time.now, Time.now + 13.months, "1 year and 1 month"], 91 | # Any numeric sequence is merely coincidental. 92 | [Time.now, Time.now + 1.year + 2.months + 3.days + 4.hours + 5.minutes + 6.seconds, "1 year, 2 months, 3 days, 4 hours, 5 minutes, and 6 seconds"], 93 | ["2009-3-16".to_time, "2008-4-14".to_time, "11 months and 2 days"], 94 | ["2009-3-16".to_time + 1.minute, "2008-4-14".to_time, "11 months, 2 days, and 1 minute"], 95 | ["2009-4-14".to_time, "2008-3-16".to_time, "1 year and 29 days"], 96 | ["2009-2-01".to_time, "2009-3-01".to_time, "1 month"], 97 | ["2008-2-01".to_time, "2008-3-01".to_time, "1 month"] 98 | ] 99 | fragments.each do |start, finish, output| 100 | it "should be #{output}" do 101 | distance_of_time_in_words(start, finish, true).should eql(output) 102 | end 103 | end 104 | 105 | describe "accumulate on" do 106 | fragments = [ 107 | [Time.now, 108 | Time.now + 10.minute, 109 | :seconds, 110 | "600 seconds"], 111 | [Time.now, 112 | Time.now + 10.hour + 10.minute + 1.second, 113 | :minutes, 114 | "610 minutes and 1 second"], 115 | [Time.now, 116 | Time.now + 2.day + 10000.hour + 10.second, 117 | :hours, 118 | "10048 hours and 10 seconds"], 119 | [Time.now, 120 | Time.now + 2.day + 10000.hour + 10.second, 121 | :days, 122 | "418 days, 16 hours, and 10 seconds"], 123 | [Time.now, 124 | Time.now + 2.day + 10000.hour + 10.second, 125 | :months, 126 | "13 months, 16 hours, and 10 seconds"] 127 | ] 128 | fragments.each do |start, finish, accumulator, output| 129 | it "should be #{output}" do 130 | distance_of_time_in_words(start, finish, true, :accumulate_on => accumulator).should eql(output) 131 | end 132 | end 133 | end # :accumulate_on 134 | 135 | describe "without finish time" do 136 | # A missing finish argument should default to zero, essentially returning 137 | # the equivalent of distance_of_time in order to be backwards-compatible 138 | # with the original rails distance_of_time_in_words helper. 139 | fragments = [ 140 | [5.minutes.to_i, "5 minutes"], 141 | [10.minutes.to_i, "10 minutes"], 142 | [1.hour.to_i, "1 hour"], 143 | [4.weeks.to_i, "28 days"], 144 | [24.weeks.to_i, "5 months and 15 days"] 145 | ] 146 | fragments.each do |start, output| 147 | it "should be #{output}" do 148 | distance_of_time_in_words(start).should eql(output) 149 | end 150 | end 151 | end 152 | 153 | end 154 | 155 | describe "with output options" do 156 | fragments = [ 157 | # Any numeric sequence is merely coincidental. 158 | [Time.now, 159 | Time.now + 1.year + 2.months + 3.days + 4.hours + 5.minutes + 6.seconds, 160 | { :words_connector => " - " }, 161 | "1 year - 2 months - 3 days - 4 hours - 5 minutes, and 6 seconds"], 162 | [Time.now, 163 | Time.now + 5.minutes + 6.seconds, 164 | { :two_words_connector => " - " }, 165 | "5 minutes - 6 seconds"], 166 | [Time.now, 167 | Time.now + 4.hours + 5.minutes + 6.seconds, 168 | { :last_word_connector => " - " }, 169 | "4 hours, 5 minutes - 6 seconds"], 170 | [Time.now, 171 | Time.now + 1.year + 2.months + 3.days + 4.hours + 5.minutes + 6.seconds, 172 | { :except => "minutes" }, 173 | "1 year, 2 months, 3 days, 4 hours, and 6 seconds"], 174 | [Time.now, 175 | Time.now + 1.hour + 1.minute, 176 | { :except => "minutes"}, "1 hour"], 177 | [Time.now, 178 | Time.now + 1.hour + 1.day + 1.minute, 179 | { :except => ["minutes", "hours"]}, 180 | "1 day"], 181 | [Time.now, 182 | Time.now + 1.hour + 1.day + 1.minute, 183 | { :only => ["minutes", "hours"]}, 184 | "1 hour and 1 minute"], 185 | [Time.now, 186 | Time.now + 1.year + 2.months + 3.days + 4.hours + 5.minutes + 6.seconds, 187 | { :vague => true }, 188 | "about 1 year"], 189 | [Time.now, 190 | Time.now + 1.year + 2.months + 3.days + 4.hours + 5.minutes + 6.seconds, 191 | { :vague => "Yes please" }, 192 | "about 1 year"], 193 | [Time.now, 194 | Time.now + 1.year + 2.months + 3.days + 4.hours + 5.minutes + 6.seconds, 195 | { :vague => false }, 196 | "1 year, 2 months, 3 days, 4 hours, 5 minutes, and 6 seconds"], 197 | [Time.now, 198 | Time.now + 1.year + 2.months + 3.days + 4.hours + 5.minutes + 6.seconds, 199 | { :vague => nil }, 200 | "1 year, 2 months, 3 days, 4 hours, 5 minutes, and 6 seconds"], 201 | [Time.now, 202 | Time.now + 1.year + 2.months + 3.days + 4.hours + 5.minutes + 6.seconds, 203 | { :except => "minutes" }, 204 | "1 year, 2 months, 3 days, 4 hours, and 6 seconds"], 205 | [Time.now, 206 | Time.now + 1.hour + 2.minutes + 3.seconds, 207 | { :highest_measure_only => true }, 208 | "1 hour"], 209 | [Time.now, 210 | Time.now + 1.hours + 2.minutes + 3.seconds, 211 | { :highest_measures => 1 }, 212 | "1 hour"], 213 | [Time.now, 214 | Time.now + 2.year + 3.months + 4.days + 5.hours + 6.minutes + 7.seconds, 215 | { :highest_measures => 3 }, 216 | "2 years, 3 months, and 4 days"], 217 | [Time.now, 218 | Time.now + 2.year + 3.weeks + 4.days + 5.hours + 6.minutes + 7.seconds, 219 | { :highest_measures => 2 }, 220 | "2 years and 25 days"], 221 | [Time.now, 222 | Time.now + 4.days + 6.minutes + 7.seconds, 223 | { :highest_measures => 3 }, 224 | "4 days, 6 minutes, and 7 seconds"], 225 | [Time.now, 226 | Time.now + 1.year + 2.weeks, 227 | { :highest_measures => 3 }, 228 | "1 year and 14 days"] 229 | ] 230 | fragments.each do |start, finish, options, output| 231 | it "should be #{output}" do 232 | distance_of_time_in_words(start, finish, true, options).should eql(output) 233 | end 234 | end 235 | 236 | describe "include_seconds" do 237 | it "is ignored if only seconds have passed" do 238 | distance_of_time_in_words(Time.now, Time.now + 1.second, false).should eql("1 second") 239 | end 240 | 241 | it "removes seconds in all other cases" do 242 | distance_of_time_in_words(Time.now, 243 | Time.now + 1.year + 2.months + 3.days + 4.hours + 5.minutes + 6.seconds, 244 | false).should eql("1 year, 2 months, 3 days, 4 hours, and 5 minutes") 245 | end 246 | end # include_seconds 247 | end 248 | 249 | describe "percentage of time" do 250 | def time_in_percent(options = {}) 251 | distance_of_time_in_percent("04-12-2009".to_time, "29-01-2010".to_time, "04-12-2010".to_time, options) 252 | end 253 | 254 | it "calculates 15%" do 255 | time_in_percent.should eql("15%") 256 | end 257 | 258 | it "calculates 15.3%" do 259 | time_in_percent(:precision => 1).should eql("15.3%") 260 | end 261 | end 262 | 263 | end 264 | --------------------------------------------------------------------------------