dotiw 3.0.1 → 5.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ruby.yml +45 -0
  3. data/.gitignore +2 -1
  4. data/.rspec +2 -0
  5. data/Appraisals +19 -0
  6. data/CHANGELOG.md +29 -0
  7. data/CONTRIBUTING.md +34 -0
  8. data/Gemfile +2 -0
  9. data/MIT-LICENSE +1 -1
  10. data/README.markdown +139 -70
  11. data/Rakefile +3 -1
  12. data/dotiw.gemspec +22 -17
  13. data/gemfiles/.bundle/config +2 -0
  14. data/gemfiles/rails_4.gemfile +9 -0
  15. data/gemfiles/rails_5.0.gemfile +9 -0
  16. data/gemfiles/rails_5.1.gemfile +9 -0
  17. data/gemfiles/rails_5.2.gemfile +9 -0
  18. data/gemfiles/rails_6.0.gemfile +9 -0
  19. data/lib/dotiw.rb +31 -13
  20. data/lib/dotiw/action_view/helpers/date_helper.rb +24 -0
  21. data/lib/dotiw/locale/ar.yml +53 -0
  22. data/lib/dotiw/locale/da.yml +23 -0
  23. data/lib/dotiw/locale/fr.yml +25 -0
  24. data/lib/dotiw/locale/id.yml +25 -0
  25. data/lib/dotiw/locale/ko.yml +25 -0
  26. data/lib/dotiw/locale/pl.yml +7 -0
  27. data/lib/dotiw/locale/pt-BR.yml +25 -0
  28. data/lib/dotiw/locale/vi.yml +25 -0
  29. data/lib/dotiw/locale/zh-CN.yml +25 -0
  30. data/lib/dotiw/methods.rb +93 -0
  31. data/lib/dotiw/time_hash.rb +65 -38
  32. data/lib/dotiw/version.rb +2 -2
  33. data/spec/lib/dotiw_spec.rb +264 -159
  34. data/spec/lib/i18n/ar.yml +2 -0
  35. data/spec/lib/i18n/da.yml +2 -0
  36. data/spec/lib/i18n/de.yml +2 -0
  37. data/spec/lib/i18n/en.yml +2 -0
  38. data/spec/lib/i18n/es.yml +3 -0
  39. data/spec/lib/i18n/fr.yml +2 -0
  40. data/spec/lib/i18n/id.yml +2 -0
  41. data/spec/lib/i18n/it.yml +3 -0
  42. data/spec/lib/i18n/ja.yml +2 -0
  43. data/spec/lib/i18n/ko.yml +2 -0
  44. data/spec/lib/i18n/nb.yml +2 -0
  45. data/spec/lib/i18n/nl.yml +2 -0
  46. data/spec/lib/i18n/pl.yml +2 -0
  47. data/spec/lib/i18n/pt-BR.yml +2 -0
  48. data/spec/lib/i18n/ru.yml +5 -0
  49. data/spec/lib/i18n/vi.yml +22 -0
  50. data/spec/lib/i18n/zh-CN.yml +2 -0
  51. data/spec/spec_helper.rb +2 -9
  52. metadata +110 -21
  53. data/.travis.yml +0 -5
  54. data/lib/dotiw/action_view_ext/helpers/date_helper.rb +0 -103
