dotiw 4.0.0 → 5.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ruby.yml +45 -0
  3. data/.rspec +2 -0
  4. data/Appraisals +19 -0
  5. data/CHANGELOG.md +38 -6
  6. data/Gemfile +2 -0
  7. data/MIT-LICENSE +1 -1
  8. data/README.markdown +151 -63
  9. data/Rakefile +3 -1
  10. data/benchmarks/time_hash_bench.rb +19 -0
  11. data/dotiw.gemspec +17 -19
  12. data/gemfiles/rails_4.gemfile +5 -3
  13. data/gemfiles/rails_5.0.gemfile +5 -3
  14. data/gemfiles/rails_5.1.gemfile +5 -3
  15. data/gemfiles/rails_5.2.gemfile +9 -0
  16. data/gemfiles/rails_6.0.gemfile +9 -0
  17. data/lib/dotiw.rb +33 -14
  18. data/lib/dotiw/action_view/helpers/date_helper.rb +32 -0
  19. data/lib/dotiw/locale/ar.yml +104 -0
  20. data/lib/dotiw/locale/da.yml +35 -0
  21. data/lib/dotiw/locale/de.yml +36 -1
  22. data/lib/dotiw/locale/en.yml +35 -0
  23. data/lib/dotiw/locale/es.yml +36 -1
  24. data/lib/dotiw/locale/fr.yml +35 -0
  25. data/lib/dotiw/locale/id.yml +35 -0
  26. data/lib/dotiw/locale/it.yml +35 -0
  27. data/lib/dotiw/locale/ja.yml +35 -0
  28. data/lib/dotiw/locale/ko.yml +48 -0
  29. data/lib/dotiw/locale/nb.yml +36 -1
  30. data/lib/dotiw/locale/nl.yml +35 -0
  31. data/lib/dotiw/locale/pl.yml +42 -0
  32. data/lib/dotiw/locale/pt-BR.yml +35 -0
  33. data/lib/dotiw/locale/ru.yml +37 -0
  34. data/lib/dotiw/locale/sv.yml +48 -0
  35. data/lib/dotiw/locale/vi.yml +48 -0
  36. data/lib/dotiw/locale/zh-CN.yml +48 -0
  37. data/lib/dotiw/locale/zh-TW.yml +60 -0
  38. data/lib/dotiw/methods.rb +108 -0
  39. data/lib/dotiw/time_hash.rb +52 -40
  40. data/lib/dotiw/version.rb +2 -2
  41. data/spec/lib/dotiw_spec.rb +303 -159
  42. data/spec/lib/i18n/ar.yml +2 -0
  43. data/spec/lib/i18n/da.yml +2 -0
  44. data/spec/lib/i18n/de.yml +2 -0
  45. data/spec/lib/i18n/en.yml +2 -0
  46. data/spec/lib/i18n/es.yml +3 -0
  47. data/spec/lib/i18n/fr.yml +2 -0
  48. data/spec/lib/i18n/id.yml +2 -0
  49. data/spec/lib/i18n/it.yml +3 -0
  50. data/spec/lib/i18n/ja.yml +2 -0
  51. data/spec/lib/i18n/ko.yml +2 -0
  52. data/spec/lib/i18n/nb.yml +2 -0
  53. data/spec/lib/i18n/nl.yml +2 -0
  54. data/spec/lib/i18n/pl.yml +2 -0
  55. data/spec/lib/i18n/pt-BR.yml +2 -0
  56. data/spec/lib/i18n/ru.yml +5 -0
  57. data/spec/lib/i18n/sv.yml +2 -0
  58. data/spec/lib/i18n/vi.yml +22 -0
  59. data/spec/lib/i18n/zh-CN.yml +2 -0
  60. data/spec/lib/i18n/zh-TW.yml +2 -0
  61. data/spec/spec_helper.rb +2 -9
  62. metadata +79 -31
  63. data/.travis.yml +0 -27
  64. data/lib/dotiw/action_view_ext/helpers/date_helper.rb +0 -103
