dotiw 5.5.0 → 5.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 32864f97b3b06dc77f8c793e11a5b17b1d2af506bdfac0c559354f3e4eef7998
4
- data.tar.gz: 20efed42fb0802a836f078f2791d78e7b863005b73780243ad4867bbf653103e
3
+ metadata.gz: 1b9778f6b8dfb3fa5013f9e365c91be2fcb955f040e19dde50d8f70b1fb28fcd
4
+ data.tar.gz: 5213a53264dd238ea225939d53c40a63523749b92331ef4a899fa3ca190d5bce
5
5
  SHA512:
6
- metadata.gz: 6bb97a0d7bc860b57432dc81cb3d8a7921c6364c5271db648d30984164f26d6f4003e21de28252f958294960df1b0270cc4fbf83ae7602be23096829404ee2fe
7
- data.tar.gz: e2d4f70428937b7e5c409666612dd7aefbe63e71d884a3738b240c222fc2f36a8df7a66b83fa4d52fef96cd27e7edd3a9fdecb15e497fa46853e9b3922f83663
6
+ metadata.gz: 2efac079381c10ed7584f921dbeab2c3b4ae96f63a4b32c470a986a2a521f0a91c94e26871e92bcdcd9718d05d2bb6c41bc051155868b9c3684695b5b662d27e
7
+ data.tar.gz: 1506fe3f4715fd1f85b72d8de416d4dfdff144e02bce2837c741ab82d7928b2fed38e6c08fac19e1a5a0e155a9ec4c947407d1878107e5c15cc7947618f731dd
@@ -9,26 +9,33 @@ jobs:
9
9
  fail-fast: false
10
10
  matrix:
11
11
  include:
12
- - ruby-version: 3.4
13
- - ruby-version: 3.3
14
- - ruby-version: 3.2
15
- - ruby-version: 3.1
16
- - ruby-version: 3.0
17
- - ruby-version: 2.7
18
- - ruby-version: 2.7
12
+ - ruby-version: '4.0'
13
+ - ruby-version: '3.4'
14
+ - ruby-version: '3.3'
15
+ - ruby-version: '3.2'
16
+ - ruby-version: '3.1'
17
+ - ruby-version: '3.0'
18
+ - ruby-version: '2.7'
19
+ - ruby-version: '2.7'
19
20
  gemfile: gemfiles/rails_5.0.gemfile
20
- - ruby-version: 2.7
21
+ - ruby-version: '2.7'
21
22
  gemfile: gemfiles/rails_5.1.gemfile
22
- - ruby-version: 2.7
23
+ - ruby-version: '2.7'
23
24
  gemfile: gemfiles/rails_5.2.gemfile
24
- - ruby-version: 3.4
25
+ - ruby-version: '3.4'
25
26
  gemfile: gemfiles/rails_6.0.gemfile
26
- - ruby-version: 3.4
27
+ - ruby-version: '3.4'
27
28
  gemfile: gemfiles/rails_7.0.gemfile
28
- - ruby-version: 3.4
29
+ - ruby-version: '3.4'
29
30
  gemfile: gemfiles/rails_7.1.gemfile
30
- - ruby-version: 3.4
31
+ - ruby-version: '3.4'
31
32
  gemfile: gemfiles/rails_7.2.gemfile
33
+ - ruby-version: '3.4'
34
+ gemfile: gemfiles/rails_8.0.gemfile
35
+ - ruby-version: '3.4'
36
+ gemfile: gemfiles/rails_8.1.gemfile
37
+ - ruby-version: '3.4'
38
+ gemfile: gemfiles/rails_8.1_i18n.gemfile
32
39
  steps:
33
40
  - uses: actions/checkout@v2
34
41
  - name: Set up Ruby
@@ -44,3 +51,20 @@ jobs:
44
51
  gem install bundler -v ${BUNDLER:-2.1.4}
45
52
  bundle install --jobs 4 --retry 3
46
53
  bundle exec rake
54
+ - name: Upload coverage to Coveralls
55
+ if: matrix.ruby-version == '3.4'
56
+ uses: coverallsapp/github-action@v2
57
+ with:
58
+ github-token: ${{ secrets.GITHUB_TOKEN }}
59
+ flag-name: ruby-${{ matrix.ruby-version }}-${{ matrix.gemfile }}
60
+ parallel: true
61
+
62
+ finish:
63
+ needs: build
64
+ runs-on: ubuntu-latest
65
+ steps:
66
+ - name: Close parallel build
67
+ uses: coverallsapp/github-action@v2
68
+ with:
69
+ github-token: ${{ secrets.GITHUB_TOKEN }}
70
+ parallel-finished: true
data/.gitignore CHANGED
@@ -1,5 +1,6 @@
1
1
  tmp
2
2
  pkg
3
+ coverage
3
4
  spec/fixtures/database.yml
4
5
  .rvmrc
5
6
  Gemfile.lock
data/Appraisals CHANGED
@@ -25,3 +25,16 @@ end
25
25
  appraise 'rails_7.2' do
26
26
  gem 'rails', '~> 7.2.0'
27
27
  end
