dotiw 4.0.1 → 5.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +45 -0
  3. data/.rspec +2 -0
  4. data/Appraisals +19 -0
  5. data/CHANGELOG.md +42 -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 +84 -26
  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 +23 -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 +23 -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 +304 -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 +76 -30
  63. data/.travis.yml +0 -27
  64. data/lib/dotiw/action_view_ext/helpers/date_helper.rb +0 -103
@@ -37,3 +37,40 @@ ru:
37
37
  many: "%{count} лет"
38
38
  other: "%{count} года"
39
39
  less_than_x: "меньше, чем %{distance}"
40
+ dotiw_compact:
41
+ seconds:
42
+ one: 1с
43
+ other: "%{count}с"
44
+ minutes:
45
+ one: 1м
46
+ other: "%{count}м"
47
+ hours:
48
+ one: 1ч
49
+ other: "%{count}ч"
50
+ days:
51
+ one: 1д
52
+ other: "%{count}д"
53
+ weeks:
54
+ one: 1н
55
+ other: "%{count}н"
56
+ months:
57
+ one: 1ме
58
+ other: "%{count}ме"
59
+ years:
60
+ one: "%{count}г"
61
+ few: "%{count}г"
62
+ many: "%{count}л"
63
+ other: "%{count}г"
64
+ less_than_x: "<%{distance}"
65
+ words_connector: ""
66
+ two_words_connector: ""
67
+ last_word_connector: ""
68
+ about_x_years:
69
+ one: ~1г
70
+ any: "~%{count}г"
71
+ over_x_years:
72
+ one: ">1г"
73
+ other: ">%{count}г"
74
+ almost_x_years:
75
+ one: ~1г
76
+ any: "~%{count}г"
@@ -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}"
@@ -23,3 +23,26 @@ zh-CN:
23
23
  one: 1 年
24
24
  other: "%{count} 年"
25
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, :compact))
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