@@ -0,0 +1,25 @@
1
+ zh-CN:
2
+ datetime:
3
+ dotiw:
4
+ seconds:
5
+ one: 1 秒
6
+ other: "%{count} 秒"
7
+ minutes:
8
+ one: 1 分钟
9
+ other: "%{count} 分钟"
10
+ hours:
11
+ one: 1 小时
12
+ other: "%{count} 小时"
13
+ days:
14
+ one: 1 天
15
+ other: "%{count} 天"
16
+ weeks:
17
+ one: 1 周
18
+ other: "%{count} 周"
19
+ months:
20
+ one: 1 月
21
+ other: "%{count} 月"
22
+ years:
23
+ one: 1 年
24
+ other: "%{count} 年"
25
+ less_than_x: "小于 %{distance}"
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DOTIW
4
+ module Methods
5
+ extend self
6
+
7
+ def distance_of_time_in_words_hash(from_time, to_time, options = {})
8
+ from_time = from_time.to_time if !from_time.is_a?(Time) && from_time.respond_to?(:to_time)
9
+ to_time = to_time.to_time if !to_time.is_a?(Time) && to_time.respond_to?(:to_time)
10
+
11
+ DOTIW::TimeHash.new(nil, from_time, to_time, options).to_hash
12
+ end
13
+
14
+ def distance_of_time(seconds, options = {})
15
+ options[:include_seconds] ||= true
16
+ _display_time_in_words DOTIW::TimeHash.new(seconds, nil, nil, options).to_hash, options
17
+ end
18
+
19
+ def distance_of_time_in_words(from_time, to_time = 0, include_seconds_or_options = {}, options = {})
20
+ if include_seconds_or_options.is_a?(Hash)
21
+ options = include_seconds_or_options
22
+ else
23
+ options[:include_seconds] ||= !!include_seconds_or_options
24
+ end
25
+ return distance_of_time(from_time, options) if to_time == 0
26
+
27
+ hash = distance_of_time_in_words_hash(from_time, to_time, options)
28
+ _display_time_in_words(hash, options)
29
+ end
30
+
31
+ def time_ago_in_words(from_time, include_seconds_or_options = {})
32
+ distance_of_time_in_words(from_time, Time.current, include_seconds_or_options)
33
+ end
34
+
35
+ private
36
+
37
+ def _display_time_in_words(hash, options = {})
38
+ options.reverse_merge!(
39
+ include_seconds: false
40
+ ).symbolize_keys!
41
+
42
+ include_seconds = options.delete(:include_seconds)
43
+ hash.delete(:seconds) if !include_seconds && hash[:minutes]
44
+
45
+ options[:except] = Array.wrap(options[:except]).map!(&:to_sym) if options[:except]
46
+ options[:only] = Array.wrap(options[:only]).map!(&:to_sym) if options[:only]
47
+
48
+ # Remove all the values that are nil or excluded. Keep the required ones.
49
+ hash.delete_if do |key, value|
50
+ value.nil? || value.zero? ||
51
+ options[:except]&.include?(key) ||
52
+ (options[:only] && !options[:only].include?(key))
53
+ end
54
+
55
+ i18n_scope = options.delete(:scope) || DOTIW::DEFAULT_I18N_SCOPE
56
+ if hash.empty?
57
+ fractions = DOTIW::TimeHash::TIME_FRACTIONS
58
+ fractions &= options[:only] if options[:only]
59
+ fractions -= options[:except] if options[:except]
60
+
61
+ I18n.with_options locale: options[:locale], scope: i18n_scope do |locale|
62
+ # e.g. try to format 'less than 1 days', fallback to '0 days'
63
+ return locale.translate :less_than_x,
64
+ distance: locale.translate(fractions.first, count: 1),
65
+ default: locale.translate(fractions.first, count: 0)
66
+ end
67
+ end
68
+
69
+ output = []
70
+ I18n.with_options locale: options[:locale], scope: i18n_scope do |locale|
71
+ output = hash.map { |key, value| locale.t(key, count: value) }
72
+ end
73
+
74
+ options.delete(:except)
75
+ options.delete(:only)
76
+ highest_measures = options.delete(:highest_measures)
77
+ highest_measures = 1 if options.delete(:highest_measure_only)
78
+ output = output[0...highest_measures] if highest_measures
79
+
80
+ options[:words_connector] ||= I18n.translate :'datetime.dotiw.words_connector',
81
+ default: :'support.array.words_connector',
82
+ locale: options[:locale]
83
+ options[:two_words_connector] ||= I18n.translate :'datetime.dotiw.two_words_connector',
84
+ default: :'support.array.two_words_connector',
85
+ locale: options[:locale]
86
+ options[:last_word_connector] ||= I18n.translate :'datetime.dotiw.last_word_connector',
87
+ default: :'support.array.last_word_connector',
88
+ locale: options[:locale]
89
+
90
+ output.to_sentence(options)
91
+ end
92
+ end
93
+ end # DOTIW
@@ -1,25 +1,25 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
3
  module DOTIW
4
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 || (@to_time_not_given = true && self.from_time + self.distance.seconds)
15
- self.smallest, self.largest = [self.from_time, self.to_time].minmax
16
- self.to_time += 1.hour if @to_time_not_given && self.smallest.dst? && !self.largest.dst?
17
- self.to_time -= 1.hour if @to_time_not_given && !self.smallest.dst? && self.largest.dst?
18
- self.smallest, self.largest = [self.from_time, self.to_time].minmax
19
- self.distance ||= begin
5
+ TIME_FRACTIONS = %i[seconds minutes hours days weeks months years].freeze
6
+
7
+ attr_reader :distance, :smallest, :largest, :from_time, :to_time
8
+
9
+ def initialize(distance, from_time, to_time = nil, options = {})
10
+ @output = {}
11
+ @options = options
12
+ @distance = distance
13
+ @from_time = from_time || Time.current
14
+ @to_time = to_time || (@to_time_not_given = true && @from_time + distance.seconds)
15
+ @smallest, @largest = [@from_time, @to_time].minmax
16
+ @to_time += 1.hour if @to_time_not_given && smallest.dst? && !largest.dst?
17
+ @to_time -= 1.hour if @to_time_not_given && !smallest.dst? && largest.dst?
18
+ @smallest, @largest = [@from_time, @to_time].minmax
19
+ @distance ||= begin
20
20
  d = largest - smallest
21
- d -= 1.hour if self.smallest.dst? && !self.largest.dst?
22
- d += 1.hour if !self.smallest.dst? && self.largest.dst?
21
+ d -= 1.hour if smallest.dst? && !largest.dst?
22
+ d += 1.hour if !smallest.dst? && largest.dst?
23
23
  d
24
24
  end
25
25
 
@@ -30,16 +30,16 @@ module DOTIW
30
30
  output
31
31
  end
32
32
 
33
- private
34
- attr_accessor :options, :output
33
+ private
34
+
35
+ attr_reader :options, :output
35
36
 
36
37
  def build_time_hash
37
38
  if accumulate_on = options.delete(:accumulate_on)
38
39
  accumulate_on = accumulate_on.to_sym
39
- if accumulate_on == :years
40
- return build_time_hash
41
- end
42
- TIME_FRACTIONS.index(accumulate_on).downto(0) { |i| self.send("build_#{TIME_FRACTIONS[i]}") }
40
+ return build_time_hash if accumulate_on == :years
41
+
42
+ TIME_FRACTIONS.index(accumulate_on).downto(0) { |i| send("build_#{TIME_FRACTIONS[i]}") }
43
43
  else
