periodical 0.2.0 → 1.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1fec77aa23c72fb853689f5157e26f7f99e1d9cc
4
- data.tar.gz: 9121f8a1b764ca0800904cee8fabc878298df225
3
+ metadata.gz: da0ceda3373a9c1cf5e18751ce263b9b0b476c16
4
+ data.tar.gz: 7d22d18c11b786f1bda8196d845f9d02d50e92da
5
5
  SHA512:
6
- metadata.gz: d4a1cfc15f47f1c6f6db749bd988ae65d2bd88927e7af67e5588787765ab64f99f28add125f8cadd1ff4dac1bc9961137023098d1f0824389d1f40dfb6068f15
7
- data.tar.gz: 5a3035e609a4abb6613ac897a282d088b3eaa571c671ab227e722c4eb85d46b2dbc88055797f82b79cfcf4ac39da7f316a5e3a6037b36f314b6f47b3a30bf56a
6
+ metadata.gz: 119d9eb7004ff7edf05efb77f27f9623ec812a1d9537d58736eeac68e3e648e7ab316f73739686e0f874aa36429fcae6f29ce038add39e8d15ebd2e253c68644
7
+ data.tar.gz: 1aadd82add21e3eb8f2c482a71d46bed39c1297e4b406e1ee604bc358ae2a362003e64bbbbe9e22cfcf2d4243c364525bf5d5a935b52e38bc300d722bd1a28d5
data/.rspec ADDED
@@ -0,0 +1,4 @@
1
+ --color
2
+ --format documentation
3
+ --backtrace
4
+ --warnings
data/.simplecov ADDED
@@ -0,0 +1,9 @@
1
+
2
+ SimpleCov.start do
3
+ add_filter "/spec/"
4
+ end
5
+
6
+ if ENV['TRAVIS']
7
+ require 'coveralls'
8
+ Coveralls.wear!
9
+ end
data/.travis.yml CHANGED
@@ -1,7 +1,12 @@
1
1
  language: ruby
2
+ sudo: false
2
3
  rvm:
3
- - "2.0"
4
- - "2.1"
4
+ - 2.1.8
5
+ - 2.2.4
6
+ - 2.3.0
7
+ - ruby-head
8
+ - rbx-2
9
+ env: COVERAGE=true
5
10
  matrix:
6
11
  allow_failures:
7
- - rvm: "1.9"
12
+ - rvm: "rbx-2"
data/Gemfile CHANGED
@@ -1,4 +1,7 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- # Specify your gem's dependencies in periodical.gemspec
4
3
  gemspec
4
+
5
+ group :test do
6
+ gem 'coveralls', platforms: [:mri]
7
+ end
data/README.md CHANGED
@@ -2,19 +2,29 @@
2
2
 
