periodical 0.1.1 → 1.2.0

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 87b19fdf8f3ac679f10b1be35c766f90222c8a78e1d01b99a7e097ea6d9eb391
4
+ data.tar.gz: 22bc626fc1c7e124490c085c80012b3f5121d800c23b922e512a24875ec99299
5
+ SHA512:
6
+ metadata.gz: 823fd79066cb4b3cea6591c27710daccca0ba31d9b32d344533b969c4bc7ec48680ebbb037b6fd349eec667aedb9cb14b500b9aa0c0e715c61697ee3acc00a56
7
+ data.tar.gz: 5caf792943a767cdb6a3e39343c2636d742372b53a73a43996735b25498c2ea47c99fe810117126794390bcbfcfe6dc515863fce47a01895d13df71b7a24d6aa
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2012 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
1
+ # Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  # of this software and associated documentation files (the "Software"), to deal
@@ -21,17 +21,9 @@
21
21
  require 'date'
22
22
 
23
23
  module Periodical
24
- class Duration
25
- def initialize(from, to)
26
- @from = from
27
- @to = to
28
- end
29
-
30
- attr :from
31
- attr :to
32
-
24
+ Duration = Struct.new(:from, :to) do
33
25
  def days
34
- @to - @from
26
+ to - from
35
27
  end
36
28
 
37
29
  def weeks
@@ -39,29 +31,29 @@ module Periodical
39
31
  end
40
32
 
41
33
  def whole_months
42
- (@to.year * 12 + @to.month) - (@from.year * 12 + @from.month)
34
+ (to.year * 12 + to.month) - (from.year * 12 + from.month)
43
35
  end
44
36
 
45
37
  def months
46
38
  whole = self.whole_months
47
39
 
48
- partial_start = @from >> whole
49
- partial_end = @from >> whole + 1
40
+ partial_start = from >> whole
41
+ partial_end = from >> whole + 1
50
42
 
51
- return whole + (@to - partial_start) / (partial_end - partial_start)
43
+ return whole + (to - partial_start) / (partial_end - partial_start)
52
44
  end
53
45
 
54
46
  def whole_years
55
- @to.year - @from.year
47
+ to.year - from.year
56
48
  end
57
49
 
58
50
  def years
59
51
  whole = self.whole_years
60
52
 
61
- partial_start = @from >> (whole * 12)
62
- partial_end = @from >> ((whole + 1) * 12)
53
+ partial_start = from >> (whole * 12)
54
+ partial_end = from >> ((whole + 1) * 12)
63
55
 
64
- return whole + (@to - partial_start) / (partial_end - partial_start)
56
+ return whole + (to - partial_start) / (partial_end - partial_start)
65
57
  end
66
58
 
67
59
  # Calculate the number of periods between from and to
