active_date_range 0.1.0 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +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: []
|