@@ -0,0 +1,48 @@
1
+ sv:
2
+ datetime:
3
+ dotiw:
4
+ seconds:
5
+ one: 1 sekund
6
+ other: "%{count} sekunder"
7
+ minutes:
8
+ one: 1 minut
9
+ other: "%{count} minuter"
10
+ hours:
11
+ one: 1 timme
12
+ other: "%{count} timmar"
13
+ days:
14
+ one: 1 dag
15
+ other: "%{count} dagar"
16
+ weeks:
17
+ one: 1 vecka
18
+ other: "%{count} veckor"
19
+ months:
20
+ one: 1 månad
21
+ other: "%{count} månader"
22
+ years:
23
+ one: 1 år
24
+ other: "%{count} år"
25
+ less_than_x: "mindre än %{distance}"
26
+ dotiw_compact:
27
+ seconds:
28
+ one: 1s
29
+ other: "%{count}s"
30
+ minutes:
31
+ one: 1m
32
+ other: "%{count}m"
33
+ hours:
34
+ one: 1t
35
+ other: "%{count}t"
36
+ days:
37
+ one: 1d
38
+ other: "%{count}d"
39
+ weeks:
40
+ one: 1v
41
+ other: "%{count}v"
42
+ months:
43
+ one: 1m
44
+ other: "%{count}m"
45
+ years:
46
+ one: 1å
47
+ other: "%{count}å"
48
+ less_than_x: "<%{distance}"
@@ -0,0 +1,48 @@
1
+ vi:
2
+ datetime:
3
+ dotiw:
4
+ seconds:
5
+ one: 1 giây
6
+ other: "%{count} giây"
7
+ minutes:
8
+ one: 1 phút
9
+ other: "%{count} phút"
10
+ hours:
11
+ one: 1 giờ
12
+ other: "%{count} giờ"
13
+ days:
14
+ one: 1 ngày
15
+ other: "%{count} ngày"
16
+ weeks:
17
+ one: 1 tuần
18
+ other: "%{count} tuần"
19
+ months:
20
+ one: 1 tháng
21
+ other: "%{count} tháng"
22
+ years:
23
+ one: 1 năm
24
+ other: "%{count} năm"
25
+ less_than_x: "ít hơn %{distance}"
26
+ dotiw_compact:
27
+ seconds:
28
+ one: 1 giây
29
+ other: "%{count}giây"
30
+ minutes:
31
+ one: 1 phút
32
+ other: "%{count}phút"
33
+ hours:
34
+ one: 1 giờ
35
+ other: "%{count}giờ"
36
+ days:
37
+ one: 1 ngày
38
+ other: "%{count}ngày"
39
+ weeks:
40
+ one: 1 tuần
41
+ other: "%{count}tuần"
42
+ months:
43
+ one: 1 tháng
44
+ other: "%{count}tháng"
45
+ years:
46
+ one: 1 năm
47
+ other: "%{count}năm"
48
+ less_than_x: "< %{distance}"
@@ -0,0 +1,48 @@
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}"
26
+ dotiw_compact:
27
+ seconds:
28
+ one: 1秒
29
+ other: "%{count}秒"
30
+ minutes:
31
+ one: 1分钟
32
+ other: "%{count}分钟"
33
+ hours:
34
+ one: 1小时
35
+ other: "%{count}小时"
36
+ days:
37
+ one: 1天
38
+ other: "%{count}天"
39
+ weeks:
40
+ one: 1周
41
+ other: "%{count}周"
42
+ months:
43
+ one: 1月
44
+ other: "%{count}月"
45
+ years:
46
+ one: 1年
47
+ other: "%{count}年"
48
+ less_than_x: "小于 %{distance}"
@@ -0,0 +1,60 @@
1
+ zh-TW:
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}"
26
+ dotiw_compact:
27
+ seconds:
28
+ one: 1秒
29
+ other: "%{count}秒"
30
+ minutes:
31
+ one: 1分鐘
32
+ other: "%{count}分鐘"
33
+ hours:
34
+ one: 1小時
35
+ other: "%{count}小時"
36
+ days:
37
+ one: 1天
38
+ other: "%{count}天"
39
+ weeks:
40
+ one: 1週
41
+ other: "%{count}週"
42
+ months:
43
+ one: 1月
44
+ other: "%{count}月"
45
+ years:
46
+ one: 1年
47
+ other: "%{count}年"
48
+ less_than_x: "小於%{distance}"
49
+ words_connector: ""
50
+ two_words_connector: ""
51
+ last_word_connector: ""
52
+ about_x_years:
53
+ one: 約1年
54
+ any: "約%{count}年"
55
+ over_x_years:
56
+ one: "大於1年"
57
+ other: "大於%{count}年"
58
+ almost_x_years:
59
+ one: 將近1年
60
+ any: "將近%{count}年"
@@ -0,0 +1,108 @@
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 = options_with_scope(options).reverse_merge(
16
+ include_seconds: true
17
+ )
18
+ options.delete(:compact)
19
+ _display_time_in_words DOTIW::TimeHash.new(seconds, nil, nil, options).to_hash, options
20
+ end
21
+
22
+ def distance_of_time_in_words(from_time, to_time = 0, include_seconds_or_options = {}, options = {})
23
+ raise ArgumentError, "nil can't be converted to a Time value" if from_time.nil? || to_time.nil?
24
+
25
+ if include_seconds_or_options.is_a?(Hash)
26
+ options = include_seconds_or_options
27
+ else
28
+ options = options.dup
29
+ options[:include_seconds] ||= !!include_seconds_or_options
30
+ end
31
+ return distance_of_time(from_time, options) if to_time == 0
32
+
33
+ options = options_with_scope(options)
34
+ hash = distance_of_time_in_words_hash(from_time, to_time, options)
35
+ _display_time_in_words(hash, options)
36
+ end
37
+
38
+ def time_ago_in_words(from_time, include_seconds_or_options = {})
39
+ distance_of_time_in_words(from_time, Time.current, include_seconds_or_options)
40
+ end
41
+
42
+ private
43
+
44
+ def options_with_scope(options)
45
+ if options.key?(:compact)
46
+ options.merge(scope: DOTIW::DEFAULT_I18N_SCOPE_COMPACT)
47
+ else
48
+ options
49
+ end
50
+ end
51
+
52
+ def _display_time_in_words(hash, options = {})
53
+ options = options.reverse_merge(
54
+ include_seconds: false
55
+ ).symbolize_keys!
56
+
57
+ include_seconds = options.delete(:include_seconds)
58
+ hash.delete(:seconds) if !include_seconds && hash[:minutes]
59
+
60
+ options[:except] = Array.wrap(options[:except]).map!(&:to_sym) if options[:except]
61
+ options[:only] = Array.wrap(options[:only]).map!(&:to_sym) if options[:only]
62
+
63
+ # Remove all the values that are nil or excluded. Keep the required ones.
64
+ hash.delete_if do |key, value|
65
+ value.nil? || value.zero? ||
66
+ options[:except]&.include?(key) ||
67
+ (options[:only] && !options[:only].include?(key))
68
+ end
69
+
70
+ i18n_scope = options.delete(:scope) || DOTIW::DEFAULT_I18N_SCOPE
71
+ if hash.empty?
72
+ fractions = DOTIW::TimeHash::TIME_FRACTIONS
73
+ fractions &= options[:only] if options[:only]
74
+ fractions -= options[:except] if options[:except]
75
+
76
+ I18n.with_options locale: options[:locale], scope: i18n_scope do |locale|
77
+ # e.g. try to format 'less than 1 days', fallback to '0 days'
78
+ return locale.translate :less_than_x,
79
+ distance: locale.translate(fractions.first, count: 1),
80
+ default: locale.translate(fractions.first, count: 0)
81
+ end
82
+ end
83
+
84
+ output = []
85
+ I18n.with_options locale: options[:locale], scope: i18n_scope do |locale|
86
+ output = hash.map { |key, value| locale.t(key, count: value) }
87
+ end
88
+
89
+ options.delete(:except)
90
+ options.delete(:only)
91
+ highest_measures = options.delete(:highest_measures)
92
+ highest_measures = 1 if options.delete(:highest_measure_only)
93
+ output = output[0...highest_measures] if highest_measures
94
+
95
+ options[:words_connector] ||= I18n.translate :"#{i18n_scope}.words_connector",
96
+ default: :'support.array.words_connector',
97
+ locale: options[:locale]
98
+ options[:two_words_connector] ||= I18n.translate :"#{i18n_scope}.two_words_connector",
99
+ default: :'support.array.two_words_connector',
100
+ locale: options[:locale]
101
+ options[:last_word_connector] ||= I18n.translate :"#{i18n_scope}.last_word_connector",
102
+ default: :'support.array.last_word_connector',
103
+ locale: options[:locale]
104
+
105
+ output.to_sentence(options.except(:accumulate_on))
106
+ end
107
+ end
108
+ end
@@ -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, :weeks, :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.current
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.dup
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,27 +30,33 @@ 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
36
+
37
+ ONE_MINUTE = 1.minute.freeze
38
+ ONE_HOUR = 1.hour.freeze
39
+ ONE_DAY = 1.day.freeze
40
+ ONE_WEEK = 7.days.freeze
41
+ FOUR_WEEKS = 28.days.freeze
35
42
 
