periodical 0.1.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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