3
3
  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`.
4
4
 
5
+ [![Build Status](https://travis-ci.org/ioquatix/periodical.svg)](https://travis-ci.org/ioquatix/periodical)
6
+ [![Code Climate](https://codeclimate.com/github/ioquatix/periodical.svg)](https://codeclimate.com/github/ioquatix/periodical)
7
+ [![Coverage Status](https://coveralls.io/repos/ioquatix/periodical/badge.svg)](https://coveralls.io/r/ioquatix/periodical)
8
+
9
+ ## Motivation
10
+
11
+ The original idea for this library came from a [Python script which performed backup rotation](http://www.scottlu.com/Content/Snapfilter.html) from 2009. In particular, I thought it had a novel way to retain backups according to a given policy (e.g. one backup every year for 10 years, one backup every month for 12 months, one backup every week for 8 weeks, one backup every day for 30 days). This is done by constructing a special slot based hash structure with keys based on the date being stored. This functionality is used by [LSync](https://github.com/ioquatix/LSync) for performing backup rotation (i.e. deleting old backups).
12
+
13
+ In addition, I had a need to implement periodical billing in [Financier](https://github.com/ioquatix/financier). Not only can this gem advance a date by a given period, it can compute the number of periods between two dates. This is useful for invoicing, say, once every 6 months for a weekly or monthly service.
14
+
5
15
  ## Installation
6
16
 
7
17
  Add this line to your application's Gemfile:
8
18
 
9
- gem 'periodical'
19
+ gem 'periodical'
10
20
 
11
21
  And then execute:
12
22
 
13
- $ bundle
23
+ $ bundle
14
24
 
15
25
  Or install it yourself as:
16
26
 
17
- $ gem install periodical
27
+ $ gem install periodical
18
28
 
19
29
  ## Usage
20
30
 
@@ -41,7 +51,7 @@ The main use case for this framework involves periodic billing or accounting (e.
41
51
 
42
52
  Released under the MIT license.
43
53
 
44
- Copyright, 2010, 2014, by [Samuel G. D. Williams](http://www.codeotaku.com/samuel-williams).
54
+ Copyright, 2010, 2014, 2016, by [Samuel G. D. Williams](http://www.codeotaku.com/samuel-williams).
45
55
 
46
56
  Permission is hereby granted, free of charge, to any person obtaining a copy
47
57
  of this software and associated documentation files (the "Software"), to deal
data/Rakefile CHANGED
@@ -1,6 +1,14 @@
1
1
  require "bundler/gem_tasks"
2
2
  require "rspec/core/rake_task"
3
3
 
4
- RSpec::Core::RakeTask.new(:spec)
4
+ RSpec::Core::RakeTask.new(:spec) do |task|
5
+ if ENV['COVERAGE']
6
+ begin
7
+ require('simplecov/version')
8
+ task.rspec_opts = %w{--require simplecov}
9
+ rescue LoadError
10
+ end
11
+ end
12
+ end
5
13
 
6
14
  task :default => :spec
@@ -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
@@ -57,7 +57,7 @@ module Periodical
57
57
  end
58
58
 
59
59
  def key(t)
60
- raise ArgumentError
60
+ raise NotImplementedError
61
61
  end
62
62
 
63
63
  def mktime(year, month=1, day=1, hour=0, minute=0, second=0)
@@ -19,83 +19,70 @@
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
34
+ self.unit.to_s
54
35
  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!")
62
- end
63
-
64
- return self.new(count, unit)
65
36
  end
66
37
 
67
- def initialize(count = 1, unit = :days)
68
- @count = count
69
- @unit = unit
38
+ def advance(date, multiple = 1)
39
+ self.send("advance_#{unit}", date, multiple * self.count)
70
40
  end
71
41
 
72
- def to_s
73
- "#{@count} #{@unit}"
42
+ private
43
+
44
+ def advance_days(date, count)
45
+ date + count
74
46
  end
75
47
 
76
- def to_formatted_s
77
- if @count == 1
78
- @unit.to_s.gsub(/s$/, '')
79
- else
80
- to_s
81
- end
48
+ def advance_weeks(date, count)
49
+ date + (7 * count)
82
50
  end
83
51
 
84
- def advance(date, multiple = 1)
85
- total = multiple * @count
52
+ def advance_months(date, count)
53
+ date >> count
54
+ end
55
+
56
+ def advance_years(date, count)
57
+ date >> (12 * count)
58
+ end
59
+
60
+ class << self
61
+ # Accepts strings in the format of "2 weeks" or "weeks"
62
+ def parse(string)
63
+ parts = string.split(/\s+/, 2)
64
+
65
+ if parts.size == 1
66
+ count = 1
67
+ unit = parts[0]
68
+ else
69
+ count, unit = parts
70
+ end
71
+
72
+ self.new(count.to_i, unit.to_sym)
73
+ end
86
74
 
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)
75
+ def load(string)
76
+ if string
77
+ string = string.strip
78
+
79
+ parse(string) unless string.empty?
80
+ end
81
+ end
82
+
83
+ def dump(period)
84
+ period.to_s if period
95
85
  end
96
86
  end
97
-
98
- attr :unit
99
- attr :count
100
87
  end
101
88
  end
@@ -19,5 +19,5 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  module Periodical
22
- VERSION = "0.2.0"
22
+ VERSION = "1.0.0"
23
23
  end
data/periodical.gemspec CHANGED
@@ -19,6 +19,6 @@ Gem::Specification.new do |spec|
19
19
  spec.required_ruby_version = '>= 2.0'
20
20
 
21
21
  spec.add_development_dependency "bundler", "~> 1.3"
22
- spec.add_development_dependency "rspec", "~> 3.0.0"
22
+ spec.add_development_dependency "rspec", "~> 3.4.0"
23
23
  spec.add_development_dependency "rake"
24
24
  end
@@ -29,5 +29,36 @@ module Periodical::PeriodSpec
29
29
 
30
30
  expect(period.advance(duration.from, 12)).to be == duration.to
31
31
  end
32
+
33
+ it "should parse a singular period" do
34
+ period = Periodical::Period.parse("years")
35
+
36
+ expect(period.count).to be == 1
37
+ expect(period.unit).to be == :years
38
+ end
39
+
40
+ it "should parse a multiple count period" do
41
+ period = Periodical::Period.parse("5 days")
42
+
43
+ expect(period.count).to be == 5
44
+ expect(period.unit).to be == :days
45
+ end
46
+
47
+ it "can load nil" do
48
+ expect(Periodical::Period.load(nil)).to be == nil
49
+ expect(Periodical::Period.load("")).to be == nil
50
+ end
51
+
52
+ it "can dump nil" do
53
+ expect(Periodical::Period.dump(nil)).to be == nil
54
+ end
55
+
56
+ it "can load string" do
57
+ expect(Periodical::Period.load("5 weeks")).to be == Periodical::Period.new(5, :weeks)
58
+ end
59
+
60
+ it "can dump nil" do
61
+ expect(Periodical::Period.dump(Periodical::Period.new(5, :weeks))).to be == "5 weeks"
62
+ end
32
63
  end
33
64
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: periodical
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-06-19 00:00:00.000000000 Z
11
+ date: 2016-03-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 3.0.0
33
+ version: 3.4.0
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 3.0.0
40
+ version: 3.4.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rake
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -59,6 +59,8 @@ executables: []
59
59
  extensions: []
60
60
  extra_rdoc_files: []
61
61
  files:
62
+ - ".rspec"
63
+ - ".simplecov"
62
64
  - ".travis.yml"
63
65
  - Gemfile
64
66
  - README.md
@@ -92,7 +94,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
92
94
  version: '0'
93
95
  requirements: []
94
96
  rubyforge_project:
95
- rubygems_version: 2.2.2
97
+ rubygems_version: 2.5.2
96
98
  signing_key:
97
99
  specification_version: 4
98
100
  summary: Periodical is a simple framework for working with durations and periods.