active_period 7.0.0 → 7.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f52b743020890ffb49224e7b0dd129901233a4d1cdd639d412a88872c66b97d0
4
- data.tar.gz: 0eb862ce0e5aa128ce81fd93e28dd887c64f0f70ec000c9dd7988e281288be39
3
+ metadata.gz: e197dc4558fd215fc7090f5a14ad074e769457abfa7665b4b2393e5ba2a23d52
4
+ data.tar.gz: 54c3de6172706d0f08e2885b6401b3f1c964526498193a63c13af4fbfab6eb99
5
5
  SHA512:
6
- metadata.gz: bce08f3f5b1b10347469b43a832d525bec3a470b09e798539f84bc80b4aca768898734565acadf5600670fcbb9e7a3e6a427b9c4fcbccc402e5b5c0109a05a62
7
- data.tar.gz: 5e3dcdb1f0328b1baf28ad298e951d893c9e9102faa703890ba47f3c6983947c666dad61e810364ff2bb2b4648542be859d7bfee5f081472da4bb7d3293da07a
6
+ metadata.gz: a228d61ffcb56d1f9206127e22775e2a3ec109822ce0b8242e6e778d8edb08531dd9b2834cc7def80cb42ec7cf148b2abc8776059228c5fb7b7d9cb90ec68562
7
+ data.tar.gz: 25550cbd55706774bfd34e68690be5d39b2e01b6681f33b3134d3c7169949c9e1d017ac40cadd24d041eef2c85d3ce6c05dbb8fe76761d6a2fabebe501230e24
data/Gemfile.lock CHANGED
@@ -1,20 +1,20 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- active_period (7.0.0)
5
- activesupport (~> 7, >= 5)
4
+ active_period (7.1.1)
5
+ activesupport (>= 5, < 8)
6
6
  i18n (~> 1)
7
7
 
8
8
  GEM
9
9
  remote: https://rubygems.org/
10
10
  specs:
11
- activesupport (7.0.2.2)
11
+ activesupport (7.0.4)
12
12
  concurrent-ruby (~> 1.0, >= 1.0.2)
13
13
  i18n (>= 1.6, < 2)
14
14
  minitest (>= 5.1)
15
15
  tzinfo (~> 2.0)
16
16
  builder (3.2.4)
17
- concurrent-ruby (1.1.9)
17
+ concurrent-ruby (1.1.10)
18
18
  cucumber (7.0.0)
19
19
  builder (~> 3.2, >= 3.2.4)
20
20
  cucumber-core (~> 10.0, >= 10.0.1)
@@ -48,17 +48,17 @@ GEM
48
48
  cucumber-messages (~> 17.0, >= 17.0.1)
49
49
  diff-lcs (1.4.4)
50
50
  ffi (1.15.3)
51
- i18n (1.10.0)
51
+ i18n (1.12.0)
52
52
  concurrent-ruby (~> 1.0)
53
53
  mime-types (3.3.1)
54
54
  mime-types-data (~> 3.2015)
55
55
  mime-types-data (3.2021.0704)
56
- minitest (5.15.0)
56
+ minitest (5.16.3)
57
57
  multi_test (0.1.2)
58
58
  rake (10.5.0)
59
59
  sys-uname (1.2.2)
60
60
  ffi (~> 1.1)
61
- tzinfo (2.0.4)
61
+ tzinfo (2.0.5)
62
62
  concurrent-ruby (~> 1.0)
63
63
 
64
64
  PLATFORMS
data/README.md CHANGED
@@ -82,6 +82,9 @@ Period.new(params[:start_date]..params[:end_date])
82
82
 
83
83
  # or from a range
84
84
  ('01/01/2000'...'01/02/2000').to_period
85
+
86
+ # you can also use [] if .new is too long for you
87
+ Period['01/01/2000'...'01/02/2000']
85
88
  ```
86
89
 
87
90
  **Note** : `to_period` will always return a **FreePeriod**
@@ -204,6 +207,41 @@ These methods return a **StandardPeriod** who include the current period
204
207
  Period.this_year.months.second.weeks.last.days.third
205
208
  ```
206
209
 