@@ -0,0 +1,138 @@
1
+ # Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'date'
22
+ require 'set'
23
+
24
+ module Periodical
25
+ # A filter module for backup rotation like behaviour, e.g. keep every hour for 24 hours, every day for 30 days, etc.
26
+ module Filter
27
+ # Keep count sorted objects per period.
28
+ class Period
29
+ # Given times a and b, should we prefer a?
30
+ ORDER = {
31
+ # We want `a` if `a` < `b`, i.e. it's older.
32
+ old: ->(a, b){a < b},
33
+
34
+ # We want `a` if `a` > `b`, i.e. it's newer.
35
+ new: ->(a, b){a > b}
36
+ }
37
+
38
+ # @param count the number of items we should retain.
39
+ def initialize(count)
40
+ @count = count
41
+ end
42
+
43
+ # @param order can be a key in ORDER or a lambda.
44
+ # @param block is applied to the value and should typically return a Time instance.
45
+ def filter(values, keep: :old, &block)
46
+ slots = {}
47
+
48
+ keep = ORDER.fetch(keep, keep)
49
+
50
+ values.each do |value|
51
+ time = block_given? ? yield(value) : value
52
+
53
+ granular_key = key(time)
54
+
55
+ # We filter out this value if the slot is already full and we prefer the existing value.
56
+ if existing_value = slots[granular_key]
57
+ existing_time = block_given? ? yield(existing_value) : existing_value
58
+ next if keep.call(existing_time, time)
59
+ end
60
+
61
+ slots[granular_key] = value
62
+ end
63
+
64
+ sorted_values = slots.values.sort
65
+
66
+ return sorted_values.first(@count)
67
+ end
68
+
69
+ def key(t)
70
+ raise NotImplementedError
71
+ end
72
+
73
+ def mktime(year, month=1, day=1, hour=0, minute=0, second=0)
74
+ return Time.new(year, month, day, hour, minute, second)
75
+ end
76
+
77
+ attr :count
78
+ end
79
+
80
+ class Hourly < Period
81
+ def key(t)
82
+ mktime(t.year, t.month, t.day, t.hour)
83
+ end
84
+ end
85
+
86
+ class Daily < Period
87
+ def key(t)
88
+ mktime(t.year, t.month, t.day)
89
+ end
90
+ end
91
+
92
+ class Weekly < Period
93
+ def key(t)
94
+ mktime(t.year, t.month, t.day) - (t.wday * 3600 * 24)
95
+ end
96
+ end
97
+
98
+ class Monthly < Period
99
+ def key(t)
100
+ mktime(t.year, t.month)
101
+ end
102
+ end
103
+
104
+ class Quarterly < Period
105
+ def key(t)
106
+ mktime(t.year, (t.month - 1) / 3 * 3 + 1)
107
+ end
108
+ end
109
+
110
+ class Yearly < Period
111
+ def key(t)
112
+ mktime(t.year)
113
+ end
114
+ end
115
+
116
+ class Policy
117
+ def initialize
118
+ @periods = {}
119
+ end
120
+
121
+ def <<(period)
122
+ @periods[period.class] = period
123
+ end
124
+
125
+ def filter(values, **options, &block)
126
+ filtered_values = Set.new
127
+
128
+ @periods.values.each do |period|
129
+ filtered_values += period.filter(values, **options, &block)
130
+ end
131
+
132
+ return filtered_values, (Set.new(values) - filtered_values)
133
+ end
134
+
135
+ attr :periods
136
+ end
137
+ end
138
+ end
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2012 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
1
+ # Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  # of this software and associated documentation files (the "Software"), to deal
@@ -19,83 +19,72 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  module Periodical
22
- class Period
23
- VALID_UNITS = [:days, :weeks, :months, :years]
22
+ Period = Struct.new(:count, :unit) do
23
+ VALID_UNITS = [:days, :weeks, :months, :years].freeze
24
24
 
25
- # Accepts strings in the format of "2 weeks" or "weeks"
26
- def self.parse(value)
27
- if Array === value
28
- parts = value
29
- else
30
- parts = value.to_s.split(/\s+/, 2)
31
- end
32
-
33
- if parts.size == 1
34
- if parts[0].to_i == 0
35
- count = 1
36
- unit = parts[0].to_sym
37
- else
38
- count = parts[0].to_i
39
- unit = :days
40
-
41
- if count == 7
42
- count = 1
43
- unit = :weeks
44
- end
45
-
46
- if count == 365
47
- count = 1
48
- unit = :years
49
- end
50
- end
25
+ # Plural is preferred, as in "1 or more days".
26
+ def initialize(count = 1, unit = :days)
27
+ super(count, unit)
28
+ end
29
+
30
+ def to_s
31
+ if self.count != 1
32
+ "#{self.count} #{self.unit}"
51
33
  else
52
- count = parts[0].to_i
53
- unit = parts[1].gsub(/\s+/, '_').downcase.to_sym
54
- end
55
-
56
- unless VALID_UNITS.include? unit
57
- raise ArgumentError.new("Invalid unit specified #{unit}!")
58
- end
59
-
60
- if count == 0
61
- raise ArgumentError.new("Count must be non-zero!")
34
+ self.unit.to_s
62
35
  end
36
+ end
37
+
38
+ def advance(date, multiple = 1)
39
+ raise TypeError unless date.is_a?(Date)
63
40
 
64
- return self.new(count, unit)
41
+ self.send("advance_#{unit}", date, multiple * self.count)
65
42
  end
