periodical 0.2.0 → 1.0.0

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