210
+ ## Period Combination with `&` and `|`
211
+
212
+ You can use `&` to combine overlapping periods
213
+ And `|` to combine overlapping and tail to head periods
214
+ If the given periods cannot combine, then `nil` will be return
215
+ The period we take the ending date from, determine if the ending date is included or excluded
216
+
217
+ #### Example for `&`
218
+ ```ruby
219
+ # Overlapping periods
220
+ (Period['01/01/2021'..'20/01/2021'] & Period['10/01/2021'...'30/01/2021']).to_s
221
+ => "From the 10 January 2021 to the 20 January 2021 included"
222
+
223
+ # Theses period cannot combine
224
+ Period.this_month & Period.next_month
225
+ => nil
226
+ ```
227
+
228
+ #### Example for `|`
229
+ ```ruby
230
+ # Overlapping periods
231
+ (Period['01/01/2021'..'20/01/2021'] | Period['10/01/2021'...'30/01/2021']).to_s
232
+ => "From the 01 January 2021 to the 30 January 2021 excluded"
233
+
234
+ # Example with tail to head
235
+ (Period.this_month | Period.next_month).to_s
236
+ => "From the 01 September 2022 to the 31 October 2022 included"
237
+ (Period['01/01/2021'..'09/01/2021'] | Period['10/01/2021'..'20/01/2021']).to_s
238
+ => "From the 01 January 2021 to the 20 January 2021 included"
239
+
240
+ # Theses period cannot combine
241
+ Period['01/01/2021'...'09/01/2021'] | Period['10/01/2021'..'20/01/2021']
242
+ => nil
243
+ ```
244
+
207
245
  ## Boundless Period
208
246
 
209
247
  Boundless period are fully supported and work as you expect them to do
@@ -38,7 +38,7 @@ Gem::Specification.new do |spec|
38
38
 
39
39
  spec.required_ruby_version = '>= 2.7'
40
40
 
41
- spec.add_runtime_dependency 'activesupport', '>= 5', '~> 7'
41
+ spec.add_runtime_dependency 'activesupport', '>= 5', '< 8'
42
42
  spec.add_runtime_dependency 'i18n', '~> 1'
43
43
 
44
44
  spec.add_development_dependency 'bundler', '~> 2.0'
@@ -5,7 +5,7 @@ fr:
5
5
  comparable:
6
6
  incomparable_error: Les arguments ne sont pas comparables
7
7
  collection:
8
- param_period_must_be_a_period: Le paramètre doit hériter de la class Period
8
+ param_period_must_be_a_period: Le paramètre doit hériter de la class Period
9
9
  period:
10
10
  base_class_is_abstract: Period est une class abstraite qui ne doit pas être utiliser directement
11
11
  begin_date_is_invalid: Date de début invalide
@@ -15,8 +15,8 @@ fr:
15
15
  param_must_be_a_range: Le paramètre doit hériter de la class Range
16
16
  free_period:
17
17
  default_format: Du %{from} au %{to} %{ending}
18
- beginless_format: Jusqu'au %{from} %{ending}
19
- endless_format: Depuis le %{to}
18
+ beginless_format: Jusqu'au %{to} %{ending}
19
+ endless_format: Depuis le %{from}
20
20
  boundless_format: Sans limite de temps
21
21
  standard_period:
22
22
  base_class_is_abstract: StandardPeriod est une class abstraite qui ne doit pas être utiliser directement
@@ -16,12 +16,14 @@ module ActivePeriod
16
16
  end
17
17
 
18
18
  def <=>(other)
19
+ raise ArgumentError, I18n.t(:incomparable_error, scope: %i[active_period comparable]) unless other.is_a?(ActiveSupport::Duration)
20
+
19
21
  if other.is_a?(ActiveSupport::Duration) || other.is_a?(Numeric)
20
22
  to_i <=> other.to_i
21
23
  elsif self.class != other.class
22
24
  raise ArgumentError, I18n.t(:incomparable_error, scope: %i[active_period comparable])
23
25
  else
24
- (from <=> other)
26
+ (self.begin <=> other)
25
27
  end
26
28
  end
27
29
 
@@ -9,7 +9,7 @@ class ActivePeriod::Period < Range
9
9
  # @param range [Range] A valid range
10
10
  # @param allow_beginless [Boolean] Is it allow to creat a beginless range
11
11
  # @param allow_endless [Boolean] Is it allow to creat an endless range
12
- # @return [self] A new instance of ActivePeriod::FreePeriod
12
+ # @return [self] A new instance of ActivePeriod::Period
13
13
  # @raise ArgumentError if the params range is not a Range
14
14
  # @raise ArgumentError if the params range is invalid
15
15
  def initialize(range, allow_beginless: true, allow_endless: true)
@@ -25,8 +25,6 @@ class ActivePeriod::Period < Range
25
25
  raise ::ArgumentError, I18n.t(:end_date_is_invalid, scope: %i[active_period period]) if !allow_endless && to.nil?
26
26
  to = to.try(:end_of_day) || to
27
27
 
28
- # raise ::ArgumentError, I18n.t(:endless_excluded_end_is_forbiden, scope: %i[active_period period]) if to.nil? && range.exclude_end?
29
- # to = to.prev_day if range.exclude_end?
30
28
  if range.exclude_end? && from && to && from.to_date == to.to_date
31
29
  raise ::ArgumentError, I18n.t(:start_is_equal_to_end_excluded, scope: %i[active_period period])
