active_period 6.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/Gemfile +5 -0
  4. data/Gemfile.lock +36 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +342 -0
  7. data/Rakefile +10 -0
  8. data/active_period.gemspec +44 -0
  9. data/bin/console +14 -0
  10. data/bin/setup +8 -0
  11. data/config/locales/en.yml +36 -0
  12. data/config/locales/fr.yml +59 -0
  13. data/lib/.DS_Store +0 -0
  14. data/lib/active_period/belongs_to/month.rb +12 -0
  15. data/lib/active_period/belongs_to/quarter.rb +12 -0
  16. data/lib/active_period/belongs_to/week.rb +12 -0
  17. data/lib/active_period/belongs_to/year.rb +12 -0
  18. data/lib/active_period/belongs_to.rb +7 -0
  19. data/lib/active_period/collection/free_period.rb +37 -0
  20. data/lib/active_period/collection/holiday_period.rb +44 -0
  21. data/lib/active_period/collection/standard_period.rb +33 -0
  22. data/lib/active_period/collection.rb +60 -0
  23. data/lib/active_period/comparable.rb +51 -0
  24. data/lib/active_period/day.rb +38 -0
  25. data/lib/active_period/free_period.rb +86 -0
  26. data/lib/active_period/has_many/days.rb +14 -0
  27. data/lib/active_period/has_many/holidays.rb +15 -0
  28. data/lib/active_period/has_many/months.rb +14 -0
  29. data/lib/active_period/has_many/quarters.rb +14 -0
  30. data/lib/active_period/has_many/weeks.rb +15 -0
  31. data/lib/active_period/has_many/years.rb +15 -0
  32. data/lib/active_period/has_many.rb +7 -0
  33. data/lib/active_period/holiday.rb +80 -0
  34. data/lib/active_period/month.rb +37 -0
  35. data/lib/active_period/period.rb +158 -0
  36. data/lib/active_period/quarter.rb +42 -0
  37. data/lib/active_period/standard_period.rb +51 -0
  38. data/lib/active_period/version.rb +5 -0
  39. data/lib/active_period/week.rb +41 -0
  40. data/lib/active_period/year.rb +34 -0
  41. data/lib/active_period.rb +28 -0
  42. data/lib/numeric.rb +6 -0
  43. data/lib/period.rb +63 -0
  44. data/lib/range.rb +8 -0
  45. metadata +146 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 244cb233b1cdd1aeac905c1c7467cdb90a9bd4179ed28d2c14928ebd5cd95308