44
44
  while distance > 0
45
45
  if distance < 1.minute
@@ -48,10 +48,12 @@ module DOTIW
48
48
  build_minutes
49
49
  elsif distance < 1.day
50
50
  build_hours
51
- elsif distance < 28.days
51
+ elsif distance < 7.days
52
52
  build_days
53
- else # greater than a month
54
- build_years_months_days
53
+ elsif distance < 28.days
54
+ build_weeks
55
+ else # greater than a week
56
+ build_years_months_weeks_days
55
57
  end
56
58
  end
57
59
  end
@@ -61,43 +63,67 @@ module DOTIW
61
63
 
62
64
  def build_seconds
63
65
  output[:seconds] = distance.to_i
64
- self.distance = 0
66
+ @distance = 0
65
67
  end
66
68
 
67
69
  def build_minutes
68
- output[:minutes], self.distance = distance.divmod(1.minute)
70
+ output[:minutes], @distance = distance.divmod(1.minute.to_i)
69
71
  end
70
72
 
71
73
  def build_hours
72
- output[:hours], self.distance = distance.divmod(1.hour)
74
+ output[:hours], @distance = distance.divmod(1.hour.to_i)
73
75
  end
74
76
 
75
77
  def build_days
76
- output[:days], self.distance = distance.divmod(1.day) if output[:days].nil?
78
+ output[:days], @distance = distance.divmod(1.day.to_i) unless output[:days]
79
+ end
80
+
81
+ def build_weeks
82
+ output[:weeks], @distance = distance.divmod(1.week.to_i) unless output[:weeks]
77
83
  end
78
84
 
79
85
  def build_months
80
- build_years_months_days
86
+ build_years_months_weeks_days
81
87
 
82
88
  if (years = output.delete(:years)) > 0
83
89
  output[:months] += (years * 12)
84
90
  end
85
91
  end
86
92
 
87
- def build_years_months_days
93
+ def build_years_months_weeks_days
88
94
  months = (largest.year - smallest.year) * 12 + (largest.month - smallest.month)
89
95
  years, months = months.divmod(12)
90
96
 
91
97
  days = largest.day - smallest.day
92
98
 
99
+ weeks, days = days.divmod(7)
100
+
93
101
  # Will otherwise incorrectly say one more day if our range goes over a day.
94
102
  days -= 1 if largest.hour < smallest.hour
95
103
 
96
104
  if days < 0
97
- # Convert the last month to days and add to total
105
+ # Convert a week to days and add to total
106
+ weeks -= 1
107
+ days += 7
108
+ end
109
+
110
+ if weeks < 0
111
+ # Convert the last month to a week and add to total
98
112
  months -= 1
99
- last_month = largest.advance(:months => -1)
100
- days += Time.days_in_month(last_month.month, last_month.year)
113
+ last_month = largest.advance(months: -1)
114
+ days_in_month = Time.days_in_month(last_month.month, last_month.year)
115
+ weeks += days_in_month / 7
116
+ days += days_in_month % 7
117
+ if days >= 7
118
+ days -= 7
119
+ weeks += 1
120
+ end
121
+
122
+ if weeks == -1
123
+ months -= 1
124
+ weeks = 4
125
+ days -= 4
126
+ end
101
127
  end
102
128
 
103
129
  if months < 0
@@ -108,11 +134,12 @@ module DOTIW
108
134
 
109
135
  output[:years] = years
110
136
  output[:months] = months
137
+ output[:weeks] = weeks
111
138
  output[:days] = days
112
139
 
113
- total_days, self.distance = distance.abs.divmod(1.day)
140
+ total_days, @distance = distance.abs.divmod(1.day.to_i)
114
141
 
115
- [total_days, self.distance]
142
+ [total_days, @distance]
116
143
  end
117
144
  end # TimeHash
118
145
  end # DOTIW
@@ -1,5 +1,5 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
3
  module DOTIW
4
- VERSION = "3.0.1"
4
+ VERSION = '5.1.0'
5
5
  end
@@ -1,13 +1,19 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
3
  require 'spec_helper'
4
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
5
+ describe 'A better distance_of_time_in_words' do
6
+ if defined?(ActionView)
7
+ include ActionView::Helpers::DateHelper
8
+ include ActionView::Helpers::TextHelper
9
+ include ActionView::Helpers::NumberHelper
9
10
 
10
- START_TIME = "01-08-2009".to_time
11
+ require 'action_controller'
12
+ else
13
+ include DOTIW::Methods
14
+ end
15
+
16
+ START_TIME = '01-08-2009'.to_time
11
17
 
12
18
  before do
13
19
  I18n.locale = :en
@@ -15,269 +21,368 @@ describe "A better distance_of_time_in_words" do
15
21
  allow(Time.zone).to receive(:now).and_return(START_TIME)
16
22
  end
17
23
 
