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
@@ -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