dotiw 4.0.1 → 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +45 -0
- data/.rspec +2 -0
- data/Appraisals +19 -0
- data/CHANGELOG.md +20 -6
- data/Gemfile +2 -0
- data/MIT-LICENSE +1 -1
- data/README.markdown +131 -62
- data/Rakefile +3 -1
- data/dotiw.gemspec +16 -18
- data/gemfiles/rails_4.gemfile +5 -3
- data/gemfiles/rails_5.0.gemfile +5 -3
- data/gemfiles/rails_5.1.gemfile +5 -3
- data/gemfiles/rails_5.2.gemfile +9 -0
- data/gemfiles/rails_6.0.gemfile +9 -0
- data/lib/dotiw.rb +22 -11
- data/lib/dotiw/action_view/helpers/date_helper.rb +24 -0
- data/lib/dotiw/locale/ar.yml +34 -27
- data/lib/dotiw/locale/pl.yml +7 -0
- data/lib/dotiw/methods.rb +91 -0
- data/lib/dotiw/time_hash.rb +38 -32
- data/lib/dotiw/version.rb +2 -2
- data/spec/lib/dotiw_spec.rb +197 -155
- data/spec/spec_helper.rb +2 -9
- metadata +35 -31
- data/.travis.yml +0 -27
- data/lib/dotiw/action_view_ext/helpers/date_helper.rb +0 -103
data/gemfiles/rails_4.gemfile
CHANGED
data/gemfiles/rails_5.0.gemfile
CHANGED
data/gemfiles/rails_5.1.gemfile
CHANGED
data/lib/dotiw.rb
CHANGED
@@ -1,30 +1,41 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'i18n'
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
require 'dotiw/action_view_ext/helpers/date_helper'
|
8
|
-
end
|
5
|
+
require 'active_support'
|
6
|
+
require 'active_support/core_ext'
|
9
7
|
|
10
8
|
module DOTIW
|
9
|
+
extend ActiveSupport::Autoload
|
10
|
+
|
11
|
+
eager_autoload do
|
12
|
+
autoload :VERSION, 'dotiw/version'
|
13
|
+
autoload :TimeHash, 'dotiw/time_hash'
|
14
|
+
autoload :Methods, 'dotiw/methods'
|
15
|
+
end
|
16
|
+
|
11
17
|
extend self
|
12
|
-
|
13
|
-
autoload :VERSION, 'dotiw/version'
|
14
|
-
autoload :TimeHash, 'dotiw/time_hash'
|
15
18
|
|
16
19
|
DEFAULT_I18N_SCOPE = :'datetime.dotiw'
|
17
20
|
|
18
|
-
def init_i18n
|
21
|
+
def init_i18n!
|
19
22
|
I18n.load_path.unshift(*locale_files)
|
20
23
|
I18n.reload!
|
21
24
|
end
|
22
25
|
|
23
|
-
protected
|
26
|
+
protected
|
27
|
+
|
24
28
|
# Returns all locale files shipped with library
|
25
29
|
def locale_files
|
26
30
|
Dir[File.join(File.dirname(__FILE__), 'dotiw', 'locale', '**/*')]
|
27
31
|
end
|
28
32
|
end # DOTIW
|
29
33
|
|
30
|
-
DOTIW.init_i18n
|
34
|
+
DOTIW.init_i18n!
|
35
|
+
|
36
|
+
begin
|
37
|
+
require 'action_view'
|
38
|
+
require_relative 'dotiw/action_view/helpers/date_helper'
|
39
|
+
rescue LoadError
|
40
|
+
# TODO: don't rely on exception
|
41
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionView
|
4
|
+
module Helpers
|
5
|
+
module DateHelper
|
6
|
+
alias_method :_distance_of_time_in_words, :distance_of_time_in_words
|
7
|
+
alias_method :_time_ago_in_words, :time_ago_in_words
|
8
|
+
|
9
|
+
include DOTIW::Methods
|
10
|
+
|
11
|
+
def distance_of_time_in_words(from_time, to_time = 0, include_seconds_or_options = {}, options = {})
|
12
|
+
return _distance_of_time_in_words(from_time, to_time, options) if options.delete(:vague)
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
def distance_of_time_in_percent(from_time, current_time, to_time, options = {})
|
17
|
+
options[:precision] ||= 0
|
18
|
+
distance = to_time - from_time
|
19
|
+
result = ((current_time - from_time) / distance) * 100
|
20
|
+
number_with_precision(result, options).to_s + '%'
|
21
|
+
end
|
22
|
+
end # DateHelper
|
23
|
+
end # Helpers
|
24
|
+
end # ActionView
|
data/lib/dotiw/locale/ar.yml
CHANGED
@@ -2,45 +2,52 @@ ar:
|
|
2
2
|
datetime:
|
3
3
|
dotiw:
|
4
4
|
seconds:
|
5
|
-
|
5
|
+
zero: ثانية
|
6
|
+
one: ثانية واحدة
|
6
7
|
two: ثانيتين
|
7
|
-
few: "%{count}
|
8
|
-
many: "%{count}
|
9
|
-
other: "%{count}
|
8
|
+
few: "%{count} توانٍ"
|
9
|
+
many: "%{count} ثانية"
|
10
|
+
other: "%{count} ثانية"
|
10
11
|
minutes:
|
11
|
-
|
12
|
+
zero: دقيقة
|
13
|
+
one: دقيقة واحدة
|
12
14
|
two: دقيقتين
|
13
15
|
few: "%{count} دقائق"
|
14
|
-
many: "%{count}
|
15
|
-
other: "%{count}
|
16
|
+
many: "%{count} دقيقة"
|
17
|
+
other: "%{count} دقيقة"
|
16
18
|
hours:
|
17
|
-
|
19
|
+
zero: ساعة
|
20
|
+
one: ساعة واحدة
|
18
21
|
two: ساعتين
|
19
22
|
few: "%{count} ساعات"
|
20
|
-
many: "%{count}
|
21
|
-
other: "%{count}
|
23
|
+
many: "%{count} ساعة"
|
24
|
+
other: "%{count} ساعة"
|
22
25
|
days:
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
26
|
+
zero: يوم
|
27
|
+
one: يوم واحد
|
28
|
+
two: يومين
|
29
|
+
few: "%{count} أيام"
|
30
|
+
many: "%{count} يومًا"
|
31
|
+
other: "%{count} يوم"
|
28
32
|
weeks:
|
29
|
-
|
30
|
-
|
33
|
+
zero: أسبوع
|
34
|
+
one: أسبوع واحدة
|
35
|
+
two: أسبوعين
|
31
36
|
few: "%{count} أسابيع"
|
32
37
|
many: "%{count} أسابيع"
|
33
|
-
other: "%{count}
|
38
|
+
other: "%{count} أسيوع"
|
34
39
|
months:
|
35
|
-
|
40
|
+
zero: شهر
|
41
|
+
one: شهر واحد
|
36
42
|
two: شهرين
|
37
43
|
few: "%{count} أشهر"
|
38
|
-
many: "%{count}
|
39
|
-
other: "%{count}
|
44
|
+
many: "%{count} شهراً"
|
45
|
+
other: "%{count} شهر"
|
40
46
|
years:
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
+
zero: عام
|
48
|
+
one: عام واحد
|
49
|
+
two: عامين
|
50
|
+
few: "%{count} أعوام"
|
51
|
+
many: "%{count} عاماً"
|
52
|
+
other: "%{count} عام"
|
53
|
+
less_than_x: "%{distance} أقل من"
|
data/lib/dotiw/locale/pl.yml
CHANGED
@@ -4,29 +4,36 @@ pl:
|
|
4
4
|
seconds:
|
5
5
|
one: "1 sekunda"
|
6
6
|
few: "%{count} sekundy"
|
7
|
+
many: "%{count} sekund"
|
7
8
|
other: "%{count} sekund"
|
8
9
|
minutes:
|
9
10
|
one: "1 minuta"
|
10
11
|
few: "%{count} minuty"
|
12
|
+
many: "%{count} minut"
|
11
13
|
other: "%{count} minut"
|
12
14
|
hours:
|
13
15
|
one: "1 godzina"
|
14
16
|
few: "%{count} godziny"
|
17
|
+
many: "%{count} godzin"
|
15
18
|
other: "%{count} godzin"
|
16
19
|
days:
|
17
20
|
one: "1 dzień"
|
18
21
|
few: "%{count} dni"
|
22
|
+
many: "%{count} dni"
|
19
23
|
other: "%{count} dni"
|
20
24
|
weeks:
|
21
25
|
one: "1 tydzień"
|
22
26
|
few: "%{count} tygodnie"
|
27
|
+
many: "%{count} tygodni"
|
23
28
|
other: "%{count} tygodni"
|
24
29
|
months:
|
25
30
|
one: "1 miesiąc"
|
26
31
|
few: "%{count} miesiące"
|
32
|
+
many: "%{count} miesięcy"
|
27
33
|
other: "%{count} miesięcy"
|
28
34
|
years:
|
29
35
|
one: "1 rok"
|
30
36
|
few: "%{count} lata"
|
37
|
+
many: "%{count} lat"
|
31
38
|
other: "%{count} lat"
|
32
39
|
less_than_x: "mniej niż %{distance}"
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DOTIW
|
4
|
+
module Methods
|
5
|
+
def distance_of_time_in_words_hash(from_time, to_time, options = {})
|
6
|
+
from_time = from_time.to_time if !from_time.is_a?(Time) && from_time.respond_to?(:to_time)
|
7
|
+
to_time = to_time.to_time if !to_time.is_a?(Time) && to_time.respond_to?(:to_time)
|
8
|
+
|
9
|
+
DOTIW::TimeHash.new(nil, from_time, to_time, options).to_hash
|
10
|
+
end
|
11
|
+
|
12
|
+
def distance_of_time(seconds, options = {})
|
13
|
+
options[:include_seconds] ||= true
|
14
|
+
_display_time_in_words DOTIW::TimeHash.new(seconds, nil, nil, options).to_hash, options
|
15
|
+
end
|
16
|
+
|
17
|
+
def distance_of_time_in_words(from_time, to_time = 0, include_seconds_or_options = {}, options = {})
|
18
|
+
if include_seconds_or_options.is_a?(Hash)
|
19
|
+
options = include_seconds_or_options
|
20
|
+
else
|
21
|
+
options[:include_seconds] ||= !!include_seconds_or_options
|
22
|
+
end
|
23
|
+
return distance_of_time(from_time, options) if to_time == 0
|
24
|
+
|
25
|
+
hash = distance_of_time_in_words_hash(from_time, to_time, options)
|
26
|
+
_display_time_in_words(hash, options)
|
27
|
+
end
|
28
|
+
|
29
|
+
def time_ago_in_words(from_time, include_seconds_or_options = {})
|
30
|
+
distance_of_time_in_words(from_time, Time.current, include_seconds_or_options)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def _display_time_in_words(hash, options = {})
|
36
|
+
options.reverse_merge!(
|
37
|
+
include_seconds: false
|
38
|
+
).symbolize_keys!
|
39
|
+
|
40
|
+
include_seconds = options.delete(:include_seconds)
|
41
|
+
hash.delete(:seconds) if !include_seconds && hash[:minutes]
|
42
|
+
|
43
|
+
options[:except] = Array.wrap(options[:except]).map!(&:to_sym) if options[:except]
|
44
|
+
options[:only] = Array.wrap(options[:only]).map!(&:to_sym) if options[:only]
|
45
|
+
|
46
|
+
# Remove all the values that are nil or excluded. Keep the required ones.
|
47
|
+
hash.delete_if do |key, value|
|
48
|
+
value.nil? || value.zero? ||
|
49
|
+
options[:except]&.include?(key) ||
|
50
|
+
(options[:only] && !options[:only].include?(key))
|
51
|
+
end
|
52
|
+
|
53
|
+
i18n_scope = options.delete(:scope) || DOTIW::DEFAULT_I18N_SCOPE
|
54
|
+
if hash.empty?
|
55
|
+
fractions = DOTIW::TimeHash::TIME_FRACTIONS
|
56
|
+
fractions &= options[:only] if options[:only]
|
57
|
+
fractions -= options[:except] if options[:except]
|
58
|
+
|
59
|
+
I18n.with_options locale: options[:locale], scope: i18n_scope do |locale|
|
60
|
+
# e.g. try to format 'less than 1 days', fallback to '0 days'
|
61
|
+
return locale.translate :less_than_x,
|
62
|
+
distance: locale.translate(fractions.first, count: 1),
|
63
|
+
default: locale.translate(fractions.first, count: 0)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
output = []
|
68
|
+
I18n.with_options locale: options[:locale], scope: i18n_scope do |locale|
|
69
|
+
output = hash.map { |key, value| locale.t(key, count: value) }
|
70
|
+
end
|
71
|
+
|
72
|
+
options.delete(:except)
|
73
|
+
options.delete(:only)
|
74
|
+
highest_measures = options.delete(:highest_measures)
|
75
|
+
highest_measures = 1 if options.delete(:highest_measure_only)
|
76
|
+
output = output[0...highest_measures] if highest_measures
|
77
|
+
|
78
|
+
options[:words_connector] ||= I18n.translate :'datetime.dotiw.words_connector',
|
79
|
+
default: :'support.array.words_connector',
|
80
|
+
locale: options[:locale]
|
81
|
+
options[:two_words_connector] ||= I18n.translate :'datetime.dotiw.two_words_connector',
|
82
|
+
default: :'support.array.two_words_connector',
|
83
|
+
locale: options[:locale]
|
84
|
+
options[:last_word_connector] ||= I18n.translate :'datetime.dotiw.last_word_connector',
|
85
|
+
default: :'support.array.last_word_connector',
|
86
|
+
locale: options[:locale]
|
87
|
+
|
88
|
+
output.to_sentence(options)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
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
|
@@ -63,23 +63,23 @@ module DOTIW
|
|
63
63
|
|
64
64
|
def build_seconds
|
65
65
|
output[:seconds] = distance.to_i
|
66
|
-
|
66
|
+
@distance = 0
|
67
67
|
end
|
68
68
|
|
69
69
|
def build_minutes
|
70
|
-
output[:minutes],
|
70
|
+
output[:minutes], @distance = distance.divmod(1.minute.to_i)
|
71
71
|
end
|
72
72
|
|
73
73
|
def build_hours
|
74
|
-
output[:hours],
|
74
|
+
output[:hours], @distance = distance.divmod(1.hour.to_i)
|
75
75
|
end
|
76
76
|
|
77
77
|
def build_days
|
78
|
-
output[:days],
|
78
|
+
output[:days], @distance = distance.divmod(1.day.to_i) unless output[:days]
|
79
79
|
end
|
80
80
|
|
81
81
|
def build_weeks
|
82
|
-
output[:weeks],
|
82
|
+
output[:weeks], @distance = distance.divmod(1.week.to_i) unless output[:weeks]
|
83
83
|
end
|
84
84
|
|
85
85
|
def build_months
|
@@ -110,7 +110,7 @@ module DOTIW
|
|
110
110
|
if weeks < 0
|
111
111
|
# Convert the last month to a week and add to total
|
112
112
|
months -= 1
|
113
|
-
last_month = largest.advance(:
|
113
|
+
last_month = largest.advance(months: -1)
|
114
114
|
days_in_month = Time.days_in_month(last_month.month, last_month.year)
|
115
115
|
weeks += days_in_month / 7
|
116
116
|
days += days_in_month % 7
|
@@ -118,6 +118,12 @@ module DOTIW
|
|
118
118
|
days -= 7
|
119
119
|
weeks += 1
|
120
120
|
end
|
121
|
+
|
122
|
+
if weeks == -1
|
123
|
+
months -= 1
|
124
|
+
weeks = 4
|
125
|
+
days -= 4
|
126
|
+
end
|
121
127
|
end
|
122
128
|
|
123
129
|
if months < 0
|
@@ -131,9 +137,9 @@ module DOTIW
|
|
131
137
|
output[:weeks] = weeks
|
132
138
|
output[:days] = days
|
133
139
|
|
134
|
-
total_days,
|
140
|
+
total_days, @distance = distance.abs.divmod(1.day.to_i)
|
135
141
|
|
136
|
-
[total_days,
|
142
|
+
[total_days, @distance]
|
137
143
|
end
|
138
144
|
end # TimeHash
|
139
145
|
end # DOTIW
|