28
+
29
+ appraise 'rails_8.0' do
30
+ gem 'rails', '~> 8.0.0'
31
+ end
32
+
33
+ appraise 'rails_8.1' do
34
+ gem 'rails', '~> 8.1.0'
35
+ end
36
+
37
+ appraise 'rails_8.1_i18n' do
38
+ gem 'rails', '~> 8.1.0'
39
+ gem 'rails-i18n'
40
+ end
data/CHANGELOG.md CHANGED
@@ -1,12 +1,28 @@
1
- ## 5.4.0 (Next)
1
+ ## 5.6.0 (2026/8/28)
2
+
3
+ * [#145](https://github.com/radar/distance_of_time_in_words/pull/145): Add `dotiw/core` require path to use dotiw without overriding the Rails date helpers, `DOTIW.distance_of_time_in_words` shortcut - [@ermolaev](https://github.com/ermolaev).
4
+ * [#146](https://github.com/radar/distance_of_time_in_words/pull/146): Add support for Rails 8 - [@dblock](https://github.com/dblock).
5
+ * [#147](https://github.com/radar/distance_of_time_in_words/pull/147): Add support for Rails 8.1, drop the `tzinfo ~> 1.2.7` development pin - [@ermolaev](https://github.com/ermolaev).
6
+ * [#148](https://github.com/radar/distance_of_time_in_words/pull/148): Add `max_unit` option to express the entire distance as a single number in one unit - [@dblock](https://github.com/dblock).
7
+ * [#79](https://github.com/radar/distance_of_time_in_words/issues/79): Speed up `distance_of_time_in_words` by avoiding `I18n.with_options`/`ActiveSupport::OptionMerger` allocation for translations, using `I18n.translate` directly instead - [@dblock](https://github.com/dblock).
8
+ * [#77](https://github.com/radar/distance_of_time_in_words/issues/77), [#120](https://github.com/radar/distance_of_time_in_words/issues/120): Fix `only`/`except` discarding the duration of excluded fractions instead of folding them into the next smaller included fraction - [@dblock](https://github.com/dblock).
9
+ * [#63](https://github.com/radar/distance_of_time_in_words/issues/63): Fix incorrect DST correction in timezones with an inverted DST scheme (e.g. Europe/Dublin) by comparing `utc_offset` instead of the unreliable `Time#dst?` - [@dblock](https://github.com/dblock).
10
+ * [#153](https://github.com/radar/distance_of_time_in_words/issues/153): Fix leftover hours/minutes leaking a historical UTC offset change (e.g. Pacific/Norfolk's 2015 offset change) into the result instead of folding it into the calendar distance - [@dblock](https://github.com/dblock).
11
+ * [#44](https://github.com/radar/distance_of_time_in_words/issues/44): Document that `vague: true` combined with a non-English `locale:` requires the host application to load Rails' own translations for that locale - [@dblock](https://github.com/dblock).
12
+ * [#156](https://github.com/radar/distance_of_time_in_words/pull/156): Add Ruby 4.0 to the test matrix - [@dblock](https://github.com/dblock).
13
+ * [#158](https://github.com/radar/distance_of_time_in_words/pull/158): Add test coverage reporting with SimpleCov and Coveralls, merged across all Ruby 3.4 CI jobs (default + pinned Rails gemfiles) using parallel builds - [@dblock](https://github.com/dblock).
14
+
15
+ ## 5.5.0 (2026/03/15)
2
16
 
3
17
  * [#131](https://github.com/radar/distance_of_time_in_words/pull/131): Deprecates `highest_measure_only`. Adds alternate form of `highest_measures` option to permit rounding up whatever part of the duration was previously silently discarded - [@seansfkelley](https://github.com/seansfkelley).
4
18
  * [#133](https://github.com/radar/distance_of_time_in_words/pull/133): Test on Ruby 3.0 and 3.1 - [@dblock](https://github.com/dblock).
5
19
  * [#134](https://github.com/radar/distance_of_time_in_words/pull/134): Add support for Dzongkha, the National language of Bhutan - [@KinWang-2013](https://github.com/KinWang-2013).
6
20
  * [#135](https://github.com/radar/distance_of_time_in_words/pull/135): Add support for Ruby 2.7 and 3.2, removed support for ruby 2.4, 2.5, 2.6 and Rails 4 - [@KinWang-2013](https://github.com/KinWang-2013).
7
21
  * [#136](https://github.com/radar/distance_of_time_in_words/pull/136): Add support for Rails 7 - [@KinWang-2013](https://github.com/KinWang-2013).
8
- * [#139](https://github.com/radar/distance_of_time_in_words/pull/139): Fix bug with pluralization of Russian translations for numbers ending in 1 - [bitberry-dev](https://github.com/bitberry-dev).
9
- * Your contribution here.
22
+ * [#139](https://github.com/radar/distance_of_time_in_words/pull/139): Fix bug with pluralization of Russian translations for numbers ending in 1 - [@bitberry-dev](https://github.com/bitberry-dev).
23
+ * [#140](https://github.com/radar/distance_of_time_in_words/pull/140): Add Ruby 3.3 and 3.4 support, added logger - [@dblock](https://github.com/dblock).
24
+ * [#141](https://github.com/radar/distance_of_time_in_words/pull/141): Fix flaky time calculation test by explicitly setting timezone - [@bitberry-dev](https://github.com/bitberry-dev).
25
+ * Fix bug when accumulating on years - [@radar](https://github.com/radar).
10
26
 
11
27
  ## 5.3.3 (2022/04/25)
12
28
 
data/README.markdown CHANGED
@@ -1,4 +1,4 @@
1
- # dotiw ![Ruby](https://github.com/radar/distance_of_time_in_words/workflows/Ruby/badge.svg)
1
+ # dotiw ![Ruby](https://github.com/radar/distance_of_time_in_words/workflows/Ruby/badge.svg) [![Coverage Status](https://coveralls.io/repos/github/radar/distance_of_time_in_words/badge.svg?branch=master)](https://coveralls.io/github/radar/distance_of_time_in_words?branch=master)
2
2
 
3
3
  The `dotiw` library that adds `distance_of_time_in_words` to any Ruby project, or overrides the default implementation in Rails with more accurate output.
4
4
 
@@ -32,6 +32,23 @@ include ActionView::Helpers::TextHelper
32
32
  include ActionView::Helpers::NumberHelper
33
33
  ```
34
34
 
35
+ ### Rails, without overriding the built-in helpers
36
+
37
+ Requiring `dotiw` replaces the Rails implementation of `distance_of_time_in_words`
38
+ and `time_ago_in_words`. If you would rather keep the built-in Rails helpers and
39
+ call dotiw explicitly, require `dotiw/core` instead:
40
+
41
+ ```ruby
42
+ gem 'dotiw', require: 'dotiw/core'
43
+ ```
44
+
45
+ This loads `DOTIW::Methods` and the dotiw translations, but leaves
46
+ `ActionView::Helpers::DateHelper` untouched:
47
+
48
+ ```ruby
49
+ DOTIW.distance_of_time_in_words(from_time, to_time, highest_measures: 1)
50
+ ```
51
+
35
52
  ## distance\_of\_time\_in\_words
36
53
 
37
54
  Take this for a totally kick-ass example:
@@ -117,6 +134,8 @@ This will also be passed to `to_sentence`.
117
134
 
118
135
  Specify this if you want it to use the old `distance_of_time_in_words`. The value can be anything except `nil` or `false`.
119
136
 
137
+ Note that `:vague` falls back to Rails/ActionView's own `distance_of_time_in_words`, which looks up its translations under `datetime.distance_in_words`. This namespace is provided by Rails core translations (e.g. via the [rails-i18n](https://github.com/svenfuchs/rails-i18n) gem) for non-English locales, not by dotiw. If you combine `:vague` with a non-English `:locale` and see a "translation missing" error, make sure your application also loads the corresponding Rails translations for that locale (see [#44](https://github.com/radar/distance_of_time_in_words/issues/44)).
138
+
120
139
  #### :include_seconds
121
140
 
122
141
  As described above this option is the equivalent to the third argument whether to include seconds.
@@ -130,38 +149,67 @@ Specifies the maximum output unit which will accumulate all the surplus. Say you
130
149
  => "121 minutes and 10 seconds"
131
150
  ```
132
151
 
152
+ Note that units below `accumulate_on` are still shown separately. If you want the entire distance expressed as a single number in one unit, use `:max_unit` instead.
153
+
154
+ #### :max_unit
155
+
156
+ Expresses the entire distance as a single number in the given unit, discarding (flooring) any remainder smaller than that unit. Unlike `:accumulate_on`, no other units are shown in the output.
157
+
158
+ ```ruby
159
+ >> distance_of_time_in_words(Time.now, Time.now + 1.day, true, max_unit: :seconds)
160
+ => "86400 seconds"
161
+
162
+ >> distance_of_time_in_words(Time.now, Time.now + 3.days + 2.hours, true, max_unit: :hours)
163
+ => "74 hours"
164
+
165
+ >> distance_of_time_in_words_hash(Time.now, Time.now + 3.days + 2.hours, max_unit: :hours)
166
+ => {hours: 74}
167
+ ```
168
+
169
+ Valid units are `:seconds`, `:minutes`, `:hours`, `:days`, `:weeks`, `:months` and `:years`.
170
+
133
171
  #### :only
134
172
 
135
173
  Only want a specific measurement of time? No problem!
136
174
 
137
175
  ```ruby
138
176
  >> distance_of_time_in_words(Time.now, Time.now + 1.hour + 1.minute, false, only: :minutes)
139
- => "1 minute"
177
+ => "61 minutes"
140
178
  ```
141
179
 
180
+ Excluded measurements are folded into the next smaller included measurement instead of being
181
+ discarded, so the returned duration is always accurate (1 hour + 1 minute = 61 minutes, not 1
182
+ minute).
183
+
142
184
  You only want some? No problem too!
143
185
 
144
186
  ```ruby
145
187
  >> distance_of_time_in_words(Time.now, Time.now + 1.hour + 1.day + 1.minute, false, only: [:minutes, :hours])
146
- => "1 hour and 1 minute"
188
+ => "25 hours and 1 minute"
147
189
  ```
148
190
 
191
+ Here the excluded day is folded into hours (1 day + 1 hour = 25 hours).
192
+
149
193
  #### :except
150
194
 
151
195
  Don't want a measurement of time? No problem!
152
196
 
153
197
  ```ruby
154
198
  >> distance_of_time_in_words(Time.now, Time.now + 1.hour + 1.minute, false, except: :minutes)
155
- => "1 hour"
199
+ => "1 hour and 60 seconds"
156
200
  ```
157
201
 
202
+ The excluded minute is folded into seconds (1 minute = 60 seconds) rather than discarded.
203
+
158
204
  Culling a whole group of measurements of time:
159
205
 
160
206
  ```ruby
161
207
  >> distance_of_time_in_words(Time.now, Time.now + 1.hour + 1.day + 1.minute, false, except: [:minutes, :hours])
162
- => "1 day"
208
+ => "1 day and 3660 seconds"
163
209
  ```
164
210
 
211
+ The excluded hour and minute cascade down into seconds (1 hour + 1 minute = 3660 seconds).
212
+
165
213
  #### :highest\_measure\_only
166
214
 
167
215
  > **Deprecated**. Use `highest_measures: 1` instead.
data/RELEASING.md ADDED
@@ -0,0 +1,71 @@
1
+ # Releasing DOTIW
2
+
3
+ There are no hard rules about when to release dotiw. Release bug fixes frequently, features not so frequently and breaking API changes rarely.
4
+
5
+ ## Release
6
+
7
+ Run tests, check that all tests succeed locally.
8
+
9
+ ```
10
+ bundle install
11
+ rake
12
+ ```
13
+
14
+ Check that the last build succeeded in [GitHub Actions](https://github.com/radar/distance_of_time_in_words/actions) for all supported platforms.
15
+
16
+ Add a date to this release in [CHANGELOG.md](CHANGELOG.md).
17
+
18
+ ```
19
+ ## 5.5.1 (2026/03/28)
20
+ ```
21
+
22
+ Remove the line with "Your contribution here.", since there will be no more contributions to this release.
23
+
24
+ Commit your changes.
25
+
26
+ ```
27
+ git add CHANGELOG.md lib/dotiw/version.rb
28
+ git commit -m "Preparing for release, 5.5.1."
29
+ git push origin master
30
+ ```
31
+
32
+ Release.
33
+
34
+ ```
35
+ rake release
36
+ ```
37
+
38
+ This builds the gem, creates and pushes a git tag, and pushes the gem to [RubyGems](https://rubygems.org/gems/dotiw).
39
+
40
+ ```
41
+ dotiw 5.5.1 built to pkg/dotiw-5.5.1.gem.
42
+ Tagged v5.5.1.
43
+ Pushed git commits and tags.
44
+ Pushed dotiw 5.5.1 to rubygems.org.
45
+ ```
46
+
47
+ Create a GitHub release for the tag using the `gh` CLI, passing the CHANGELOG entries for this version as the release notes.
48
+
49
+ ```
50
+ gh release create v5.5.1 --title "5.5.1" --notes "$(sed -n '/^## 5.5.1/,/^## /p' CHANGELOG.md | sed '$d')"
51
+ ```
52
+
53
+ ## Prepare for the Next Version
54
+
55
+ Add the next release to [CHANGELOG.md](CHANGELOG.md).
56
+
57
+ ```
58
+ ## 5.5.2 (Next)
59
+
60
+ * Your contribution here.
61
+ ```
62
+
63
+ Increment the patch version number in [lib/dotiw/version.rb](lib/dotiw/version.rb).
64
+
65
+ Commit your changes.
66
+
67
+ ```
68
+ git add CHANGELOG.md lib/dotiw/version.rb
69
+ git commit -m "Preparing for next developer iteration, 5.5.2."
70
+ git push origin master
71
+ ```
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Benchmark for https://github.com/radar/distance_of_time_in_words/issues/79
4
+ #
5
+ # Compares dotiw's distance_of_time_in_words against Rails' original
6
+ # ActionView::Helpers::DateHelper#distance_of_time_in_words, using the
7
+ # scenario from the issue and its follow-up comments.
8
+ #
9
+ # Run with `ruby benchmarks/distance_of_time_in_words_bench.rb` after
10
+ # `gem install benchmark-ips benchmark-memory actionview`.
11
+
12
+ require 'active_support'
13
+ require 'active_support/core_ext'
14
+ require 'benchmark/ips'
15
+ require 'benchmark/memory'
16
+
17
+ require_relative '../lib/dotiw'
18
+
19
+ begin
20
+ require 'action_view'
21
+ rescue LoadError
22
+ warn "This benchmark requires actionview: gem install actionview"
23
+ exit 1
24
+ end
25
+
26
+ include ActionView::Helpers::DateHelper
27
+
28
+ now = Time.now
29
+ future_time = Time.now + 100.years + 30.days + 50.seconds
30
+
31
+ %i[ips memory].each do |type|
32
+ Benchmark.public_send(type) do |x|
33
+ x.report('dotiw') { DOTIW.distance_of_time_in_words(now, future_time) }
34
+ x.report('rails') { distance_of_time_in_words(now, future_time, include_seconds: true) }
35
+
36
+ x.compare!
37
+ end
38
+ end
data/dotiw.gemspec CHANGED
@@ -26,10 +26,10 @@ Gem::Specification.new do |s|
26
26
  s.add_development_dependency 'pry'
27
27
  s.add_development_dependency 'rake'
28
28
  s.add_development_dependency 'rspec', '~> 3.0'
29
- s.add_development_dependency 'tzinfo', '~> 1.2.7'
30
29
  s.add_development_dependency 'mutex_m'
31
30
  s.add_development_dependency 'base64'
32
31
  s.add_development_dependency 'bigdecimal'
32
+ s.add_development_dependency 'simplecov'
33
33
 
34
34
  s.files = `git ls-files`.split("\n")
35
35
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file was generated by Appraisal
4
+
5
+ source 'http://rubygems.org'
6
+
7
+ gem 'rails', '~> 8.0.0'
8
+ gem 'tzinfo', '~> 2.0'
9
+
10
+
11
+ gemspec path: '../'
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file was generated by Appraisal
4
+
5
+ source 'http://rubygems.org'
6
+
7
+ gem 'rails', '~> 8.1.0'
8
+ gem 'tzinfo', '~> 2.0'
9
+
10
+
11
+ gemspec path: '../'
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file was generated by Appraisal
4
+
5
+ source 'http://rubygems.org'
6
+
7
+ gem 'rails', '~> 8.1.0'
8
+ gem 'rails-i18n'
9
+ gem 'tzinfo', '~> 2.0'
10
+
11
+ gemspec path: '../'
data/lib/dotiw/core.rb ADDED
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'i18n'
4
+ require 'logger'
5
+ require 'active_support'
6
+ require 'active_support/core_ext'
7
+
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
+
17
+ extend self
18
+
19
+ DEFAULT_I18N_SCOPE = :'datetime.dotiw'
20
+ DEFAULT_I18N_SCOPE_COMPACT = :'datetime.dotiw_compact'
21
+
22
+ # DOTIW.distance_of_time_in_words(...), DOTIW.time_ago_in_words(...), etc.
23
+ extend Methods
24
+
25
+ def init_i18n!
26
+ I18n.load_path.unshift(*locale_files)
27
+ I18n.reload!
28
+ end
29
+
30
+ def languages
31
+ @languages ||= (locale_files.map { |path| path.split(%r{[/.]})[-2].to_sym })
32
+ end
33
+
34
+ def locale_files
35
+ files 'locale', '*.yml'
36
+ end
37
+
38
+ protected
39
+
40
+ def files(directory, ext)
41
+ Dir[File.join File.dirname(__FILE__), directory, ext]
42
+ end
43
+ end
44
+
45
+ DOTIW.init_i18n!
data/lib/dotiw/methods.rb CHANGED
@@ -102,9 +102,23 @@ module DOTIW
102
102
  options[:except] = Array.wrap(options[:except]).map!(&:to_sym) if options[:except]
103
103
  options[:only] = Array.wrap(options[:only]).map!(&:to_sym) if options[:only]
104
104
 
105
- DOTIW::TimeHash::TIME_FRACTIONS.each do |fraction|
106
- if options[:except]&.include?(fraction) || (options[:only] && !options[:only].include?(fraction))
107
- discarded_hash[fraction] = hash.delete fraction
105
+ if options[:except] || options[:only]
106
+ # Fold excluded fractions into the next smaller fraction instead of discarding their
107
+ # duration outright, e.g. weeks excluded via only: [:years, :months, :days] are converted
108
+ # to days and added to the days count, rather than losing those weeks entirely. Larger
109
+ # fractions are processed first so that a run of consecutive excluded fractions cascades
110
+ # down to the largest fraction that's still included (or is dropped if none remain).
111
+ DOTIW::TimeHash::TIME_FRACTIONS.reverse_each do |fraction|
112
+ next unless hash.key?(fraction)
113
+ next unless options[:except]&.include?(fraction) || (options[:only] && !options[:only].include?(fraction))
114
+
115
+ value = hash.delete(fraction)
116
+ discarded_hash[fraction] = value
117
+
118
+ next_fraction = _next_smaller_fraction(fraction)
119
+ next unless next_fraction
120
+
121
+ hash[next_fraction] = hash.fetch(next_fraction, 0) + (value * ROLLUP_THRESHOLDS[next_fraction])
108
122
  end
109
123
  end
110
124
 
@@ -117,12 +131,13 @@ module DOTIW
117
131
  fractions &= options[:only] if options[:only]
118
132
  fractions -= options[:except] if options[:except]
119
133
 
120
- I18n.with_options locale: options[:locale], scope: i18n_scope do |locale|
121
- # e.g. try to format 'less than 1 days', fallback to '0 days'
122
- return locale.translate :less_than_x,
123
- distance: locale.translate(fractions.first, count: 1),
124
- default: locale.translate(fractions.first, count: 0)
125
- end
134
+ locale = options[:locale]
135
+ # e.g. try to format 'less than 1 days', fallback to '0 days'
136
+ return I18n.translate :less_than_x,
137
+ locale: locale,
138
+ scope: i18n_scope,
139
+ distance: I18n.translate(fractions.first, locale: locale, scope: i18n_scope, count: 1),
140
+ default: I18n.translate(fractions.first, locale: locale, scope: i18n_scope, count: 0)
126
141
  end
127
142
 
128
143
  options.delete(:except)
@@ -137,10 +152,8 @@ module DOTIW
137
152
  _maybe_round! hash, discarded_hash, highest_measures[:remainder]
138
153
  end
139
154
 
140
- phrases = []
141
- I18n.with_options locale: options[:locale], scope: i18n_scope do |locale|
142
- phrases = hash.map { |key, value| locale.t(key, count: value) }
143
- end
155
+ locale = options[:locale]
156
+ phrases = hash.map { |key, value| I18n.translate(key, locale: locale, scope: i18n_scope, count: value) }
144
157
 
145
158
  options[:words_connector] ||= I18n.translate :"#{i18n_scope}.words_connector",
146
159
  default: :'support.array.words_connector',
@@ -152,7 +165,7 @@ module DOTIW
152
165
  default: :'support.array.last_word_connector',
153
166
  locale: options[:locale]
154
167
 
155
- phrases.to_sentence(options.except(:accumulate_on, :compact))
168
+ phrases.to_sentence(options.except(:accumulate_on, :max_unit, :compact))
156
169
  end
157
170
 
158
171
  def _compute_highest_measures!(options)
@@ -192,6 +205,13 @@ module DOTIW
192
205
  end
193
206
  end
194
207
 
208
+ def _next_smaller_fraction(fraction)
209
+ index = DOTIW::TimeHash::TIME_FRACTIONS.index(fraction)
210
+ return nil if index.nil? || index.zero?
211
+
212
+ DOTIW::TimeHash::TIME_FRACTIONS[index - 1]
213
+ end
214
+
195
215
  def _rollup!(hash, smallest_measure_index)
196
216
  DOTIW::TimeHash::TIME_FRACTIONS[smallest_measure_index..-1].each_with_index do |fraction, index|
197
217
  if hash.fetch(fraction, 0) >= ROLLUP_THRESHOLDS[fraction]
@@ -13,15 +13,9 @@ module DOTIW
13
13
  @from_time = from_time || Time.current
14
14
  @to_time = to_time || (@to_time_not_given = true && @from_time + distance.seconds)
15
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?
16
+ @to_time += offset_delta(smallest, largest) if @to_time_not_given
18
17
  @smallest, @largest = [@from_time, @to_time].minmax
19
- @distance ||= begin
20
- d = largest - smallest
21
- d -= 1.hour if smallest.dst? && !largest.dst?
22
- d += 1.hour if !smallest.dst? && largest.dst?
23
- d
24
- end
18
+ @distance ||= (largest - smallest) + offset_delta(smallest, largest)
25
19
 
26
20
  build_time_hash
27
21
  end
@@ -40,8 +34,22 @@ module DOTIW
40
34
  ONE_WEEK = 7.days.freeze
41
35
  FOUR_WEEKS = 28.days.freeze
42
36
 
37
+ # The distance between two Time objects computed via subtraction already
38
+ # reflects any change in UTC offset between them (whether from a DST
39
+ # transition or a permanent tzdata rule change, e.g. Pacific/Norfolk's
40
+ # 2015 UTC offset change), which is what we want when reporting a raw
41
+ # elapsed duration. However, when we split that distance into calendar
42
+ # fields (years/months/weeks/days), we want the offset difference
43
+ # folded away so the leftover hours/minutes/seconds reflect only actual
44
+ # elapsed wall-clock time, not artifacts of an offset shift.
45
+ def offset_delta(smallest, largest)
46
+ largest.utc_offset - smallest.utc_offset
47
+ end
48
+
43
49
  def build_time_hash
44
- if accumulate_on = options[:accumulate_on]
50
+ if (max_unit = options[:max_unit])
51
+ build_max_unit(max_unit.to_sym)
52
+ elsif accumulate_on = options[:accumulate_on]
45
53
  accumulate_on = accumulate_on.to_sym
46
54
  TIME_FRACTIONS.index(accumulate_on).downto(0) { |i| send("build_#{TIME_FRACTIONS[i]}") }
47
55
  else
@@ -86,6 +94,44 @@ module DOTIW
86
94
  output[:weeks], @distance = distance.divmod(ONE_WEEK.to_i) unless output[:weeks]
87
95
  end
88
96
 
97
+ # Expresses the entire distance as a single number in the given unit, discarding
98
+ # (flooring) any remainder smaller than that unit, unlike +accumulate_on+ which keeps
99
+ # showing the smaller units below the target.
100
+ def build_max_unit(unit)
101
+ case unit
102
+ when :seconds
103
+ output[:seconds] = distance.to_i
104
+ when :minutes
105
+ output[:minutes] = (distance / ONE_MINUTE).floor
106
+ when :hours
107
+ output[:hours] = (distance / ONE_HOUR).floor
108
+ when :days
109
+ output[:days] = (distance / ONE_DAY).floor
110
+ when :weeks
111
+ output[:weeks] = (distance / ONE_WEEK).floor
112
+ when :months
113
+ output[:months] = whole_months_between(smallest, largest)
114
+ when :years
115
+ output[:years] = whole_years_between(smallest, largest)
116
+ else
117
+ raise ArgumentError, "unrecognized max_unit #{unit.inspect}"
118
+ end
119
+
120
+ @distance = 0
121
+ end
122
+
123
+ def whole_months_between(from, to)
124
+ months = (to.year - from.year) * 12 + (to.month - from.month)
125
+ months -= 1 if to.advance(months: -months) < from
126
+ months
127
+ end
128
+
129
+ def whole_years_between(from, to)
130
+ years = to.year - from.year
131
+ years -= 1 if to.advance(years: -years) < from
132
+ years
133
+ end
134
+
89
135
  def build_months
90
136
  build_years_months_weeks_days
91
137
 
data/lib/dotiw/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DOTIW
4
- VERSION = '5.5.0'
4
+ VERSION = '5.6.0'
5
5
  end
data/lib/dotiw.rb CHANGED
@@ -1,45 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'i18n'
4
- require 'logger'
5
- require 'active_support'
6
- require 'active_support/core_ext'
7
-
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
-
17
- extend self
18
-
19
- DEFAULT_I18N_SCOPE = :'datetime.dotiw'
20
- DEFAULT_I18N_SCOPE_COMPACT = :'datetime.dotiw_compact'
21
-
22
- def init_i18n!
23
- I18n.load_path.unshift(*locale_files)
24
- I18n.reload!
25
- end
26
-
27
- def languages
28
- @languages ||= (locale_files.map { |path| path.split(%r{[/.]})[-2].to_sym })
29
- end
30
-
31
- def locale_files
32
- files 'dotiw/locale', '*.yml'
33
- end
34
-
35
- protected
36
-
37
- def files(directory, ext)
38
- Dir[File.join File.dirname(__FILE__), directory, ext]
39
- end
40
- end
41
-
42
- DOTIW.init_i18n!
3
+ require_relative 'dotiw/core'
43
4
 
44
5
  begin
45
6
  require 'action_view'
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe DOTIW do
6
+ START_TIME_MODULE = '01-08-2009'.to_time(:utc)
7
+
8
+ before do
9
+ I18n.locale = :en
10
+ end
11
+
12
+ describe '.distance_of_time_in_words' do
13
+ it 'returns the exact distance' do
14
+ expect(
15
+ described_class.distance_of_time_in_words(START_TIME_MODULE, START_TIME_MODULE + 1.hour + 30.minutes)
16
+ ).to eq('1 hour and 30 minutes')
17
+ end
18
+
19
+ it 'accepts options' do
20
+ expect(
21
+ described_class.distance_of_time_in_words(
22
+ START_TIME_MODULE, START_TIME_MODULE + 1.hour + 30.minutes, highest_measures: 1
23
+ )
24
+ ).to eq('1 hour')
25
+ end
26
+ end
27
+
28
+ describe '.time_ago_in_words' do
29
+ it 'returns the distance to now' do
30
+ allow(Time).to receive(:now).and_return(START_TIME_MODULE)
31
+ allow(Time.zone).to receive(:now).and_return(START_TIME_MODULE)
32
+
33
+ expect(
34
+ described_class.time_ago_in_words(START_TIME_MODULE - 90, include_seconds: true)
35
+ ).to eq('1 minute and 30 seconds')
36
+ end
37
+ end
38
+
39
+ describe '.distance_of_time' do
40
+ it 'returns the distance for a number of seconds' do
41
+ expect(described_class.distance_of_time(90)).to eq('1 minute and 30 seconds')
42
+ end
43
+ end
44
+
45
+ describe '.distance_of_time_in_words_hash' do
46
+ it 'returns the distance as a hash' do
47
+ expect(
48
+ described_class.distance_of_time_in_words_hash(START_TIME_MODULE, START_TIME_MODULE + 1.hour + 30.minutes)
49
+ ).to include(hours: 1, minutes: 30)
50
+ end
51
+ end
52
+ end
@@ -17,7 +17,11 @@ describe 'A better distance_of_time_in_words' do
17
17
 
18
18
  before do
19
19
  I18n.locale = :en
20
- ActiveSupport.to_time_preserves_timezone = :zone
20
+ # This config is deprecated as of Rails 8.1 (it becomes the permanent,
21
+ # non-configurable behavior) and is removed entirely in Rails 8.2.
22
+ if Gem::Version.new(ActiveSupport::VERSION::STRING) < Gem::Version.new('8.1')
23
+ ActiveSupport.to_time_preserves_timezone = :zone
24
+ end
21
25
 
22
26
  allow(Time).to receive(:now).and_return(START_TIME)
23
27
  allow(Time.zone).to receive(:now).and_return(START_TIME)
@@ -193,6 +197,63 @@ describe 'A better distance_of_time_in_words' do
193
197
  end
194
198
  end
195
199
 
200
+ context 'in timezones with an inverted DST scheme (#63)' do
201
+ # Real, non-mocked reproduction from #63: running the suite under
202
+ # certain timezones (e.g. Europe/Dublin, where IST is "standard" time
203
+ # and GMT is technically the DST offset) previously failed because
204
+ # Time#dst? disagreed between a Time built via Time.at(datetime) and
205
+ # one built via datetime.to_time for the exact same instant, even
206
+ # though their utc_offset was identical. This caused a spurious +/-1
207
+ # hour "DST correction" to be applied.
208
+ around do |example|
209
+ original_tz = ENV.fetch('TZ', nil)
210
+ ENV['TZ'] = tz
211
+ example.run
212
+ ENV['TZ'] = original_tz
213
+ end
214
+
215
+ %w[
216
+ Europe/Dublin
217
+ Africa/Casablanca
218
+ Asia/Tehran
219
+ Europe/Moscow
220
+ Asia/Gaza
221
+ ].each do |timezone|
222
+ context "TZ=#{timezone}" do
223
+ let(:tz) { timezone }
224
+
225
+ it 'is 1 minute' do
226
+ start = Time.at(DateTime.now)
227
+ finish = DateTime.now + 1.minute
228
+
229
+ expect(distance_of_time_in_words(start, finish)).to eq('1 minute')
230
+ end
231
+ end
232
+ end
233
+ end
234
+
235
+ context 'across a historical UTC offset change (#153)' do
236
+ # Real, non-mocked reproduction from #153: Pacific/Norfolk permanently
237
+ # changed its UTC offset from +11:30 to +11:00 on 2015-10-04 (a
238
+ # tzdata rule change, not a recurring DST transition - both endpoints
239
+ # report dst? == false). The calendar distance (years/months/weeks/
240
+ # days) was correct, but the leftover hours/minutes leaked the 30
241
+ # minute offset difference as a spurious "23 hours and 30 minutes".
242
+ around do |example|
243
+ original_tz = ENV.fetch('TZ', nil)
244
+ ENV['TZ'] = 'Pacific/Norfolk'
245
+ example.run
246
+ ENV['TZ'] = original_tz
247
+ end
248
+
249
+ it 'is 1 year and 2 months, not 1 year, 2 months, 23 hours, and 30 minutes' do
250
+ start = '2015-1-15'.to_time
251
+ finish = '2016-3-15'.to_time
252
+
253
+ expect(distance_of_time_in_words(start, finish, true)).to eq('1 year and 2 months')
254
+ end
255
+ end
256
+
196
257
  describe 'accumulate_on:' do
197
258
  [
198
259
  [START_TIME,
@@ -228,6 +289,55 @@ describe 'A better distance_of_time_in_words' do
228
289
  end
229
290
  end
230
291
 
292
+ describe 'max_unit:' do
293
+ [
294
+ [START_TIME,
295
+ START_TIME + 10.minute,
296
+ :seconds,
297
+ { seconds: 600 },
298
+ '600 seconds'],
299
+ [START_TIME,
300
+ START_TIME + 10.hour + 10.minute + 1.second,
301
+ :minutes,
302
+ { minutes: 610 },
303
+ '610 minutes'],
304
+ [START_TIME,
305
+ START_TIME + 2.day + 10_000.hour + 10.second,
306
+ :hours,
307
+ { hours: 10_048 },
308
+ '10048 hours'],
309
+ [START_TIME,
310
+ START_TIME + 2.day + 10_000.hour + 10.second,
311
+ :days,
312
+ { days: 418 },
313
+ '418 days'],
314
+ [START_TIME,
315
+ START_TIME + 2.day + 10_000.hour + 10.second,
316
+ :weeks,
317
+ { weeks: 59 },
318
+ '59 weeks'],
319
+ ['2015-1-15'.to_time, '2016-3-15'.to_time, :months, { months: 14 }, '14 months'],
320
+ ['2015-1-15'.to_time, '2016-3-15'.to_time, :years, { years: 1 }, '1 year']
321
+ ].each do |start, finish, unit, hash, output|
322
+ it "should be #{output}" do
323
+ expect(distance_of_time_in_words_hash(start, finish, max_unit: unit)).to eq(hash)
324
+ expect(distance_of_time_in_words(start, finish, true, max_unit: unit)).to eq(output)
325
+ end
326
+ end
327
+
328
+ it 'floors and discards any remainder smaller than the unit' do
329
+ expect(
330
+ distance_of_time_in_words_hash(START_TIME, START_TIME + 1.year - 1.day, max_unit: :years)
331
+ ).to eq(years: 0)
332
+ end
333
+
334
+ it 'raises for an unrecognized unit' do
335
+ expect do
336
+ distance_of_time_in_words_hash(START_TIME, START_TIME + 1.day, max_unit: :fortnights)
337
+ end.to raise_error(ArgumentError, /unrecognized max_unit/)
338
+ end
339
+ end
340
+
231
341
  describe 'without finish time' do
232
342
  # A missing finish argument should default to zero, essentially returning
233
343
  # the equivalent of distance_of_time in order to be backwards-compatible
@@ -297,22 +407,28 @@ describe 'A better distance_of_time_in_words' do
297
407
  [START_TIME,
298
408
  START_TIME + 1.year + 2.months + 3.days + 4.hours + 5.minutes + 6.seconds,
299
409
  { except: 'minutes' },
300
- '1 year, 2 months, 3 days, 4 hours, and 6 seconds'],
410
+ # 5 excluded minutes are folded into seconds (5 * 60 + 6 = 306) instead of being discarded.
411
+ '1 year, 2 months, 3 days, 4 hours, and 306 seconds'],
301
412
  [START_TIME,
302
413
  START_TIME + 1.hour + 1.minute,
303
- { except: 'minutes' }, '1 hour'],
414
+ { except: 'minutes' },
415
+ # The excluded minute is folded into seconds (1 * 60 = 60) instead of being discarded.
416
+ '1 hour and 60 seconds'],
304
417
  [START_TIME,
305
418
  START_TIME + 1.hour + 1.day + 1.minute,
306
419
  { except: %w[minutes hours] },
307
- '1 day'],
420
+ # The excluded hour and minute cascade down into seconds (1 * 24 * 60 * 60 + 1 * 60 = 3660).
421
+ '1 day and 3660 seconds'],
308
422
  [START_TIME,
309
423
  START_TIME + 1.hour + 1.day + 1.minute,
310
424
  { only: %w[minutes hours] },
311
- '1 hour and 1 minute'],
425
+ # The excluded day is folded into hours (1 * 24 + 1 = 25) instead of being discarded.
426
+ '25 hours and 1 minute'],
312
427
  [START_TIME,
313
428
  START_TIME + 1.year + 2.months + 3.weeks + 4.days + 5.hours + 6.minutes + 7.seconds,
314
429
  { except: 'minutes' },
315
- '1 year, 2 months, 3 weeks, 4 days, 5 hours, and 7 seconds'],
430
+ # 6 excluded minutes are folded into seconds (6 * 60 + 7 = 367) instead of being discarded.
431
+ '1 year, 2 months, 3 weeks, 4 days, 5 hours, and 367 seconds'],
316
432
  [START_TIME,
317
433
  START_TIME + 1.hour + 2.minutes + 3.seconds,
318
434
  { highest_measure_only: true },
@@ -395,6 +511,34 @@ describe 'A better distance_of_time_in_words' do
395
511
  end.to raise_error(ArgumentError)
396
512
  end
397
513
 
514
+ describe 'folding excluded fractions into included ones (regression for #77 and #120)' do
515
+ it 'folds excluded weeks into days rather than discarding them' do
516
+ from = '2019-01-02'.to_time
517
+ to = '2020-12-28'.to_time
518
+ expect(distance_of_time_in_words(from, to, false, only: %i[years months days]))
519
+ .to eq('1 year, 11 months, and 26 days')
520
+ end
521
+
522
+ it 'folds excluded weeks into days when using except' do
523
+ from = '2019-01-02'.to_time
524
+ to = '2020-12-28'.to_time
525
+ expect(distance_of_time_in_words(from, to, false, except: :weeks))
526
+ .to eq('1 year, 11 months, and 26 days')
527
+ end
528
+
529
+ it 'folds an excluded month into days' do
530
+ expect(distance_of_time_in_words(START_TIME, START_TIME + 1.month + 9.days, false, only: %i[months days]))
531
+ .to eq('1 month and 9 days')
532
+ end
533
+
534
+ it 'converts a negative day count into a week when the day-of-month matches but the hour rolls back' do
535
+ from = Time.new(2024, 5, 1, 10, 0, 0)
536
+ to = Time.new(2024, 6, 1, 9, 0, 0)
537
+ expect(distance_of_time_in_words(from, to, false, only: %i[months weeks days]))
538
+ .to eq('4 weeks and 2 days')
539
+ end
540
+ end
541
+
398
542
  if defined?(ActionView)
399
543
  describe 'ActionView without include seconds argument' do
400
544
  [
@@ -440,13 +584,31 @@ describe 'A better distance_of_time_in_words' do
440
584
  [START_TIME,
441
585
  START_TIME + 1.year + 2.months + 3.weeks + 4.days + 5.hours + 6.minutes + 7.seconds,
442
586
  { vague: nil },
443
- '1 year, 2 months, 3 weeks, 4 days, 5 hours, 6 minutes, and 7 seconds']
587
+ '1 year, 2 months, 3 weeks, 4 days, 5 hours, 6 minutes, and 7 seconds'],
588
+ [START_TIME,
589
+ START_TIME + 1.year + 2.months,
590
+ { vague: true, locale: :en },
591
+ 'about 1 year']
444
592
  ].each do |start, finish, options, output|
445
593
  it "should be #{output}" do
446
594
  expect(distance_of_time_in_words(start, finish, true, options)).to eq(output)
447
595
  end
448
596
  end
449
597
 
598
+ # requires the rails-i18n gem for non-English datetime.distance_in_words translations, see #44
599
+ if defined?(RailsI18n)
600
+ it 'should be rundt 1 år for vague: true, locale: :nb' do
601
+ finish = START_TIME + 1.year + 2.months
602
+ expect(distance_of_time_in_words(START_TIME, finish, true, vague: true, locale: :nb)).to eq('rundt 1 år')
603
+ end
604
+ else
605
+ it 'returns a translation missing message for vague: true, locale: :nb without the rails-i18n gem' do
606
+ finish = START_TIME + 1.year + 2.months
607
+ expect(distance_of_time_in_words(START_TIME, finish, true, vague: true, locale: :nb))
608
+ .to eq('Translation missing: nb.datetime.distance_in_words.about_x_years')
609
+ end
610
+ end
611
+
450
612
  context 'via ActionController::Base.helpers' do
451
613
  it '#distance_of_time_in_words' do
452
614
  end_time = START_TIME + 1.year + 2.months + 3.weeks + 4.days + 5.hours + 6.minutes + 7.seconds
data/spec/spec_helper.rb CHANGED
@@ -3,8 +3,27 @@
3
3
  ROOT_PATH = File.join(File.dirname(__FILE__), '..')
4
4
  $LOAD_PATH.unshift ROOT_PATH unless $LOAD_PATH.include? ROOT_PATH
5
5
 
6
+ require 'simplecov'
7
+ SimpleCov.start do
8
+ add_filter '/spec/'
9
+ end
10
+
11
+ # Ruby 3.4 dropped concurrent-ruby's implicit require of logger, which older
12
+ # Rails versions rely on being already loaded, see https://github.com/rails/rails/issues/54271.
13
+ require 'logger'
14
+ require 'bundler'
15
+ Bundler.require
16
+
6
17
  require 'dotiw'
7
18
 
19
+ # Loads Rails' own datetime.distance_in_words translations for non-English
20
+ # locales, needed by the vague: true option, which falls back to Rails'
21
+ # native distance_of_time_in_words instead of dotiw's own translations, see #44.
22
+ # Only present when the rails-i18n gem is in the Gemfile (see gemfiles/rails_8.1_i18n.gemfile).
23
+ if defined?(RailsI18n)
24
+ I18n.load_path += Dir[File.join(Gem.loaded_specs['rails-i18n'].gem_dir, 'rails', 'locale', '*.yml')]
25
+ end
26
+
8
27
  Time.zone = 'UTC'
9
28
 
10
29
  I18n.locale = :en
metadata CHANGED
@@ -1,14 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dotiw
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.5.0
4
+ version: 5.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Bigg
8
8
  - Lauran Jansen
9
+ autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 1980-01-02 00:00:00.000000000 Z
12
+ date: 2026-08-28 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: activesupport
@@ -109,21 +110,21 @@ dependencies:
109
110
  - !ruby/object:Gem::Version
110
111
  version: '3.0'
111
112
  - !ruby/object:Gem::Dependency
112
- name: tzinfo
113
+ name: mutex_m
113
114
  requirement: !ruby/object:Gem::Requirement
114
115
  requirements:
115
- - - "~>"
116
+ - - ">="
116
117
  - !ruby/object:Gem::Version
117
- version: 1.2.7
118
+ version: '0'
118
119
  type: :development
119
120
  prerelease: false
120
121
  version_requirements: !ruby/object:Gem::Requirement
121
122
  requirements:
122
- - - "~>"
123
+ - - ">="
123
124
  - !ruby/object:Gem::Version
124
- version: 1.2.7
125
+ version: '0'
125
126
  - !ruby/object:Gem::Dependency
126
- name: mutex_m
127
+ name: base64
127
128
  requirement: !ruby/object:Gem::Requirement
128
129
  requirements:
129
130
  - - ">="
@@ -137,7 +138,7 @@ dependencies:
137
138
  - !ruby/object:Gem::Version
138
139
  version: '0'
139
140
  - !ruby/object:Gem::Dependency
140
- name: base64
141
+ name: bigdecimal
141
142
  requirement: !ruby/object:Gem::Requirement
142
143
  requirements:
143
144
  - - ">="
@@ -151,7 +152,7 @@ dependencies:
151
152
  - !ruby/object:Gem::Version
152
153
  version: '0'
153
154
  - !ruby/object:Gem::Dependency
154
- name: bigdecimal
155
+ name: simplecov
155
156
  requirement: !ruby/object:Gem::Requirement
156
157
  requirements:
157
158
  - - ">="
@@ -187,7 +188,9 @@ files:
187
188
  - Gemfile
188
189
  - MIT-LICENSE
189
190
  - README.markdown
191
+ - RELEASING.md
190
192
  - Rakefile
193
+ - benchmarks/distance_of_time_in_words_bench.rb
191
194
  - benchmarks/time_hash_bench.rb
192
195
  - dotiw.gemspec
193
196
  - gemfiles/.bundle/config
@@ -198,8 +201,12 @@ files:
198
201
  - gemfiles/rails_7.0.gemfile
199
202
  - gemfiles/rails_7.1.gemfile
200
203
  - gemfiles/rails_7.2.gemfile
204
+ - gemfiles/rails_8.0.gemfile
205
+ - gemfiles/rails_8.1.gemfile
206
+ - gemfiles/rails_8.1_i18n.gemfile
201
207
  - lib/dotiw.rb
202
208
  - lib/dotiw/action_view/helpers/date_helper.rb
209
+ - lib/dotiw/core.rb
203
210
  - lib/dotiw/locale/ar.yml
204
211
  - lib/dotiw/locale/da.yml
205
212
  - lib/dotiw/locale/de.yml
@@ -223,6 +230,7 @@ files:
223
230
  - lib/dotiw/methods.rb
224
231
  - lib/dotiw/time_hash.rb
225
232
  - lib/dotiw/version.rb
233
+ - spec/lib/dotiw_module_spec.rb
226
234
  - spec/lib/dotiw_spec.rb
227
235
  - spec/lib/i18n/ar.yml
228
236
  - spec/lib/i18n/da.yml
@@ -249,6 +257,7 @@ homepage: https://github.com/radar/distance_of_time_in_words
249
257
  licenses:
250
258
  - MIT
251
259
  metadata: {}
260
+ post_install_message:
252
261
  rdoc_options: []
253
262
  require_paths:
254
263
  - lib
@@ -263,10 +272,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
263
272
  - !ruby/object:Gem::Version
264
273
  version: '0'
265
274
  requirements: []
266
- rubygems_version: 3.6.9
275
+ rubygems_version: 3.5.16
276
+ signing_key:
267
277
  specification_version: 4
268
278
  summary: Better distance_of_time_in_words for Rails
269
279
  test_files:
280
+ - spec/lib/dotiw_module_spec.rb
270
281
  - spec/lib/dotiw_spec.rb
271
282
  - spec/lib/i18n/ar.yml
272
283
  - spec/lib/i18n/da.yml