18
- describe "distance of time" do
19
- fragments = [
20
- [0.5.minutes, "30 seconds"],
21
- [4.5.minutes, "4 minutes and 30 seconds"],
22
- [5.minutes.to_i, "5 minutes"],
23
- [10.minutes.to_i, "10 minutes"],
24
- [1.hour.to_i, "1 hour"],
25
- [1.hour + 30.seconds, "1 hour and 30 seconds"],
26
- [4.weeks.to_i, "28 days"],
27
- [24.weeks.to_i, "5 months and 15 days"]
28
- ]
29
- fragments.each do |number, result|
24
+ describe '#distance_of_time' do
25
+ [
26
+ [0.5.minutes, '30 seconds'],
27
+ [4.5.minutes, '4 minutes and 30 seconds'],
28
+ [5.minutes, '5 minutes'],
29
+ [10.minutes, '10 minutes'],
30
+ [1.hour, '1 hour'],
31
+ [1.hour + 30.seconds, '1 hour and 30 seconds'],
32
+ [4.weeks, '4 weeks'],
33
+ [4.weeks + 2.days, '4 weeks and 2 days'],
34
+ [24.weeks, '5 months, 2 weeks, and 1 day']
35
+ ].each do |number, result|
30
36
  it "#{number} == #{result}" do
31
37
  expect(distance_of_time(number)).to eq(result)
32
38
  end
33
39
  end
34
40
 
35
- describe "with options" do
36
- it "except:seconds should skip seconds" do
37
- expect(distance_of_time(1.2.minute, except: 'seconds')).to eq("1 minute")
38
- expect(distance_of_time(2.5.hours + 30.seconds, except: 'seconds')).to eq("2 hours and 30 minutes")
41
+ describe 'with options' do
42
+ it 'except:seconds should skip seconds' do
43
+ expect(distance_of_time(1.2.minute, except: 'seconds')).to eq('1 minute')
44
+ expect(distance_of_time(2.5.hours + 30.seconds, except: 'seconds')).to eq('2 hours and 30 minutes')
39
45
  end
40
46
 
41
- it "except:seconds har higher presedence than include_seconds:true" do
47
+ it 'except:seconds has higher precedence than include_seconds:true' do
42
48
  expect(distance_of_time(1.2.minute, include_seconds: true, except: 'seconds')).to eq('1 minute')
43
49
  end
44
50
  end
45
-
46
51
  end
47
52
 
48
- describe "hash version" do
49
- describe "giving correct numbers of" do
50
-
51
- [:years, :months, :days, :minutes, :seconds].each do |name|
53
+ describe '#distance_of_time_in_words_hash' do
54
+ describe 'giving correct numbers of' do
55
+ %i[years months weeks days minutes seconds].each do |name|
52
56
  describe name do
53
- it "exactly" do
57
+ it 'exactly' do
54
58
  hash = distance_of_time_in_words_hash(START_TIME, START_TIME + 1.send(name))
55
59
  expect(hash[name]).to eq(1)
56
60
  end
57
61
 
58
- it "two" do
62
+ it 'two' do
59
63
  hash = distance_of_time_in_words_hash(START_TIME, START_TIME + 2.send(name))
60
64
  expect(hash[name]).to eq(2)
61
65
  end
62
66
  end
63
67
  end
64
68
 
65
- it "should be happy with lots of measurements" do
66
- hash = distance_of_time_in_words_hash(START_TIME,
67
- START_TIME + 1.year + 2.months + 3.days + 4.hours + 5.minutes + 6.seconds)
69
+ it 'should be happy with lots of measurements' do
70
+ hash = distance_of_time_in_words_hash(
71
+ START_TIME,
72
+ START_TIME + 1.year + 2.months + 3.weeks + 4.days + 5.hours + 6.minutes + 7.seconds
73
+ )
68
74
  expect(hash[:years]).to eq(1)
69
75
  expect(hash[:months]).to eq(2)
70
- expect(hash[:days]).to eq(3)
71
- expect(hash[:hours]).to eq(4)
72
- expect(hash[:minutes]).to eq(5)
73
- expect(hash[:seconds]).to eq(6)
76
+ expect(hash[:weeks]).to eq(3)
77
+ expect(hash[:days]).to eq(4)
78
+ expect(hash[:hours]).to eq(5)
79
+ expect(hash[:minutes]).to eq(6)
80
+ expect(hash[:seconds]).to eq(7)
74
81
  end
75
82
  end
76
83
  end
77
84
 
78
- describe "real version" do
79
- it "debe hablar español" do
80
- expect(distance_of_time_in_words(START_TIME, START_TIME + 1.days, :locale => :es)).to eq("un día")
81
- expect(distance_of_time_in_words(START_TIME, START_TIME + 5.days, :locale => :es)).to eq("5 días")
85
+ describe '#time_ago_in_words' do
86
+ it 'aliases to distance_of_time_in_words' do
87
+ expect(time_ago_in_words(Time.now - 3.days - 14.minutes)).to eq('3 days and 14 minutes')
82
88
  end
89
+ end
90
+
91
+ describe '#distance_of_time_in_words' do
92
+ context 'locale' do
93
+ it 'includes known languages' do
94
+ expect(DOTIW.languages).to include :en
95
+ expect(DOTIW.languages).to include :ru
96
+ end
83
97
 
