dotiw 3.0.1 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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