time_calc 0.0.1
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 +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +149 -0
- data/lib/time_calc.rb +273 -0
- data/lib/time_calc/diff.rb +291 -0
- data/lib/time_calc/dst.rb +54 -0
- data/lib/time_calc/op.rb +70 -0
- data/lib/time_calc/sequence.rb +130 -0
- data/lib/time_calc/types.rb +58 -0
- data/lib/time_calc/units.rb +57 -0
- data/lib/time_calc/value.rb +223 -0
- data/lib/time_calc/version.rb +6 -0
- metadata +211 -0
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class TimeCalc
|
4
|
+
# @private
|
5
|
+
# Tries to encapsulate all the differences between Time, Date, DateTime
|
6
|
+
module Types
|
7
|
+
extend self
|
8
|
+
|
9
|
+
ATTRS = {
|
10
|
+
Time => %i[year month day hour min sec subsec utc_offset],
|
11
|
+
Date => %i[year month day],
|
12
|
+
DateTime => %i[year month day hour min sec sec_fraction zone]
|
13
|
+
}.freeze
|
14
|
+
|
15
|
+
def compatible?(v1, v2)
|
16
|
+
[v1, v2].all?(Date) || [v1, v2].all?(Time)
|
17
|
+
end
|
18
|
+
|
19
|
+
def compare(v1, v2)
|
20
|
+
compatible?(v1, v2) ? v1 <=> v2 : v1.to_time <=> v2.to_time
|
21
|
+
end
|
22
|
+
|
23
|
+
def convert(v, klass)
|
24
|
+
return v if v.class == klass
|
25
|
+
|
26
|
+
v.public_send("to_#{klass.name.downcase}")
|
27
|
+
end
|
28
|
+
|
29
|
+
def merge_time(value, **attrs)
|
30
|
+
_merge(value, **attrs)
|
31
|
+
.tap { |h|
|
32
|
+
h[:sec] += h.delete(:subsec)
|
33
|
+
h[:utc_offset] = value.zone if value.zone.respond_to?(:utc_to_local) # Ruby 2.6 real timezones
|
34
|
+
}
|
35
|
+
.values.then { |components| Time.new(*components) }
|
36
|
+
end
|
37
|
+
|
38
|
+
def merge_date(value, **attrs)
|
39
|
+
_merge(value, **attrs).values.then { |components| Date.new(*components) }
|
40
|
+
end
|
41
|
+
|
42
|
+
def merge_datetime(value, **attrs)
|
43
|
+
# When we truncate, we use :subsec key as a sign to zeroefy second fractions
|
44
|
+
attrs[:sec_fraction] ||= attrs.delete(:subsec) if attrs.key?(:subsec)
|
45
|
+
|
46
|
+
_merge(value, **attrs)
|
47
|
+
.tap { |h| h[:sec] += h.delete(:sec_fraction) }
|
48
|
+
.values.then { |components| DateTime.new(*components) }
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def _merge(value, attrs)
|
54
|
+
attr_names = ATTRS.fetch(value.class)
|
55
|
+
attr_names.to_h { |u| [u, value.public_send(u)] }.merge(**attrs.slice(*attr_names))
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class TimeCalc
|
4
|
+
# @private
|
5
|
+
# Unit-related constants and utilities for fetching their values
|
6
|
+
module Units
|
7
|
+
ALL = %i[year month week day hour min sec].freeze
|
8
|
+
NATURAL = %i[year month day hour min sec].freeze
|
9
|
+
STRUCTURAL = %i[year month day hour min sec subsec].freeze
|
10
|
+
|
11
|
+
SYNONYMS = {
|
12
|
+
second: :sec,
|
13
|
+
seconds: :sec,
|
14
|
+
minute: :min,
|
15
|
+
minutes: :min,
|
16
|
+
hours: :hour,
|
17
|
+
days: :day,
|
18
|
+
weeks: :week,
|
19
|
+
months: :month,
|
20
|
+
years: :year
|
21
|
+
}.freeze
|
22
|
+
|
23
|
+
DEFAULTS = {
|
24
|
+
month: 1,
|
25
|
+
day: 1,
|
26
|
+
hour: 0,
|
27
|
+
min: 0,
|
28
|
+
sec: 0,
|
29
|
+
subsec: 0
|
30
|
+
}.freeze
|
31
|
+
|
32
|
+
MULTIPLIERS = {
|
33
|
+
sec: 1,
|
34
|
+
min: 60,
|
35
|
+
hour: 60 * 60,
|
36
|
+
day: 24 * 60 * 60
|
37
|
+
}.freeze
|
38
|
+
|
39
|
+
def self.call(unit)
|
40
|
+
SYNONYMS.fetch(unit, unit)
|
41
|
+
.tap { |u| ALL.include?(u) or fail ArgumentError, "Unsupported unit: #{u}" }
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.multiplier_for(klass, unit, precise: false)
|
45
|
+
res = MULTIPLIERS.fetch(unit)
|
46
|
+
d = MULTIPLIERS.fetch(:day)
|
47
|
+
case klass.name
|
48
|
+
when 'Time'
|
49
|
+
res
|
50
|
+
when 'DateTime'
|
51
|
+
res / d.to_f
|
52
|
+
when 'Date'
|
53
|
+
precise ? res / d.to_f : res / d
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,223 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'backports/2.6.0/enumerable/to_h'
|
4
|
+
require 'backports/2.6.0/array/to_h'
|
5
|
+
require 'backports/2.6.0/hash/to_h'
|
6
|
+
require 'backports/2.6.0/kernel/then'
|
7
|
+
require 'backports/2.5.0/hash/slice'
|
8
|
+
require 'backports/2.5.0/enumerable/all'
|
9
|
+
|
10
|
+
class TimeCalc
|
11
|
+
# Wrapper (one can say "monad") around date/time value, allowing to perform several TimeCalc
|
12
|
+
# operations in a chain.
|
13
|
+
#
|
14
|
+
# @example
|
15
|
+
# TimeCalc.wrap(Time.parse('2019-06-01 14:50')).+(1, :year).-(1, :month).round(:week).unwrap
|
16
|
+
# # => 2020-05-04 00:00:00 +0300
|
17
|
+
#
|
18
|
+
class Value
|
19
|
+
# @private
|
20
|
+
TIMEY = proc { |t| t.respond_to?(:to_time) }
|
21
|
+
|
22
|
+
# @private
|
23
|
+
def self.wrap(value)
|
24
|
+
case value
|
25
|
+
when Time, Date, DateTime
|
26
|
+
new(value)
|
27
|
+
when Value
|
28
|
+
value
|
29
|
+
when TIMEY
|
30
|
+
wrap(value.to_time)
|
31
|
+
else
|
32
|
+
fail ArgumentError, "Unsupported value: #{value}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# @private
|
37
|
+
attr_reader :internal
|
38
|
+
|
39
|
+
# @note
|
40
|
+
# Prefer {TimeCalc.wrap} to create a Value.
|
41
|
+
# @param time_or_date [Time, Date, DateTime]
|
42
|
+
def initialize(time_or_date)
|
43
|
+
@internal = time_or_date
|
44
|
+
end
|
45
|
+
|
46
|
+
# @return [Time, Date, DateTime] The value of the original type that was wrapped and processed
|
47
|
+
def unwrap
|
48
|
+
@internal
|
49
|
+
end
|
50
|
+
|
51
|
+
# @private
|
52
|
+
def inspect
|
53
|
+
'#<%s(%s)>' % [self.class, internal]
|
54
|
+
end
|
55
|
+
|
56
|
+
# @return [1, 0, -1]
|
57
|
+
def <=>(other)
|
58
|
+
return unless other.is_a?(self.class)
|
59
|
+
|
60
|
+
Types.compare(internal, other.internal)
|
61
|
+
end
|
62
|
+
|
63
|
+
include Comparable
|
64
|
+
|
65
|
+
Units::ALL.each { |u| define_method(u) { internal.public_send(u) } }
|
66
|
+
|
67
|
+
def dst?
|
68
|
+
return unless internal.respond_to?(:dst?)
|
69
|
+
|
70
|
+
internal.dst?
|
71
|
+
end
|
72
|
+
|
73
|
+
# Produces new value with some components of underlying time/date replaced.
|
74
|
+
#
|
75
|
+
# @example
|
76
|
+
# TimeCalc.from(Date.parse('2018-06-01')).merge(year: 1983)
|
77
|
+
# # => #<TimeCalc::Value(1983-06-01)>
|
78
|
+
#
|
79
|
+
# @param attrs [Hash<Symbol => Integer>]
|
80
|
+
# @return [Value]
|
81
|
+
def merge(**attrs)
|
82
|
+
Value.new(Types.public_send("merge_#{internal.class.name.downcase}", internal, **attrs))
|
83
|
+
end
|
84
|
+
|
85
|
+
# Truncates all time components lower than `unit`. In other words, "floors" (rounds down)
|
86
|
+
# underlying date/time to nearest `unit`.
|
87
|
+
#
|
88
|
+
# @example
|
89
|
+
# TimeCalc.from(Time.parse('2018-06-23 12:30')).floor(:month)
|
90
|
+
# # => #<TimeCalc::Value(2018-06-01 00:00:00 +0300)>
|
91
|
+
#
|
92
|
+
# @param unit [Symbol]
|
93
|
+
# @return Value
|
94
|
+
def truncate(unit)
|
95
|
+
unit = Units.(unit)
|
96
|
+
return floor_week if unit == :week
|
97
|
+
|
98
|
+
Units::STRUCTURAL
|
99
|
+
.drop_while { |u| u != unit }
|
100
|
+
.drop(1)
|
101
|
+
.then { |keys| Units::DEFAULTS.slice(*keys) }
|
102
|
+
.then(&method(:merge))
|
103
|
+
end
|
104
|
+
|
105
|
+
alias floor truncate
|
106
|
+
|
107
|
+
# Ceils (rounds up) underlying date/time to nearest `unit`.
|
108
|
+
#
|
109
|
+
# @example
|
110
|
+
# TimeCalc.from(Time.parse('2018-06-23 12:30')).ceil(:month)
|
111
|
+
# # => #<TimeCalc::Value(2018-07-01 00:00:00 +0300)>
|
112
|
+
#
|
113
|
+
# @param unit [Symbol]
|
114
|
+
# @return [Value]
|
115
|
+
def ceil(unit)
|
116
|
+
floor(unit).then { |res| res == self ? res : res.+(1, unit) }
|
117
|
+
end
|
118
|
+
|
119
|
+
# Rounds up or down underlying date/time to nearest `unit`.
|
120
|
+
#
|
121
|
+
# @example
|
122
|
+
# TimeCalc.from(Time.parse('2018-06-23 12:30')).round(:month)
|
123
|
+
# # => #<TimeCalc::Value(2018-07-01 00:00:00 +0300)>
|
124
|
+
#
|
125
|
+
# @param unit [Symbol]
|
126
|
+
# @return Value
|
127
|
+
def round(unit)
|
128
|
+
f, c = floor(unit), ceil(unit)
|
129
|
+
|
130
|
+
(internal - f.internal).abs < (internal - c.internal).abs ? f : c
|
131
|
+
end
|
132
|
+
|
133
|
+
# Add `<span units>` to wrapped value.
|
134
|
+
#
|
135
|
+
# @param span [Integer]
|
136
|
+
# @param unit [Symbol]
|
137
|
+
# @return [Value]
|
138
|
+
def +(span, unit)
|
139
|
+
unit = Units.(unit)
|
140
|
+
case unit
|
141
|
+
when :sec, :min, :hour, :day
|
142
|
+
plus_seconds(span, unit)
|
143
|
+
when :week
|
144
|
+
self.+(span * 7, :day)
|
145
|
+
when :month
|
146
|
+
plus_months(span)
|
147
|
+
when :year
|
148
|
+
merge(year: year + span)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# @overload -(span, unit)
|
153
|
+
# Subtracts `span units` from wrapped value.
|
154
|
+
# @param span [Integer]
|
155
|
+
# @param unit [Symbol]
|
156
|
+
# @return [Value]
|
157
|
+
# @overload -(date_or_time)
|
158
|
+
# Produces {Diff}, allowing to calculate structured difference between two points in time.
|
159
|
+
# @param date_or_time [Date, Time, DateTime]
|
160
|
+
# @return [Diff]
|
161
|
+
# Subtracts `span units` from wrapped value.
|
162
|
+
def -(span_or_other, unit = nil)
|
163
|
+
unit.nil? ? Diff.new(self, span_or_other) : self.+(-span_or_other, unit)
|
164
|
+
end
|
165
|
+
|
166
|
+
# Produces {Sequence} from this value to `date_or_time`
|
167
|
+
#
|
168
|
+
# @param date_or_time [Date, Time, DateTime]
|
169
|
+
# @return [Sequence]
|
170
|
+
def to(date_or_time)
|
171
|
+
Sequence.new(from: self).to(date_or_time)
|
172
|
+
end
|
173
|
+
|
174
|
+
# Produces endless {Sequence} from this value, with step specified.
|
175
|
+
#
|
176
|
+
# @overload step(unit)
|
177
|
+
# Shortcut for `step(1, unit)`
|
178
|
+
# @param unit [Symbol]
|
179
|
+
# @overload step(span, unit)
|
180
|
+
# @param span [Integer]
|
181
|
+
# @param unit [Symbol]
|
182
|
+
# @return [Sequence]
|
183
|
+
def step(span, unit = nil)
|
184
|
+
span, unit = 1, span if unit.nil?
|
185
|
+
Sequence.new(from: self).step(span, unit)
|
186
|
+
end
|
187
|
+
|
188
|
+
# Produces {Sequence} from this value to `this + <span units>`
|
189
|
+
#
|
190
|
+
# @param span [Integer]
|
191
|
+
# @param unit [Symbol]
|
192
|
+
# @return [Sequence]
|
193
|
+
def for(span, unit)
|
194
|
+
to(self.+(span, unit))
|
195
|
+
end
|
196
|
+
|
197
|
+
# @private
|
198
|
+
def convert(klass)
|
199
|
+
return dup if internal.class == klass
|
200
|
+
|
201
|
+
Value.new(Types.convert(internal, klass))
|
202
|
+
end
|
203
|
+
|
204
|
+
private
|
205
|
+
|
206
|
+
def floor_week
|
207
|
+
extra_days = (internal.wday.nonzero? || 7) - 1
|
208
|
+
floor(:day).-(extra_days, :days)
|
209
|
+
end
|
210
|
+
|
211
|
+
def plus_months(span)
|
212
|
+
target = month + span.to_i
|
213
|
+
m = (target - 1) % 12 + 1
|
214
|
+
dy = (target - 1) / 12
|
215
|
+
merge(year: year + dy, month: m)
|
216
|
+
end
|
217
|
+
|
218
|
+
def plus_seconds(span, unit)
|
219
|
+
Value.new(internal + span * Units.multiplier_for(internal.class, unit))
|
220
|
+
.then { |res| unit == :day ? DST.fix_value(res, self) : res }
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
metadata
ADDED
@@ -0,0 +1,211 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: time_calc
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Victor Shepelev
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-07-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: backports
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 3.15.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 3.15.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rubocop
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.72.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.72.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rubocop-rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 1.17.1
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 1.17.1
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.8'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.8'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec-its
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '1'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '1'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: saharspec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: simplecov
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0.9'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0.9'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: tzinfo
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: rake
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: rubygems-tasks
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: yard
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
description: |2
|
168
|
+
TimeCalc is a library for idiomatic time calculations, like "plus N days", "floor to month start",
|
169
|
+
"how many hours between those dates", "sequence of months from this to that". It intends to
|
170
|
+
be small and easy to remember without any patching of core classes.
|
171
|
+
email: zverok.offline@gmail.com
|
172
|
+
executables: []
|
173
|
+
extensions: []
|
174
|
+
extra_rdoc_files: []
|
175
|
+
files:
|
176
|
+
- LICENSE.txt
|
177
|
+
- README.md
|
178
|
+
- lib/time_calc.rb
|
179
|
+
- lib/time_calc/diff.rb
|
180
|
+
- lib/time_calc/dst.rb
|
181
|
+
- lib/time_calc/op.rb
|
182
|
+
- lib/time_calc/sequence.rb
|
183
|
+
- lib/time_calc/types.rb
|
184
|
+
- lib/time_calc/units.rb
|
185
|
+
- lib/time_calc/value.rb
|
186
|
+
- lib/time_calc/version.rb
|
187
|
+
homepage: https://github.com/zverok/time_calc
|
188
|
+
licenses:
|
189
|
+
- MIT
|
190
|
+
metadata: {}
|
191
|
+
post_install_message:
|
192
|
+
rdoc_options: []
|
193
|
+
require_paths:
|
194
|
+
- lib
|
195
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
196
|
+
requirements:
|
197
|
+
- - ">="
|
198
|
+
- !ruby/object:Gem::Version
|
199
|
+
version: 2.3.0
|
200
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
201
|
+
requirements:
|
202
|
+
- - ">="
|
203
|
+
- !ruby/object:Gem::Version
|
204
|
+
version: '0'
|
205
|
+
requirements: []
|
206
|
+
rubyforge_project:
|
207
|
+
rubygems_version: 2.6.14
|
208
|
+
signing_key:
|
209
|
+
specification_version: 4
|
210
|
+
summary: Easy time math
|
211
|
+
test_files: []
|