84
- it "deve parlare l'italiano" do
85
- expect(distance_of_time_in_words(START_TIME, START_TIME + 1.days, true, :locale => :it)).to eq("un giorno")
86
- expect(distance_of_time_in_words(START_TIME, START_TIME + 5.days, true, :locale => :it)).to eq("5 giorni")
98
+ it 'includes all the languages in specs' do
99
+ languages = Dir[File.join(File.dirname(__FILE__), 'i18n', '*.yml')].map { |f| File.basename(f, '.yml') }
100
+ expect(DOTIW.languages.map(&:to_s).sort).to eq languages.sort
101
+ end
102
+
103
+ DOTIW.languages.each do |lang|
104
+ context lang do
105
+ YAML.safe_load(
106
+ File.read(
107
+ File.join(
108
+ File.dirname(__FILE__), 'i18n', "#{lang}.yml"
109
+ )
110
+ )
111
+ ).each_pair do |category, fixtures|
112
+ context category do
113
+ fixtures.each_pair do |k, v|
114
+ it v do
115
+ expect(
116
+ distance_of_time_in_words(
117
+ START_TIME,
118
+ START_TIME + eval(k),
119
+ true,
120
+ locale: lang
121
+ )
122
+ ).to eq(v)
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
87
129
  end
88
130
 
89
- fragments = [
90
- [START_TIME, START_TIME + 5.days + 3.minutes, "5 days and 3 minutes"],
91
- [START_TIME, START_TIME + 1.minute, "1 minute"],
92
- [START_TIME, START_TIME + 3.years, "3 years"],
93
- [START_TIME, START_TIME + 10.years, "10 years"],
94
- [START_TIME, START_TIME + 10.years, "10 years"],
95
- [START_TIME, START_TIME + 3.hour, "3 hours"],
96
- [START_TIME, START_TIME + 13.months, "1 year and 1 month"],
131
+ [
132
+ [START_TIME, START_TIME + 5.days + 3.minutes, '5 days and 3 minutes'],
133
+ [START_TIME, START_TIME + 1.minute, '1 minute'],
134
+ [START_TIME, START_TIME + 3.years, '3 years'],
135
+ [START_TIME, START_TIME + 10.years, '10 years'],
136
+ [START_TIME, START_TIME + 8.months, '8 months'],
137
+ [START_TIME, START_TIME + 3.hour, '3 hours'],
138
+ [START_TIME, START_TIME + 13.months, '1 year and 1 month'],
97
139
  # Any numeric sequence is merely coincidental.
98
- [START_TIME, START_TIME + 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"],
99
- ["2009-3-16".to_time, "2008-4-14".to_time, "11 months and 2 days"],
100
- ["2009-3-16".to_time + 1.minute, "2008-4-14".to_time, "11 months, 2 days, and 1 minute"],
101
- ["2009-4-14".to_time, "2008-3-16".to_time, "1 year and 29 days"],
102
- ["2009-2-01".to_time, "2009-3-01".to_time, "1 month"],
103
- ["2008-2-01".to_time, "2008-3-01".to_time, "1 month"]
104
- ]
105
- fragments.each do |start, finish, output|
140
+ [START_TIME, START_TIME + 1.year + 2.months + 3.weeks + 4.days + 5.hours + 6.minutes + 7.seconds, '1 year, 2 months, 3 weeks, 4 days, 5 hours, 6 minutes, and 7 seconds'],
141
+ ['2009-3-16'.to_time, '2008-4-14'.to_time, '11 months and 2 days'],
142
+ ['2009-3-16'.to_time + 1.minute, '2008-4-14'.to_time, '11 months, 2 days, and 1 minute'],
143
+ ['2009-4-14'.to_time, '2008-3-16'.to_time, '1 year, 4 weeks, and 1 day'],
144
+ ['2009-2-01'.to_time, '2009-3-01'.to_time, '1 month'],
145
+ ['2008-2-01'.to_time, '2008-3-01'.to_time, '1 month'],
146
+ [Date.parse('31.03.2015').to_time, Time.parse('01.03.2016'), '10 months, 4 weeks, and 2 days'],
147
+ [Date.new(2014, 1, 31), Date.new(2014, 3, 1), '4 weeks and 1 day'],
148
+ ['2008-2-01'.to_time, '2008-3-01'.to_time, '1 month'],
149
+ ['2014-1-31'.to_time, '2014-3-01'.to_time, '4 weeks and 1 day'],
150
+ ['2014-1-31'.to_time, '2014-3-02'.to_time, '4 weeks and 2 days'],
151
+ ['2016-1-31'.to_time, '2016-3-01'.to_time, '4 weeks and 2 days'],
152
+ ['2016-1-31'.to_time, '2016-3-02'.to_time, '1 month']
153
+ ].each do |start, finish, output|
106
154
  it "should be #{output}" do
107
155
  expect(distance_of_time_in_words(start, finish, true)).to eq(output)
156
+ expect(distance_of_time_in_words(finish, start, true)).to eq(output)
108
157
  end
109
158
  end
110
159
 
111
- describe "accumulate on" do
112
- fragments = [
160
+ [
161
+ [Time.zone.now, Time.zone.now + 1.day - 1.minute, '23 hours and 59 minutes'],
162
+ [Time.zone.now, Time.zone.now + 15.days - 1.minute, '14 days, 23 hours, and 59 minutes'],
163
+ [Time.zone.now, Time.zone.now + 29.days - 1.minute, '28 days, 23 hours, and 59 minutes'],
164
+ [Time.zone.now, Time.zone.now + 30.days - 1.minute, '29 days, 23 hours, and 59 minutes'],
165
+ [Time.zone.now, Time.zone.now + 31.days - 1.minute, '30 days, 23 hours, and 59 minutes'],
166
+ [Time.zone.now, Time.zone.now + 32.days - 1.minute, '31 days, 23 hours, and 59 minutes'],
167
+ [Time.zone.now, Time.zone.now + 33.days - 1.minute, '32 days, 23 hours, and 59 minutes']
168
+ ].each do |start, finish, output|
169
+ it "should be #{output}" do
170
+ expect(distance_of_time_in_words(start, finish, accumulate_on: 'days')).to eq(output)
171
+ end
172
+ end
173
+
174
+ [
175
+ [Time.at(1), Time.at(100), '1 minute'],
176
+ [DateTime.now, DateTime.now + 1.minute, '1 minute'],
177
+ [Date.new(2000, 1, 2), Date.new(2000, 1, 3), '1 day'],
178
+ [Time.at(DateTime.now), DateTime.now + 1.minute, '1 minute']
179
+ ].each do |start, finish, output|
180
+ it "should be #{output}" do
181
+ expect(distance_of_time_in_words(start, finish)).to eq(output)
182
+ end
183
+ end
184
+
185
+ describe 'accumulate_on:' do
186
+ [
113
187
  [START_TIME,
114
188
  START_TIME + 10.minute,
115
189
  :seconds,
116
- "600 seconds"],
190
+ '600 seconds'],
117
191
  [START_TIME,
118
192
  START_TIME + 10.hour + 10.minute + 1.second,
119
193
  :minutes,
120
- "610 minutes and 1 second"],
194
+ '610 minutes and 1 second'],
121
195
  [START_TIME,
122
- START_TIME + 2.day + 10000.hour + 10.second,
196
+ START_TIME + 2.day + 10_000.hour + 10.second,
123
197
  :hours,
124
- "10048 hours and 10 seconds"],
198
+ '10048 hours and 10 seconds'],
125
199
  [START_TIME,
126
- START_TIME + 2.day + 10000.hour + 10.second,
200
+ START_TIME + 2.day + 10_000.hour + 10.second,
127
201
  :days,
128
- "418 days, 16 hours, and 10 seconds"],
202
+ '418 days, 16 hours, and 10 seconds'],
203
+ [START_TIME,
204
+ START_TIME + 2.day + 10_000.hour + 10.second,
205
+ :weeks,
206
+ '59 weeks, 5 days, 16 hours, and 10 seconds'],
129
207
  [START_TIME,
130
- START_TIME + 2.day + 10000.hour + 10.second,
208
+ START_TIME + 2.day + 10_000.hour + 10.second,
131
209
  :months,
132
- "13 months, 22 days, 16 hours, and 10 seconds"],
133
- ["2015-1-15".to_time, "2016-3-15".to_time, :months, "14 months"]
134
-
135
- ]
136
- fragments.each do |start, finish, accumulator, output|
210
+ '13 months, 3 weeks, 1 day, 16 hours, and 10 seconds'],
211
+ ['2015-1-15'.to_time, '2016-3-15'.to_time, :months, '14 months']
212
+ ].each do |start, finish, accumulator, output|
137
213
  it "should be #{output}" do