36
43
  def build_time_hash
37
- if accumulate_on = options.delete(:accumulate_on)
44
+ if accumulate_on = options[:accumulate_on]
38
45
  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]}") }
46
+ return build_time_hash if accumulate_on == :years
47
+
48
+ TIME_FRACTIONS.index(accumulate_on).downto(0) { |i| send("build_#{TIME_FRACTIONS[i]}") }
43
49
  else
44
50
  while distance > 0
45
- if distance < 1.minute
51
+ if distance < ONE_MINUTE
46
52
  build_seconds
47
- elsif distance < 1.hour
53
+ elsif distance < ONE_HOUR
48
54
  build_minutes
49
- elsif distance < 1.day
55
+ elsif distance < ONE_DAY
50
56
  build_hours
51
- elsif distance < 7.days
57
+ elsif distance < ONE_WEEK
52
58
  build_days
53
- elsif distance < 28.days
59
+ elsif distance < FOUR_WEEKS
54
60
  build_weeks
55
61
  else # greater than a week
56
62
  build_years_months_weeks_days
@@ -63,23 +69,23 @@ module DOTIW
63
69
 
64
70
  def build_seconds
65
71
  output[:seconds] = distance.to_i
66
- self.distance = 0
72
+ @distance = 0
67
73
  end