66
43
 
67
- def initialize(count = 1, unit = :days)
68
- @count = count
69
- @unit = unit
44
+ private
45
+
46
+ def advance_days(date, count)
47
+ date + count
70
48
  end
71
49
 
72
- def to_s
73
- "#{@count} #{@unit}"
50
+ def advance_weeks(date, count)
51
+ advance_days(date, count*7)
74
52
  end
75
53
 
76
- def to_formatted_s
77
- if @count == 1
78
- @unit.to_s.gsub(/s$/, '')
79
- else
80
- to_s
81
- end
54
+ def advance_months(date, count)
55
+ date >> count
82
56
  end
83
57
 
84
- def advance(date, multiple = 1)
85
- total = multiple * @count
58
+ def advance_years(date, count)
59
+ advance_months(date, count*12)
60
+ end
61
+
62
+ class << self
63
+ # Accepts strings in the format of "2 weeks" or "weeks"
64
+ def parse(string)
65
+ parts = string.split(/\s+/, 2)
66
+
67
+ if parts.size == 1
68
+ count = 1
69
+ unit = parts[0]
70
+ else
71
+ count, unit = parts
72
+ end
73
+
74
+ self.new(count.to_i, unit.to_sym)
75
+ end
86
76
 
87
- if unit == :days
88
- date + total
89
- elsif unit == :weeks
90
- date + (total * 7)
91
- elsif unit == :months
92
- date >> total
93
- elsif unit == :years
94
- date >> (total * 12)
77
+ def load(string)
78
+ if string
79
+ string = string.strip
80
+
81
+ parse(string) unless string.empty?
82
+ end
83
+ end
84
+
85
+ def dump(period)
86
+ period.to_s if period
95
87
  end
96
88
  end
97
-
98
- attr :unit
99
- attr :count
100
89
  end
101
90
  end
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2012 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
1
+ # Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  # of this software and associated documentation files (the "Software"), to deal
@@ -19,11 +19,5 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  module Periodical
22
- module VERSION
23
- MAJOR = 0
24
- MINOR = 1
25
- TINY = 1
26
-
27
- STRING = [MAJOR, MINOR, TINY].join('.')
28
- end
22
+ VERSION = "1.2.0"
29
23
  end
metadata CHANGED
@@ -1,53 +1,76 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: periodical
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
5
- prerelease:
4
+ version: 1.2.0
6
5
  platform: ruby
7
6
  authors:
8
7
  - Samuel Williams
9
- autorequire:
8
+ autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2012-07-26 00:00:00.000000000 Z
13
- dependencies: []
14
- description:
15
- email: samuel.williams@oriontransfer.co.nz
11
+ date: 2020-06-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bake-bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bake-modernize
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description:
42
+ email:
16
43
  executables: []
17
44
  extensions: []
18
45
  extra_rdoc_files: []
19
46
  files:
47
+ - lib/periodical.rb
20
48
  - lib/periodical/duration.rb
49
+ - lib/periodical/filter.rb
21
50
  - lib/periodical/period.rb
22
51
  - lib/periodical/version.rb
23
- - lib/periodical.rb
24
- - test/helper.rb
25
- - test/test_duration.rb
26
- - test/test_period.rb
27
- - README.md
28
- homepage: http://www.oriontransfer.co.nz/
29
- licenses: []
30
- post_install_message:
52
+ homepage: https://github.com/ioquatix/periodical
53
+ licenses:
54
+ - MIT
55
+ metadata:
56
+ funding_uri: https://github.com/sponsors/ioquatix/
57
+ post_install_message:
31
58
  rdoc_options: []
32
59
  require_paths:
33
60
  - lib
34
61
  required_ruby_version: !ruby/object:Gem::Requirement
35
- none: false
36
62
  requirements:
37
- - - ! '>='
63
+ - - ">="
38
64
  - !ruby/object:Gem::Version
39
- version: '0'
65
+ version: '2.0'
40
66
  required_rubygems_version: !ruby/object:Gem::Requirement
41
- none: false
42
67
  requirements:
43
- - - ! '>='
68
+ - - ">="
44
69
  - !ruby/object:Gem::Version
45
70
  version: '0'
46
71
  requirements: []
47
- rubyforge_project:
48
- rubygems_version: 1.8.23
49
- signing_key:
50
- specification_version: 3
72
+ rubygems_version: 3.1.2
73
+ signing_key:
74
+ specification_version: 4
51
75
  summary: Periodical is a simple framework for working with durations and periods.
52
76
  test_files: []
53
- has_rdoc:
data/README.md DELETED
@@ -1,45 +0,0 @@
1
- Periodical
2
- =========
3
-
4
- * Author: Samuel G. D. Williams (<http://www.oriontransfer.co.nz>)
5
- * Copyright (C) 2012 Samuel G. D. Williams.
6
- * Released under the MIT license.
7
-
8
- Periodical is a simple framework for working with durations and periods. A duration measures a range of time bounded by a `from` date and `to` date. A period is a relative unit of time such as `4 weeks`.
9
-
10
- Basic Usage
11
- -----------
12
-
13
- The main use case for this framework involves periodic billing or accounting (e.g. calculating fortnightly rental payments).
14
-
15
- duration = Periodical::Duration.new(Date.parse("2010-01-01"), Date.parse("2010-02-01"))
16
- period = Periodical::Period.new(2, :weeks)
17
-
18
- # How many periods in the duration?
19
- count = duration / period
20
-
21
- # Calculate the date which is 2 * (2 weeks)
22
- next = period.advance(duration.from, 2)
23
-
24
- License
25
- -------
26
-
27
- Copyright (c) 2010, 2011 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
28
-
29
- Permission is hereby granted, free of charge, to any person obtaining a copy
30
- of this software and associated documentation files (the "Software"), to deal
31
- in the Software without restriction, including without limitation the rights
32
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
33
- copies of the Software, and to permit persons to whom the Software is
34
- furnished to do so, subject to the following conditions:
35
-
36
- The above copyright notice and this permission notice shall be included in
37
- all copies or substantial portions of the Software.
38
-
39
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
40
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
41
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
42
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
43
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
44
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
45
- THE SOFTWARE.
@@ -1,4 +0,0 @@
1
-
2
- $LOAD_PATH.unshift File.expand_path("../../lib/", __FILE__)
3
-
4
- require 'test/unit'
@@ -1,34 +0,0 @@
1
-
2
- require 'helper'
3
-
4
- require 'periodical'
5
-
6
- class DurationTest < Test::Unit::TestCase
7
- def setup
8
- end
9
-
10
- def test_duration
11
- duration = Periodical::Duration.new(Date.parse("2010-01-01"), Date.parse("2010-02-01"))
12
- assert_equal 31, duration.days
13
- assert_equal Rational(31, 7), duration.weeks
14
- assert_equal 1, duration.months
15
- assert_equal Rational(31, 365), duration.years
16
-
17
- assert_equal 1, duration.whole_months
18
- assert_equal 0, duration.whole_years
19
- end
20
-
21
- def test_weekly_period
22
- duration = Periodical::Duration.new(Date.parse("2010-01-01"), Date.parse("2010-02-01"))
23
- period = Periodical::Period.new(2, :weeks)
24
-
25
- assert_equal Rational(31, 14), duration / period
26
- end
27
-
28
- def test_monthly_period
29
- duration = Periodical::Duration.new(Date.parse("2010-01-01"), Date.parse("2011-03-01"))
30
- period = Periodical::Period.new(2, :months)
31
-
32
- assert_equal Rational(14, 2), duration / period
33
- end
34
- end
@@ -1,15 +0,0 @@
1
-
2
- require 'helper'
3
-
4
- require 'periodical'
5
-
6
- class PeriodTest < Test::Unit::TestCase
7
- def setup
8
- end
9
-
10
- def test_advance
11
- duration = Periodical::Duration.new(Date.parse("2010-01-01"), Date.parse("2011-01-01"))
12
- period = Periodical::Period.new(1, :months)
13
- assert_equal duration.to, period.advance(duration.from, 12)
14
- end
15
- end