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.
- checksums.yaml +5 -5
- data/.github/workflows/ruby.yml +45 -0
- data/.gitignore +2 -1
- data/.rspec +2 -0
- data/Appraisals +19 -0
- data/CHANGELOG.md +29 -0
- data/CONTRIBUTING.md +34 -0
- data/Gemfile +2 -0
- data/MIT-LICENSE +1 -1
- data/README.markdown +139 -70
- data/Rakefile +3 -1
- data/dotiw.gemspec +22 -17
- data/gemfiles/.bundle/config +2 -0
- data/gemfiles/rails_4.gemfile +9 -0
- data/gemfiles/rails_5.0.gemfile +9 -0
- data/gemfiles/rails_5.1.gemfile +9 -0
- data/gemfiles/rails_5.2.gemfile +9 -0
- data/gemfiles/rails_6.0.gemfile +9 -0
- data/lib/dotiw.rb +31 -13
- data/lib/dotiw/action_view/helpers/date_helper.rb +24 -0
- data/lib/dotiw/locale/ar.yml +53 -0
- data/lib/dotiw/locale/da.yml +23 -0
- data/lib/dotiw/locale/fr.yml +25 -0
- data/lib/dotiw/locale/id.yml +25 -0
- data/lib/dotiw/locale/ko.yml +25 -0
- data/lib/dotiw/locale/pl.yml +7 -0
- data/lib/dotiw/locale/pt-BR.yml +25 -0
- data/lib/dotiw/locale/vi.yml +25 -0
- data/lib/dotiw/locale/zh-CN.yml +25 -0
- data/lib/dotiw/methods.rb +93 -0
- data/lib/dotiw/time_hash.rb +65 -38
- data/lib/dotiw/version.rb +2 -2
- data/spec/lib/dotiw_spec.rb +264 -159
- data/spec/lib/i18n/ar.yml +2 -0
- data/spec/lib/i18n/da.yml +2 -0
- data/spec/lib/i18n/de.yml +2 -0
- data/spec/lib/i18n/en.yml +2 -0
- data/spec/lib/i18n/es.yml +3 -0
- data/spec/lib/i18n/fr.yml +2 -0
- data/spec/lib/i18n/id.yml +2 -0
- data/spec/lib/i18n/it.yml +3 -0
- data/spec/lib/i18n/ja.yml +2 -0
- data/spec/lib/i18n/ko.yml +2 -0
- data/spec/lib/i18n/nb.yml +2 -0
- data/spec/lib/i18n/nl.yml +2 -0
- data/spec/lib/i18n/pl.yml +2 -0
- data/spec/lib/i18n/pt-BR.yml +2 -0
- data/spec/lib/i18n/ru.yml +5 -0
- data/spec/lib/i18n/vi.yml +22 -0
- data/spec/lib/i18n/zh-CN.yml +2 -0
- data/spec/spec_helper.rb +2 -9
- metadata +110 -21
- data/.travis.yml +0 -5
- 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
|
data/lib/dotiw/time_hash.rb
CHANGED
@@ -1,25 +1,25 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module DOTIW
|
4
4
|
class TimeHash
|
5
|
-
TIME_FRACTIONS = [
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
def initialize(distance, from_time
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
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
|
22
|
-
d += 1.hour if !
|
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
|
-
|
34
|
-
|
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
|
-
|
41
|
-
|
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 <
|
51
|
+
elsif distance < 7.days
|
52
52
|
build_days
|
53
|
-
|
54
|
-
|
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
|
-
|
66
|
+
@distance = 0
|
65
67
|
end
|
66
68
|
|
67
69
|
def build_minutes
|
68
|
-
output[:minutes],
|
70
|
+
output[:minutes], @distance = distance.divmod(1.minute.to_i)
|
69
71
|
end
|
70
72
|
|
71
73
|
def build_hours
|
72
|
-
output[:hours],
|
74
|
+
output[:hours], @distance = distance.divmod(1.hour.to_i)
|
73
75
|
end
|
74
76
|
|
75
77
|
def build_days
|
76
|
-
output[:days],
|
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
|
-
|
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
|
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
|
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(:
|
100
|
-
|
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,
|
140
|
+
total_days, @distance = distance.abs.divmod(1.day.to_i)
|
114
141
|
|
115
|
-
[total_days,
|
142
|
+
[total_days, @distance]
|
116
143
|
end
|
117
144
|
end # TimeHash
|
118
145
|
end # DOTIW
|
data/lib/dotiw/version.rb
CHANGED
data/spec/lib/dotiw_spec.rb
CHANGED
@@ -1,13 +1,19 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'spec_helper'
|
4
4
|
|
5
|
-
describe
|
6
|
-
|
7
|
-
|
8
|
-
|
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
|
-
|
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
|
19
|
-
|
20
|
-
[0.5.minutes,
|
21
|
-
[4.5.minutes,
|
22
|
-
[5.minutes
|
23
|
-
[10.minutes
|
24
|
-
[1.hour
|
25
|
-
[1.hour + 30.seconds,
|
26
|
-
[4.weeks
|
27
|
-
[
|
28
|
-
|
29
|
-
|
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
|
36
|
-
it
|
37
|
-
expect(distance_of_time(1.2.minute, except: 'seconds')).to eq(
|
38
|
-
expect(distance_of_time(2.5.hours + 30.seconds, except: 'seconds')).to eq(
|
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
|
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
|
49
|
-
describe
|
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
|
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
|
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
|
66
|
-
hash = distance_of_time_in_words_hash(
|
67
|
-
|
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[:
|
71
|
-
expect(hash[:
|
72
|
-
expect(hash[:
|
73
|
-
expect(hash[:
|
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
|
79
|
-
it
|
80
|
-
expect(
|
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
|
-
|
85
|
-
|
86
|
-
|
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
|
-
|
90
|
-
[START_TIME, START_TIME + 5.days + 3.minutes,
|
91
|
-
[START_TIME, START_TIME + 1.minute,
|
92
|
-
[START_TIME, START_TIME + 3.years,
|
93
|
-
[START_TIME, START_TIME + 10.years,
|
94
|
-
[START_TIME, START_TIME +
|
95
|
-
[START_TIME, START_TIME + 3.hour,
|
96
|
-
[START_TIME, START_TIME + 13.months,
|
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.
|
99
|
-
[
|
100
|
-
[
|
101
|
-
[
|
102
|
-
[
|
103
|
-
[
|
104
|
-
|
105
|
-
|
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
|
-
|
112
|
-
|
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
|
-
|
190
|
+
'600 seconds'],
|
117
191
|
[START_TIME,
|
118
192
|
START_TIME + 10.hour + 10.minute + 1.second,
|
119
193
|
:minutes,
|
120
|
-
|
194
|
+
'610 minutes and 1 second'],
|
121
195
|
[START_TIME,
|
122
|
-
START_TIME + 2.day +
|
196
|
+
START_TIME + 2.day + 10_000.hour + 10.second,
|
123
197
|
:hours,
|
124
|
-
|
198
|
+
'10048 hours and 10 seconds'],
|
125
199
|
[START_TIME,
|
126
|
-
START_TIME + 2.day +
|
200
|
+
START_TIME + 2.day + 10_000.hour + 10.second,
|
127
201
|
:days,
|
128
|
-
|
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 +
|
208
|
+
START_TIME + 2.day + 10_000.hour + 10.second,
|
131
209
|
:months,
|
132
|
-
|
133
|
-
[
|
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, :
|
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
|
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
|
-
|
148
|
-
[5.minutes.to_i,
|
149
|
-
[10.minutes.to_i,
|
150
|
-
[1.hour.to_i,
|
151
|
-
[
|
152
|
-
[
|
153
|
-
|
154
|
-
|
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
|
164
|
-
|
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.
|
168
|
-
{ :
|
169
|
-
|
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
|
-
{ :
|
173
|
-
|
247
|
+
{ two_words_connector: ' - ' },
|
248
|
+
'5 minutes - 6 seconds'],
|
174
249
|
[START_TIME,
|
175
|
-
START_TIME + 4.hours +
|
176
|
-
{ :
|
177
|
-
|
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
|
-
{ :
|
181
|
-
|
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
|
-
{ :
|
259
|
+
{ except: 'minutes' }, '1 hour'],
|
185
260
|
[START_TIME,
|
186
261
|
START_TIME + 1.hour + 1.day + 1.minute,
|
187
|
-
{ :
|
188
|
-
|
262
|
+
{ except: %w[minutes hours] },
|
263
|
+
'1 day'],
|
189
264
|
[START_TIME,
|
190
265
|
START_TIME + 1.hour + 1.day + 1.minute,
|
191
|
-
{ :
|
192
|
-
|
266
|
+
{ only: %w[minutes hours] },
|
267
|
+
'1 hour and 1 minute'],
|
193
268
|
[START_TIME,
|
194
|
-
START_TIME + 1.year + 2.months + 3.
|
195
|
-
{ :
|
196
|
-
|
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.
|
199
|
-
{ :
|
200
|
-
|
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
|
-
{ :
|
220
|
-
|
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
|
-
{ :
|
224
|
-
|
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
|
-
{ :
|
228
|
-
|
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
|
-
{ :
|
232
|
-
|
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
|
-
{ :
|
236
|
-
|
294
|
+
{ highest_measures: 3 },
|
295
|
+
'1 year and 2 weeks'],
|
237
296
|
[START_TIME,
|
238
297
|
START_TIME + 1.days,
|
239
|
-
{ :
|
240
|
-
|
298
|
+
{ only: %i[years months] },
|
299
|
+
'less than 1 month'],
|
241
300
|
[START_TIME,
|
242
301
|
START_TIME + 5.minutes,
|
243
|
-
{ :
|
244
|
-
|
302
|
+
{ except: %i[hours minutes seconds] },
|
303
|
+
'less than 1 day'],
|
245
304
|
[START_TIME,
|
246
305
|
START_TIME + 1.days,
|
247
|
-
{ :
|
248
|
-
|
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
|
-
|
257
|
-
|
258
|
-
|
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
|
262
|
-
expect(
|
263
|
-
|
264
|
-
|
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
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
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
|
-
|
275
|
-
|
276
|
-
|
379
|
+
it 'calculates 15%' do
|
380
|
+
expect(time_in_percent).to eq('15%')
|
381
|
+
end
|
277
382
|
|
278
|
-
|
279
|
-
|
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
|