active_date_range 0.1.0 → 0.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.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +57 -0
- data/.rubocop.yml +0 -1
- data/CHANGELOG.md +52 -1
- data/Gemfile +3 -1
- data/Gemfile.lock +52 -51
- data/Guardfile +5 -3
- data/README.md +27 -1
- data/Rakefile +3 -1
- data/active_date_range.gemspec +8 -5
- data/bin/console +2 -0
- data/lib/active_date_range/active_model_type.rb +13 -0
- data/lib/active_date_range/date_range.rb +102 -27
- data/lib/active_date_range/humanizer.rb +9 -2
- data/lib/active_date_range/i18n.rb +2 -0
- data/lib/active_date_range/locale/en.yml +3 -0
- data/lib/active_date_range/version.rb +1 -1
- data/lib/active_date_range.rb +2 -0
- metadata +26 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 35a7b655ef32c770ab582f7228e59a90091cdba9553de211e1b18d4efbbb016c
|
4
|
+
data.tar.gz: 2f8b685dcaa03b9df69a3d17876023c6e0de5dd8d3bc2f2c0a8208ecffd6b1df
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 292d7bc594e974903c6f73e4edf7b4e3775901f1c970ec54623950d019a8c327ba04e25b0d743c36605df2f514ee752471bd3787d86ff25ece1cdf9903b5b157
|
7
|
+
data.tar.gz: 156323981ba292945aebe705431e86eb5474dcf2adfc1caea88fa9703a60b6f4b164222312ae14710b825b3ed8e2482fdd8545ae839bb3165dcb666a683b8ae0
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# This workflow uses actions that are not certified by GitHub.
|
2
|
+
# They are provided by a third-party and are governed by
|
3
|
+
# separate terms of service, privacy policy, and support
|
4
|
+
# documentation.
|
5
|
+
# This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
|
6
|
+
# For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
|
7
|
+
|
8
|
+
name: Ruby
|
9
|
+
|
10
|
+
on:
|
11
|
+
push:
|
12
|
+
branches: [ main ]
|
13
|
+
pull_request:
|
14
|
+
branches: [ main ]
|
15
|
+
|
16
|
+
jobs:
|
17
|
+
test:
|
18
|
+
runs-on: ubuntu-latest
|
19
|
+
strategy:
|
20
|
+
matrix:
|
21
|
+
ruby-version: ['2.7', '3.0']
|
22
|
+
|
23
|
+
steps:
|
24
|
+
- uses: actions/checkout@v2
|
25
|
+
- name: Set up Ruby
|
26
|
+
# To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
|
27
|
+
# change this to (see https://github.com/ruby/setup-ruby#versioning):
|
28
|
+
# uses: ruby/setup-ruby@v1
|
29
|
+
uses: ruby/setup-ruby@473e4d8fe5dd94ee328fdfca9f8c9c7afc9dae5e
|
30
|
+
with:
|
31
|
+
ruby-version: ${{ matrix.ruby-version }}
|
32
|
+
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
33
|
+
- name: Run tests
|
34
|
+
run: bundle exec rake
|
35
|
+
|
36
|
+
lint:
|
37
|
+
runs-on: ubuntu-latest
|
38
|
+
|
39
|
+
steps:
|
40
|
+
- uses: actions/checkout@v2
|
41
|
+
- name: Set up Ruby 2.7
|
42
|
+
uses: ruby/setup-ruby@v1
|
43
|
+
with:
|
44
|
+
ruby-version: 2.7
|
45
|
+
- name: Cache gems
|
46
|
+
uses: actions/cache@v1
|
47
|
+
with:
|
48
|
+
path: vendor/bundle
|
49
|
+
key: ${{ runner.os }}-rubocop-${{ hashFiles('**/Gemfile.lock') }}
|
50
|
+
restore-keys: |
|
51
|
+
${{ runner.os }}-rubocop-
|
52
|
+
- name: Install gems
|
53
|
+
run: |
|
54
|
+
bundle config path vendor/bundle
|
55
|
+
bundle install --jobs 4 --retry 3
|
56
|
+
- name: Run RuboCop
|
57
|
+
run: bundle exec rubocop --parallel
|
data/.rubocop.yml
CHANGED
@@ -8,7 +8,6 @@ AllCops:
|
|
8
8
|
# RuboCop has a bunch of cops enabled by default. This setting tells RuboCop
|
9
9
|
# to ignore them, so only the ones explicitly set in this file are enabled.
|
10
10
|
DisabledByDefault: true
|
11
|
-
SuggestExtensions: false
|
12
11
|
Exclude:
|
13
12
|
- '**/tmp/**/*'
|
14
13
|
- '**/templates/**/*'
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,55 @@
|
|
1
|
-
|
1
|
+
## 0.3.1
|
2
|
+
|
3
|
+
* Fix issue with `next` not returning a full year when leap years are in the range
|
4
|
+
|
5
|
+
*Edwin Vlieg*
|
6
|
+
|
7
|
+
## 0.3.0
|
8
|
+
|
9
|
+
* `include?` now behaves like `cover?` for better performance
|
10
|
+
|
11
|
+
*Edwin Vlieg*
|
12
|
+
|
13
|
+
* Add intersection support:
|
14
|
+
|
15
|
+
```
|
16
|
+
date_range.intersection(other_date_range) # => DateRange
|
17
|
+
```
|
18
|
+
|
19
|
+
*Edwin Vlieg*
|
20
|
+
|
21
|
+
|
22
|
+
* Add support for boundless ranges:
|
23
|
+
|
24
|
+
```
|
25
|
+
date_range = DateRange.parse('202101..')
|
26
|
+
date_range.boundless? # => true
|
27
|
+
date_range.in_groups_of(:month) # => Enumerator::Lazy
|
28
|
+
Model.where(date: date_range) # => SQL "WHERE date >= 2021-01-01"
|
29
|
+
```
|
30
|
+
|
31
|
+
*Edwin Vlieg*
|
32
|
+
|
33
|
+
* Add ActiveModel type for date range:
|
34
|
+
|
35
|
+
```
|
36
|
+
attribute :period, :date_range
|
37
|
+
```
|
38
|
+
|
39
|
+
*Edwin Vlieg*
|
40
|
+
|
41
|
+
## 0.2.0
|
42
|
+
|
43
|
+
* Add support for weeks:
|
44
|
+
|
45
|
+
- Shorthands for `this_week`, `next_week` and `prev_week`
|
46
|
+
- `full_week?` and `one_week?`
|
47
|
+
- `next` and `previous` now handle weeks correctly
|
48
|
+
- Tests for biweekly calculations
|
49
|
+
|
50
|
+
*Edwin Vlieg*
|
51
|
+
|
52
|
+
## 0.1.0
|
2
53
|
|
3
54
|
* Initial import of the DateRange implementation from the internal implementation in the Moneybird code.
|
4
55
|
|
data/Gemfile
CHANGED
@@ -1,10 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
source "https://rubygems.org"
|
2
4
|
|
3
5
|
# Specify your gem's dependencies in active_date_range.gemspec
|
4
6
|
gemspec
|
5
7
|
|
6
8
|
gem "rake", "~> 12.0"
|
7
|
-
gem "railties", "
|
9
|
+
gem "railties", "> 6.1"
|
8
10
|
gem "minitest", "~> 5.0"
|
9
11
|
gem "guard"
|
10
12
|
gem "guard-minitest"
|
data/Gemfile.lock
CHANGED
@@ -1,76 +1,75 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
active_date_range (0.1
|
5
|
-
activesupport (
|
4
|
+
active_date_range (0.3.1)
|
5
|
+
activesupport (> 6.1)
|
6
6
|
i18n
|
7
7
|
|
8
8
|
GEM
|
9
9
|
remote: https://rubygems.org/
|
10
10
|
specs:
|
11
|
-
actionpack (
|
12
|
-
actionview (=
|
13
|
-
activesupport (=
|
14
|
-
rack (~> 2.0, >= 2.0
|
11
|
+
actionpack (7.0.0.alpha2)
|
12
|
+
actionview (= 7.0.0.alpha2)
|
13
|
+
activesupport (= 7.0.0.alpha2)
|
14
|
+
rack (~> 2.0, >= 2.2.0)
|
15
15
|
rack-test (>= 0.6.3)
|
16
16
|
rails-dom-testing (~> 2.0)
|
17
17
|
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
18
|
-
actionview (
|
19
|
-
activesupport (=
|
18
|
+
actionview (7.0.0.alpha2)
|
19
|
+
activesupport (= 7.0.0.alpha2)
|
20
20
|
builder (~> 3.1)
|
21
21
|
erubi (~> 1.4)
|
22
22
|
rails-dom-testing (~> 2.0)
|
23
23
|
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
24
|
-
|
24
|
+
activemodel (7.0.0.alpha2)
|
25
|
+
activesupport (= 7.0.0.alpha2)
|
26
|
+
activesupport (7.0.0.alpha2)
|
25
27
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
26
28
|
i18n (>= 1.6, < 2)
|
27
29
|
minitest (>= 5.1)
|
28
30
|
tzinfo (~> 2.0)
|
29
|
-
|
30
|
-
ast (2.4.1)
|
31
|
+
ast (2.4.2)
|
31
32
|
builder (3.2.4)
|
32
33
|
coderay (1.1.3)
|
33
|
-
concurrent-ruby (1.1.
|
34
|
+
concurrent-ruby (1.1.9)
|
34
35
|
crass (1.0.6)
|
35
36
|
erubi (1.10.0)
|
36
|
-
ffi (1.
|
37
|
-
formatador (0.
|
38
|
-
guard (2.
|
37
|
+
ffi (1.15.4)
|
38
|
+
formatador (0.3.0)
|
39
|
+
guard (2.18.0)
|
39
40
|
formatador (>= 0.2.4)
|
40
41
|
listen (>= 2.7, < 4.0)
|
41
42
|
lumberjack (>= 1.0.12, < 2.0)
|
42
43
|
nenv (~> 0.1)
|
43
44
|
notiffany (~> 0.0)
|
44
|
-
pry (>= 0.
|
45
|
+
pry (>= 0.13.0)
|
45
46
|
shellany (~> 0.0)
|
46
47
|
thor (>= 0.18.1)
|
47
48
|
guard-compat (1.2.1)
|
48
49
|
guard-minitest (2.4.6)
|
49
50
|
guard-compat (~> 1.2)
|
50
51
|
minitest (>= 3.0)
|
51
|
-
i18n (1.8.
|
52
|
+
i18n (1.8.10)
|
52
53
|
concurrent-ruby (~> 1.0)
|
53
|
-
listen (3.
|
54
|
+
listen (3.7.0)
|
54
55
|
rb-fsevent (~> 0.10, >= 0.10.3)
|
55
56
|
rb-inotify (~> 0.9, >= 0.9.10)
|
56
|
-
loofah (2.
|
57
|
+
loofah (2.12.0)
|
57
58
|
crass (~> 1.0.2)
|
58
59
|
nokogiri (>= 1.5.9)
|
59
60
|
lumberjack (1.2.8)
|
60
61
|
method_source (1.0.0)
|
61
|
-
|
62
|
-
minitest (5.14.2)
|
62
|
+
minitest (5.14.4)
|
63
63
|
nenv (0.3.0)
|
64
|
-
nokogiri (1.
|
65
|
-
mini_portile2 (~> 2.5.0)
|
64
|
+
nokogiri (1.12.5-x86_64-linux)
|
66
65
|
racc (~> 1.4)
|
67
66
|
notiffany (0.1.3)
|
68
67
|
nenv (~> 0.1)
|
69
68
|
shellany (~> 0.0)
|
70
|
-
parallel (1.
|
71
|
-
parser (
|
69
|
+
parallel (1.21.0)
|
70
|
+
parser (3.0.2.0)
|
72
71
|
ast (~> 2.4.1)
|
73
|
-
pry (0.14.
|
72
|
+
pry (0.14.1)
|
74
73
|
coderay (~> 1.1)
|
75
74
|
method_source (~> 1.0)
|
76
75
|
racc (1.5.2)
|
@@ -80,59 +79,61 @@ GEM
|
|
80
79
|
rails-dom-testing (2.0.3)
|
81
80
|
activesupport (>= 4.2.0)
|
82
81
|
nokogiri (>= 1.6)
|
83
|
-
rails-html-sanitizer (1.
|
82
|
+
rails-html-sanitizer (1.4.2)
|
84
83
|
loofah (~> 2.3)
|
85
|
-
railties (
|
86
|
-
actionpack (=
|
87
|
-
activesupport (=
|
84
|
+
railties (7.0.0.alpha2)
|
85
|
+
actionpack (= 7.0.0.alpha2)
|
86
|
+
activesupport (= 7.0.0.alpha2)
|
88
87
|
method_source
|
89
|
-
rake (>= 0.
|
88
|
+
rake (>= 0.13)
|
90
89
|
thor (~> 1.0)
|
90
|
+
zeitwerk (~> 2.5.0.beta3)
|
91
91
|
rainbow (3.0.0)
|
92
92
|
rake (12.3.3)
|
93
|
-
rb-fsevent (0.
|
93
|
+
rb-fsevent (0.11.0)
|
94
94
|
rb-inotify (0.10.1)
|
95
95
|
ffi (~> 1.0)
|
96
|
-
regexp_parser (1.
|
97
|
-
rexml (3.2.
|
98
|
-
rubocop (
|
96
|
+
regexp_parser (2.1.1)
|
97
|
+
rexml (3.2.5)
|
98
|
+
rubocop (1.21.0)
|
99
99
|
parallel (~> 1.10)
|
100
|
-
parser (>=
|
100
|
+
parser (>= 3.0.0.0)
|
101
101
|
rainbow (>= 2.2.2, < 4.0)
|
102
|
-
regexp_parser (>= 1.
|
102
|
+
regexp_parser (>= 1.8, < 3.0)
|
103
103
|
rexml
|
104
|
-
rubocop-ast (>=
|
104
|
+
rubocop-ast (>= 1.9.1, < 2.0)
|
105
105
|
ruby-progressbar (~> 1.7)
|
106
|
-
unicode-display_width (>= 1.4.0, <
|
107
|
-
rubocop-ast (
|
108
|
-
parser (>=
|
106
|
+
unicode-display_width (>= 1.4.0, < 3.0)
|
107
|
+
rubocop-ast (1.11.0)
|
108
|
+
parser (>= 3.0.1.1)
|
109
109
|
rubocop-packaging (0.5.1)
|
110
110
|
rubocop (>= 0.89, < 2.0)
|
111
|
-
rubocop-performance (1.
|
112
|
-
rubocop (>=
|
111
|
+
rubocop-performance (1.11.5)
|
112
|
+
rubocop (>= 1.7.0, < 2.0)
|
113
113
|
rubocop-ast (>= 0.4.0)
|
114
|
-
rubocop-rails (2.
|
114
|
+
rubocop-rails (2.12.2)
|
115
115
|
activesupport (>= 4.2.0)
|
116
116
|
rack (>= 1.1)
|
117
|
-
rubocop (>=
|
118
|
-
ruby-progressbar (1.
|
117
|
+
rubocop (>= 1.7.0, < 2.0)
|
118
|
+
ruby-progressbar (1.11.0)
|
119
119
|
shellany (0.0.1)
|
120
120
|
thor (1.1.0)
|
121
121
|
tzinfo (2.0.4)
|
122
122
|
concurrent-ruby (~> 1.0)
|
123
|
-
unicode-display_width (1.
|
124
|
-
zeitwerk (2.
|
123
|
+
unicode-display_width (2.1.0)
|
124
|
+
zeitwerk (2.5.0.beta3)
|
125
125
|
|
126
126
|
PLATFORMS
|
127
|
-
|
127
|
+
x86_64-linux
|
128
128
|
|
129
129
|
DEPENDENCIES
|
130
130
|
active_date_range!
|
131
|
+
activemodel
|
131
132
|
guard
|
132
133
|
guard-minitest
|
133
134
|
minitest (~> 5.0)
|
134
135
|
pry
|
135
|
-
railties (
|
136
|
+
railties (> 6.1)
|
136
137
|
rake (~> 12.0)
|
137
138
|
rubocop
|
138
139
|
rubocop-packaging
|
@@ -140,4 +141,4 @@ DEPENDENCIES
|
|
140
141
|
rubocop-rails
|
141
142
|
|
142
143
|
BUNDLED WITH
|
143
|
-
2.
|
144
|
+
2.3.5
|
data/Guardfile
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# A sample Guardfile
|
2
4
|
# More info at https://github.com/guard/guard#readme
|
3
5
|
|
@@ -19,11 +21,11 @@ clearing :on
|
|
19
21
|
|
20
22
|
guard :minitest do
|
21
23
|
# with Minitest::Unit
|
22
|
-
watch(%r{^test/(.*)
|
23
|
-
watch(%r{^test/(.*)
|
24
|
+
watch(%r{^test/(.*)/?test_(.*)\.rb$})
|
25
|
+
watch(%r{^test/(.*)/?(.*)_test\.rb$})
|
24
26
|
watch(%r{^lib/(.*/)?([^/]+)\.rb$}) { |m| "test/#{m[1]}test_#{m[2]}.rb" }
|
25
27
|
watch(%r{^lib/(.*/)?([^/]+)\.rb$}) { |m| "test/#{m[1]}#{m[2]}_test.rb" }
|
26
|
-
watch(%r{^test/test_helper\.rb$}) {
|
28
|
+
watch(%r{^test/test_helper\.rb$}) { "test" }
|
27
29
|
|
28
30
|
# with Minitest::Spec
|
29
31
|
# watch(%r{^spec/(.*)_spec\.rb$})
|
data/README.md
CHANGED
@@ -29,18 +29,21 @@ ActiveDateRange::DateRange.new(Date.new(2021, 1, 1), Date.new(2021, 12, 31))
|
|
29
29
|
ActiveDateRange::DateRange.new(Date.new(2021, 1, 1)..Date.new(2021, 12, 31))
|
30
30
|
```
|
31
31
|
|
32
|
-
You can also use shorthands to initialize a range relative to today. Shorthands are available for `this`, `prev` and `next` for the ranges `month`, `quarter` and `
|
32
|
+
You can also use shorthands to initialize a range relative to today. Shorthands are available for `this`, `prev` and `next` for the ranges `month`, `quarter`, `year` and `week`:
|
33
33
|
|
34
34
|
```ruby
|
35
35
|
ActiveDateRange::DateRange.this_month
|
36
36
|
ActiveDateRange::DateRange.this_year
|
37
37
|
ActiveDateRange::DateRange.this_quarter
|
38
|
+
ActiveDateRange::DateRange.this_week
|
38
39
|
ActiveDateRange::DateRange.prev_month
|
39
40
|
ActiveDateRange::DateRange.prev_year
|
40
41
|
ActiveDateRange::DateRange.prev_quarter
|
42
|
+
ActiveDateRange::DateRange.prev_week
|
41
43
|
ActiveDateRange::DateRange.next_month
|
42
44
|
ActiveDateRange::DateRange.next_year
|
43
45
|
ActiveDateRange::DateRange.next_quarter
|
46
|
+
ActiveDateRange::DateRange.next_week
|
44
47
|
```
|
45
48
|
|
46
49
|
The third option is to use parse:
|
@@ -71,6 +74,7 @@ date_range.months # => 12
|
|
71
74
|
date_range.quarters # => 4
|
72
75
|
date_range.years # => 1
|
73
76
|
date_range.one_month? # => false
|
77
|
+
date_range.one_week? # => false
|
74
78
|
date_range.one_year? # => true
|
75
79
|
date_range.full_year? # => true
|
76
80
|
date_range.same_year? # => true
|
@@ -89,6 +93,16 @@ date_range.previous(2) # => DateRange.parse('201901..20
|
|
89
93
|
date_range.next # => DateRange.parse('202201..202212')
|
90
94
|
date_range + DateRange.parse('202201..202202') # => DateRange.parse('202101..202202')
|
91
95
|
date_range.in_groups_of(:month) # => [DateRange.parse('202101..202101'), ..., DateRange.parse('202112..202112')]
|
96
|
+
date_range.intersection(DateRange.parse('202101..202102')) # => DateRange.parse('202101..202102')
|
97
|
+
```
|
98
|
+
|
99
|
+
Support for boundless ranges is also available:
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
date_range = DateRange.parse('202101..')
|
103
|
+
date_range.boundless? # => true
|
104
|
+
date_range.in_groups_of(:month) # => Enumerator::Lazy
|
105
|
+
Model.where(date: date_range) # => SQL "WHERE date >= 2021-01-01"
|
92
106
|
```
|
93
107
|
|
94
108
|
And lastly you can call `.humanize` to get a localizable human representation of the range for in the user interface:
|
@@ -100,6 +114,18 @@ date_range.humanize(format: :explicit) # => 'January 1st, 2021 - December 31st
|
|
100
114
|
|
101
115
|
See [active_date_range/locale/en.yml](https://github.com/moneybird/active-date-range/blob/main/lib/active_date_range/locale/en.yml) for all the I18n keys you need to translate for your application.
|
102
116
|
|
117
|
+
### ActiveModel type
|
118
|
+
|
119
|
+
Date ranges are also available as an ActiveModel type. So you can use a date range attribute and the value will automatically be converted:
|
120
|
+
|
121
|
+
```ruby
|
122
|
+
class Report
|
123
|
+
include ActiveModel::Attributes
|
124
|
+
|
125
|
+
attribute :period, :date_range
|
126
|
+
end
|
127
|
+
```
|
128
|
+
|
103
129
|
### Usage example
|
104
130
|
|
105
131
|
Use the shorthands to link to a specific period:
|
data/Rakefile
CHANGED
data/active_date_range.gemspec
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/active_date_range/version"
|
2
4
|
|
3
5
|
Gem::Specification.new do |spec|
|
4
6
|
spec.name = "active_date_range"
|
@@ -9,26 +11,27 @@ Gem::Specification.new do |spec|
|
|
9
11
|
spec.summary = "DateRange for ActiveSupport"
|
10
12
|
spec.description = "ActiveDateRange provides a range of dates with a powerful API to manipulate and use date ranges in your software."
|
11
13
|
spec.homepage = "https://github.com/moneybird/active-date-range"
|
12
|
-
spec.required_ruby_version = Gem::Requirement.new(">= 2.
|
14
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.7.0")
|
13
15
|
|
14
16
|
spec.metadata["homepage_uri"] = spec.homepage
|
15
17
|
spec.metadata["source_code_uri"] = "https://github.com/moneybird/active-date-range"
|
16
|
-
spec.metadata["changelog_uri"] = "https://github.com/moneybird/active-date-range/CHANGELOG.md"
|
18
|
+
spec.metadata["changelog_uri"] = "https://github.com/moneybird/active-date-range/blob/main/CHANGELOG.md"
|
17
19
|
|
18
20
|
# Specify which files should be added to the gem when it is released.
|
19
21
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
20
|
-
spec.files
|
22
|
+
spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do
|
21
23
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
22
24
|
end
|
23
25
|
spec.bindir = "exe"
|
24
26
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
25
27
|
spec.require_paths = ["lib"]
|
26
28
|
|
27
|
-
spec.add_dependency "activesupport", "
|
29
|
+
spec.add_dependency "activesupport", "> 6.1"
|
28
30
|
spec.add_dependency "i18n"
|
29
31
|
|
30
32
|
spec.add_development_dependency "rubocop"
|
31
33
|
spec.add_development_dependency "rubocop-packaging"
|
32
34
|
spec.add_development_dependency "rubocop-performance"
|
33
35
|
spec.add_development_dependency "rubocop-rails"
|
36
|
+
spec.add_development_dependency "activemodel"
|
34
37
|
end
|
data/bin/console
CHANGED
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveDateRange
|
4
|
+
class DateRangeType < ActiveModel::Type::String
|
5
|
+
def cast(value)
|
6
|
+
ActiveDateRange::DateRange.parse(value)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
if defined?(ActiveModel)
|
12
|
+
ActiveModel::Type.register(:date_range, ActiveDateRange::DateRangeType)
|
13
|
+
end
|
@@ -12,7 +12,10 @@ module ActiveDateRange
|
|
12
12
|
next_quarter: -> { DateRange.new(3.months.from_now.to_date.all_quarter) },
|
13
13
|
this_year: -> { DateRange.new(Time.zone.today.all_year) },
|
14
14
|
prev_year: -> { DateRange.new(12.months.ago.to_date.all_year) },
|
15
|
-
next_year: -> { DateRange.new(12.months.from_now.to_date.all_year) }
|
15
|
+
next_year: -> { DateRange.new(12.months.from_now.to_date.all_year) },
|
16
|
+
this_week: -> { DateRange.new(Time.zone.today.all_week) },
|
17
|
+
prev_week: -> { DateRange.new(1.week.ago.to_date.all_week) },
|
18
|
+
next_week: -> { DateRange.new(1.week.from_now.to_date.all_week) }
|
16
19
|
}.freeze
|
17
20
|
|
18
21
|
RANGE_PART_REGEXP = %r{\A(?<year>((1\d|2\d)\d\d))-?(?<month>0[1-9]|1[012])-?(?<day>[0-2]\d|3[01])?\z}
|
@@ -32,12 +35,14 @@ module ActiveDateRange
|
|
32
35
|
return SHORTHANDS[input.to_sym].call if SHORTHANDS.key?(input.to_sym)
|
33
36
|
|
34
37
|
begin_date, end_date = input.split("..")
|
35
|
-
raise InvalidDateRangeFormat, "#{input} doesn't have a begin..end format"
|
38
|
+
raise InvalidDateRangeFormat, "#{input} doesn't have a begin..end format" if begin_date.blank? && end_date.blank?
|
36
39
|
|
37
40
|
DateRange.new(parse_date(begin_date), parse_date(end_date, last: true))
|
38
41
|
end
|
39
42
|
|
40
43
|
def self.parse_date(input, last: false)
|
44
|
+
return if input.blank?
|
45
|
+
|
41
46
|
match_data = input.match(RANGE_PART_REGEXP)
|
42
47
|
raise InvalidDateRangeFormat, "#{input} isn't a valid date format YYYYMMDD or YYYYMM" unless match_data
|
43
48
|
|
@@ -59,12 +64,13 @@ module ActiveDateRange
|
|
59
64
|
# Make sures the begin date is before the end date.
|
60
65
|
def initialize(begin_date, end_date = nil)
|
61
66
|
begin_date, end_date = begin_date.begin, begin_date.end if begin_date.kind_of?(Range)
|
67
|
+
begin_date, end_date = begin_date.first, begin_date.last if begin_date.kind_of?(Array)
|
62
68
|
begin_date = begin_date.to_date if begin_date.kind_of?(Time)
|
63
69
|
end_date = end_date.to_date if end_date.kind_of?(Time)
|
64
70
|
|
65
|
-
raise InvalidDateRange, "Date range invalid, begin should be a date"
|
66
|
-
raise InvalidDateRange, "Date range invalid, end should be a date"
|
67
|
-
raise InvalidDateRange, "Date range invalid, begin #{begin_date} is after end #{end_date}" if begin_date > end_date
|
71
|
+
raise InvalidDateRange, "Date range invalid, begin should be a date" if begin_date && !begin_date.kind_of?(Date)
|
72
|
+
raise InvalidDateRange, "Date range invalid, end should be a date" if end_date && !end_date.kind_of?(Date)
|
73
|
+
raise InvalidDateRange, "Date range invalid, begin #{begin_date} is after end #{end_date}" if begin_date && end_date && begin_date > end_date
|
68
74
|
|
69
75
|
super(begin_date, end_date)
|
70
76
|
end
|
@@ -81,8 +87,14 @@ module ActiveDateRange
|
|
81
87
|
self.begin <=> other.begin
|
82
88
|
end
|
83
89
|
|
90
|
+
def boundless?
|
91
|
+
self.begin.nil? || self.end.nil?
|
92
|
+
end
|
93
|
+
|
84
94
|
# Returns the number of days in the range
|
85
95
|
def days
|
96
|
+
return if boundless?
|
97
|
+
|
86
98
|
@days ||= (self.end - self.begin).to_i + 1
|
87
99
|
end
|
88
100
|
|
@@ -107,19 +119,31 @@ module ActiveDateRange
|
|
107
119
|
months / 12
|
108
120
|
end
|
109
121
|
|
122
|
+
# Returns the number of weeks on the range or nil when range is no full week
|
123
|
+
def weeks
|
124
|
+
return nil unless full_week?
|
125
|
+
|
126
|
+
days / 7
|
127
|
+
end
|
128
|
+
|
110
129
|
# Returns true when begin of the range is at the beginning of the month
|
111
130
|
def begin_at_beginning_of_month?
|
112
|
-
self.begin.day == 1
|
131
|
+
self.begin.present? && self.begin.day == 1
|
113
132
|
end
|
114
133
|
|
115
134
|
# Returns true when begin of the range is at the beginning of the quarter
|
116
135
|
def begin_at_beginning_of_quarter?
|
117
|
-
begin_at_beginning_of_month? && [1, 4, 7, 10].include?(self.begin.month)
|
136
|
+
self.begin.present? && begin_at_beginning_of_month? && [1, 4, 7, 10].include?(self.begin.month)
|
118
137
|
end
|
119
138
|
|
120
139
|
# Returns true when begin of the range is at the beginning of the year
|
121
140
|
def begin_at_beginning_of_year?
|
122
|
-
begin_at_beginning_of_month? && self.begin.month == 1
|
141
|
+
self.begin.present? && begin_at_beginning_of_month? && self.begin.month == 1
|
142
|
+
end
|
143
|
+
|
144
|
+
# Returns true when begin of the range is at the beginning of the week
|
145
|
+
def begin_at_beginning_of_week?
|
146
|
+
self.begin.present? && self.begin == self.begin.at_beginning_of_week
|
123
147
|
end
|
124
148
|
|
125
149
|
# Returns true when the range is exactly one month long
|
@@ -143,44 +167,57 @@ module ActiveDateRange
|
|
143
167
|
self.end == self.begin.at_end_of_year
|
144
168
|
end
|
145
169
|
|
170
|
+
def one_week?
|
171
|
+
days == 7 &&
|
172
|
+
begin_at_beginning_of_week? &&
|
173
|
+
self.end == self.begin.at_end_of_week
|
174
|
+
end
|
175
|
+
|
146
176
|
# Returns true when the range is exactly one or more months long
|
147
177
|
def full_month?
|
148
|
-
begin_at_beginning_of_month? && self.end == self.end.at_end_of_month
|
178
|
+
begin_at_beginning_of_month? && self.end.present? && self.end == self.end.at_end_of_month
|
149
179
|
end
|
150
180
|
|
151
181
|
alias :full_months? :full_month?
|
152
182
|
|
153
183
|
# Returns true when the range is exactly one or more quarters long
|
154
184
|
def full_quarter?
|
155
|
-
begin_at_beginning_of_quarter? && self.end == self.end.at_end_of_quarter
|
185
|
+
begin_at_beginning_of_quarter? && self.end.present? && self.end == self.end.at_end_of_quarter
|
156
186
|
end
|
157
187
|
|
158
188
|
alias :full_quarters? :full_quarter?
|
159
189
|
|
160
190
|
# Returns true when the range is exactly one or more years long
|
161
191
|
def full_year?
|
162
|
-
begin_at_beginning_of_year? && self.end == self.end.at_end_of_year
|
192
|
+
begin_at_beginning_of_year? && self.end.present? && self.end == self.end.at_end_of_year
|
163
193
|
end
|
164
194
|
|
165
195
|
alias :full_years? :full_year?
|
166
196
|
|
197
|
+
# Returns true when the range is exactly one or more weeks long
|
198
|
+
def full_week?
|
199
|
+
begin_at_beginning_of_week? && self.end.present? && self.end == self.end.at_end_of_week
|
200
|
+
end
|
201
|
+
|
202
|
+
alias :full_weeks? :full_week?
|
203
|
+
|
167
204
|
# Returns true when begin and end are in the same year
|
168
205
|
def same_year?
|
169
|
-
self.begin.year == self.end.year
|
206
|
+
!boundless? && self.begin.year == self.end.year
|
170
207
|
end
|
171
208
|
|
172
209
|
# Returns true when the date range is before the given date. Accepts both a <tt>Date</tt>
|
173
210
|
# and <tt>DateRange</tt> as input.
|
174
211
|
def before?(date)
|
175
212
|
date = date.begin if date.kind_of?(DateRange)
|
176
|
-
self.end.before?(date)
|
213
|
+
self.end.present? && self.end.before?(date)
|
177
214
|
end
|
178
215
|
|
179
216
|
# Returns true when the date range is after the given date. Accepts both a <tt>Date</tt>
|
180
217
|
# and <tt>DateRange</tt> as input.
|
181
218
|
def after?(date)
|
182
219
|
date = date.end if date.kind_of?(DateRange)
|
183
|
-
self.begin.after?(date)
|
220
|
+
self.begin.present? && self.begin.after?(date)
|
184
221
|
end
|
185
222
|
|
186
223
|
# Returns the granularity of the range. Returns either <tt>:year</tt>, <tt>:quarter</tt> or
|
@@ -196,6 +233,8 @@ module ActiveDateRange
|
|
196
233
|
:quarter
|
197
234
|
elsif one_month?
|
198
235
|
:month
|
236
|
+
elsif one_week?
|
237
|
+
:week
|
199
238
|
end
|
200
239
|
end
|
201
240
|
|
@@ -225,7 +264,7 @@ module ActiveDateRange
|
|
225
264
|
relative_param
|
226
265
|
else
|
227
266
|
format = full_month? ? "%Y%m" : "%Y%m%d"
|
228
|
-
"#{self.begin
|
267
|
+
"#{self.begin&.strftime(format)}..#{self.end&.strftime(format)}"
|
229
268
|
end
|
230
269
|
end
|
231
270
|
|
@@ -244,13 +283,19 @@ module ActiveDateRange
|
|
244
283
|
# DateRange.this_month.previous # => DateRange.prev_month
|
245
284
|
# DateRange.this_month.previous(2) # => DateRange.prev_month.previous + DateRange.prev_month
|
246
285
|
def previous(periods = 1)
|
247
|
-
if
|
248
|
-
|
286
|
+
raise BoundlessRangeError, "Can't calculate previous for boundless range" if boundless?
|
287
|
+
|
288
|
+
begin_date = if granularity
|
289
|
+
self.begin - periods.send(granularity)
|
249
290
|
elsif full_month?
|
250
|
-
|
291
|
+
in_groups_of(:month).first.previous(periods * months).begin
|
251
292
|
else
|
252
|
-
|
293
|
+
(self.begin - (periods * days).days)
|
253
294
|
end
|
295
|
+
|
296
|
+
begin_date = begin_date.at_beginning_of_month if full_month?
|
297
|
+
|
298
|
+
DateRange.new(begin_date, self.begin - 1.day)
|
254
299
|
end
|
255
300
|
|
256
301
|
# Returns the period next to the current period. `periods` can be raised to return more
|
@@ -259,11 +304,18 @@ module ActiveDateRange
|
|
259
304
|
# DateRange.this_month.next # => DateRange.next_month
|
260
305
|
# DateRange.this_month.next(2) # => DateRange.next_month + DateRange.next_month.next
|
261
306
|
def next(periods = 1)
|
262
|
-
if
|
263
|
-
|
307
|
+
raise BoundlessRangeError, "Can't calculate next for boundless range" if boundless?
|
308
|
+
|
309
|
+
end_date = if granularity
|
310
|
+
self.end + periods.send(granularity)
|
311
|
+
elsif full_month?
|
312
|
+
in_groups_of(:month).last.next(periods * months).end
|
264
313
|
else
|
265
|
-
|
314
|
+
self.end + (periods * days).days
|
266
315
|
end
|
316
|
+
end_date = end_date.at_end_of_month if full_month?
|
317
|
+
|
318
|
+
DateRange.new(self.end + 1.day, end_date)
|
267
319
|
end
|
268
320
|
|
269
321
|
# Returns an array with date ranges containing full months/quarters/years in the current range.
|
@@ -277,17 +329,40 @@ module ActiveDateRange
|
|
277
329
|
# DateRange.parse("202101..202103").in_groups_of(:month) # => [DateRange.parse("202001..202001"), DateRange.parse("202002..202002"), DateRange.parse("202003..202003")]
|
278
330
|
# DateRange.parse("202101..202106").in_groups_of(:month, amount: 2) # => [DateRange.parse("202001..202002"), DateRange.parse("202003..202004"), DateRange.parse("202005..202006")]
|
279
331
|
def in_groups_of(granularity, amount: 1)
|
280
|
-
raise
|
332
|
+
raise BoundlessRangeError, "Can't group date range without a begin." if self.begin.nil?
|
281
333
|
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
334
|
+
if boundless?
|
335
|
+
grouped_collection(granularity, amount: amount)
|
336
|
+
else
|
337
|
+
grouped_collection(granularity, amount: amount).to_a
|
338
|
+
end
|
286
339
|
end
|
287
340
|
|
288
341
|
# Returns a human readable format for the date range. See DateRange::Humanizer for options.
|
289
342
|
def humanize(format: :short)
|
290
343
|
Humanizer.new(self, format: format).humanize
|
291
344
|
end
|
345
|
+
|
346
|
+
# Returns the intersection of the current and the other date range
|
347
|
+
def intersection(other)
|
348
|
+
intersection = self.to_a.intersection(other.to_a).sort
|
349
|
+
DateRange.new(intersection) if intersection.any?
|
350
|
+
end
|
351
|
+
|
352
|
+
def include?(other)
|
353
|
+
cover?(other)
|
354
|
+
end
|
355
|
+
|
356
|
+
private
|
357
|
+
def grouped_collection(granularity, amount: 1)
|
358
|
+
raise UnknownGranularity, "Unknown granularity #{granularity}. Valid are: month, quarter and year" unless %w[month quarter year].include?(granularity.to_s)
|
359
|
+
|
360
|
+
lazy
|
361
|
+
.chunk { |d| [d.year, d.send(granularity)] }
|
362
|
+
.map { |_, group| DateRange.new(group.first..group.last) }
|
363
|
+
.with_index
|
364
|
+
.slice_before { |_, index| index % amount == 0 }
|
365
|
+
.map { |group| group.map(&:first).inject(:+) }
|
366
|
+
end
|
292
367
|
end
|
293
368
|
end
|
@@ -108,12 +108,19 @@ module ActiveDateRange
|
|
108
108
|
month_format = date_range.full_month? ? "month" : "day_month"
|
109
109
|
abbr = "abbr_" if date_range.same_year?
|
110
110
|
|
111
|
+
begin_formatted = I18n.localize(date_range.begin, format: :"#{abbr}#{format}_#{month_format}") if date_range.begin
|
112
|
+
end_formatted = I18n.localize(date_range.end, format: :"#{format}_#{month_format}") if date_range.end
|
113
|
+
|
111
114
|
range(
|
112
|
-
|
113
|
-
|
115
|
+
begin_formatted || infinite,
|
116
|
+
end_formatted || infinite
|
114
117
|
)
|
115
118
|
end
|
116
119
|
|
120
|
+
def infinite
|
121
|
+
"∞"
|
122
|
+
end
|
123
|
+
|
117
124
|
def range(range_begin, range_end)
|
118
125
|
I18n.translate(:range, scope: :date, begin: range_begin, end: range_end)
|
119
126
|
end
|
@@ -25,12 +25,15 @@ en:
|
|
25
25
|
relative_range:
|
26
26
|
next_month: "next month"
|
27
27
|
next_quarter: "next quarter"
|
28
|
+
next_week: "next week"
|
28
29
|
next_year: "next year"
|
29
30
|
prev_month: "the previous month"
|
30
31
|
prev_quarter: "the previous quarter"
|
32
|
+
prev_week: "the previous week"
|
31
33
|
prev_year: "the previous year"
|
32
34
|
this_month: "this month"
|
33
35
|
this_quarter: "this quarter"
|
36
|
+
this_week: "this week"
|
34
37
|
this_year: "this year"
|
35
38
|
today: "today"
|
36
39
|
long_quarter: "quarter %{quarter}"
|
data/lib/active_date_range.rb
CHANGED
@@ -11,6 +11,7 @@ require "active_date_range/core_ext/date"
|
|
11
11
|
require "active_date_range/version"
|
12
12
|
require "active_date_range/date_range"
|
13
13
|
require "active_date_range/humanizer"
|
14
|
+
require "active_date_range/active_model_type"
|
14
15
|
|
15
16
|
module ActiveDateRange
|
16
17
|
class Error < StandardError; end
|
@@ -18,4 +19,5 @@ module ActiveDateRange
|
|
18
19
|
class InvalidAddition < Error; end
|
19
20
|
class InvalidDateRangeFormat < Error; end
|
20
21
|
class UnknownGranularity < Error; end
|
22
|
+
class BoundlessRangeError < Error; end
|
21
23
|
end
|
metadata
CHANGED
@@ -1,27 +1,27 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_date_range
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1
|
4
|
+
version: 0.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Edwin Vlieg
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-02-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">"
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '6.1'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - ">"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '6.1'
|
27
27
|
- !ruby/object:Gem::Dependency
|
@@ -94,6 +94,20 @@ dependencies:
|
|
94
94
|
- - ">="
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: activemodel
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
97
111
|
description: ActiveDateRange provides a range of dates with a powerful API to manipulate
|
98
112
|
and use date ranges in your software.
|
99
113
|
email:
|
@@ -102,6 +116,7 @@ executables: []
|
|
102
116
|
extensions: []
|
103
117
|
extra_rdoc_files: []
|
104
118
|
files:
|
119
|
+
- ".github/workflows/ruby.yml"
|
105
120
|
- ".gitignore"
|
106
121
|
- ".rubocop.yml"
|
107
122
|
- ".travis.yml"
|
@@ -116,6 +131,7 @@ files:
|
|
116
131
|
- bin/console
|
117
132
|
- bin/setup
|
118
133
|
- lib/active_date_range.rb
|
134
|
+
- lib/active_date_range/active_model_type.rb
|
119
135
|
- lib/active_date_range/core_ext/date.rb
|
120
136
|
- lib/active_date_range/core_ext/integer.rb
|
121
137
|
- lib/active_date_range/date_range.rb
|
@@ -128,8 +144,8 @@ licenses: []
|
|
128
144
|
metadata:
|
129
145
|
homepage_uri: https://github.com/moneybird/active-date-range
|
130
146
|
source_code_uri: https://github.com/moneybird/active-date-range
|
131
|
-
changelog_uri: https://github.com/moneybird/active-date-range/CHANGELOG.md
|
132
|
-
post_install_message:
|
147
|
+
changelog_uri: https://github.com/moneybird/active-date-range/blob/main/CHANGELOG.md
|
148
|
+
post_install_message:
|
133
149
|
rdoc_options: []
|
134
150
|
require_paths:
|
135
151
|
- lib
|
@@ -137,15 +153,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
137
153
|
requirements:
|
138
154
|
- - ">="
|
139
155
|
- !ruby/object:Gem::Version
|
140
|
-
version: 2.
|
156
|
+
version: 2.7.0
|
141
157
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
142
158
|
requirements:
|
143
159
|
- - ">="
|
144
160
|
- !ruby/object:Gem::Version
|
145
161
|
version: '0'
|
146
162
|
requirements: []
|
147
|
-
rubygems_version: 3.2.
|
148
|
-
signing_key:
|
163
|
+
rubygems_version: 3.2.9
|
164
|
+
signing_key:
|
149
165
|
specification_version: 4
|
150
166
|
summary: DateRange for ActiveSupport
|
151
167
|
test_files: []
|