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
@@ -0,0 +1,86 @@
1
+ require_relative 'has_many.rb'
2
+ require_relative 'has_many/days.rb'
3
+ require_relative 'has_many/weeks.rb'
4
+ require_relative 'has_many/months.rb'
5
+ require_relative 'has_many/quarters.rb'
6
+ require_relative 'has_many/years.rb'
7
+
8
+ module ActivePeriod
9
+ class FreePeriod < ActivePeriod::Period
10
+ include ActivePeriod::HasMany::Days
11
+ include ActivePeriod::HasMany::Weeks
12
+ include ActivePeriod::HasMany::Months
13
+ include ActivePeriod::HasMany::Quarters
14
+ include ActivePeriod::HasMany::Years
15
+
16
+ # Don't return an Integer. ActiveSupport::Duration is a better numeric
17
+ # representation a in time manipulation context
18
+ # @return [ActiveSupport::Duration] Number of day
19
+ def to_i
20
+ return Float::INFINITY if infinite?
21
+ days.count.days
22
+ end
23
+
24
+ # Shift a period to the past
25
+ # @params see https://api.rubyonrails.org/classes/ActiveSupport/TimeWithZone.html#method-i-2B
26
+ # @return [self] A new period of the same kind
27
+ def -(duration)
28
+ self.class.new((from - duration)..(to - duration))
29
+ end
30
+
31
+ # Shift a period to the future
32
+ # @params see https://api.rubyonrails.org/classes/ActiveSupport/TimeWithZone.html#method-i-2B
33
+ # @return [self] A new period of the same kind
34
+ def +(duration)
35
+ self.class.new((from + duration)..(to + duration))
36
+ end
37
+
38
+ # @param format [String] A valid format for I18n.l
39
+ # @return [String] Formated string
40
+ def strftime(format)
41
+ to_s(format: format)
42
+ end
43
+
44
+ # @param format [String] A valid format for I18n.l
45
+ # @return [String] Formated string
46
+ def to_s(format: '%d %B %Y')
47
+ I18n.t(bounding_format,
48
+ scope: %i[active_period free_period],
49
+ from: I18n.l(self.begin, format: format, default: nil),
50
+ to: I18n.l(self.end, format: format, default: nil),
51
+ ending: I18n.t(ending, scope: %i[active_period]))
52
+ end
53
+
54
+ def bounding_format
55
+ if boundless?
56
+ :boundless_format
57
+ elsif beginless?
58
+ :beginless_format
59
+ elsif endless?
60
+ :endless_format
61
+ else
62
+ :default_format
63
+ end
64
+ end
65
+
66
+ def ending
67
+ if exclude_end?
68
+ :excluded
69
+ else
70
+ :included
71
+ end
72
+ end
73
+
74
+ # If no block given, it's an alias to to_s
75
+ # For a block {|from,to| ... }
76
+ # @yieldparam from [DateTime|Nil] the start of the period
77
+ # @yieldparam to [DateTime|Nil] the end of the period
78
+ # @yieldparam exclude_end? [Boolean] is the ending of the period excluded
79
+ def i18n(&block)
80
+ return yield(from, to, exclude_end?) if block.present?
81
+
82
+ to_s
83
+ end
84
+
85
+ end
86
+ end
@@ -0,0 +1,14 @@
1
+ module ActivePeriod
2
+ module HasMany
3
+ # @author Lucas Billaudot <billau_l@modulotech.fr>
4
+ # @note when include this module provide access to the days of the
5
+ # FreePeriod
6
+ module Days
7
+ include ActivePeriod::HasMany
8
+
9
+ def days
10
+ @days ||= ActivePeriod::Collection.new(ActivePeriod::Day, self)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ module ActivePeriod
2
+ module HasMany
3
+ # @author Lucas Billaudot <billau_l@modulotech.fr>
4
+ # @note when include this module provide access to the holidays of the
5
+ # FreePeriod
6
+ module Holidays
7
+ include ActivePeriod::HasMany
8
+
9
+ def holidays(*args, &block)
10
+ raise I18n.t(:gem_require, scope: %i[active_period holiday_period]) unless Object.const_defined?('Holidays')
11
+ ActivePeriod::Collection::HolidayPeriod.new(ActivePeriod::Holiday, self, *args, &block)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ module ActivePeriod
2
+ module HasMany
3
+ # @author Lucas Billaudot <billau_l@modulotech.fr>
4
+ # @note when include this module provide itterable access to the months of
5
+ # the FreePeriod
6
+ module Months
7
+ include ActivePeriod::HasMany
8
+
9
+ def months
10
+ @months ||= ActivePeriod::Collection.new(ActivePeriod::Month, self)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ module ActivePeriod
2
+ module HasMany
3
+ # @author Lucas Billaudot <billau_l@modulotech.fr>
4
+ # @note when include this module provide itterable access to the quarters of
5
+ # the FreePeriod
6
+ module Quarters
7
+ include ActivePeriod::HasMany
8
+
9
+ def quarters
10
+ @quarters ||= ActivePeriod::Collection.new(ActivePeriod::Quarter, self)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ module ActivePeriod
2
+ module HasMany
3
+ # @author Lucas Billaudot <billau_l@modulotech.fr>
4
+ # @note when include this module provide itterable access to the weeks of
5
+ # the FreePeriod
6
+ module Weeks
7
+ include ActivePeriod::HasMany
8
+
9
+ def weeks
10
+ @weeks ||= ActivePeriod::Collection.new(ActivePeriod::Week, self)
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module ActivePeriod
2
+ module HasMany
3
+ # @author Lucas Billaudot <billau_l@modulotech.fr>
4
+ # @note when include this module provide itterable access to the years of
5
+ # the FreePeriod
6
+ module Years
7
+ include ActivePeriod::HasMany
8
+
9
+ def years
10
+ @years ||= ActivePeriod::Collection.new(ActivePeriod::Year, self)
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,7 @@
1
+ # @author Lucas Billaudot <billau_l@modulotech.fr>
2
+ # @note This module define all period of time, who can be included in period
3
+ module ActivePeriod
4
+ module HasMany
5
+
6
+ end
7
+ end
@@ -0,0 +1,80 @@
1
+ require_relative 'day.rb'
2
+
3
+ class ActivePeriod::Holiday < ActivePeriod::Day
4
+
5
+ # @!attribute [r] name
6
+ # @return [String] The name of the holiday
7
+ attr_reader :name
8
+
9
+ # @!attribute [r] regions
10
+ # @return [<Symbol>] regions where the Holiday occure
11
+ attr_reader :regions
12
+
13
+
14
+ # @param date [...] A valid param for Period.day(...)
15
+ # @param name [String] The name of the Holiday
16
+ # @param regions [<Symbol>] region where the Holiday occure
17
+ # @return [Type] ActivePeriod::Holiday
18
+ # @raise RuntimeError if the gem "holidays" is not included
19
+ def initialize(date: , name:, regions: )
20
+ raise I18n.t(:gem_require, scope: %i[active_period holiday_period]) unless Object.const_defined?('Holidays')
21
+ super(date)
22
+
23
+ @name = name
24
+ @regions = regions
25
+ end
26
+
27
+ def next
28
+ raise NotImplementedError
29
+ end
30
+ alias succ next
31
+
32
+ def prev
33
+ raise NotImplementedError
34
+ end
35
+
36
+ def _period
37
+ self.class._period
38
+ end
39
+
40
+ def self._period
41
+ 'day'
42
+ end
43
+
44
+ # Shift a period to the past acording to her starting point
45
+ # @return [self] A new period of the same kind
46
+ def -(duration)
47
+ raise NotImplementedError
48
+ end
49
+
50
+ # Shift a period to the past acording to her ending point
51
+ # @return [self] A new period of the same kind
52
+ def +(duration)
53
+ raise NotImplementedError
54
+ end
55
+
56
+ def strftime(format)
57
+ from.strftime(format)
58
+ end
59
+
60
+ def to_s(format: '%d/%m/%Y')
61
+ strftime(format)
62
+ end
63
+
64
+ def i18n(&block)
65
+ return yield(from, to) if block.present?
66
+
67
+ I18n.t(:default_format,
68
+ scope: i18n_scope,
69
+ name: name,
70
+ wday: I18n.l(from, format: '%A').capitalize,
71
+ day: from.day,
72
+ month: I18n.l(from, format: '%B').capitalize,
73
+ year: from.year)
74
+ end
75
+
76
+ def i18n_scope
77
+ [:active_period, :holiday_period]
78
+ end
79
+
80
+ end
@@ -0,0 +1,37 @@
1
+ require_relative 'standard_period.rb'
2
+ require_relative 'has_many.rb'
3
+ require_relative 'has_many/days.rb'
4
+ require_relative 'has_many/weeks.rb'
5
+ require_relative 'belongs_to.rb'
6
+ require_relative 'belongs_to/quarter.rb'
7
+ require_relative 'belongs_to/year.rb'
8
+
9
+ # @author Lucas Billaudot <billau_l@modulotech.fr>
10
+ # @note One of the StandardPeriod defined in the gem
11
+ module ActivePeriod
12
+ class Month < ActivePeriod::StandardPeriod
13
+ include ActivePeriod::HasMany::Days
14
+ include ActivePeriod::HasMany::Weeks
15
+
16
+ include ActivePeriod::BelongsTo::Quarter
17
+ include ActivePeriod::BelongsTo::Year
18
+
19
+ def strftime(format)
20
+ from.strftime(format)
21
+ end
22
+
23
+ def to_s(format: '%m/%Y')
24
+ strftime(format)
25
+ end
26
+
27
+ def i18n(&block)
28
+ return yield(from, to) if block.present?
29
+
30
+ I18n.t(:default_format,
31
+ scope: i18n_scope,
32
+ month: I18n.l(from, format: '%B').capitalize,
33
+ year: from.year)
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,158 @@
1
+ require_relative 'comparable.rb'
2
+ require_relative 'has_many/holidays.rb'
3
+
4
+ class ActivePeriod::Period < Range
5
+ include ActivePeriod::Comparable
6
+ include ActivePeriod::HasMany::Holidays
7
+
8
+ # @author Lucas Billaudot <billau_l@modulotech.fr>
9
+ # @param range [Range] A valid range
10
+ # @param allow_beginless [Boolean] Is it allow to creat a beginless range
11
+ # @param allow_endless [Boolean] Is it allow to creat an endless range
12
+ # @return [self] A new instance of ActivePeriod::FreePeriod
13
+ # @raise ArgumentError if the params range is not a Range
14
+ # @raise ArgumentError if the params range is invalid
15
+ def initialize(range, allow_beginless: true, allow_endless: true)
16
+ I18n.t(:base_class_is_abstract, scope: %i[active_period period]) if self.class == ActivePeriod::Period
17
+
18
+ raise ::ArgumentError, I18n.t(:param_must_be_a_range, scope: %i[active_period period]) unless range.class.ancestors.include?(Range)
19
+
20
+ from = time_parse(range.begin, I18n.t(:begin_date_is_invalid, scope: %i[active_period period]))
21
+ raise ::ArgumentError, I18n.t(:begin_date_is_invalid, scope: %i[active_period period]) if !allow_beginless && from.nil?
22
+ from = from.try(:beginning_of_day) || from
23
+
24
+ to = time_parse(range.end, I18n.t(:end_date_is_invalid, scope: %i[active_period period]))
25
+ raise ::ArgumentError, I18n.t(:end_date_is_invalid, scope: %i[active_period period]) if !allow_endless && to.nil?
26
+ to = to.try(:end_of_day) || to
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
+ if range.exclude_end? && from && to && from.to_date == to.to_date
31
+ raise ::ArgumentError, I18n.t(:start_is_equal_to_end_excluded, scope: %i[active_period period])
32
+ end
33
+
34
+ if from && to && from > to
35
+ raise ::ArgumentError, I18n.t(:start_is_greater_than_end, scope: %i[active_period period])
36
+ end
37
+
38
+ super(from, to, range.exclude_end?)
39
+ end
40
+
41
+ alias from first
42
+ alias beginning first
43
+
44
+ alias to last
45
+ alias ending last
46
+
47
+ # @raise NotImplementedError This method cen be implemented in daughter class
48
+ def next
49
+ raise NotImplementedError
50
+ end
51
+ alias succ next
52
+
53
+ # @raise NotImplementedError This method cen be implemented in daughter class
54
+ def prev
55
+ raise NotImplementedError
56
+ end
57
+
58
+ # @raise NotImplementedError This method should be implemented in daughter class
59
+ def to_i
60
+ raise NotImplementedError
61
+ end
62
+
63
+ # @raise NotImplementedError This method should be implemented in daughter class
64
+ def -(duration)
65
+ raise NotImplementedError
66
+ end
67
+
68
+ # @raise NotImplementedError This method should be implemented in daughter class
69
+ def +(duration)
70
+ raise NotImplementedError
71
+ end
72
+
73
+ # @param other [ActivePeriod::FreePeriod] Any kind of ActivePeriod::FreePeriod object
74
+ # @return [Boolean] true if period are equals, false otherwise
75
+ # @raise ArgumentError if params other is not a ActivePeriod::FreePeriod of some kind
76
+ def ==(other)
77
+ if other.class.ancestors.include?(ActivePeriod::Period)
78
+ from == other.from && self.calculated_end == other.calculated_end
79
+ elsif other.is_a?(ActiveSupport::Duration) || other.is_a?(Numeric)
80
+ super(other)
81
+ else
82
+ raise ArgumentError
83
+ end
84
+ end
85
+
86
+ # @raise NotImplementedError This method should be implemented in daughter class
87
+ def strftime
88
+ raise NotImplementedError
89
+ end
90
+
91
+ # @raise NotImplementedError This method should be implemented in daughter class
92
+ def to_s
93
+ raise NotImplementedError
94
+ end
95
+
96
+ # @raise NotImplementedError This method must be implemented in daughter class
97
+ def i18n
98
+ raise NotImplementedError
99
+ end
100
+
101
+ # @author Lucas Billaudot <billau_l@modulotech.fr>
102
+ # @return [DateTime] The real value of end acording to exclude_end
103
+ def calculated_end
104
+ if endless?
105
+ Date::Infinity.new
106
+ else
107
+ if exclude_end?
108
+ self.end.prev_day
109
+ else
110
+ self.end
111
+ end
112
+ end
113
+ end
114
+
115
+ def calculated_begin
116
+ if beginless?
117
+ -Date::Infinity.new
118
+ else
119
+ self.begin
120
+ end
121
+ end
122
+
123
+ def endless?
124
+ self.end.nil?
125
+ end
126
+
127
+ def beginless?
128
+ self.begin.nil?
129
+ end
130
+
131
+ def boundless?
132
+ beginless? && endless?
133
+ end
134
+
135
+ def infinite?
136
+ beginless? || endless?
137
+ end
138
+
139
+ private
140
+
141
+ def time_parse(time, msg)
142
+ time = time.presence
143
+ case time
144
+ when NilClass, Date::Infinity, Float::INFINITY, -Float::INFINITY
145
+ nil
146
+ when String, Date
147
+ Period.env_time.parse(time.to_s)
148
+ when Numeric
149
+ Time.at time
150
+ when Time, ActiveSupport::TimeWithZone
151
+ time
152
+ else
153
+ raise ::ArgumentError, msg
154
+ end
155
+ rescue StandardError
156
+ raise ::ArgumentError, msg
157
+ end
158
+ end