4
+ data.tar.gz: 98776fbef793f99dc2f0ac63bb8dbd380fb709878f9eb2985cb393e008622f99
5
+ SHA512:
6
+ metadata.gz: 27251125b3cb392249e94eeeec0feb6faf4836dff714a1bd9996ffa032ea782b264798b07fea01ccf019fd8d2f2583a049fda7b0438156a297a4f20e0484fdf8
7
+ data.tar.gz: eae48296375f9d0323c4e2e2b5356a135dd9287dbe345f315526363b9e5c578ae858239434c1729fb0b9b01c6c80a5de01cbf259eb83cbe7637814d5dfb453bb
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in period.gemspec
4
+
5
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,36 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ active_period (6.0.0)
5
+ activesupport (~> 6)
6
+ i18n (~> 1)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ activesupport (6.1.3.1)
12
+ concurrent-ruby (~> 1.0, >= 1.0.2)
13
+ i18n (>= 1.6, < 2)
14
+ minitest (>= 5.1)
15
+ tzinfo (~> 2.0)
16
+ zeitwerk (~> 2.3)
17
+ concurrent-ruby (1.1.8)
18
+ i18n (1.8.10)
19
+ concurrent-ruby (~> 1.0)
20
+ minitest (5.14.4)
21
+ rake (10.5.0)
22
+ tzinfo (2.0.4)
23
+ concurrent-ruby (~> 1.0)
24
+ zeitwerk (2.4.2)
25
+
26
+ PLATFORMS
27
+ ruby
28
+ x86_64-darwin-19
29
+
30
+ DEPENDENCIES
31
+ active_period!
32
+ bundler (~> 2.0)
33
+ rake (~> 10.0)
34
+
35
+ BUNDLED WITH
36
+ 2.2.3
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 billau_l
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,342 @@
1
+ # ActivePeriod
2
+ [![Gem Version](https://badge.fury.io/rb/active_period.svg)](https://badge.fury.io/rb/active_period)
3
+ [![Code Climate](https://codeclimate.com/github/billaul/period.svg)](https://codeclimate.com/github/billaul/period)
4
+ [![Inline docs](http://inch-ci.org/github/billaul/period.svg)](http://inch-ci.org/github/billaul/period)
5
+ [![RubyGems](http://img.shields.io/gem/dt/active_period.svg?style=flat)](http://rubygems.org/gems/active_period)
6
+
7
+ ActivePeriod aims to simplify Time-range manipulation.
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'active_period'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install active_period
24
+
25
+ ## Usage
26
+
27
+ **ActivePeriod** was designed to simplify time-range manipulation, specialy with rails (>= 5) and user input
28
+
29
+ **Warning** :
30
+ - A time-range take place between two **date** and it's different from an abstract duration of time
31
+ - **ActivePeriod** is limited at full day of time and will always round the starting and ending to the beginning and the ending of the day
32
+
33
+
34
+ ## Quick view (TL;DR)
35
+ ``` ruby
36
+ require 'active_period'
37
+
38
+ # Get all user created today
39
+ User.where(created_at: Period.today)
40
+
41
+ # Get how many days there is from the Voyager 2 launch
42
+ ('20/07/1977'..Time.now).to_period.days.count
43
+
44
+ # Are we in 2021 ?
45
+ Time.now.in? Period.year('01/01/2021')
46
+
47
+ # Boundless period are also supported
48
+ Period.new('24/04/1990'..).days.each # => Enumerable
49
+ Period.new(..Time.now).months.reverse_each # => Enumerable
50
+
51
+ # Write a date for me (I18n supported)
52
+ Period.new('20/01/2017'...'20/01/2021').to_s
53
+ => "From the 20 January 2017 to the 20 January 2021 excluded"
54
+ ```
55
+
56
+ ## Detailed view
57
+
58
+ There's two way to create and manipulate a period of time `FreePeriod` and `StandardPeriod`
59
+
60
+ ### FreePeriod of time
61
+
62
+ You can declare **FreePeriod** as simply as :
63
+
64
+ ```ruby
65
+ # With Date objects
66
+ Period.new(3.month.ago..Date.today)
67
+
68
+ # or with Strings
69
+ Period.new('01/01/2000'...'01/02/2000')
70
+
71
+ # or with a mix
72
+ Period.new('01/01/2000'..1.week.ago)
73
+
74
+ # with one bound only
75
+ Period.new('01/01/2000'..)
76
+
77
+ # or in a rails Controller with params
78
+ Period.new(params[:start_date]..params[:end_date])
79
+
80
+ # or from a range
81
+ ('01/01/2000'...'01/02/2000').to_period
82
+ ```
83
+
84
+ **Note** : `to_period` will always return a **FreePeriod**
85
+
86
+ **FreePeriod** can be manipulated with `+` and `-`
87
+ Doing so will move the start **and** the end of the period
88
+ ```ruby
89
+ Period.new('01/01/2000'..'05/01/2000') + 3.day
90
+ # is equal to
91
+ Period.new('04/01/2000'..'08/01/2000')
92
+ ```
93
+
94
+ ### StandardPeriod of time
95
+
96
+ Using **StandardPeriod** you are limited to strictly bordered periods of time
97
+ These periods are `day`, `week`, `month`, `quarter` and `year`
98
+
99
+ ```ruby
100
+ # To get the week, 42th day ago
101
+ Period.week(42.day.ago)
102
+
103
+ # To get the first month of 2020
104
+ Period.month('01/01/2020')
105
+
106
+ # or if you like it verbious
107
+ ActivePeriod::Month.new('01/01/2020')
108
+
109
+ # or if you need the current week
110
+ Period.week(Time.now)
111
+ ```
112
+
113
+ **Note** : If you ask for a `month`, `quarter` of `year`, the day part of your param doesn't matter `01/01/2020` give the same result as `14/01/2020` or `29/01/2020`
114
+
115
+ **StandardPeriod** can be manipulated with `+` and `-` and will always return a **StandardPeriod** of the same type
116
+ ```ruby
117
+ # Subtraction are made from the start of the period
118
+ Period.month('10/02/2000') - 1.day
119
+ # Return the previous month
120
+
121
+ # Addition are made from the end
122
+ Period.month('10/02/2000') + 1.day
123
+ # Return the next month
124
+
125
+ Period.week('10/02/2000') + 67.day
126
+ # Return a week
127
+ ```
128
+ **StandardPeriod** also respond to `.next` and `.prev`
129
+ ```ruby
130
+ Period.month('01/01/2000').next.next.next
131
+ # Return the month of April 2020
132
+ ```
133
+
134
+ You can quickly access convenient periods of time with `.(last|this|next)_(day|week|month|quarter|year)` and `.yesterday` `.today` `.tomorrow`
135
+
136
+ ```ruby
137
+ Period.this_week
138
+ # Same as Period.week(Time.now) but shorter
139
+
140
+ Period.next_month
141
+ # Return the next month
142
+
143
+ Period.last_year
144
+ # Return the last year
145
+
146
+ Period.today
147
+ # No comment
148
+ ```
149
+
150
+ ## HasMany
151
+
152
+ **FreePeriod** and some **StandardPeriod** respond to `.days`, `.weeks`, `.months`, `.quarters` and `.years`
153
+
154
+ | HasMany -> [\<StandardPeriod>] | .days | .weeks | .months | .quarters | .years |
155
+ |-------------------------------|:----:|:-----:|:------:|:--------:|:-----:|
156
+ | FreePeriod | X | X | X | X | X |
157
+ | StandardPeriod::Day | | | | | |
158
+ | StandardPeriod::Week | X | | | | |
159
+ | StandardPeriod::Month | X | X | | | |
160
+ | StandardPeriod::Quarter | X | X | X | | |
161
+ | StandardPeriod::Year | X | X | X | X | |
162
+
163
+ Called from a **FreePeriod** all overlapping **StandardPeriod** are return
164
+ Called from a **StandardPeriod** only strictly included **StandardPeriod** are return
165
+ These methods return an **ActivePeriod::Collection** implementing **Enumerable**
166
+
167
+ #### Example
168
+ ```ruby
169
+ # The FreePeriod from 01/01/2021 to 01/02/2021 has 5 weeks
170
+ Period.new('01/01/2021'...'01/02/2021').weeks.count # 5
171
+
172
+ # The StandardPeriod::Month for 01/01/2021 has 4 weeks
173
+ Period.month('01/01/2021').weeks.count # 4
174
+
175
+ # How many day in the current quarter
176
+ Period.this_quarter.days.count
177
+
178
+ # Get all the quarters overlapping a Period of time
179
+ Period.new(Time.now..2.month.from_now).quarters.to_a
180
+ ```
181
+
182
+ ## BelongsTo
183
+
184
+ **StandardPeriod** respond to `.day`, `.week`, `.month`, `.quarter` and `.year`
185
+ These methods return a **StandardPeriod** who include the current period
186
+ **FreePeriod** does not respond to these methods
187
+
188
+ | BelongTo -> StandardPeriod | .day | .week | .month | .quarter | .year |
189
+ |----------------------------|:---:|:----:|:-----:|:-------:|:----:|
190
+ | FreePeriod | | | | | |
191
+ | StandardPeriod::Day | | X | X | X | X |
192
+ | StandardPeriod::Week | | | X | X | X |
193
+ | StandardPeriod::Month | | | | X | X |
194
+ | StandardPeriod::Quarter | | | | | X |
195
+ | StandardPeriod::Year | | | | | |
196
+
197
+ #### Example with BelongTo and HasMany
198
+
199
+ ```ruby
200
+ # Get the third day, of the last week, of the second month, of the current year
201
+ Period.this_year.months.second.weeks.last.days.third
202
+ ```
203
+
204
+ ## Boundless Period
205
+
206
+ Boundless period are fully supported and work as you expect them to do
207
+ The values `nil`, `''`, `Date::Infinity`, `Float::INFINITY` and `-Float::INFINITY` are supported as start and end
208
+ You can iterate on the `days`, `weeks`, `months`, `quarters` and `years` of an Endless period
209
+ ```ruby
210
+ ('01/01/2021'..nil).days.each { ... }
211
+ ('01/01/2021'..'').days.each { ... }
212
+ ('01/01/2021'..).days.each { ... }
213
+ ```
214
+ You can reverse iterate on the `days`, `weeks`, `months`, `quarters` and `years` of an Beginless period
215
+ ```ruby
216
+ (nil..'01/01/2021').days.reverse_each { ... }
217
+ (''..'01/01/2021').days.reverse_each { ... }
218
+ (..'01/01/2021').days.reverse_each { ... }
219
+ ```
220
+
221
+ You can create an infinite period of time
222
+ Obviously it's not iterable
223
+ ```ruby
224
+ Period.new(nil..nil).to_s
225
+ => "Limitless time range"
226
+ ```
227
+
228
+ You can specifically forbid boundless period with `allow_endless`, `allow_beginless` or with `Period.bounded`
229
+ ```ruby
230
+ Period.new('01/01/2020'..'', allow_endless: false)
231
+ Period.bounded('01/01/2020'..)
232
+ => ArgumentError (The end date is invalid)
233
+
234
+ Period.new(..'01/01/2020', allow_beginless: false)
235
+ Period.bounded(..'01/01/2020')
236
+ => ArgumentError (The start date is invalid)
237
+ ```
238
+
239
+ ## ActiveRecord
240
+
241
+ As **Period** inherit from **Range**, you can natively use them in **ActiveRecord** query
242
+
243
+ ```ruby
244
+ # Get all book published this year
245
+ Book.where(published_at: Period.this_year)
246
+
247
+ # Get all users created after 01/01/2020
248
+ User.where(created_at: ('01/01/2020'..).to_period)
249
+ ```
250
+
251
+ ## Rails Controller
252
+
253
+ In a Controller, use the error handling to validate the date for you
254
+
255
+ ```ruby
256
+ class BookController < ApplicationController
257
+ def between
258
+ begin
259
+ # Retrieve books from the DB
260
+ @books = Book.where(published: Period.bounded(params[:from]..params[:to]))
261
+ rescue ArgumentError => e
262
+ # Period will handle mis-formatted date and incoherent period
263
+ # I18n is supported for errors messages
264
+ flash[:alert] = e.message
265
+ end
266
+ end
267
+ end
268
+ ```
269
+
270
+ ## I18n and to_s
271
+
272
+ I18n is supported for `en` and `fr`
273
+
274
+ ```ruby
275
+ Period.new('01/01/2000'...'01/02/2001').to_s
276
+ => "From the 01 January 2000 to the 31 January 2001 included"
277
+
278
+ I18n.locale = :fr
279
+ Period.new('01/01/2000'...'01/02/2001').to_s
280
+ => "Du 01 janvier 2000 au 31 janvier 2001 inclus"
281
+ ```
282
+ Errors are also supported
283
+ ```ruby
284
+ Period.new 'Foo'..'Bar'
285
+ => ArgumentError (The start date is invalid)
286
+
287
+ Period.new '01/02/3030'..'Bar'
288
+ Period.bounded '01/02/3030'..
289
+ => ArgumentError (The end date is invalid)
290
+
291
+ Period.new '01/02/3030'..'01/01/2020'
292
+ => ArgumentError (The start date is greater than the end date)
293
+ ```
294
+
295
+ See `locales/en.yml` to implement your language support
296
+
297
+ If you need to change the format for a single call
298
+
299
+ ```ruby
300
+ period.to_s(format: 'Your Format')
301
+ # or
302
+ period.strftime('Your Format')
303
+ ```
304
+ For a FreePeriod or if you need to print the start and the end of your period differently, use `.i18n`
305
+ ```ruby
306
+ period.i18n do |from, to, excluded_end|
307
+ "You have from #{from.strftime(...)} until #{to.strftime(...)} to deliver the money !"
308
+ end
309
+ ```
310
+
311
+ ## The tricky case of Weeks
312
+
313
+ Weeks are implemented following the [ISO 8601](https://en.wikipedia.org/wiki/ISO_week_date)
314
+ So `Period.this_month.weeks.first` doesn't necessarily include the first days of the month
315
+ Also a **StandardPeriod** and a **FreePeriod** covering the same range of time, may not includes the same `Weeks`
316
+
317
+ ## TimeZone
318
+
319
+ Time zone are supported
320
+ If you change the global `Time.zone` of your app
321
+ If your Period [begin in a time zone and end in another](https://en.wikipedia.org/wiki/Daylight_saving_time), you have nothing to do
322
+
323
+ ## Planned updates
324
+
325
+ - [ ] ActiveRecord Serializer (maybe)
326
+ - [ ] Holidays support (gem Holidays) -> COMING SOON
327
+
328
+ ## Bug reports
329
+
330
+ If you discover any bugs, feel free to create an [issue on GitHub](https://github.com/billaul/active_period/issues)
331
+ Please add as much information as possible to help us in fixing the potential bug
332
+ We also encourage you to help even more by forking and sending us a pull request
333
+
334
+ No issues will be addressed outside GitHub
335
+
336
+ ## Maintainer
337
+
338
+ * Myself (https://github.com/billaul)
339
+
340
+ ## License
341
+
342
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'bundler/gem_tasks'
2
+ task default: :spec
3
+
4
+ require 'rake/testtask'
5
+ Rake::TestTask.new do |t|
6
+ t.libs << 'test'
7
+ end
8
+
9
+ desc 'Run tests'
10
+ task default: :test
@@ -0,0 +1,44 @@
1
+ lib = File.expand_path('lib', __dir__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'active_period/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'active_period'
7
+ spec.version = ActivePeriod::VERSION
8
+ spec.authors = ['billau_l']
9
+
10
+ spec.summary = 'Manage time ranges without brain damage.'
11
+ # spec.description = "Period.new('01/01/2020'..Time.now)"
12
+ spec.homepage = "https://github.com/billaul/period"
13
+ spec.license = 'MIT'
14
+
15
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
16
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
17
+ if spec.respond_to?(:metadata)
18
+ # spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
19
+
20
+ spec.metadata["bug_tracker_uri"] = spec.homepage + '/issues'
21
+ spec.metadata["homepage_uri"] = spec.homepage
22
+ spec.metadata["documentation_uri"] = spec.homepage
23
+ spec.metadata["source_code_uri"] = spec.homepage
24
+ # spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
25
+ else
26
+ raise "RubyGems 2.0 or newer is required to protect against " \
27
+ "public gem pushes."
28
+ end
29
+
30
+ # Specify which files should be added to the gem when it is released.
31
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
32
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
33
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
34
+ end
35
+ spec.bindir = 'exe'
36
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
37
+ spec.require_paths = %w[lib config]
38
+
39
+ spec.required_ruby_version = '>= 2.7'
40
+ spec.add_runtime_dependency 'activesupport', '~> 5'
41
+ spec.add_runtime_dependency 'i18n', '~> 1'
42
+ spec.add_development_dependency 'bundler', '~> 2.0'
43
+ spec.add_development_dependency 'rake', '~> 10.0'
44
+ end