68
74
 
69
75
  def build_minutes
70
- output[:minutes], self.distance = distance.divmod(1.minute.to_i)
76
+ output[:minutes], @distance = distance.divmod(ONE_MINUTE.to_i)
71
77
  end
72
78
 
73
79
  def build_hours
74
- output[:hours], self.distance = distance.divmod(1.hour.to_i)
80
+ output[:hours], @distance = distance.divmod(ONE_HOUR.to_i)
75
81
  end
76
82
 
77
83
  def build_days
78
- output[:days], self.distance = distance.divmod(1.day.to_i) if output[:days].nil?
84
+ output[:days], @distance = distance.divmod(ONE_DAY.to_i) unless output[:days]
79
85
  end
80
86
 
81
87
  def build_weeks
82
- output[:weeks], self.distance = distance.divmod(1.week.to_i) if output[:weeks].nil?
88
+ output[:weeks], @distance = distance.divmod(ONE_WEEK.to_i) unless output[:weeks]
83
89
  end
84
90
 
85
91
  def build_months
@@ -110,7 +116,7 @@ module DOTIW
110
116
  if weeks < 0
111
117
  # Convert the last month to a week and add to total
112
118
  months -= 1
113
- last_month = largest.advance(:months => -1)
119
+ last_month = largest.advance(months: -1)
114
120
  days_in_month = Time.days_in_month(last_month.month, last_month.year)
115
121
  weeks += days_in_month / 7
116
122
  days += days_in_month % 7
@@ -118,6 +124,12 @@ module DOTIW
118
124
  days -= 7
119
125
  weeks += 1
120
126
  end
127
+
128
+ if weeks == -1
129
+ months -= 1
130
+ weeks = 4
131
+ days -= 4
132
+ end
121
133
  end
122
134
 
123
135
  if months < 0
@@ -131,9 +143,9 @@ module DOTIW
131
143
  output[:weeks] = weeks
132
144
  output[:days] = days
133
145
 
134
- total_days, self.distance = distance.abs.divmod(1.day.to_i)
146
+ total_days, @distance = distance.abs.divmod(ONE_DAY.to_i)
135
147
 
136
- [total_days, self.distance]
148
+ [total_days, @distance]
137
149
  end
138
- end # TimeHash
139
- end # DOTIW
150
+ end
151
+ end