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
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'active_period'
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require 'irb'
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,36 @@
1
+ en:
2
+ active_period:
3
+ included: included
4
+ excluded: excluded
5
+ comparable:
6
+ incomparable_error: Cannot compare Arguments
7
+ collection:
8
+ param_period_must_be_a_period: The parameter perod must inherit from the Period class
9
+ period:
10
+ base_class_is_abstract: Period is not intended to be used directly
11
+ begin_date_is_invalid: The beginning date is invalid
12
+ end_date_is_invalid: The ending date is invalid
13
+ start_is_equal_to_end_excluded: The beginning date and ending date cannot be equal when the ending date is excluded
14
+ start_is_greater_than_end: The beginning date is greater than the ending date
15
+ param_must_be_a_range: The parameter must inherit from the Range class
16
+ free_period:
17
+ default_format: From the %{from} to the %{to} %{ending}
18
+ beginless_format: Until the %{to} %{ending}
19
+ endless_format: From the %{from}
20
+ boundless_format: Limitless time range
21
+ standard_period:
22
+ base_class_is_abstract: StandardPeriod is not intended to be used directly
23
+ date_is_invalid: The date is invalid
24
+ day:
25
+ default_format: '%{wday} %{day} %{month} %{year}'
26
+ week:
27
+ default_format: Week %{week} of %{year}
28
+ month:
29
+ default_format: '%{month} %{year}'
30
+ quarter:
31
+ default_format: '%{quarter_nb} quarter %{year}'
32
+ year:
33
+ default_format: '%{year}'
34
+ holiday_period:
35
+ gem_require: 'The gem "holidays" is needed for this feature to work'
36
+ default_format: '%{name} - %{wday} %{day} %{month} %{year}'
@@ -0,0 +1,59 @@
1
+ fr:
2
+ active_period:
3
+ included: inclus
4
+ excluded: exclue
5
+ comparable:
6
+ incomparable_error: Les arguments ne sont pas comparables
7
+ collection:
8
+ param_period_must_be_a_period: Le paramètre doit hériter de la class Period
9
+ period:
10
+ base_class_is_abstract: Period est une class abstraite qui ne doit pas être utiliser directement
11
+ begin_date_is_invalid: Date de début invalide
12
+ end_date_is_invalid: Date de fin invalide
13
+ start_is_equal_to_end_excluded: La date de début et de fin ne peuvent pas être égale quand la date de fin est exclue
14
+ start_is_greater_than_end: Date de début supérieur à la date de fin
15
+ param_must_be_a_range: Le paramètre doit hériter de la class Range
16
+ free_period:
17
+ default_format: Du %{from} au %{to} %{ending}
18
+ beginless_format: Jusqu'au %{from} %{ending}
19
+ endless_format: Depuis le %{to}
20
+ boundless_format: Sans limite de temps
21
+ standard_period:
22
+ base_class_is_abstract: StandardPeriod est une class abstraite qui ne doit pas être utiliser directement
23
+ date_is_invalid: La date est invalide
24
+ day:
25
+ default_format: '%{wday} %{day} %{month} %{year}'
26
+ week:
27
+ default_format: Semaine %{week} de l'année %{year}
28
+ month:
29
+ default_format: '%{month} %{year}'
30
+ quarter:
31
+ default_format: '%{quarter_nb} semestre %{year}'
32
+ year:
33
+ default_format: '%{year}'
34
+ holiday_period:
35
+ gem_require: 'La gem "holidays" est requise pour activer cette fonctionnalitée'
36
+ default_format: '%{name} - %{wday} %{day} %{month} %{year}'
37
+ date:
38
+ month_names:
39
+ -
40
+ - janvier
41
+ - février
42
+ - mars
43
+ - avril
44
+ - mai
45
+ - juin
46
+ - juillet
47
+ - août
48
+ - septembre
49
+ - octobre
50
+ - novembre
51
+ - décembre
52
+ day_names:
53
+ - dimanche
54
+ - lundi
55
+ - mardi
56
+ - mercredi
57
+ - jeudi
58
+ - vendredi
59
+ - samedi
data/lib/.DS_Store ADDED
Binary file
@@ -0,0 +1,12 @@
1
+ module ActivePeriod
2
+ module BelongsTo
3
+ # @author Lucas Billaudot <billau_l@modulotech.fr>
4
+ # @note when include this module provide access to the month of the
5
+ # FreePeriod
6
+ module Month
7
+ def month
8
+ ActivePeriod::Month.new(self.begin)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module ActivePeriod
2
+ module BelongsTo
3
+ # @author Lucas Billaudot <billau_l@modulotech.fr>
4
+ # @note when include this module provide access to the quarter of the
5
+ # FreePeriod
6
+ module Quarter
7
+ def quarter
8
+ ActivePeriod::Quarter.new(self.begin)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module ActivePeriod
2
+ module BelongsTo
3
+ # @author Lucas Billaudot <billau_l@modulotech.fr>
4
+ # @note when include this module provide access to the week of the
5
+ # FreePeriod
6
+ module Week
7
+ def week
8
+ ActivePeriod::Week.new(self.begin)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module ActivePeriod
2
+ module BelongsTo
3
+ # @author Lucas Billaudot <billau_l@modulotech.fr>
4
+ # @note when include this module provide access to the year of the
5
+ # FreePeriod
6
+ module Year
7
+ def year
8
+ ActivePeriod::Year.new(self.begin)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,7 @@
1
+ # @author Lucas Billaudot <billau_l@modulotech.fr>
2
+ # @note This module define all period of time, who include the current period
3
+ module ActivePeriod
4
+ module BelongsTo
5
+
6
+ end
7
+ end
@@ -0,0 +1,37 @@
1
+ module ActivePeriod
2
+ module Collection
3
+ class FreePeriod
4
+ include ActivePeriod::Collection
5
+
6
+ private
7
+
8
+ def enumerator
9
+ raise RangeError.new "cannot get the first element of beginless range" if period.beginless?
10
+
11
+ Enumerator.new do |yielder|
12
+ current = klass.new(period.begin)
13
+ while period.calculated_end.nil? || period.include?(current.begin) || period.include?(current.calculated_end)
14
+ yielder << current
15
+ current = current.next
16
+ end
17
+ # At the end (if there is one) the Collection will be return
18
+ self
19
+ end
20
+ end
21
+
22
+ def reverse_enumerator
23
+ raise RangeError.new "cannot get the last element of endless range" if period.endless?
24
+
25
+ Enumerator.new do |yielder|
26
+ current = klass.new(period.end)
27
+ while period.begin.nil? || period.include?(current.begin) || period.include?(current.calculated_end)
28
+ yielder << current
29
+ current = current.prev
30
+ end
31
+ # At the end (if there is one) the Collection will be return
32
+ self
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,44 @@
1
+ module ActivePeriod
2
+ module Collection
3
+ class HolidayPeriod
4
+ include ActivePeriod::Collection
5
+
6
+ # @!attribute [r] options
7
+ # @return [Array] The array of options for Holidays.on
8
+ attr_reader :options
9
+
10
+ def initialize(klass, period, *args)
11
+ super(klass, period)
12
+ @options = args
13
+ end
14
+
15
+ private
16
+
17
+ def enumerator
18
+ Enumerator.new do |yielder|
19
+ days = period.try(:days) || [period]
20
+ days.each do |day|
21
+ Holidays.on(day.begin.to_date, *options).each do |hash|
22
+ yielder << ActivePeriod::Holiday.new(**hash)
23
+ end
24
+ end
25
+ # At the end (if there is one) the Collection will be return
26
+ self
27
+ end
28
+ end
29
+
30
+ def reverse_enumerator
31
+ Enumerator.new do |yielder|
32
+ days = period.try(:days) || [period]
33
+ days.reverse_each do |day|
34
+ Holidays.on(day.begin.to_date, *options).each do |hash|
35
+ yielder << ActivePeriod::Holiday.new(**hash)
36
+ end
37
+ end
38
+ # At the end (if there is one) the Collection will be return
39
+ self
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,33 @@
1
+ module ActivePeriod
2
+ module Collection
3
+ class StandardPeriod
4
+ include ActivePeriod::Collection
5
+
6
+ private
7
+
8
+ def enumerator
9
+ Enumerator.new do |yielder|
10
+ current = klass.new(period.begin)
11
+ while current.end <= period.end || period.include?(current)
12
+ yielder << current if period.include?(current)
13
+ current = current.next
14
+ end
15
+ # At the end (if there is one) the Collection will be return
16
+ self
17
+ end
18
+ end
19
+
20
+ def reverse_enumerator
21
+ Enumerator.new do |yielder|
22
+ current = klass.new(period.end)
23
+ while current.begin <= period.begin || period.include?(current)
24
+ yielder << current if period.include?(current)
25
+ current = current.prev
26
+ end
27
+ # At the end (if there is one) the Collection will be return
28
+ self
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,60 @@
1
+ module ActivePeriod
2
+ module Collection
3
+ include Enumerable
4
+
5
+ # @!attribute [r] klass
6
+ # @return [ActivePeriod::StandardPeriod] Any kind of ActivePeriod::StandardPeriod object
7
+ attr_reader :klass
8
+
9
+ # @!attribute [r] period
10
+ # @return [ActivePeriod::FreePeriod] Any kind of ActivePeriod::FreePeriod object
11
+ attr_reader :period
12
+
13
+ def self.new(klass, period)
14
+ if period.is_a? ActivePeriod::StandardPeriod
15
+ ActivePeriod::Collection::StandardPeriod.new(klass, period)
16
+ else
17
+ ActivePeriod::Collection::FreePeriod.new(klass, period)
18
+ end
19
+ end
20
+
21
+ def initialize(klass, period)
22
+ raise I18n.t(:param_period_must_be_a_period, scope: %i[active_period collection]) unless period.class.ancestors.include?(ActivePeriod::Period)
23
+
24
+ @klass = klass
25
+ @period = period
26
+ end
27
+
28
+ def second
29
+ find_nth(1)
30
+ end
31
+
32
+ def third
33
+ find_nth(2)
34
+ end
35
+
36
+ def fourth
37
+ find_nth(3)
38
+ end
39
+
40
+ def find_nth(limit)
41
+ each.with_index do |element, index|
42
+ return element if index == limit
43
+ end
44
+ return nil
45
+ end
46
+
47
+ def each(&block)
48
+ block_given? ? enumerator.each(&block) : enumerator
49
+ end
50
+
51
+ def reverse_each(&block)
52
+ block_given? ? reverse_enumerator.each(&block) : reverse_enumerator
53
+ end
54
+
55
+ def last
56
+ reverse_each.first
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,51 @@
1
+ module ActivePeriod
2
+ module Comparable
3
+ include ::Comparable
4
+
5
+ def include?(other)
6
+ case other
7
+ when DateTime, Time, ActiveSupport::TimeWithZone
8
+ include_time?(other)
9
+ when Date
10
+ include_period?(ActivePeriod::Day.new(other))
11
+ when ActivePeriod::Period
12
+ include_period?(other)
13
+ else
14
+ raise ArgumentError, I18n.t(:incomparable_error, scope: %i[active_period comparable])
15
+ end
16
+ end
17
+
18
+ def <=>(other)
19
+ if other.is_a?(ActiveSupport::Duration) || other.is_a?(Numeric)
20
+ to_i <=> other.to_i
21
+ elsif self.class != other.class
22
+ raise ArgumentError, I18n.t(:incomparable_error, scope: %i[active_period comparable])
23
+ else
24
+ (from <=> other)
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def include_period?(other)
31
+ if self.class.in?([Month, Quarter, Year]) && other.is_a?(Week)
32
+ self.include_time?(other.include_date)
33
+ # elsif (other.class.in?([Month, Quarter, Year]) && self.is_a?(Week)
34
+ # other.include_time?(self.include_date)
35
+ else
36
+ self.include_time?(other.begin) && self.include_time?(other.calculated_end)
37
+ end
38
+ end
39
+
40
+ # this method could been shorten by chaining condition with ||
41
+ # yet it would make it less readable
42
+ def include_time?(other)
43
+ return true if self.boundless?
44
+ return true if self.endless? && self.begin <= other
45
+ return true if self.beginless? && self.calculated_end >= other
46
+ return true if self.begin.to_i <= other.to_i && other.to_i <= self.calculated_end.to_i
47
+ false
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,38 @@
1
+ require_relative 'standard_period.rb'
2
+ require_relative 'belongs_to.rb'
3
+ require_relative 'belongs_to/week.rb'
4
+ require_relative 'belongs_to/month.rb'
5
+ require_relative 'belongs_to/quarter.rb'
6
+ require_relative 'belongs_to/year.rb'
7
+
8
+ class ActivePeriod::Day < ActivePeriod::StandardPeriod
9
+ include ActivePeriod::BelongsTo::Week
10
+ include ActivePeriod::BelongsTo::Month
11
+ include ActivePeriod::BelongsTo::Quarter
12
+ include ActivePeriod::BelongsTo::Year
13
+
14
+ def strftime(format)
15
+ from.strftime(format)
16
+ end
17
+
18
+ def to_s(format: '%d/%m/%Y')
19
+ strftime(format)
20
+ end
21
+
22
+ def i18n(&block)
23
+ return yield(from, to) if block.present?
24
+
25
+ I18n.t(:default_format,
26
+ scope: i18n_scope,
27
+ wday: I18n.l(from, format: '%A').capitalize,
28
+ day: from.day,
29
+ month: I18n.l(from, format: '%B').capitalize,
30
+ year: from.year)
31
+ end
32
+
33
+ def holiday?(...)
34
+ raise I18n.t(:gem_require, scope: %i[active_period holiday_period]) unless Object.const_defined?('Holidays')
35
+ holidays(...).first.present?
36
+ end
37
+
38
+ end