138
- expect(distance_of_time_in_words(start, finish, true, :accumulate_on => accumulator)).to eq(output)
214
+ expect(distance_of_time_in_words(start, finish, true, accumulate_on: accumulator)).to eq(output)
139
215
  end
140
216
  end
141
217
  end # :accumulate_on
142
218
 
143
- describe "without finish time" do
219
+ describe 'without finish time' do
144
220
  # A missing finish argument should default to zero, essentially returning
145
221
  # the equivalent of distance_of_time in order to be backwards-compatible
146
222
  # with the original rails distance_of_time_in_words helper.
147
- fragments = [
148
- [5.minutes.to_i, "5 minutes"],
149
- [10.minutes.to_i, "10 minutes"],
150
- [1.hour.to_i, "1 hour"],
151
- [4.weeks.to_i, "28 days"],
152
- [24.weeks.to_i, "5 months and 15 days"]
153
- ]
154
- fragments.each do |start, output|
223
+ [
224
+ [5.minutes.to_i, '5 minutes'],
225
+ [10.minutes.to_i, '10 minutes'],
226
+ [1.hour.to_i, '1 hour'],
227
+ [6.days.to_i, '6 days'],
228
+ [4.weeks.to_i, '4 weeks'],
229
+ [24.weeks.to_i, '5 months, 2 weeks, and 1 day']
230
+ ].each do |start, output|
155
231
  it "should be #{output}" do
156
232
  expect(distance_of_time_in_words(start)).to eq(output)
157
233
  end
158
234
  end
159
235
  end
160
-
161
236
  end
162
237
 
