active_period 6.1.1

Sign up to get free protection for your applications and to get access to all the features.
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