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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 22ee5b11ae14a9e2e58653f628b51f24a17d14d4ee29db368efd5694bcd7b7d1
4
- data.tar.gz: d2624c3cd86dd005c36cf93b95bb3a31d35d11be59a97b35504668d8a2317df5
3
+ metadata.gz: 53e852c09e2708211c4e1e449ebb9dcb6be20c63b4194b0499896e3109576ab1
4
+ data.tar.gz: 1c3f7b52275f5119052e18cd7b27c394d3041b863061feb4e127e2094996ac12
5
5
  SHA512:
6
- metadata.gz: 1771d384dcf7c1eb59a777de9e80ba18e3e32a3916f7105b20560c4ddba32d9abb3479ad5b7cc2b995fc0418eaeccf08d61ba63c0a7d64401066a411fac31b26
7
- data.tar.gz: 695482edc786be5d2d3a51e4a2f7df7eb74339d221c6d1a51e0ee1f93aa8955e76d37308ee07cf74810cedcc8164ef1b62f8d99df3cc8c297e12fc7aa43e7ed4
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
@@ -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
- class << self
9
- attr_accessor :duration_method, :end_method, :frequency_count_method, :start_method, :time_unit_method
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 self
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.1.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-02-18 00:00:00.000000000 Z
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
- homepage: https://rubygems.org/gems/date_continuity
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: {}