dotiw 3.0.1 → 5.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|