From aa508448f3505e49ed2881b7b64efa43b678f3e7 Mon Sep 17 00:00:00 2001 From: Joel Courtney Date: Mon, 14 May 2018 22:35:01 +1000 Subject: [PATCH 1/5] Update to support inclusive and timezones better --- .gitignore | 1 + .rubocop.yml | 7 + .rubocop_todo.yml | 7 + .travis.yml | 27 ++- CHANGELOG.md | 12 +- Gemfile.activesupport51 | 8 +- Rakefile | 6 +- lib/time_difference.rb | 225 +++++++++++++++++++----- spec/spec_helper.rb | 4 +- spec/time_difference_spec.rb | 328 ++++++++++++++++++++++++----------- time_difference.gemspec | 31 ++-- 11 files changed, 488 insertions(+), 168 deletions(-) create mode 100644 .rubocop.yml create mode 100644 .rubocop_todo.yml mode change 100644 => 100755 Rakefile diff --git a/.gitignore b/.gitignore index d87d4be..1e2a35e 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ spec/reports test/tmp test/version_tmp tmp +.DS_STORE diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..cb91144 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,7 @@ +Metrics/BlockLength: + Exclude: + - spec/* +Metrics/ClassLength: + Max: 125 +Metrics/MethodLength: + Max: 12 diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml new file mode 100644 index 0000000..60e1386 --- /dev/null +++ b/.rubocop_todo.yml @@ -0,0 +1,7 @@ +# This configuration was generated by +# `rubocop --auto-gen-config` +# on 2018-05-14 10:24:21 +0000 using RuboCop version 0.52.1. +# The point is for the user to remove these configuration records +# one by one as the offenses are removed from the code base. +# Note that changes in the inspected code, or installation of new +# versions of RuboCop, may require this file to be generated again. diff --git a/.travis.yml b/.travis.yml index 6beb99a..25cd4fc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,27 @@ language: ruby rvm: - - 2.2 - - 2.3 - - 2.4 + - 2.6.0-preview1 + - 2.5.1 + - 2.5.0 + - 2.4.4 + - 2.4.3 + - 2.4.2 + - 2.4.1 + - 2.4.0 + - 2.3.7 + - 2.3.6 + - 2.3.5 + - 2.3.4 + - 2.3.3 + - 2.3.2 + - 2.3.1 + - 2.3.0 + - 2.2.9 + - 2.2.8 + - 2.2.7 + - 2.2.6 + - 2.2.5 + - ruby-head - jruby-19mode # JRuby in 1.9 mode # uncomment this line if your project needs to run something other than `rake`: script: bundle exec rspec spec @@ -10,4 +29,4 @@ gemfile: - Gemfile.activesupport51 before_install: - - gem install bundler \ No newline at end of file + - gem install bundler diff --git a/CHANGELOG.md b/CHANGELOG.md index 06a22e8..6dd4863 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +# CHANGELOG + +## v0.8.0 + +* Supports inclusive vs exclusive date ranges +* Respects timezones +* Correctly calculates differences over leap years (does not assume 365.25 days + in a year or 30.42 days in a month) +* Correctly corrects for daylight savings + ## v0.4.2 * Support Time, DateTime, and Date class as input @@ -9,4 +19,4 @@ ## v0.1.0 -* First release at v0.1.0, here we go! \ No newline at end of file +* First release at v0.1.0, here we go! diff --git a/Gemfile.activesupport51 b/Gemfile.activesupport51 index 65b4cf0..83934ed 100644 --- a/Gemfile.activesupport51 +++ b/Gemfile.activesupport51 @@ -1,4 +1,4 @@ -source "https://rubygems.org" -gemspec - -gem 'activesupport', '~> 5.1' \ No newline at end of file +source 'https://rubygems.org' +gemspec + +gem 'activesupport', '~> 5.1' diff --git a/Rakefile b/Rakefile old mode 100644 new mode 100755 index 16069ae..49f6f49 --- a/Rakefile +++ b/Rakefile @@ -1,7 +1,7 @@ #!/usr/bin/env rake -require "bundler/gem_tasks" +require 'bundler/gem_tasks' require 'rspec/core/rake_task' -task :default => :spec -RSpec::Core::RakeTask.new('spec') \ No newline at end of file +task default: :spec +RSpec::Core::RakeTask.new('spec') diff --git a/lib/time_difference.rb b/lib/time_difference.rb index 589e202..99eb661 100644 --- a/lib/time_difference.rb +++ b/lib/time_difference.rb @@ -1,55 +1,128 @@ require 'rubygems' -require "active_support/all" - +require 'active_support/all' + +# [TimeDifference] +# +# TimeDifference is the missing Ruby method to calculate difference between two +# given time. You can do a Ruby time difference in year, month, week, day, +# hour, minute, and seconds. +# +# @since 2013-04-12 +# @author TM Lee , Joel Courtney class TimeDifference - + # @group Class Constants + TIME_COMPONENTS = %i[years months weeks days hours minutes seconds].freeze + DEFAULT_OPTIONS = { + force_timezone: false, + inclusive: false, + timezone: 'UTC' + }.freeze + # Ensure .initialize is a private method private_class_method :new - TIME_COMPONENTS = [:years, :months, :weeks, :days, :hours, :minutes, :seconds] - - def self.between(start_time, end_time) - new(start_time, end_time) + # Initialize for a range + # + # Default options are: + # inclusive: false + # timezone: 'UTC' + # + # @param [Date, Time, DateTime] start_time + # @param [Date, Time, DateTime] end_time + # @param [Hash] options + # @option options [String] :inclusive whether the calculations for dates + # consider a Date to be inclusive or exclusive. + # @option options [String] :timezone what timezone to use for calculations. + # @return [TimeDifference] + def self.between(start_time, end_time, options = {}) + new(start_time, end_time, options) end - def in_years - in_component(:years) + # The difference in years + # + # @todo account for daylight savings + # + # @param [Integer] rounding the rounding of the numbers + # @return [Numeric] + def in_years(rounding = 2) + in_period(:years, rounding) end - def in_months - (@time_diff / (1.day * 30.42)).round(2) + # The difference in months + # + # @todo account for daylight savings + # + # @param [Integer] rounding the rounding of the numbers + # @return [Numeric] + def in_months(rounding = 2) + in_period(:months, rounding) end - def in_weeks - in_component(:weeks) + # The difference in consistent (groups of 7). + # + # @todo account for daylight savings + # + # @param [Integer] rounding the rounding of the numbers + # @return [Numeric] + def in_weeks(rounding = 2) + in_period(:weeks, rounding) end - def in_days - in_component(:days) + # The difference in days + # + # @todo account for daylight savings + # + # @param [Integer] rounding the rounding of the numbers + # @return [Numeric] + def in_days(rounding = 2) + in_period(:days, rounding) end - def in_hours - in_component(:hours) + # The difference in hours + # + # @param [Integer] rounding the rounding of the numbers + # @return [Numeric] + def in_hours(rounding = 2) + in_component(:hours, rounding) end - def in_minutes - in_component(:minutes) + # The difference in minutes + # + # @param [Integer] rounding the rounding of the numbers + # @return [Numeric] + def in_minutes(rounding = 2) + in_component(:minutes, rounding) end - def in_seconds + # The difference in seconds + # + # @param [Integer] rounding the rounding of the numbers + # @return [Numeric] + def in_seconds(_rounding = nil) @time_diff end - def in_each_component + # The difference in each component available. + # + # @param [Integer] rounding the rounding of the numbers + # @return [Hash] + def in_each_component(rounding = 2) Hash[TIME_COMPONENTS.map do |time_component| - [time_component, public_send("in_#{time_component}")] + [time_component, public_send("in_#{time_component}", rounding)] end] end - def in_general + # The general approach to provide inputs for human readable + # + # @todo work out how to work with timezones + # + # @return [Hash] + def in_general(rounding = 2) remaining = @time_diff Hash[TIME_COMPONENTS.map do |time_component| if remaining > 0 - rounded_time_component = (remaining / 1.send(time_component).seconds).round(2).floor + rounded_time_component = ( + remaining / 1.send(time_component).seconds + ).round(rounding).floor remaining -= rounded_time_component.send(time_component) [time_component, rounded_time_component] else @@ -58,42 +131,108 @@ def in_general end] end - def humanize + # Returns the difference in human readable form. + # + # @todo I18n support + # @todo work out how to work with timezones + # + # @return [String] + def humanize(rounding = 2) diff_parts = [] - in_general.each do |part,quantity| + in_general(rounding).each do |part, quantity| next if quantity <= 0 part = part.to_s.humanize - - if quantity <= 1 - part = part.singularize - end - + part = part.singularize if quantity <= 1 diff_parts << "#{quantity} #{part}" end last_part = diff_parts.pop - if diff_parts.empty? - return last_part - else - return [diff_parts.join(', '), last_part].join(' and ') - end + return last_part if diff_parts.empty? + + [diff_parts.join(', '), last_part].join(' and ') end private - def initialize(start_time, end_time) - start_time = time_in_seconds(start_time) - end_time = time_in_seconds(end_time) - - @time_diff = (end_time - start_time).abs + # rubocop:disable Metrics/AbcSize + # Creates a new instance of TimeDifference + # + # @param [Date, Time, DateTime] start_time + # @param [Date, Time, DateTime] end_time + # @param [Hash] options + # @option options [Boolean] :inclusive + # @option options [String] :timezone + # @option optinos [Boolean] :force_timezone + # @return [TimeDifference] + def initialize(start_time, end_time, options) + start_time, end_time = end_time, start_time if end_time < start_time + end_time += 1.day if @inclusive && end_time.is_a?(Date) + + @force_timezone = options.fetch( + :force_timezone, + DEFAULT_OPTIONS[:force_timezone] + ) + @inclusive = options.fetch(:inclusive, DEFAULT_OPTIONS[:inclusive]) + @timezone = options.fetch(:timezone, DEFAULT_OPTIONS[:timezone]) + + @start = in_time_zone(start_time) + @finish = in_time_zone(end_time) + + @time_diff = (@finish.to_f - @start.to_f) + end + # rubocop:enable Metrics/AbcSize + + # Transforms to a timezone if needed + # + # @param [Date, Time, DateTime] time + # @return [Time] + def in_time_zone(time) + time = time.in_time_zone(@timezone) if !time.is_a?(Time) || @force_timezone + time end + # Returns the time in seconds + # + # @param [Time, Date, DateTime] time + # @return [Numeric] def time_in_seconds(time) time.to_time.to_f end - def in_component(component) - (@time_diff / 1.send(component)).round(2) + # Returns the time in a given component + # + # @param [Symbol] component + # @param [Integer] rounding the rounding of the numbers + # @return [Numeric] + def in_component(component, rounding) + if %i[years months weeks days].include?(component) + return in_period(component, rounding) + end + + (@time_diff / 1.send(component)).round(rounding) end + # rubocop:disable Metrics/AbcSize + # Returns the length of time of the difference in a given period_type + # + # @param [Symbol] period_type + # @param [Integer] rounding the rounding of the numbers + # @return [Numeric] + def in_period(period_type, rounding = 2) + # First step iterate through years + periods = 0.0 + pointer = @start + 1.send(period_type) + remainder = @time_diff + last_number_in_s = pointer.to_f - (pointer - 1.send(period_type)).to_f + + while pointer < @finish + periods += 1 + remainder -= last_number_in_s + pointer += 1.send(period_type) + last_number_in_s = pointer.to_f - (pointer - 1.send(period_type)).to_f + end + + periods + (remainder / last_number_in_s).round(rounding) + end + # rubocop:enable Metrics/AbcSize end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 06b4c1d..9cfed4d 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -2,8 +2,8 @@ require 'time_difference' RSpec.configure do |config| - config.formatter = 'documentation' + config.formatter = 'documentation' # Configure Timezone for proper tests ENV['TZ'] = 'UTC' -end \ No newline at end of file +end diff --git a/spec/time_difference_spec.rb b/spec/time_difference_spec.rb index e1e7f8c..1a67ed4 100644 --- a/spec/time_difference_spec.rb +++ b/spec/time_difference_spec.rb @@ -1,183 +1,315 @@ require 'spec_helper' describe TimeDifference do - def self.with_each_class(&block) classes = [Time, Date, DateTime] - classes.each do |clazz| - context "with a #{clazz.name} class" do - instance_exec clazz, &block + classes.each do |klass| + context "with a #{klass.name} class" do + instance_exec klass, &block end end end - describe ".between" do - with_each_class do |clazz| - it "returns a new TimeDifference instance in each component" do - start_time = clazz.new(2011, 1) - end_time = clazz.new(2011, 12) + def self.with_each_time_class(&block) + classes = [Time, DateTime] - expect(TimeDifference.between(start_time, end_time)).to be_a(TimeDifference) + classes.each do |klass| + context "with a #{klass.name} class" do + instance_exec klass, &block end end end - describe "#in_each_component" do - with_each_class do |clazz| - it "returns time difference in each component" do - start_time = clazz.new(2011, 1) - end_time = clazz.new(2011, 12) + def self.with_each_date_class(&block) + classes = [Date] + + classes.each do |klass| + context "with a #{klass.name} class" do + instance_exec klass, &block + end + end + end + + # Let us use Australia/Sydney because it's got Daylight Savings Time + let(:timezone) { 'Australia/Sydney' } + + describe '.between' do + context 'options: inclusive: true' do + with_each_class do |klass| + it 'returns a new TimeDifference instance in each component' do + start_time = klass.new(2011, 1) + end_time = klass.new(2011, 12) + + expect(TimeDifference.between(start_time, end_time, inclusive: true)) + .to be_a(TimeDifference) + end + end + end + + context 'options: timezone: Australia/Sydney' do + with_each_class do |klass| + it 'returns a new TimeDifference instance in each component' do + start_time = klass.new(2011, 1) + end_time = klass.new(2011, 12) + + td = TimeDifference.between(start_time, end_time, timezone: timezone) + + expect(td).to be_a(TimeDifference) + end + end + end + + context 'options: inclusive: true, timezone: Australia/Sydney' do + let(:options) { { inclusive: true, timezone: timezone } } + + with_each_class do |klass| + it 'returns a new TimeDifference instance in each component' do + start_time = klass.new(2011, 1) + end_time = klass.new(2011, 12) - expect(TimeDifference.between(start_time, end_time).in_each_component).to eql({years: 0.91, months: 10.98, weeks: 47.71, days: 334.0, hours: 8016.0, minutes: 480960.0, seconds: 28857600.0}) + expect(TimeDifference.between(start_time, end_time, options)) + .to be_a(TimeDifference) + end end end end - describe "#in_general" do - with_each_class do |clazz| - it "returns time difference in general that matches the total seconds" do - start_time = clazz.new(2009, 11) - end_time = clazz.new(2011, 1) + describe '#in_each_component' do + context 'General case' do + with_each_class do |klass| + it 'returns time difference in each component' do + start_time = klass.new(2011, 1) + end_time = klass.new(2011, 12) + + expect(TimeDifference.between(start_time, end_time).in_each_component) + .to eql( + years: 0.92, + months: 11.0, + weeks: 47.71, + days: 334.0, + hours: 8016.0, + minutes: 480_960.0, + seconds: 28_857_600.0 + ) + end + end + end + + context 'Covers a leap year' do + with_each_class do |klass| + it 'returns time difference in each component' do + start_time = klass.new(2012, 1) # 2012-01-01T00:00:00Z + end_time = klass.new(2013, 2) # 2013-02-01T00:00:00Z + + expect(TimeDifference.between(start_time, end_time).in_each_component) + .to eql( + years: 1.08, + months: 13.00, + weeks: 56.71, + days: 397.00, + hours: 9528.00, + minutes: 571_680.00, + seconds: 34_300_800.00 + ) + end + end + end + + context 'Covers the DST transition' do + before { ENV['TZ'] = timezone } + after { ENV['TZ'] = 'UTC' } + + with_each_class do |klass| + it 'returns time difference in each component' do + start_time = klass.parse('2018-03-29T00:00:00+11:00') + end_time = klass.parse('2018-04-04T00:00:00+10:00') + + td = TimeDifference.between(start_time, end_time, timezone: timezone) + + expect(td.in_each_component) + .to eql( + years: 0.02, + months: 0.19, + weeks: 0.86, + days: 6.0, + hours: 145.0, + minutes: 8_700.0, + seconds: 522_000.0 + ) + end + end + end + end - expect(TimeDifference.between(start_time, end_time).in_general).to eql({years: 1, months: 2, weeks: 0, days: 0, hours: 0, minutes: 0, seconds: 0}) + describe '#in_general' do + with_each_class do |klass| + it 'returns time difference in general that matches the total seconds' do + start_time = klass.new(2009, 11) + end_time = klass.new(2011, 1) + + expect(TimeDifference.between(start_time, end_time).in_general) + .to eql( + years: 1, + months: 2, + weeks: 0, + days: 0, + hours: 0, + minutes: 0, + seconds: 0 + ) end end end - describe "#humanize" do - with_each_class do |clazz| - it "returns a string representing the time difference from in_general" do - start_time = clazz.new(2009, 11) - end_time = clazz.new(2011, 1) + describe '#humanize' do + with_each_class do |klass| + it 'returns a string representing the time difference from in_general' do + start_time = klass.new(2009, 11) + end_time = klass.new(2011, 1) - expect(TimeDifference.between(start_time, end_time).humanize).to eql("1 Year and 2 Months") + expect(TimeDifference.between(start_time, end_time).humanize) + .to eql('1 Year and 2 Months') end end end - describe "#in_years" do - with_each_class do |clazz| - it "returns time difference in years based on Wolfram Alpha" do - start_time = clazz.new(2011, 1) - end_time = clazz.new(2011, 12) + describe '#in_years' do + with_each_class do |klass| + it 'returns time difference in years based on Wolfram Alpha' do + start_time = klass.new(2011, 1) + end_time = klass.new(2011, 12) - expect(TimeDifference.between(start_time, end_time).in_years).to eql(0.91) + expect(TimeDifference.between(start_time, end_time).in_years) + .to eql(0.92) end - it "returns an absolute difference" do - start_time = clazz.new(2011, 12) - end_time = clazz.new(2011, 1) + it 'returns an absolute difference' do + start_time = klass.new(2011, 12) + end_time = klass.new(2011, 1) - expect(TimeDifference.between(start_time, end_time).in_years).to eql(0.91) + expect(TimeDifference.between(start_time, end_time).in_years) + .to eql(0.92) end end end - describe "#in_months" do - with_each_class do |clazz| - it "returns time difference in months based on Wolfram Alpha" do - start_time = clazz.new(2011, 1) - end_time = clazz.new(2011, 12) + describe '#in_months' do + with_each_class do |klass| + it 'returns time difference in months based on Wolfram Alpha' do + start_time = klass.new(2011, 1) + end_time = klass.new(2011, 12) - expect(TimeDifference.between(start_time, end_time).in_months).to eql(10.98) + expect(TimeDifference.between(start_time, end_time).in_months) + .to eql(11.0) end - it "returns an absolute difference" do - start_time = clazz.new(2011, 12) - end_time = clazz.new(2011, 1) + it 'returns an absolute difference' do + start_time = klass.new(2011, 12) + end_time = klass.new(2011, 1) - expect(TimeDifference.between(start_time, end_time).in_months).to eql(10.98) + expect(TimeDifference.between(start_time, end_time).in_months) + .to eql(11.0) end end end - describe "#in_weeks" do - with_each_class do |clazz| - it "returns time difference in weeks based on Wolfram Alpha" do - start_time = clazz.new(2011, 1) - end_time = clazz.new(2011, 12) + describe '#in_weeks' do + with_each_class do |klass| + it 'returns time difference in weeks based on Wolfram Alpha' do + start_time = klass.new(2011, 1) + end_time = klass.new(2011, 12) - expect(TimeDifference.between(start_time, end_time).in_weeks).to eql(47.71) + expect(TimeDifference.between(start_time, end_time).in_weeks) + .to eql(47.71) end - it "returns an absolute difference" do - start_time = clazz.new(2011, 12) - end_time = clazz.new(2011, 1) + it 'returns an absolute difference' do + start_time = klass.new(2011, 12) + end_time = klass.new(2011, 1) - expect(TimeDifference.between(start_time, end_time).in_weeks).to eql(47.71) + expect(TimeDifference.between(start_time, end_time).in_weeks) + .to eql(47.71) end end end - describe "#in_days" do - with_each_class do |clazz| - it "returns time difference in weeks based on Wolfram Alpha" do - start_time = clazz.new(2011, 1) - end_time = clazz.new(2011, 12) + describe '#in_days' do + with_each_class do |klass| + it 'returns time difference in weeks based on Wolfram Alpha' do + start_time = klass.new(2011, 1) + end_time = klass.new(2011, 12) - expect(TimeDifference.between(start_time, end_time).in_days).to eql(334.0) + expect(TimeDifference.between(start_time, end_time).in_days) + .to eql(334.0) end - it "returns an absolute difference" do - start_time = clazz.new(2011, 12) - end_time = clazz.new(2011, 1) + it 'returns an absolute difference' do + start_time = klass.new(2011, 12) + end_time = klass.new(2011, 1) - expect(TimeDifference.between(start_time, end_time).in_days).to eql(334.0) + expect(TimeDifference.between(start_time, end_time).in_days) + .to eql(334.0) end end end - describe "#in_hours" do - with_each_class do |clazz| - it "returns time difference in hours based on Wolfram Alpha" do - start_time = clazz.new(2011, 1) - end_time = clazz.new(2011, 12) + describe '#in_hours' do + with_each_class do |klass| + it 'returns time difference in hours based on Wolfram Alpha' do + start_time = klass.new(2011, 1) + end_time = klass.new(2011, 12) - expect(TimeDifference.between(start_time, end_time).in_hours).to eql(8016.0) + expect(TimeDifference.between(start_time, end_time).in_hours) + .to eql(8016.0) end - it "returns an absolute difference" do - start_time = clazz.new(2011, 12) - end_time = clazz.new(2011, 1) + it 'returns an absolute difference' do + start_time = klass.new(2011, 12) + end_time = klass.new(2011, 1) - expect(TimeDifference.between(start_time, end_time).in_hours).to eql(8016.0) + expect(TimeDifference.between(start_time, end_time).in_hours) + .to eql(8016.0) end end end - describe "#in_minutes" do - with_each_class do |clazz| - it "returns time difference in minutes based on Wolfram Alpha" do - start_time = clazz.new(2011, 1) - end_time = clazz.new(2011, 12) + describe '#in_minutes' do + with_each_class do |klass| + it 'returns time difference in minutes based on Wolfram Alpha' do + start_time = klass.new(2011, 1) + end_time = klass.new(2011, 12) - expect(TimeDifference.between(start_time, end_time).in_minutes).to eql(480960.0) + expect(TimeDifference.between(start_time, end_time).in_minutes) + .to eql(480_960.0) end - it "returns an absolute difference" do - start_time = clazz.new(2011, 12) - end_time = clazz.new(2011, 1) + it 'returns an absolute difference' do + start_time = klass.new(2011, 12) + end_time = klass.new(2011, 1) - expect(TimeDifference.between(start_time, end_time).in_minutes).to eql(480960.0) + expect(TimeDifference.between(start_time, end_time).in_minutes) + .to eql(480_960.0) end end end - describe "#in_seconds" do - with_each_class do |clazz| - it "returns time difference in seconds based on Wolfram Alpha" do - start_time = clazz.new(2011, 1) - end_time = clazz.new(2011, 12) + describe '#in_seconds' do + with_each_class do |klass| + it 'returns time difference in seconds based on Wolfram Alpha' do + start_time = klass.new(2011, 1) + end_time = klass.new(2011, 12) - expect(TimeDifference.between(start_time, end_time).in_seconds).to eql(28857600.0) + expect(TimeDifference.between(start_time, end_time).in_seconds) + .to eql(28_857_600.0) end - it "returns an absolute difference" do - start_time = clazz.new(2011, 12) - end_time = clazz.new(2011, 1) + it 'returns an absolute difference' do + start_time = klass.new(2011, 12) + end_time = klass.new(2011, 1) - expect(TimeDifference.between(start_time, end_time).in_seconds).to eql(28857600.0) + expect(TimeDifference.between(start_time, end_time).in_seconds) + .to eql(28_857_600.0) end end end diff --git a/time_difference.gemspec b/time_difference.gemspec index 5ece9c4..a34463f 100644 --- a/time_difference.gemspec +++ b/time_difference.gemspec @@ -1,21 +1,26 @@ -# -*- encoding: utf-8 -*- + Gem::Specification.new do |gem| - gem.authors = ["TM Lee"] - gem.email = ["tmlee.ltm@gmail.com"] - gem.description = "TimeDifference is the missing Ruby method to calculate difference between two given time. You can do a Ruby time difference in year, month, week, day, hour, minute, and seconds." - gem.summary = "TimeDifference is the missing Ruby method to calculate difference between two given time. You can do a Ruby time difference in year, month, week, day, hour, minute, and seconds." - gem.homepage = "" + gem.authors = ['TM Lee', 'Joel Courtney'] + gem.email = ['tmlee.ltm@gmail.com', 'jcourtney@cozero.com.au'] + gem.description = 'TimeDifference is the missing Ruby method to calculate '\ + 'difference between two given time. You can do a Ruby '\ + 'time difference in year, month, week, day, hour, '\ + 'minute, and seconds.' + gem.summary = 'TimeDifference is the missing Ruby method to calculate '\ + 'difference between two given time. You can do a Ruby '\ + 'time difference in year, month, week, day, hour, '\ + 'minute, and seconds.' + gem.homepage = 'https://github.com/tmlee/time_difference' - gem.files = `git ls-files`.split($\) - gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } + gem.files = `git ls-files`.split($OUTPUT_RECORD_SEPARATOR) + gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) } gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) - gem.name = "time_difference" - gem.require_paths = ["lib"] - gem.version = "0.7.0" + gem.name = 'time_difference' + gem.require_paths = ['lib'] + gem.version = '0.8.0' gem.license = 'MIT' gem.add_runtime_dependency('activesupport', '~> 5.1') - gem.add_development_dependency('rspec', '~> 3.7.0') gem.add_development_dependency('rake') - + gem.add_development_dependency('rspec', '~> 3.7.0') end From 703f580439a33657003e40069642f4cea2092230 Mon Sep 17 00:00:00 2001 From: Joel Courtney Date: Tue, 15 May 2018 00:09:59 +1000 Subject: [PATCH 2/5] Calendar Years and Calendar Month calculations supported --- CHANGELOG.md | 2 + lib/time_difference.rb | 57 +++++- spec/spec_helper.rb | 11 ++ spec/time_difference_spec.rb | 337 +++++++++++++++++++++++++++++++++-- time_difference.gemspec | 2 + 5 files changed, 393 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6dd4863..5505504 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ * Correctly calculates differences over leap years (does not assume 365.25 days in a year or 30.42 days in a month) * Correctly corrects for daylight savings +* Support for in_calendar_years and in_calendar_months for monthly and yearly + billing cycle calculation support ## v0.4.2 * Support Time, DateTime, and Date class as input diff --git a/lib/time_difference.rb b/lib/time_difference.rb index 99eb661..b6389f0 100644 --- a/lib/time_difference.rb +++ b/lib/time_difference.rb @@ -47,6 +47,23 @@ def in_years(rounding = 2) in_period(:years, rounding) end + # The difference in calendar years + # + # @param [Integer] rounding + # @return [Numeric] + def in_calendar_years(rounding = 2) + year_diff = (@finish.year - @start.year) - 1 + + if year_diff < 0 + year_diff = (@finish - @start) / seconds_in_year_for_time(@start) + else + year_diff += ((@start.beginning_of_year + 1.year) - @start) / seconds_in_year_for_time(@start) + year_diff += (@finish - @finish.beginning_of_year) / seconds_in_year_for_time(@finish) + end + + year_diff.round(rounding) + end + # The difference in months # # @todo account for daylight savings @@ -57,6 +74,23 @@ def in_months(rounding = 2) in_period(:months, rounding) end + # The difference in calendar months + # + # @param [Integer] rounding + # @return [Numeric] + def in_calendar_months(rounding = 2) + month_diff = (12 * @finish.year + @finish.month) - (12 * @start.year + @start.month) - 1 + + if month_diff < 0 + month_diff = (@finish - @start) / seconds_in_month_for_time(@start) + else + month_diff += ((@start.beginning_of_month + 1.month) - @start) / seconds_in_month_for_time(@start) + month_diff += (@finish - @finish.beginning_of_month) / seconds_in_month_for_time(@finish) + end + + month_diff.round(rounding) + end + # The difference in consistent (groups of 7). # # @todo account for daylight savings @@ -166,7 +200,6 @@ def humanize(rounding = 2) # @return [TimeDifference] def initialize(start_time, end_time, options) start_time, end_time = end_time, start_time if end_time < start_time - end_time += 1.day if @inclusive && end_time.is_a?(Date) @force_timezone = options.fetch( :force_timezone, @@ -175,6 +208,8 @@ def initialize(start_time, end_time, options) @inclusive = options.fetch(:inclusive, DEFAULT_OPTIONS[:inclusive]) @timezone = options.fetch(:timezone, DEFAULT_OPTIONS[:timezone]) + end_time += 1.day if @inclusive && end_time.is_a?(Date) + @start = in_time_zone(start_time) @finish = in_time_zone(end_time) @@ -205,10 +240,6 @@ def time_in_seconds(time) # @param [Integer] rounding the rounding of the numbers # @return [Numeric] def in_component(component, rounding) - if %i[years months weeks days].include?(component) - return in_period(component, rounding) - end - (@time_diff / 1.send(component)).round(rounding) end @@ -235,4 +266,20 @@ def in_period(period_type, rounding = 2) periods + (remainder / last_number_in_s).round(rounding) end # rubocop:enable Metrics/AbcSize + + # Returns the number of seconds in a year for a given time + # + # @param [Time] seconds_in_year_for_time + # @return [Numeric] + def seconds_in_year_for_time(time) + ((time.beginning_of_year + 1.year) - time.beginning_of_year).to_f + end + + # Returns the number of seconds in a year for a given time + # + # @param [Time] seconds_in_year_for_time + # @return [Numeric] + def seconds_in_month_for_time(time) + ((time.beginning_of_month + 1.month) - time.beginning_of_month).to_f + end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 9cfed4d..a143a0b 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,4 +1,15 @@ require 'rspec' +require 'simplecov' +require 'coveralls' + +SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new( + [ + SimpleCov::Formatter::HTMLFormatter, + Coveralls::SimpleCov::Formatter + ] +) +SimpleCov.start + require 'time_difference' RSpec.configure do |config| diff --git a/spec/time_difference_spec.rb b/spec/time_difference_spec.rb index 1a67ed4..f873559 100644 --- a/spec/time_difference_spec.rb +++ b/spec/time_difference_spec.rb @@ -75,6 +75,14 @@ def self.with_each_date_class(&block) end end + describe '#time_in_seconds' do + let(:time_diff) { TimeDifference.between(Time.parse('2017-01-01T00:00:00+00:00'), Time.parse('2017-01-02T00:00:00+00:00')) } + + it 'is 86_400' do + expect(time_diff.send(:time_in_seconds, Time.parse('2017-01-01T00:00:00+00:00'))).to eq(1_483_228_800.0) + end + end + describe '#in_each_component' do context 'General case' do with_each_class do |klass| @@ -174,22 +182,329 @@ def self.with_each_date_class(&block) end end + describe '#in_calendar_years' do + context 'default within year' do + with_each_class do |klass| + it 'returns time difference in years' do + start_time = klass.new(2011, 7) + end_time = klass.new(2011, 8) + + expect(TimeDifference.between(start_time, end_time).in_calendar_years) + .to eql(0.08) + end + + it 'returns an absolute difference' do + start_time = klass.new(2011, 8) + end_time = klass.new(2011, 7) + + expect(TimeDifference.between(start_time, end_time).in_calendar_years) + .to eql(0.08) + end + end + end + + context 'default across year' do + with_each_class do |klass| + it 'returns time difference in years' do + start_time = klass.new(2011, 7) + end_time = klass.new(2012, 3) + + expect(TimeDifference.between(start_time, end_time).in_calendar_years) + .to eql(0.67) + end + + it 'returns an absolute difference' do + start_time = klass.new(2012, 3) + end_time = klass.new(2011, 7) + + expect(TimeDifference.between(start_time, end_time).in_calendar_years) + .to eql(0.67) + end + end + end + + context 'default long time' do + with_each_class do |klass| + it 'returns time difference in years' do + start_time = klass.new(2011, 2) + end_time = klass.new(2016, 3) + + expect(TimeDifference.between(start_time, end_time).in_calendar_years) + .to eql(5.08) + end + + it 'returns an absolute difference' do + start_time = klass.new(2016, 3) + end_time = klass.new(2011, 2) + + expect(TimeDifference.between(start_time, end_time).in_calendar_years) + .to eql(5.08) + end + end + end + + context 'rounding: 10' do + with_each_class do |klass| + it 'returns time difference in years based on Wolfram Alpha' do + start_time = klass.new(2011, 1) + end_time = klass.new(2011, 12) + + expect(TimeDifference.between(start_time, end_time).in_years(10)) + .to eql(0.9150684932) + end + + it 'returns an absolute difference' do + start_time = klass.new(2011, 12) + end_time = klass.new(2011, 1) + + expect(TimeDifference.between(start_time, end_time).in_years(10)) + .to eql(0.9150684932) + end + end + end + + context 'inclusive: true' do + with_each_date_class do |klass| + it 'returns time difference in years based on Wolfram Alpha' do + start_time = klass.new(2011, 1) + end_time = klass.new(2011, 12) + + expect(TimeDifference.between(start_time, end_time, inclusive: true).in_years) + .to eql(0.92) + end + + it 'returns an absolute difference' do + start_time = klass.new(2011, 12) + end_time = klass.new(2011, 1) + + expect(TimeDifference.between(start_time, end_time, inclusive: true).in_years) + .to eql(0.92) + end + end + end + + context 'inclusive: true, rounding: 10' do + with_each_date_class do |klass| + it 'returns time difference in years based on Wolfram Alpha' do + start_time = klass.new(2011, 1) + end_time = klass.new(2011, 12) + time_diff = TimeDifference.between(start_time, end_time, inclusive: true) + + expect(time_diff.in_years(10)).to eql(0.9178082192) + end + + it 'returns an absolute difference' do + start_time = klass.new(2011, 12) + end_time = klass.new(2011, 1) + + expect(TimeDifference.between(start_time, end_time, inclusive: true).in_years(10)) + .to eql(0.9178082192) + end + end + end + end + + describe '#in_calendar_months' do + context 'default within month' do + with_each_class do |klass| + it 'returns time difference in years' do + start_time = klass.new(2011, 7, 3) + end_time = klass.new(2011, 7, 17) + + expect(TimeDifference.between(start_time, end_time).in_calendar_months) + .to eql(0.45) + end + + it 'returns an absolute difference' do + start_time = klass.new(2011, 7, 17) + end_time = klass.new(2011, 7, 3) + + expect(TimeDifference.between(start_time, end_time).in_calendar_months) + .to eql(0.45) + end + end + end + + context 'default across month' do + with_each_class do |klass| + it 'returns time difference in calendar months' do + start_time = klass.new(2011, 7, 17) + end_time = klass.new(2011, 8, 21) + + expect(TimeDifference.between(start_time, end_time).in_calendar_months) + .to eql(1.13) + end + + it 'returns an absolute difference' do + start_time = klass.new(2011, 8, 21) + end_time = klass.new(2011, 7, 17) + + expect(TimeDifference.between(start_time, end_time).in_calendar_months) + .to eql(1.13) + end + end + end + + context 'default long time' do + with_each_class do |klass| + it 'returns time difference in years' do + start_time = klass.new(2011, 2) + end_time = klass.new(2016, 3) + + expect(TimeDifference.between(start_time, end_time).in_calendar_months) + .to eql(61.0) + end + + it 'returns an absolute difference' do + start_time = klass.new(2016, 3) + end_time = klass.new(2011, 2) + + expect(TimeDifference.between(start_time, end_time).in_calendar_months) + .to eql(61.0) + end + end + end + + context 'rounding: 10' do + with_each_class do |klass| + it 'returns time difference in years based on Wolfram Alpha' do + start_time = klass.new(2011, 1) + end_time = klass.new(2011, 12) + + expect(TimeDifference.between(start_time, end_time).in_calendar_months(10)) + .to eql(11.0) + end + + it 'returns an absolute difference' do + start_time = klass.new(2011, 12) + end_time = klass.new(2011, 1) + + expect(TimeDifference.between(start_time, end_time).in_calendar_months(10)) + .to eql(11.0) + end + end + end + + context 'inclusive: true' do + with_each_date_class do |klass| + it 'returns time difference in years based on Wolfram Alpha' do + start_time = klass.new(2011, 1) + end_time = klass.new(2011, 12) + + expect(TimeDifference.between(start_time, end_time, inclusive: true).in_calendar_months) + .to eql(11.03) + end + + it 'returns an absolute difference' do + start_time = klass.new(2011, 12) + end_time = klass.new(2011, 1) + + expect(TimeDifference.between(start_time, end_time, inclusive: true).in_calendar_months) + .to eql(11.03) + end + end + end + + context 'inclusive: true, rounding: 10' do + with_each_date_class do |klass| + it 'returns time difference in years based on Wolfram Alpha' do + start_time = klass.new(2011, 1) + end_time = klass.new(2011, 12) + time_diff = TimeDifference.between(start_time, end_time, inclusive: true) + + expect(time_diff.in_calendar_months(10)).to eql(11.0322580645) + end + + it 'returns an absolute difference' do + start_time = klass.new(2011, 12) + end_time = klass.new(2011, 1) + time_diff = TimeDifference.between(start_time, end_time, inclusive: true) + + expect(time_diff.in_calendar_months(10)) + .to eql(11.0322580645) + end + end + end + end + describe '#in_years' do - with_each_class do |klass| - it 'returns time difference in years based on Wolfram Alpha' do - start_time = klass.new(2011, 1) - end_time = klass.new(2011, 12) + context 'default' do + with_each_class do |klass| + it 'returns time difference in years based on Wolfram Alpha' do + start_time = klass.new(2011, 1) + end_time = klass.new(2011, 12) - expect(TimeDifference.between(start_time, end_time).in_years) - .to eql(0.92) + expect(TimeDifference.between(start_time, end_time).in_years) + .to eql(0.92) + end + + it 'returns an absolute difference' do + start_time = klass.new(2011, 12) + end_time = klass.new(2011, 1) + + expect(TimeDifference.between(start_time, end_time).in_years) + .to eql(0.92) + end end + end - it 'returns an absolute difference' do - start_time = klass.new(2011, 12) - end_time = klass.new(2011, 1) + context 'rounding: 10' do + with_each_class do |klass| + it 'returns time difference in years based on Wolfram Alpha' do + start_time = klass.new(2011, 1) + end_time = klass.new(2011, 12) + + expect(TimeDifference.between(start_time, end_time).in_years(10)) + .to eql(0.9150684932) + end + + it 'returns an absolute difference' do + start_time = klass.new(2011, 12) + end_time = klass.new(2011, 1) + + expect(TimeDifference.between(start_time, end_time).in_years(10)) + .to eql(0.9150684932) + end + end + end + + context 'inclusive: true' do + with_each_date_class do |klass| + it 'returns time difference in years based on Wolfram Alpha' do + start_time = klass.new(2011, 1) + end_time = klass.new(2011, 12) + + expect(TimeDifference.between(start_time, end_time, inclusive: true).in_years) + .to eql(0.92) + end + + it 'returns an absolute difference' do + start_time = klass.new(2011, 12) + end_time = klass.new(2011, 1) + + expect(TimeDifference.between(start_time, end_time, inclusive: true).in_years) + .to eql(0.92) + end + end + end + + context 'inclusive: true, rounding: 10' do + with_each_date_class do |klass| + it 'returns time difference in years based on Wolfram Alpha' do + start_time = klass.new(2011, 1) + end_time = klass.new(2011, 12) + time_diff = TimeDifference.between(start_time, end_time, inclusive: true) + + expect(time_diff.in_years(10)).to eql(0.9178082192) + end - expect(TimeDifference.between(start_time, end_time).in_years) - .to eql(0.92) + it 'returns an absolute difference' do + start_time = klass.new(2011, 12) + end_time = klass.new(2011, 1) + + expect(TimeDifference.between(start_time, end_time, inclusive: true).in_years(10)) + .to eql(0.9178082192) + end end end end diff --git a/time_difference.gemspec b/time_difference.gemspec index a34463f..7b9f117 100644 --- a/time_difference.gemspec +++ b/time_difference.gemspec @@ -21,6 +21,8 @@ Gem::Specification.new do |gem| gem.license = 'MIT' gem.add_runtime_dependency('activesupport', '~> 5.1') + gem.add_development_dependency('coveralls') gem.add_development_dependency('rake') gem.add_development_dependency('rspec', '~> 3.7.0') + gem.add_development_dependency('simplecov') end From 13b4af08afaf6710c8b6410aedcd94676ea88bf4 Mon Sep 17 00:00:00 2001 From: Joel Courtney Date: Tue, 15 May 2018 00:20:42 +1000 Subject: [PATCH 3/5] Lower expectations on ActiveSupport to >= 4.2.6 --- time_difference.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/time_difference.gemspec b/time_difference.gemspec index 7b9f117..ea5d110 100644 --- a/time_difference.gemspec +++ b/time_difference.gemspec @@ -20,7 +20,7 @@ Gem::Specification.new do |gem| gem.version = '0.8.0' gem.license = 'MIT' - gem.add_runtime_dependency('activesupport', '~> 5.1') + gem.add_runtime_dependency('activesupport', '>= 4.2.6', '< 5.2') gem.add_development_dependency('coveralls') gem.add_development_dependency('rake') gem.add_development_dependency('rspec', '~> 3.7.0') From e56ac7bfccc54e9a835f55f48dd27453edc5ec57 Mon Sep 17 00:00:00 2001 From: Joel Courtney Date: Tue, 15 May 2018 11:50:11 +1000 Subject: [PATCH 4/5] Move to using options on calls to #in_* with key :rounding and default remaining as 2 --- lib/time_difference.rb | 104 +++++++++++++++++++++-------------- spec/time_difference_spec.rb | 64 ++++++++++----------- 2 files changed, 94 insertions(+), 74 deletions(-) diff --git a/lib/time_difference.rb b/lib/time_difference.rb index b6389f0..013bf8a 100644 --- a/lib/time_difference.rb +++ b/lib/time_difference.rb @@ -12,10 +12,13 @@ class TimeDifference # @group Class Constants TIME_COMPONENTS = %i[years months weeks days hours minutes seconds].freeze - DEFAULT_OPTIONS = { + DEFAULT_DATE_OPTIONS = { force_timezone: false, inclusive: false, - timezone: 'UTC' + timezone: 'UTC', + }.freeze + DEFAULT_IN_OPTIONS = { + rounding: 2 }.freeze # Ensure .initialize is a private method private_class_method :new @@ -41,17 +44,19 @@ def self.between(start_time, end_time, options = {}) # # @todo account for daylight savings # - # @param [Integer] rounding the rounding of the numbers + # @param [Hash] options + # @option options [Integer] :rounding the rounding of the numbers # @return [Numeric] - def in_years(rounding = 2) - in_period(:years, rounding) + def in_years(options = {}) + in_period(:years, options) end # The difference in calendar years # - # @param [Integer] rounding + # @param [Hash] options + # @option options [Integer] :rounding the rounding of the numbers # @return [Numeric] - def in_calendar_years(rounding = 2) + def in_calendar_years(options = {}) year_diff = (@finish.year - @start.year) - 1 if year_diff < 0 @@ -61,24 +66,25 @@ def in_calendar_years(rounding = 2) year_diff += (@finish - @finish.beginning_of_year) / seconds_in_year_for_time(@finish) end - year_diff.round(rounding) + year_diff.round(DEFAULT_IN_OPTIONS.merge(options)[:rounding]) end # The difference in months # # @todo account for daylight savings # - # @param [Integer] rounding the rounding of the numbers + # @param [Hash] options + # @option options [Integer] :rounding the rounding of the numbers # @return [Numeric] - def in_months(rounding = 2) - in_period(:months, rounding) + def in_months(options = {}) + in_period(:months, options) end # The difference in calendar months # # @param [Integer] rounding # @return [Numeric] - def in_calendar_months(rounding = 2) + def in_calendar_months(options = {}) month_diff = (12 * @finish.year + @finish.month) - (12 * @start.year + @start.month) - 1 if month_diff < 0 @@ -88,48 +94,53 @@ def in_calendar_months(rounding = 2) month_diff += (@finish - @finish.beginning_of_month) / seconds_in_month_for_time(@finish) end - month_diff.round(rounding) + month_diff.round(DEFAULT_IN_OPTIONS.merge(options)[:rounding]) end # The difference in consistent (groups of 7). # # @todo account for daylight savings # - # @param [Integer] rounding the rounding of the numbers + # @param [Hash] options + # @option options [Integer] :rounding the rounding of the numbers # @return [Numeric] - def in_weeks(rounding = 2) - in_period(:weeks, rounding) + def in_weeks(options = {}) + in_period(:weeks, options) end # The difference in days # # @todo account for daylight savings # - # @param [Integer] rounding the rounding of the numbers + # @param [Hash] options + # @option options [Integer] :rounding the rounding of the numbers # @return [Numeric] - def in_days(rounding = 2) - in_period(:days, rounding) + def in_days(options = {}) + in_period(:days, options) end # The difference in hours # - # @param [Integer] rounding the rounding of the numbers + # @param [Hash] options + # @option options [Integer] :rounding the rounding of the numbers # @return [Numeric] - def in_hours(rounding = 2) - in_component(:hours, rounding) + def in_hours(options = {}) + in_component(:hours, options) end # The difference in minutes # - # @param [Integer] rounding the rounding of the numbers + # @param [Hash] options + # @option options [Integer] :rounding the rounding of the numbers # @return [Numeric] - def in_minutes(rounding = 2) - in_component(:minutes, rounding) + def in_minutes(options = {}) + in_component(:minutes, options) end # The difference in seconds # - # @param [Integer] rounding the rounding of the numbers + # @param [Hash] options + # @option options [Integer] :rounding the rounding of the numbers # @return [Numeric] def in_seconds(_rounding = nil) @time_diff @@ -137,11 +148,18 @@ def in_seconds(_rounding = nil) # The difference in each component available. # - # @param [Integer] rounding the rounding of the numbers + # @param [Hash] options + # @option options [Integer] :rounding the rounding of the numbers # @return [Hash] - def in_each_component(rounding = 2) + def in_each_component(options = {}) Hash[TIME_COMPONENTS.map do |time_component| - [time_component, public_send("in_#{time_component}", rounding)] + [ + time_component, + public_send( + "in_#{time_component}", + options + ) + ] end] end @@ -150,13 +168,13 @@ def in_each_component(rounding = 2) # @todo work out how to work with timezones # # @return [Hash] - def in_general(rounding = 2) + def in_general(options = {}) remaining = @time_diff Hash[TIME_COMPONENTS.map do |time_component| if remaining > 0 rounded_time_component = ( remaining / 1.send(time_component).seconds - ).round(rounding).floor + ).round(DEFAULT_IN_OPTIONS.merge(options)[:rounding]).floor remaining -= rounded_time_component.send(time_component) [time_component, rounded_time_component] else @@ -171,9 +189,9 @@ def in_general(rounding = 2) # @todo work out how to work with timezones # # @return [String] - def humanize(rounding = 2) + def humanize(options = {}) diff_parts = [] - in_general(rounding).each do |part, quantity| + in_general(options).each do |part, quantity| next if quantity <= 0 part = part.to_s.humanize part = part.singularize if quantity <= 1 @@ -203,10 +221,10 @@ def initialize(start_time, end_time, options) @force_timezone = options.fetch( :force_timezone, - DEFAULT_OPTIONS[:force_timezone] + DEFAULT_DATE_OPTIONS[:force_timezone] ) - @inclusive = options.fetch(:inclusive, DEFAULT_OPTIONS[:inclusive]) - @timezone = options.fetch(:timezone, DEFAULT_OPTIONS[:timezone]) + @inclusive = options.fetch(:inclusive, DEFAULT_DATE_OPTIONS[:inclusive]) + @timezone = options.fetch(:timezone, DEFAULT_DATE_OPTIONS[:timezone]) end_time += 1.day if @inclusive && end_time.is_a?(Date) @@ -237,19 +255,21 @@ def time_in_seconds(time) # Returns the time in a given component # # @param [Symbol] component - # @param [Integer] rounding the rounding of the numbers + # @param [Hash] options + # @option options [Integer] :rounding the rounding of the numbers # @return [Numeric] - def in_component(component, rounding) - (@time_diff / 1.send(component)).round(rounding) + def in_component(component, options = {}) + (@time_diff / 1.send(component)).round(DEFAULT_IN_OPTIONS.merge(options)[:rounding]) end # rubocop:disable Metrics/AbcSize # Returns the length of time of the difference in a given period_type # # @param [Symbol] period_type - # @param [Integer] rounding the rounding of the numbers + # @param [Hash] options + # @option options [Integer] :rounding the rounding of the numbers # @return [Numeric] - def in_period(period_type, rounding = 2) + def in_period(period_type, options = {}) # First step iterate through years periods = 0.0 pointer = @start + 1.send(period_type) @@ -263,7 +283,7 @@ def in_period(period_type, rounding = 2) last_number_in_s = pointer.to_f - (pointer - 1.send(period_type)).to_f end - periods + (remainder / last_number_in_s).round(rounding) + periods + (remainder / last_number_in_s).round(DEFAULT_IN_OPTIONS.merge(options)[:rounding]) end # rubocop:enable Metrics/AbcSize diff --git a/spec/time_difference_spec.rb b/spec/time_difference_spec.rb index f873559..e951923 100644 --- a/spec/time_difference_spec.rb +++ b/spec/time_difference_spec.rb @@ -249,7 +249,7 @@ def self.with_each_date_class(&block) start_time = klass.new(2011, 1) end_time = klass.new(2011, 12) - expect(TimeDifference.between(start_time, end_time).in_years(10)) + expect(TimeDifference.between(start_time, end_time).in_years(rounding: 10)) .to eql(0.9150684932) end @@ -257,7 +257,7 @@ def self.with_each_date_class(&block) start_time = klass.new(2011, 12) end_time = klass.new(2011, 1) - expect(TimeDifference.between(start_time, end_time).in_years(10)) + expect(TimeDifference.between(start_time, end_time).in_years(rounding: 10)) .to eql(0.9150684932) end end @@ -290,15 +290,15 @@ def self.with_each_date_class(&block) end_time = klass.new(2011, 12) time_diff = TimeDifference.between(start_time, end_time, inclusive: true) - expect(time_diff.in_years(10)).to eql(0.9178082192) + expect(time_diff.in_years(rounding: 10)).to eql(0.9178082192) end it 'returns an absolute difference' do start_time = klass.new(2011, 12) end_time = klass.new(2011, 1) + time_diff = TimeDifference.between(start_time, end_time, inclusive: true) - expect(TimeDifference.between(start_time, end_time, inclusive: true).in_years(10)) - .to eql(0.9178082192) + expect(time_diff.in_years(rounding: 10)).to eql(0.9178082192) end end end @@ -307,7 +307,7 @@ def self.with_each_date_class(&block) describe '#in_calendar_months' do context 'default within month' do with_each_class do |klass| - it 'returns time difference in years' do + it 'returns time difference in calendar months' do start_time = klass.new(2011, 7, 3) end_time = klass.new(2011, 7, 17) @@ -347,7 +347,7 @@ def self.with_each_date_class(&block) context 'default long time' do with_each_class do |klass| - it 'returns time difference in years' do + it 'returns time difference in calendar months' do start_time = klass.new(2011, 2) end_time = klass.new(2016, 3) @@ -367,52 +367,52 @@ def self.with_each_date_class(&block) context 'rounding: 10' do with_each_class do |klass| - it 'returns time difference in years based on Wolfram Alpha' do + it 'returns time difference in calendar months' do start_time = klass.new(2011, 1) end_time = klass.new(2011, 12) + time_diff = TimeDifference.between(start_time, end_time) - expect(TimeDifference.between(start_time, end_time).in_calendar_months(10)) - .to eql(11.0) + expect(time_diff.in_calendar_months(rounding: 10)).to eql(11.0) end it 'returns an absolute difference' do start_time = klass.new(2011, 12) end_time = klass.new(2011, 1) + time_diff = TimeDifference.between(start_time, end_time) - expect(TimeDifference.between(start_time, end_time).in_calendar_months(10)) - .to eql(11.0) + expect(time_diff.in_calendar_months(rounding: 10)).to eql(11.0) end end end context 'inclusive: true' do with_each_date_class do |klass| - it 'returns time difference in years based on Wolfram Alpha' do + it 'returns time difference in calendar months' do start_time = klass.new(2011, 1) end_time = klass.new(2011, 12) + time_diff = TimeDifference.between(start_time, end_time, inclusive: true) - expect(TimeDifference.between(start_time, end_time, inclusive: true).in_calendar_months) - .to eql(11.03) + expect(time_diff.in_calendar_months).to eql(11.03) end it 'returns an absolute difference' do start_time = klass.new(2011, 12) end_time = klass.new(2011, 1) + time_diff = TimeDifference.between(start_time, end_time, inclusive: true) - expect(TimeDifference.between(start_time, end_time, inclusive: true).in_calendar_months) - .to eql(11.03) + expect(time_diff.in_calendar_months).to eql(11.03) end end end context 'inclusive: true, rounding: 10' do with_each_date_class do |klass| - it 'returns time difference in years based on Wolfram Alpha' do + it 'returns time difference in calendar months' do start_time = klass.new(2011, 1) end_time = klass.new(2011, 12) time_diff = TimeDifference.between(start_time, end_time, inclusive: true) - expect(time_diff.in_calendar_months(10)).to eql(11.0322580645) + expect(time_diff.in_calendar_months(rounding: 10)).to eql(11.0322580645) end it 'returns an absolute difference' do @@ -420,8 +420,7 @@ def self.with_each_date_class(&block) end_time = klass.new(2011, 1) time_diff = TimeDifference.between(start_time, end_time, inclusive: true) - expect(time_diff.in_calendar_months(10)) - .to eql(11.0322580645) + expect(time_diff.in_calendar_months(rounding: 10)).to eql(11.0322580645) end end end @@ -453,17 +452,17 @@ def self.with_each_date_class(&block) it 'returns time difference in years based on Wolfram Alpha' do start_time = klass.new(2011, 1) end_time = klass.new(2011, 12) + time_diff = TimeDifference.between(start_time, end_time) - expect(TimeDifference.between(start_time, end_time).in_years(10)) - .to eql(0.9150684932) + expect(time_diff.in_years(rounding: 10)).to eql(0.9150684932) end it 'returns an absolute difference' do start_time = klass.new(2011, 12) end_time = klass.new(2011, 1) + time_diff = TimeDifference.between(start_time, end_time) - expect(TimeDifference.between(start_time, end_time).in_years(10)) - .to eql(0.9150684932) + expect(time_diff.in_years(rounding: 10)).to eql(0.9150684932) end end end @@ -473,17 +472,17 @@ def self.with_each_date_class(&block) it 'returns time difference in years based on Wolfram Alpha' do start_time = klass.new(2011, 1) end_time = klass.new(2011, 12) + time_diff = TimeDifference.between(start_time, end_time) - expect(TimeDifference.between(start_time, end_time, inclusive: true).in_years) - .to eql(0.92) + expect(time_diff.in_years).to eql(0.92) end it 'returns an absolute difference' do start_time = klass.new(2011, 12) end_time = klass.new(2011, 1) + time_diff = TimeDifference.between(start_time, end_time) - expect(TimeDifference.between(start_time, end_time, inclusive: true).in_years) - .to eql(0.92) + expect(time_diff.in_years).to eql(0.92) end end end @@ -495,15 +494,16 @@ def self.with_each_date_class(&block) end_time = klass.new(2011, 12) time_diff = TimeDifference.between(start_time, end_time, inclusive: true) - expect(time_diff.in_years(10)).to eql(0.9178082192) + expect(time_diff.in_years(rounding: 10)).to eql(0.9178082192) end it 'returns an absolute difference' do start_time = klass.new(2011, 12) end_time = klass.new(2011, 1) - expect(TimeDifference.between(start_time, end_time, inclusive: true).in_years(10)) - .to eql(0.9178082192) + time_diff = TimeDifference.between(start_time, end_time, inclusive: true) + + expect(time_diff.in_years(rounding: 10)).to eql(0.9178082192) end end end From 110eb6a660f9c469b6477e58e36072e5f0daedf7 Mon Sep 17 00:00:00 2001 From: Josh Mostafa Date: Mon, 22 Oct 2018 11:45:43 +1100 Subject: [PATCH 5/5] gemspec --- time_difference.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/time_difference.gemspec b/time_difference.gemspec index ea5d110..6e77fa8 100644 --- a/time_difference.gemspec +++ b/time_difference.gemspec @@ -20,7 +20,7 @@ Gem::Specification.new do |gem| gem.version = '0.8.0' gem.license = 'MIT' - gem.add_runtime_dependency('activesupport', '>= 4.2.6', '< 5.2') + gem.add_runtime_dependency('activesupport', '>= 4.2.6', '< 5.3') gem.add_development_dependency('coveralls') gem.add_development_dependency('rake') gem.add_development_dependency('rspec', '~> 3.7.0')