163
- describe "with output options" do
164
- fragments = [
238
+ describe 'with output options' do
239
+ [
165
240
  # Any numeric sequence is merely coincidental.
166
241
  [START_TIME,
167
- START_TIME + 1.year + 2.months + 3.days + 4.hours + 5.minutes + 6.seconds,
168
- { :words_connector => " - " },
169
- "1 year - 2 months - 3 days - 4 hours - 5 minutes, and 6 seconds"],
242
+ START_TIME + 1.year + 2.months + 3.weeks + 4.days + 5.hours + 6.minutes + 7.seconds,
243
+ { words_connector: ' - ' },
244
+ '1 year - 2 months - 3 weeks - 4 days - 5 hours - 6 minutes, and 7 seconds'],
170
245
  [START_TIME,
171
246
  START_TIME + 5.minutes + 6.seconds,
172
- { :two_words_connector => " - " },
173
- "5 minutes - 6 seconds"],
247
+ { two_words_connector: ' - ' },
248
+ '5 minutes - 6 seconds'],
174
249
  [START_TIME,
175
- START_TIME + 4.hours + 5.minutes + 6.seconds,
176
- { :last_word_connector => " - " },
177
- "4 hours, 5 minutes - 6 seconds"],
250
+ START_TIME + 4.hours + 5.minutes + 6.seconds,
251
+ { last_word_connector: ' - ' },
252
+ '4 hours, 5 minutes - 6 seconds'],
178
253
  [START_TIME,
179
254
  START_TIME + 1.year + 2.months + 3.days + 4.hours + 5.minutes + 6.seconds,
180
- { :except => "minutes" },
181
- "1 year, 2 months, 3 days, 4 hours, and 6 seconds"],
255
+ { except: 'minutes' },
256
+ '1 year, 2 months, 3 days, 4 hours, and 6 seconds'],
182
257
  [START_TIME,
183
258
  START_TIME + 1.hour + 1.minute,
184
- { :except => "minutes"}, "1 hour"],
259
+ { except: 'minutes' }, '1 hour'],
185
260
  [START_TIME,
186
261
  START_TIME + 1.hour + 1.day + 1.minute,
187
- { :except => ["minutes", "hours"]},
188
- "1 day"],
262
+ { except: %w[minutes hours] },
263
+ '1 day'],
189
264
  [START_TIME,
190
265
  START_TIME + 1.hour + 1.day + 1.minute,
191
- { :only => ["minutes", "hours"]},
192
- "1 hour and 1 minute"],
266
+ { only: %w[minutes hours] },
267
+ '1 hour and 1 minute'],
193
268
  [START_TIME,
194
- START_TIME + 1.year + 2.months + 3.days + 4.hours + 5.minutes + 6.seconds,
195
- { :vague => true },
196
- "about 1 year"],
269
+ START_TIME + 1.year + 2.months + 3.weeks + 4.days + 5.hours + 6.minutes + 7.seconds,
270
+ { except: 'minutes' },
271
+ '1 year, 2 months, 3 weeks, 4 days, 5 hours, and 7 seconds'],
197
272
  [START_TIME,
198
- START_TIME + 1.year + 2.months + 3.days + 4.hours + 5.minutes + 6.seconds,
199
- { :vague => "Yes please" },
200
- "about 1 year"],
201
- [START_TIME,
202
- START_TIME + 1.year + 2.months + 3.days + 4.hours + 5.minutes + 6.seconds,
203
- { :vague => false },
204
- "1 year, 2 months, 3 days, 4 hours, 5 minutes, and 6 seconds"],
205
- [START_TIME,
206
- START_TIME + 1.year + 2.months + 3.days + 4.hours + 5.minutes + 6.seconds,
207
- { :vague => nil },
208
- "1 year, 2 months, 3 days, 4 hours, 5 minutes, and 6 seconds"],
209
- [START_TIME,
210
- START_TIME + 1.year + 2.months + 3.days + 4.hours + 5.minutes + 6.seconds,
211
- { :except => "minutes" },
212
- "1 year, 2 months, 3 days, 4 hours, and 6 seconds"],
213
- [START_TIME,
214
- START_TIME + 1.hour + 2.minutes + 3.seconds,
215
- { :highest_measure_only => true },
216
- "1 hour"],
273
+ START_TIME + 1.hour + 2.minutes + 3.seconds,
274
+ { highest_measure_only: true },
275
+ '1 hour'],
217
276
  [START_TIME,
218
277
  START_TIME + 1.hours + 2.minutes + 3.seconds,
219
- { :highest_measures => 1 },
220
- "1 hour"],
278
+ { highest_measures: 1 },
279
+ '1 hour'],
221
280
  [START_TIME,
222
281
  START_TIME + 2.year + 3.months + 4.days + 5.hours + 6.minutes + 7.seconds,
223
- { :highest_measures => 3 },
224
- "2 years, 3 months, and 4 days"],
282
+ { highest_measures: 3 },
283
+ '2 years, 3 months, and 4 days'],
225
284
  [START_TIME,
226
285
  START_TIME + 2.year + 3.weeks + 4.days + 5.hours + 6.minutes + 7.seconds,
227
- { :highest_measures => 2 },
228
- "2 years and 25 days"],
286
+ { highest_measures: 2 },
287
+ '2 years and 3 weeks'],
229
288
  [START_TIME,
230
289
  START_TIME + 4.days + 6.minutes + 7.seconds,
231
- { :highest_measures => 3 },
232
- "4 days, 6 minutes, and 7 seconds"],
290
+ { highest_measures: 3 },
291
+ '4 days, 6 minutes, and 7 seconds'],
233
292
  [START_TIME,
234
293
  START_TIME + 1.year + 2.weeks,
235
- { :highest_measures => 3 },
236
- "1 year and 14 days"],
294
+ { highest_measures: 3 },
295
+ '1 year and 2 weeks'],
237
296
  [START_TIME,
238
297
  START_TIME + 1.days,
239
- { :only => [:years, :months] },
240
- "less than 1 month"],
298
+ { only: %i[years months] },
299
+ 'less than 1 month'],
241
300
  [START_TIME,
242
301
  START_TIME + 5.minutes,
243
- { :except => [:hours, :minutes, :seconds] },
244
- "less than 1 day"],
302
+ { except: %i[hours minutes seconds] },
303
+ 'less than 1 day'],
245
304
  [START_TIME,
246
305
  START_TIME + 1.days,
247
- { :highest_measures => 1, :only => [:years, :months] },
248
- "less than 1 month"]
249
- ]
250
- fragments.each do |start, finish, options, output|
306
+ { highest_measures: 1, only: %i[years months] },
307
+ 'less than 1 month']
308
+ ].each do |start, finish, options, output|
251
309
  it "should be #{output}" do