32
30
  end
@@ -70,19 +68,79 @@ class ActivePeriod::Period < Range
70
68
  raise NotImplementedError
71
69
  end
72
70
 
73
- # @param other [ActivePeriod::FreePeriod] Any kind of ActivePeriod::FreePeriod object
71
+ # @param other [ActivePeriod::Period, ActiveSupport::Duration, Numeric] Any kind of ActivePeriod::Period, ActiveSupport::Duration or Numeric (in seconds)
74
72
  # @return [Boolean] true if period are equals, false otherwise
75
- # @raise ArgumentError if params other is not a ActivePeriod::FreePeriod of some kind
73
+ # @raise ArgumentError if params other is not a ActivePeriod::Period of some kind
76
74
  def ==(other)
77
75
  if other.class.ancestors.include?(ActivePeriod::Period)
78
76
  from == other.from && self.calculated_end == other.calculated_end
79
- elsif other.is_a?(ActiveSupport::Duration) || other.is_a?(Numeric)
77
+ elsif other.is_a?(ActiveSupport::Duration)
80
78
  super(other)
79
+ elsif other.is_a?(Numeric)
80
+ super(other.seconds)
81
81
  else
82
82
  raise ArgumentError
83
83
  end
84
84
  end
85
85
 
86
+ # @param other [ActivePeriod::Period] Any kind of ActivePeriod::Period object
87
+ # @return [Boolean] true if period and class are the same, false otherwise
88
+ # @raise ArgumentError if params other is not a ActivePeriod::Period of some kind
89
+ def ===(other)
90
+ self == other && self.class == other.class
91
+ end
92
+
93
+ # @param other [ActivePeriod::Period] Any kind of ActivePeriod::Period object
94
+ # @return [ActivePeriod::Period, nil] Any kind of ActivePeriod::FreePeriod if period overlap, nil otherwise
95
+ # @raise ArgumentError if params other is not a ActivePeriod::Period of some kind
96
+ def &(other)
97
+ raise ArgumentError, I18n.t(:incomparable_error, scope: %i[active_period comparable]) unless other.class.ancestors.include?(ActivePeriod::Period)
98
+
99
+ # self 9------12
100
+ # other 1-----------10
101
+ if self.begin.in?(other) && !calculated_end.in?(other)
102
+ Period.new( Range.new(self.begin, other.to, other.exclude_end?) )
103
+ # self 1-----------10
104
+ # other 9------12
105
+ elsif !self.begin.in?(other) && calculated_end.in?(other)
106
+ Period.new( Range.new(other.begin, to, exclude_end?) )
107
+ # self 5-----8
108
+ # other 1-----------10
109
+ elsif other.include?(self)
110
+ other
111
+ # self 1-----------10
112
+ # other 5-----8
113
+ elsif self.include?(other)
114
+ self
115
+ else
116
+ nil
117
+ end
118
+ end
119
+
120
+ # @param other [ActivePeriod::Period] Any kind of ActivePeriod::Period object
121
+ # @return [ActivePeriod::Period, nil] Any kind of ActivePeriod::FreePeriod if period overlap, nil otherwise
122
+ # @raise ArgumentError if params other is not a ActivePeriod::Period of some kind
123
+ def |(other)
124
+ raise ArgumentError, I18n.t(:incomparable_error, scope: %i[active_period comparable]) unless other.class.ancestors.include?(ActivePeriod::Period)
125
+
126
+ # overlapping or tail to head
127
+ if self.begin.in?(other) || self.calculated_end.in?(other) || (
128
+ ((self.calculated_end+1.day).beginning_of_day == other.from) ^
129
+ ((other.calculated_end+1.day).beginning_of_day == self.from)
130
+ )
131
+ Period.new(
132
+ Range.new(
133
+ [self.begin, other.begin].min,
134
+ [self.to, other.to].max,
135
+ self.calculated_end > other.calculated_end ? self.exclude_end? : other.exclude_end?
136
+ )
137
+ )
138
+ # no overlapping
139
+ else
140
+ nil
141
+ end
142
+ end
143
+
86
144
  # @raise NotImplementedError This method should be implemented in daughter class
87
145
  def strftime
88
146
  raise NotImplementedError
@@ -1,5 +1,5 @@
1
1
  module ActivePeriod
2
2
 
3
- VERSION = '7.0.0'.freeze
3
+ VERSION = '7.1.2'.freeze
4
4
 
5
5
  end
data/lib/period.rb CHANGED
@@ -12,6 +12,11 @@ module Period
12
12
  ActivePeriod::FreePeriod.new(range, allow_beginless: false, allow_endless: false)
13
13
  end
14
14
 
15
+ # Shorthand to Period.new
16
+ def self.[](range)
17
+ Period.new(range)
18
+ end
19
+
15
20
  # env_time provide a Fallback if the project dont specify any Time.zone
