duranged 0.0.1 → 0.0.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
  SHA1:
3
- metadata.gz: a2951d10f37ccb4455790939b928f13d81f99f66
4
- data.tar.gz: 86b329f53282b75ce0fcbd6b1cb0c391e6808544
3
+ metadata.gz: 46db5544d738bea7c2e616f797e6667e817cd9a1
4
+ data.tar.gz: e7fe1eae2d4205fc344175e7ea9f9298549e77fd
5
5
  SHA512:
6
- metadata.gz: 1e1561da9807d9564a2403c990f60ec5c297351bd1187f093c6fc83001440d1cd707eeb9a4155761bb79d629e4f5591156ce8852250fbb4fd5785a023873a56e
7
- data.tar.gz: 776ca9e9111541289f471c0482433e86b19ed24eb1f2d47faa4a962f05b709422c20bef8a7f020c994bcc2bdd9812a024107146ff1537f443f36e4092907d723
6
+ metadata.gz: c38eba26ed2b9208cd0c95bfa61224287940af2cf5f92bc0b4604bf1dc8ef762d63a9a1103d5ec38d79fe46cb59287b0a3e151a948b375b33eec24f18911e979
7
+ data.tar.gz: 2a7319101bfb44860c33ead635bac7d27634c6a0f62ab8c56057cf83dfc6734e51d0d5cdf25269c806c9a089bc9b1c0e24e54ea20c326aadff7c320ac107b9ff
@@ -5,6 +5,8 @@ require 'active_support/core_ext/numeric'
5
5
  require 'active_support/core_ext/time'
6
6
  require 'canfig'
7
7
  require 'chronic_duration'
8
+ require 'duranged/periods'
9
+ require 'duranged/formatters'
8
10
  require 'duranged/base'
9
11
  require 'duranged/duration'
10
12
  require 'duranged/interval'
@@ -1,17 +1,10 @@
1
1
  module Duranged
2
2
  class Base
3
3
  include Comparable
4
- attr_reader :value
4
+ include Periods
5
+ include Formatters
5
6
 
6
- PARTS = [:years, :months, :weeks, :days_after_weeks, :days, :hours, :minutes, :seconds]
7
- FORMATTERS = { 's' => -> (pad,with) { pad_value seconds, pad, with },
8
- 'm' => -> (pad,with) { pad_value minutes, pad, with },
9
- 'h' => -> (pad,with) { pad_value hours, pad, with },
10
- 'd' => -> (pad,with) { pad_value days, pad, with },
11
- 'D' => -> (pad,with) { pad_value days_after_weeks, pad, with },
12
- 'w' => -> (pad,with) { pad_value weeks, pad, with },
13
- 'M' => -> (pad,with) { pad_value months, pad, with },
14
- 'y' => -> (pad,with) { pad_value years, pad, with } }
7
+ attr_reader :value
15
8
 
16
9
  class << self
17
10
  def dump(obj)
@@ -35,118 +28,6 @@ module Duranged
35
28
  end
36
29
  end
37
30
 