252
310
  expect(distance_of_time_in_words(start, finish, true, options)).to eq(output)
253
311
  end
254
312
  end
255
313
 
256
- describe "include_seconds" do
257
- it "is ignored if only seconds have passed" do
258
- expect(distance_of_time_in_words(START_TIME, START_TIME + 1.second, false)).to eq("1 second")
314
+ if defined?(ActionView)
315
+ describe 'ActionView' do
316
+ [
317
+ [START_TIME,
318
+ START_TIME + 1.year + 2.months + 3.weeks + 4.days + 5.hours + 6.minutes + 7.seconds,
319
+ { vague: true },
320
+ 'about 1 year'],
321
+ [START_TIME,
322
+ START_TIME + 1.year + 2.months + 3.weeks + 4.days + 5.hours + 6.minutes + 7.seconds,
323
+ { vague: 'Yes please' },
324
+ 'about 1 year'],
325
+ [START_TIME,
326
+ START_TIME + 1.year + 2.months + 3.weeks + 4.days + 5.hours + 6.minutes + 7.seconds,
327
+ { vague: false },
328
+ '1 year, 2 months, 3 weeks, 4 days, 5 hours, 6 minutes, and 7 seconds'],
329
+ [START_TIME,
330
+ START_TIME + 1.year + 2.months + 3.weeks + 4.days + 5.hours + 6.minutes + 7.seconds,
331
+ { vague: nil },
332
+ '1 year, 2 months, 3 weeks, 4 days, 5 hours, 6 minutes, and 7 seconds']
333
+ ].each do |start, finish, options, output|
334
+ it "should be #{output}" do
335
+ expect(distance_of_time_in_words(start, finish, true, options)).to eq(output)
336
+ end
337
+ end
338
+
339
+ context 'via ActionController::Base.helpers' do
340
+ it '#distance_of_time_in_words' do
341
+ end_time = START_TIME + 1.year + 2.months + 3.weeks + 4.days + 5.hours + 6.minutes + 7.seconds
342
+ expected = '1 year, 2 months, 3 weeks, 4 days, 5 hours, 6 minutes, and 7 seconds'
343
+ actual = ActionController::Base.helpers.distance_of_time_in_words(START_TIME, end_time, true, { vague: false })
344
+ expect(actual).to eq(expected)
345
+ end
346
+
347
+ it '#time_ago_in_words' do
348
+ expected = '3 days and 14 minutes'
349
+ actual = ActionController::Base.helpers.time_ago_in_words(Time.now - 3.days - 14.minutes)
350
+ expect(actual).to eq(expected)
351
+ end
352
+ end
353
+ end
354
+ end
355
+
356
+ describe 'include_seconds' do
357
+ it 'is ignored if only seconds have passed' do
358
+ expect(distance_of_time_in_words(START_TIME, START_TIME + 1.second, false)).to eq('1 second')
259
359
  end
260
360
 
261
- it "removes seconds in all other cases" do
262
- expect(distance_of_time_in_words(START_TIME,
263
- START_TIME + 1.year + 2.months + 3.days + 4.hours + 5.minutes + 6.seconds,
264
- false)).to eq("1 year, 2 months, 3 days, 4 hours, and 5 minutes")
361
+ it 'removes seconds in all other cases' do
362
+ expect(
363
+ distance_of_time_in_words(
364
+ START_TIME,
365
+ START_TIME + 1.year + 2.months + 3.weeks + 4.days + 5.hours + 6.minutes + 7.seconds,
366
+ false
367
+ )
368
+ ).to eq('1 year, 2 months, 3 weeks, 4 days, 5 hours, and 6 minutes')
265
369
  end
266
370
  end # include_seconds
267
371
  end
268
372
 
269
- describe "percentage of time" do
270
- def time_in_percent(options = {})
271
- distance_of_time_in_percent("04-12-2009".to_time, "29-01-2010".to_time, "04-12-2010".to_time, options)
272
- end
373
+ if defined?(ActionView)
374
+ describe 'percentage of time' do
375
+ def time_in_percent(options = {})
376
+ distance_of_time_in_percent('04-12-2009'.to_time, '29-01-2010'.to_time, '04-12-2010'.to_time, options)
377
+ end
273
378
 
274
- it "calculates 15%" do
275
- expect(time_in_percent).to eq("15%")
276
- end
379
+ it 'calculates 15%' do
380
+ expect(time_in_percent).to eq('15%')
381
+ end
277
382
 
278
- it "calculates 15.3%" do
279
- expect(time_in_percent(:precision => 1)).to eq("15.3%")
383
+ it 'calculates 15.3%' do
384
+ expect(time_in_percent(precision: 1)).to eq('15.3%')
385
+ end
280
386
  end
281
387
  end
282
-
283
388
  end