16
21
  def self.env_time
17
22
  (Time.zone || Time)
@@ -45,32 +50,56 @@ module Period
45
50
  alias tomorrow next_day
46
51
  alias today this_day
47
52
 
48
- # Experimental non-documented feature
53
+ LAST_OR_NEXT_REGEX = /^(:?last|next)_(\d+)_(day|week|month|quarter|year)s?(_from_now)?$/
54
+ LAST_AND_NEXT_REGEX = /^last_(\d+)_(day|week|month|quarter|year)s?_to_next_(\d+)_(day|week|month|quarter|year)s?$/
55
+
56
+ # Experimenta l non-documented feature
49
57
  # Inpired form ActiveRecord dynamic find_by_x like User.find_by_name
50
58
  # Example: Period.last_3_weeks_from_now == Period.mew(2.weeks.ago.beginning_of_week..Time.now.end_of_week)
51
- # Note : Maybe it should return a collection of StandardPeriod
52
59
  def method_missing(method_name, *arguments, &block)
53
- super unless method_name.match?(/(last|next)_\d+_(day|week|month|quarter|year)s?(_from_now)?/)
54
- last_next, count, klass = method_name.to_s.split('_')
60
+ if method_name.match? LAST_OR_NEXT_REGEX
61
+ missing_last_or_next(method_name)
62
+ elsif method_name.match? LAST_AND_NEXT_REGEX
63
+ missing_last_and_next(method_name)
64
+ else
65
+ super
66
+ end
67
+ end
68
+
69
+ def missing_last_or_next(method_name)
70
+ last_next, count, klass, from_now = method_name.to_s.scan(LAST_OR_NEXT_REGEX).flatten
55
71
  klass = klass.singularize
56
72
 
57
73
  case last_next
58
74
  when 'last'
59
75
  from = count.to_i.send(klass).ago.send("beginning_of_#{klass}")
60
76
  to = env_time.now
61
- to -= 1.send(klass) unless method_name.match?(/from_now$/)
77
+ to -= 1.send(klass) unless from_now
62
78
  to = to.send("end_of_#{klass}")
63
79
  when 'next'
64
80
  from = env_time.now
65
- from += 1.send(klass) unless method_name.match?(/from_now$/)
81
+ from += 1.send(klass) unless from_now
66
82
  from = from.send("beginning_of_#{klass}")
67
83
  to = count.to_i.send(klass).from_now.send("end_of_#{klass}")
68
84
  end
69
85
  self.new(from..to)
70
86
  end
71
87
 
88
+ def missing_last_and_next(method_name)
89
+ last_count, last_klass, next_count, next_klass = method_name.to_s.scan(LAST_AND_NEXT_REGEX).flatten
90
+ last_klass = last_klass.singularize
91
+ next_klass = next_klass.singularize
92
+
93
+ from = last_count.to_i.send(last_klass).ago.send("beginning_of_#{last_klass}")
94
+ to = next_count.to_i.send(next_klass).from_now.send("end_of_#{next_klass}")
95
+
96
+ self.new(from..to)
97
+ end
98
+
72
99
  def respond_to_missing?(method_name, include_private = false)
73
- method_name.match?(/(last|next)_\d+_(day|week|month|quarter|year)s?(_from_now)?/) || super
100
+ method_name.match?(/(last|next)_\d+_(day|week|month|quarter|year)s?(_from_now)?/) ||
101
+ method_name.match?(/(last)_\d+_(day|week|month|quarter|year)s?_to_next_\d+_(day|week|month|quarter|year)s?/ ) ||
102
+ super
74
103
  end
75
104
  end
76
105
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_period
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.0.0
4
+ version: 7.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - billau_l
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-02-22 00:00:00.000000000 Z
11
+ date: 2023-05-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -17,9 +17,9 @@ dependencies:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '5'
20
- - - "~>"
20
+ - - "<"
21
21
  - !ruby/object:Gem::Version
22
- version: '7'
22
+ version: '8'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
@@ -27,9 +27,9 @@ dependencies:
27
27
  - - ">="
28
28
  - !ruby/object:Gem::Version
29
29
  version: '5'
30
- - - "~>"
30
+ - - "<"
31
31
  - !ruby/object:Gem::Version
32
- version: '7'
32
+ version: '8'
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: i18n
35
35
  requirement: !ruby/object:Gem::Requirement
@@ -159,7 +159,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
159
159
  - !ruby/object:Gem::Version
160
160
  version: '0'
161
161
  requirements: []
162
- rubygems_version: 3.1.2
162
+ rubygems_version: 3.3.22
163
163
  signing_key:
164
164
  specification_version: 4
165
165
  summary: Manage time ranges without brain damage.