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.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +36 -0
- data/LICENSE.txt +21 -0
- data/README.md +342 -0
- data/Rakefile +10 -0
- data/active_period.gemspec +44 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/config/locales/en.yml +36 -0
- data/config/locales/fr.yml +59 -0
- data/lib/.DS_Store +0 -0
- data/lib/active_period/belongs_to/month.rb +12 -0
- data/lib/active_period/belongs_to/quarter.rb +12 -0
- data/lib/active_period/belongs_to/week.rb +12 -0
- data/lib/active_period/belongs_to/year.rb +12 -0
- data/lib/active_period/belongs_to.rb +7 -0
- data/lib/active_period/collection/free_period.rb +37 -0
- data/lib/active_period/collection/holiday_period.rb +44 -0
- data/lib/active_period/collection/standard_period.rb +33 -0
- data/lib/active_period/collection.rb +60 -0
- data/lib/active_period/comparable.rb +51 -0
- data/lib/active_period/day.rb +38 -0
- data/lib/active_period/free_period.rb +86 -0
- data/lib/active_period/has_many/days.rb +14 -0
- data/lib/active_period/has_many/holidays.rb +15 -0
- data/lib/active_period/has_many/months.rb +14 -0
- data/lib/active_period/has_many/quarters.rb +14 -0
- data/lib/active_period/has_many/weeks.rb +15 -0
- data/lib/active_period/has_many/years.rb +15 -0
- data/lib/active_period/has_many.rb +7 -0
- data/lib/active_period/holiday.rb +80 -0
- data/lib/active_period/month.rb +37 -0
- data/lib/active_period/period.rb +158 -0
- data/lib/active_period/quarter.rb +42 -0
- data/lib/active_period/standard_period.rb +51 -0
- data/lib/active_period/version.rb +5 -0
- data/lib/active_period/week.rb +41 -0
- data/lib/active_period/year.rb +34 -0
- data/lib/active_period.rb +28 -0
- data/lib/numeric.rb +6 -0
- data/lib/period.rb +63 -0
- data/lib/range.rb +8 -0
- 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,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
|