date_continuity 0.1.0 → 0.2.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 +4 -4
- data/lib/date_continuity/configuration.rb +15 -0
- data/lib/date_continuity/errors.rb +31 -0
- data/lib/date_continuity/model.rb +129 -0
- data/lib/date_continuity/occurrences.rb +35 -0
- data/lib/date_continuity.rb +8 -3
- metadata +22 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 53e852c09e2708211c4e1e449ebb9dcb6be20c63b4194b0499896e3109576ab1
|
4
|
+
data.tar.gz: 1c3f7b52275f5119052e18cd7b27c394d3041b863061feb4e127e2094996ac12
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 951f4f4338f3c6dab46df4e5b5a929610fb41b48335001a8f9303e4af8704a9549421a5e741ec4bfe93703920181ff18a3e2ce4c182f79239ebcefb7e56b8f35
|
7
|
+
data.tar.gz: 1c83dd1a839c7d0293d189f5bab0940175473801592d4b050aaba71ac65a8fdd161277a96f6804d93973793017a80703d30ea74207daf36bea181aedab87a2a2
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DateContinuity
|
4
|
+
class Configuration
|
5
|
+
attr_accessor :duration_method, :end_method, :frequency_count_method, :start_method, :time_unit_method
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@duration_method = :duration
|
9
|
+
@end_method = :end_at
|
10
|
+
@frequency_count_method = :frequency
|
11
|
+
@start_method = :start_at
|
12
|
+
@time_unit_method = :time_unit
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DateContinuity
|
4
|
+
class UnsupportedTimeUnitError < StandardError
|
5
|
+
def initialize(time_unit_value)
|
6
|
+
super(
|
7
|
+
"Unsupported time unit #{time_unit_value}. \
|
8
|
+
Must be one of the following: \
|
9
|
+
#{DateContinuity::Model::TIME_UNIT_VALUES.to_sentence(last_word_connector: " or ")}".squish
|
10
|
+
)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class NotEnoughInformationError < StandardError
|
15
|
+
def initialize(missing_columns)
|
16
|
+
super("Missing Information: #{missing_columns.to_sentence} must be set.")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
module Errors
|
21
|
+
extend ActiveSupport::Concern
|
22
|
+
|
23
|
+
def _raise_unsupported_time_unit_error(value)
|
24
|
+
raise UnsupportedTimeUnitError.new(value)
|
25
|
+
end
|
26
|
+
|
27
|
+
def _raise_not_enough_information_error(required_columns)
|
28
|
+
raise NotEnoughInformationError.new(required_columns)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/all"
|
4
|
+
require "date_continuity/errors"
|
5
|
+
require "date_continuity/time_between"
|
6
|
+
require "date_continuity/occurrences"
|
7
|
+
|
8
|
+
module DateContinuity
|
9
|
+
module Model
|
10
|
+
TIME_UNIT_VALUES = %w(second minute hour day week month year).freeze
|
11
|
+
DAY_EQUIVALENTS = {
|
12
|
+
second: 86_400,
|
13
|
+
minute: 1_440,
|
14
|
+
hour: 24,
|
15
|
+
day: 1,
|
16
|
+
week: (1.0 / 7.0)
|
17
|
+
}.freeze
|
18
|
+
|
19
|
+
extend ActiveSupport::Concern
|
20
|
+
|
21
|
+
delegate :duration_method, :end_method, :frequency_count_method,
|
22
|
+
:start_method, :time_unit_method, to: :config
|
23
|
+
|
24
|
+
included do
|
25
|
+
include DateContinuity::Errors
|
26
|
+
include DateContinuity::TimeBetween
|
27
|
+
include DateContinuity::Occurrences
|
28
|
+
|
29
|
+
alias_method "calc_#{DateContinuity.configuration.duration_method}".to_sym, :calc_duration
|
30
|
+
alias_method "calc_#{DateContinuity.configuration.end_method}".to_sym, :calc_end
|
31
|
+
alias_method "calc_#{DateContinuity.configuration.start_method}".to_sym, :calc_start
|
32
|
+
end
|
33
|
+
|
34
|
+
# Calculation Methods
|
35
|
+
|
36
|
+
def calc_end
|
37
|
+
if start_value.present? && duration_value.present?
|
38
|
+
start_value + duration_object
|
39
|
+
else
|
40
|
+
_raise_not_enough_information_error [start_method, duration_method]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def calc_start
|
45
|
+
if end_value.present? && duration_value.present?
|
46
|
+
end_value - duration_object
|
47
|
+
else
|
48
|
+
_raise_not_enough_information_error [end_method, duration_method]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def calc_duration(start_datetime = start_value, end_datetime = end_value)
|
53
|
+
# DateTime subtraction returns a fraction representing days
|
54
|
+
if start_datetime && end_datetime
|
55
|
+
duration = case time_unit_value
|
56
|
+
when "month" then months_between(start_datetime, end_datetime)
|
57
|
+
when "year" then years_between(start_datetime, end_datetime)
|
58
|
+
else
|
59
|
+
(end_datetime - start_datetime) * DAY_EQUIVALENTS[time_unit_value.to_sym]
|
60
|
+
end + 1
|
61
|
+
(duration * frequency_value).to_i
|
62
|
+
else
|
63
|
+
_raise_not_enough_information_error [start_method, end_method]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def duration_object(duration = duration_value - 1)
|
68
|
+
(duration.public_send(time_unit_value) / frequency_value)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Setter methods
|
72
|
+
|
73
|
+
def set_duration
|
74
|
+
send("#{duration_method}=", calc_duration)
|
75
|
+
self
|
76
|
+
end
|
77
|
+
|
78
|
+
def set_end
|
79
|
+
send("#{end_method}=", calc_end)
|
80
|
+
self
|
81
|
+
end
|
82
|
+
|
83
|
+
def set_start
|
84
|
+
send("#{start_method}=", calc_start)
|
85
|
+
self
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def config
|
91
|
+
@config ||= DateContinuity.configuration
|
92
|
+
end
|
93
|
+
|
94
|
+
def interval_object
|
95
|
+
1.public_send(time_unit_value) / frequency_value
|
96
|
+
end
|
97
|
+
|
98
|
+
def time_unit_less_than_day?
|
99
|
+
DAY_EQUIVALENTS[time_unit_value.to_sym] && DAY_EQUIVALENTS[time_unit_value.to_sym] < 1
|
100
|
+
end
|
101
|
+
|
102
|
+
# Getter methods
|
103
|
+
def start_value
|
104
|
+
public_send(start_method)&.to_datetime
|
105
|
+
end
|
106
|
+
|
107
|
+
def end_value
|
108
|
+
public_send(end_method)&.to_datetime
|
109
|
+
end
|
110
|
+
|
111
|
+
def duration_value
|
112
|
+
public_send(duration_method).to_i
|
113
|
+
end
|
114
|
+
|
115
|
+
def frequency_value
|
116
|
+
if respond_to?(frequency_count_method)
|
117
|
+
send(frequency_count_method).to_f
|
118
|
+
else
|
119
|
+
1.0
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def time_unit_value
|
124
|
+
send(time_unit_method).downcase.singularize.tap do |value|
|
125
|
+
_raise_unsupported_time_unit_error(value) unless TIME_UNIT_VALUES.include?(value)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DateContinuity
|
4
|
+
module Occurrences
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
def all_occurrences
|
8
|
+
if start_value.present? && duration_value.present?
|
9
|
+
Array.new(duration_value) do |count|
|
10
|
+
start_value + duration_object(count)
|
11
|
+
end
|
12
|
+
else
|
13
|
+
_raise_not_enough_information_error [start_method, duration_method]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def prev_occurrence(current_time = DateTime.current)
|
18
|
+
set_start if start_value.blank?
|
19
|
+
current_time = current_time.beginning_of_day unless time_unit_less_than_day?
|
20
|
+
return if start_value >= current_time
|
21
|
+
|
22
|
+
duration_from_start_to_now = calc_duration(start_value, current_time) - 1
|
23
|
+
start_value + duration_object(duration_from_start_to_now)
|
24
|
+
end
|
25
|
+
|
26
|
+
def next_occurrence(current_time = DateTime.current)
|
27
|
+
set_end if end_value.blank?
|
28
|
+
current_time = current_time.beginning_of_day unless time_unit_less_than_day?
|
29
|
+
return if current_time >= end_value
|
30
|
+
return start_value unless prev_occurrence(current_time)
|
31
|
+
|
32
|
+
prev_occurrence(current_time) + interval_object
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/date_continuity.rb
CHANGED
@@ -2,14 +2,19 @@
|
|
2
2
|
|
3
3
|
require "active_record"
|
4
4
|
|
5
|
+
require "date_continuity/configuration"
|
5
6
|
require "date_continuity/model"
|
6
7
|
|
7
8
|
module DateContinuity
|
8
|
-
|
9
|
-
|
9
|
+
def self.configuration
|
10
|
+
@configuration ||= DateContinuity::Configuration.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.configuration=(config)
|
14
|
+
@configuration = config
|
10
15
|
end
|
11
16
|
|
12
17
|
def self.configure
|
13
|
-
yield
|
18
|
+
yield configuration
|
14
19
|
end
|
15
20
|
end
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: date_continuity
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Merritt
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
12
|
-
dependencies:
|
11
|
+
date: 2022-03-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3'
|
13
27
|
description: Calculate fields based on a minimum set of information.
|
14
28
|
email: andrew.w.merritt@gmail.com
|
15
29
|
executables: []
|
@@ -17,7 +31,11 @@ extensions: []
|
|
17
31
|
extra_rdoc_files: []
|
18
32
|
files:
|
19
33
|
- lib/date_continuity.rb
|
20
|
-
|
34
|
+
- lib/date_continuity/configuration.rb
|
35
|
+
- lib/date_continuity/errors.rb
|
36
|
+
- lib/date_continuity/model.rb
|
37
|
+
- lib/date_continuity/occurrences.rb
|
38
|
+
homepage: https://github.com/amerritt14/date_continuity
|
21
39
|
licenses:
|
22
40
|
- MIT
|
23
41
|
metadata: {}
|