38
- def years
39
- (value.to_f / 60 / 60 / 24 / 365.25).to_i
40
- end
41
-
42
- def months
43
- ((value - years.years) / 60 / 60 / 24 / 30).floor
44
- end
45
-
46
- def weeks
47
- (((value - months.months - years.years) / 60 / 60 / 24).floor / 7).floor
48
- end
49
-
50
- def days_after_weeks
51
- ((value - weeks.weeks - months.months - years.years) / 60 / 60 / 24).floor
52
- end
53
-
54
- def days
55
- ((value - months.months - years.years) / 60 / 60 / 24).floor
56
- end
57
-
58
- def hours
59
- ((value - days.days - months.months - years.years) / 60 / 60).floor
60
- end
61
-
62
- def minutes
63
- ((value - hours.hours - days.days - months.months - years.years) / 60).floor
64
- end
65
-
66
- def seconds
67
- (value - minutes.minutes - hours.hours - days.days - months.months - years.years).floor
68
- end
69
-
70
- def +(other)
71
- if other.is_a?(Duration) || other.is_a?(Interval)
72
- Duranged.logger.warn "Warning: You are adding a #{other.class.name} to a #{self.class.name}, which will result in a #{self.class.name}. If you would like a #{other.class.name} object to be returned you must add your #{self.class.name} to your #{other.class.name} instead." if other.is_a?(Range)
73
- self.class.new(value + other.value)
74
- elsif other.is_a?(Integer)
75
- self.class.new(value + other)
76
- else
77
- raise ArgumentError, "value must be an Integer, Duranged::Duration or Duranged::Interval"
78
- end
79
- end
80
-
81
- def -(other)
82
- if other.is_a?(Duration) || other.is_a?(Interval)
83
- Duranged.logger.warn "Warning: You are subtracting a #{other.class.name} from a #{self.class.name}, which will result in a #{self.class.name}. If you would like a #{other.class.name} object to be returned you must subtract your #{self.class.name} from your #{other.class.name} instead." if other.is_a?(Range)
84
- self.class.new(value - other.value)
85
- elsif other.is_a?(Integer)
86
- self.class.new(value - other)
87
- else
88
- raise ArgumentError, "value must be an Integer, Duranged::Duration or Duranged::Interval"
89
- end
90
- end
91
-
92
- def as_json(options=nil)
93
- value
94
- end
95
-
96
- def to_h
97
- PARTS.map do |part|
98
- [part, send(part)]
99
- end.to_h
100
- end
101
-
102
- def to_s
103
- ChronicDuration.output(value, format: :long, joiner: ', ').to_s
104
- end
105
-
106
- def strfdur(format)
107
- str = format.to_s
108
-
109
- # :years(%Y years, ):months(%N months, )%D :days
110
- PARTS.each do |part|
111
- while matches = str.match(/:(#{part})(\((.+)\))?/i) do
112
- if matches[3]
113
- # only replaces if the value is > 0, otherwise blank
114
- matched = ''
115
- depth = 0
116
- matches[3].chars.to_a.each do |char|
117
- depth += 1 if char == '('
118
- depth -= 1 if char == ')'
119
- break if depth == -1
120
- matched += char
121
- end
122
- value = send(part) > 0 ? strfdur(matched.dup) : ''
123
- str.gsub!(":#{part}(#{matched})", value)
124
- else
125
- # if no nested format was passed, replace with a singular
126
- # or plural part name as appropriate
127
- value = send(part) == 1 ? matches[1].to_s.singularize : matches[1].to_s
128
- str.gsub!(matches[0], value)
129
- end
130
- end
131
- end
132
-
133
- FORMATTERS.each do |conversion, block|
134
- while matches = str.match(/%([-_])?([0-9]+)?(#{conversion})/) do
135
- pad_with = matches[1] == '_' ? :space : :zero
136
- value = instance_exec(matches[2] || 2, pad_with, &block)
137
- value = value.to_i.to_s.lstrip if matches[1] == '-'
138
-
139
- str.gsub!(matches[0], value)
140
- end
141
- end
142
-
143
- str
144
- end
145
-
146
- def to_i
147
- value
148
- end
149
-
150
31
  def <=>(other)
151
32
  if value < other.to_i
152
33
  -1
@@ -157,8 +38,14 @@ module Duranged
157
38
  end
158
39
  end
159
40
 
160
- def call
161
- value
41
+ def +(other)
42
+ Duranged.logger.warn "Warning: You are adding a #{other.class.name} to a #{self.class.name}, which will result in a #{self.class.name}. If you would like a #{other.class.name} object to be returned you must add your #{self.class.name} to your #{other.class.name} instead." if other.is_a?(Range)
43
+ self.class.new(to_i + other.to_i)
44
+ end
45
+
46
+ def -(other)
47
+ Duranged.logger.warn "Warning: You are subtracting a #{other.class.name} from a #{self.class.name}, which will result in a #{self.class.name}. If you would like a #{other.class.name} object to be returned you must subtract your #{self.class.name} from your #{other.class.name} instead." if other.is_a?(Range)
48
+ self.class.new(to_i - other.to_i)
162
49
  end
163
50
 
164
51
  protected
@@ -166,17 +53,5 @@ module Duranged
166
53
  def parse_hash(hash)
167
54
  hash.sum { |k,v| v.to_i.send(k.to_sym) }
168
55
  end
169
-
170
- def pad_value(value, pad=2, with=:zero)
171
- send("#{with}_pad".to_sym, value, pad)
172
- end
173
-
174
- def zero_pad(value, pad=2)
175
- "%0#{pad}d" % value
176
- end
177
-
178
- def space_pad(value, pad=2)
179
- "%#{pad}d" % value
180
- end
181
56
  end
182
57
  end
@@ -0,0 +1,108 @@
1
+ module Duranged
2
+ module Formatters
3
+ PERIODS = [:years, :months, :weeks, :days_after_weeks, :days, :hours, :minutes, :seconds]
4
+ FORMATTERS = { 's' => -> (pad,with) { pad_value seconds, pad, with },
5
+ 'm' => -> (pad,with) { pad_value minutes, pad, with },
6
+ 'h' => -> (pad,with) { pad_value hours, pad, with },
7
+ 'd' => -> (pad,with) { pad_value days, pad, with },
8
+ 'D' => -> (pad,with) { pad_value days_after_weeks, pad, with },
9
+ 'w' => -> (pad,with) { pad_value weeks, pad, with },
10
+ 'M' => -> (pad,with) { pad_value months, pad, with },
11
+ 'y' => -> (pad,with) { pad_value years, pad, with } }
12
+
13
+ def as_json(options=nil)
14
+ value
15
+ end
16
+
17
+ def to_h
18
+ Hash[PERIODS.map do |part|
19
+ [part, send(part)]
20
+ end]
21
+ end
22
+
23
+ def to_s
24
+ ChronicDuration.output(value, format: :long, joiner: ', ').to_s
25
+ end
26
+
27
+ def strfdur(format)
28
+ str = format.to_s
29
+
30
+ # :years(%Y years, ):months(%N months, )%D :days
31
+ str = strfparts(str)
32
+
33
+ # %y %M %w %d %m %s
34
+ str = strfformatters(str)
35
+
36
+ str
37
+ end
38
+
39
+ def to_i
40
+ value
41
+ end
42
+
43
+ protected
44
+
45
+ def strfparts(str)
46
+ PERIODS.each do |part|
47
+ while matches = str.match(/:(#{part})(\((.+)\))?/i) do
48
+ if matches[3]
49
+ str = strfpart(str, part)
50
+ else
51
+ # if no nested format was passed, replace with a singular
52
+ # or plural part name as appropriate
53
+ value = send(part) == 1 ? matches[1].to_s.singularize : matches[1].to_s
54
+
55
+ str.gsub!(matches[0], value)
56
+ end
57
+ end
58
+ end
59
+
60
+ str
61
+ end
62
+
63
+ def strfpart(str, part)
64
+ matched = parse_match(str)
65
+ value = send(part) > 0 ? strfdur(matched.dup) : ''
66
+
67
+ str.gsub!(":#{part}(#{matched})", value)
68
+ end
69
+
70
+ def strfformatters(str)
71
+ FORMATTERS.each do |conversion, block|
72
+ while matches = str.match(/%([-_])?([0-9]+)?(#{conversion})/) do
73
+ pad_with = matches[1] == '_' ? :space : :zero
74
+ value = instance_exec(matches[2] || 2, pad_with, &block)
75
+ value = value.to_i.to_s.lstrip if matches[1] == '-'
76
+
77
+ str.gsub!(matches[0], value)
78
+ end
79
+ end
80
+
81
+ str
82
+ end
83
+
84
+ def pad_value(value, pad=2, with=:zero)
85
+ send("#{with}_pad".to_sym, value, pad)
86
+ end
87
+
88
+ def zero_pad(value, pad=2)
89
+ "%0#{pad}d" % value
90
+ end
91
+
92
+ def space_pad(value, pad=2)
93
+ "%#{pad}d" % value
94
+ end
95
+
96
+ def parse_match(match_str)
97
+ match_substr = ''
98
+ depth = 0
99
+ match_str.chars.to_a.each do |char|
100
+ depth += 1 if char == '('
101
+ depth -= 1 if char == ')'
102
+ break if depth == -1
103
+ match_substr += char
104
+ end
105
+ match_substr
106
+ end
107
+ end
108
+ end
@@ -35,6 +35,15 @@ module Duranged
35
35
  end
36
36
  end
37
37
 
38
+ def as_json(options=nil)
39
+ hash = { occurrences: occurrences }
40
+ hash[:interval] = interval.as_json(options) if interval
41
+ hash[:duration] = duration.as_json(options) if duration
42
+ hash[:range] = range.as_json(options) if range
43
+ hash
44
+ end
45
+ alias_method :to_h, :as_json
46
+
38
47
  def occurrences_string
39
48
  case occurrences
40
49
  when 0
@@ -48,15 +57,6 @@ module Duranged
48
57
  end
49
58
  end
50
59
 
51
- def as_json(options=nil)
52
- hash = { occurrences: occurrences }
53
- hash[:interval] = interval.as_json(options) if interval
54
- hash[:duration] = duration.as_json(options) if duration
55
- hash[:range] = range.as_json(options) if range
56
- hash
57
- end
58
- alias_method :to_h, :as_json
59
-
60
60
  def to_s
61
61
  return occurrences_string if occurrences == 0
62
62
 
@@ -86,21 +86,28 @@ module Duranged
86
86
  def strfocc(format)
87
87
  str = format.to_s
88
88
 
89
+ str = strfoccurrences(str)
90
+ str = strfrange(str)
91
+ str = strfdurint(str)
92
+
93
+ str
94
+ end
95
+
96
+ protected
97
+
98
+ def strfoccurrences(str)
89
99
  while matches = str.match(/:occurrence[s]?/) do
90
100
  str.gsub!(matches[0], occurrences.to_s)
91
101
  end
92
-
102
+
103
+ str
104
+ end
105
+
106
+ def strfrange(str)
93
107
  if range
94
108
  while matches = str.match(/:range(\((.+)\))?/) do
95
109
  if matches[2]
96
- matched = ''
97
- depth = 0
98
- matches[2].chars.to_a.each do |char|
99
- depth += 1 if char == '('
100
- depth -= 1 if char == ')'
101
- break if depth == -1
102
- matched += char
103
- end
110
+ matched = parse_match(matches[2])
104
111
  value = range.strfrange(matched.dup)
105
112
  str.gsub!(":range(#{matched})", value)
106
113
  else
@@ -110,6 +117,10 @@ module Duranged
110
117
  end
111
118
  end
112
119
 
120
+ str
121
+ end
122
+
123
+ def strfdurint(str)
113
124
  if duration || interval
114
125
  while matches = str.match(/:(duration|interval)(\(([^\)]+)\))/) do
115
126
  matched = send(matches[1])
@@ -120,5 +131,17 @@ module Duranged
120
131
 
121
132
  str
122
133
  end
134
+
135
+ def parse_match(match_str)
136
+ match_substr = ''
137
+ depth = 0
138
+ match_str.chars.to_a.each do |char|
139
+ depth += 1 if char == '('
140
+ depth -= 1 if char == ')'
141
+ break if depth == -1
142
+ match_substr += char
143
+ end
144
+ match_substr
145
+ end
123
146
  end
124
147
  end
@@ -0,0 +1,66 @@
1
+ module Duranged
2
+ module Periods
3
+ def years
4
+ (value.to_f / 60 / 60 / 24 / 365.25).to_i
5
+ end
6
+
7
+ def months
8
+ (seconds_after_years / 60 / 60 / 24 / 30).floor
9
+ end
10
+
11
+ def weeks
12
+ ((seconds_after_months / 60 / 60 / 24).floor / 7).floor
13
+ end
14
+
15
+ def days_after_weeks
16
+ (seconds_after_weeks / 60 / 60 / 24).floor
17
+ end
18
+
19
+ def days_after_months
20
+ (seconds_after_months / 60 / 60 / 24).floor
21
+ end
22
+
23
+ def days
24
+ # TODO make this a configuration option with the ability to default to :days_after_weeks
25
+ days_after_months
26
+ end
27
+
28
+ def hours
29
+ (seconds_after_days / 60 / 60).floor
30
+ end
31
+
32
+ def minutes
33
+ (seconds_after_hours / 60).floor
34
+ end
35
+
36
+ def seconds
37
+ seconds_after_minutes.floor
38
+ end
39
+
40
+ protected
41
+
42
+ def seconds_after_years
43
+ value - years.years
44
+ end
45
+
46
+ def seconds_after_months
47
+ seconds_after_years - months.months
48
+ end
49
+
50
+ def seconds_after_weeks
51
+ seconds_after_months - weeks.weeks
52
+ end
53
+
54
+ def seconds_after_days
55
+ seconds_after_months - days.days
56
+ end
57
+
58
+ def seconds_after_hours
59
+ seconds_after_days - hours.hours
60
+ end
61
+
62
+ def seconds_after_minutes
63
+ seconds_after_hours - minutes.minutes
64
+ end
65
+ end
66
+ end
@@ -49,23 +49,11 @@ module Duranged
49
49
  alias_method :end_time, :end_at
50
50
 
51
51
  def +(other)
52
- if other.is_a?(Duration) || other.is_a?(Interval)
53
- self.class.new(start_at, value + other.value)
54
- elsif other.is_a?(Integer)
55
- self.class.new(start_at, value + other)
56
- else
57
- raise ArgumentError, "value must be an Integer, Duranged::Duration or Duranged::Interval"
58
- end
52
+ self.class.new(start_at, to_i + other.to_i)
59
53
  end
60
54
 
61
55
  def -(other)
62
- if other.is_a?(Duration) || other.is_a?(Interval)
63
- self.class.new(start_at, value - other.value)
64
- elsif other.is_a?(Integer)
65
- self.class.new(start_at, value - other)
66
- else
67
- raise ArgumentError, "value must be an Integer, Duranged::Duration or Duranged::Interval"
68
- end
56
+ self.class.new(start_at, to_i - other.to_i)
69
57
  end
70
58
 
71
59
  def to_duration
@@ -89,23 +77,32 @@ module Duranged
89
77
  def strfrange(format)
90
78
  str = format.to_s
91
79
 
80
+ str = strfstartend(str)
81
+ str = strfduration(str)
82
+
83
+ str
84
+ end
85
+
86
+ def same_day?
87
+ start_at('%j') == end_at('%j')
88
+ end
89
+
90
+ protected
91
+
92
+ def strfstartend(str)
92
93
  while matches = str.match(/:(start_at|end_at|)\((([^\)]+))\)/) do
93
94
  str.gsub!(matches[0], send(matches[1], matches[2]))
94
95
  end
95
96
 
97
+ str
98
+ end
99
+
100
+ def strfduration(str)
96
101
  while matches = str.match(/:duration\(([^\)]+)\)/) do
97
102
  str.gsub!(matches[0], strfdur(matches[1]))
98
103
  end
99
104
 
100
105
  str
101
106
  end
102
-
103
- def call
104
- to_s
105
- end
106
-
107
- def same_day?
108
- start_at('%j') == end_at('%j')
109
- end
110
107
  end
111
108
  end
@@ -1,3 +1,3 @@
1
1
  module Duranged
2
- VERSION = '0.0.1'
2
+ VERSION = '0.0.2'
3
3
  end
@@ -1,7 +1,7 @@
1
- require 'duranged'
2
- require 'rspec'
3
1
  require 'coveralls'
4
2
  Coveralls.wear!
3
+ require 'duranged'
4
+ require 'rspec'
5
5
 
6
6
  Dir[File.join(File.dirname(__FILE__), '..', "spec/support/**/*.rb")].each { |f| require f }
7
7
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: duranged
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mark Rebec
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-10-11 00:00:00.000000000 Z
11
+ date: 2015-10-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -80,7 +80,8 @@ dependencies:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
- description: API for working with durations and time ranges.
83
+ description: A set of classes to facilitate working with and formatting durations,
84
+ intervals, time ranges and occurrences.
84
85
  email:
85
86
  - mark@markrebec.com
86
87
  executables: []
@@ -90,8 +91,10 @@ files:
90
91
  - lib/duranged.rb
91
92
  - lib/duranged/base.rb
92
93
  - lib/duranged/duration.rb
94
+ - lib/duranged/formatters.rb
93
95
  - lib/duranged/interval.rb
94
96
  - lib/duranged/occurrence.rb
97
+ - lib/duranged/periods.rb
95
98
  - lib/duranged/range.rb
96
99
  - lib/duranged/version.rb
97
100
  - spec/duranged/base_spec.rb
@@ -124,7 +127,8 @@ rubyforge_project:
124
127
  rubygems_version: 2.2.2
125
128
  signing_key:
126
129
  specification_version: 4
127
- summary: API for working with durations and time ranges.
130
+ summary: A set of classes to facilitate working with and formatting durations, intervals,
131
+ time ranges and occurrences.
128
132
  test_files:
129
133
  - spec/duranged/base_spec.rb
130
134
  - spec/duranged/duration_spec.rb