easy_time 0.1.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 +7 -0
- data/.circleci/config.yml +132 -0
- data/.gitignore +11 -0
- data/.rspec +4 -0
- data/.rubocop.yml +38 -0
- data/.travis.yml +6 -0
- data/.yardopts +4 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +137 -0
- data/Guardfile +35 -0
- data/LICENSE.txt +21 -0
- data/README.md +300 -0
- data/Rakefile +19 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/easy_time.gemspec +63 -0
- data/lib/easy_time.rb +381 -0
- data/lib/easy_time/convert.rb +148 -0
- data/lib/easy_time/version.rb +3 -0
- metadata +304 -0
data/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require "rspec/core/rake_task"
|
3
|
+
require 'yard'
|
4
|
+
|
5
|
+
RSpec::Core::RakeTask.new(:spec)
|
6
|
+
|
7
|
+
namespace :spec do
|
8
|
+
desc "run tests with code coverage"
|
9
|
+
task :coverage do
|
10
|
+
sh "CODE_COVERAGE=1 bundle exec rake spec"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
YARD::Rake::YardocTask.new do |t|
|
15
|
+
t.options += ['--title', "EasyTime #{EasyTime::VERSION} Documentation"]
|
16
|
+
t.stats_options = ['--list-undoc']
|
17
|
+
end
|
18
|
+
|
19
|
+
task :default => :spec
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "easy_time"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/easy_time.gemspec
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
require_relative 'lib/easy_time/version'
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "easy_time"
|
5
|
+
spec.version = EasyTime::VERSION
|
6
|
+
spec.authors = ["Alan Stebbens"]
|
7
|
+
spec.email = ["aks@stebbens.org"]
|
8
|
+
|
9
|
+
spec.summary = %q{Easy auto-conversion of most date and time values with tolerant-comparisons}
|
10
|
+
spec.description =
|
11
|
+
<<~'DESC'
|
12
|
+
|
13
|
+
A class that wraps the Time class and makes it easy to work with most
|
14
|
+
known time values, including various time strings, automatically
|
15
|
+
converting them to Time values, and perform tolerant comparisons.
|
16
|
+
Several time classes, and the String class, are extended with the
|
17
|
+
".easy_time" method to perform an auto-conversion. A tolerant comparison
|
18
|
+
allows for times from differing systems to be compared, even when the
|
19
|
+
systems are out of sync, using the relationship operators and methods
|
20
|
+
like "newer?", "older?", "same?" and "between?". A tolerant comparison
|
21
|
+
for equality is where the difference of two values is less than the
|
22
|
+
tolerance value (1 minute by default). The tolerance can be configured,
|
23
|
+
even set to zero. Finally, all of the Time class and instance methods
|
24
|
+
are available on the EasyTime class and instances.
|
25
|
+
|
26
|
+
DESC
|
27
|
+
spec.homepage = 'https://github.com/aks/easy_time'
|
28
|
+
spec.license = "MIT"
|
29
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
|
30
|
+
|
31
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
32
|
+
|
33
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
34
|
+
spec.metadata["source_code_uri"] = "https://github.com/aks/easy_time"
|
35
|
+
# spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
|
36
|
+
|
37
|
+
# Specify which files should be added to the gem when it is released.
|
38
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
39
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
40
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
41
|
+
end
|
42
|
+
spec.bindir = "bin"
|
43
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
44
|
+
spec.require_paths = ["lib"]
|
45
|
+
|
46
|
+
spec.add_development_dependency "bundler", "~> 2.1.4"
|
47
|
+
spec.add_development_dependency "fuubar", ">= 2.5.0"
|
48
|
+
spec.add_development_dependency "guard"
|
49
|
+
spec.add_development_dependency "guard-rspec"
|
50
|
+
spec.add_development_dependency "guard-yard"
|
51
|
+
spec.add_development_dependency "pry-byebug"
|
52
|
+
spec.add_development_dependency "rake"
|
53
|
+
spec.add_development_dependency "rspec"
|
54
|
+
spec.add_development_dependency "rspec_junit"
|
55
|
+
spec.add_development_dependency "rspec_junit_formatter"
|
56
|
+
spec.add_development_dependency "redcarpet"
|
57
|
+
spec.add_development_dependency "rubocop", ">= 0.82.0"
|
58
|
+
spec.add_development_dependency "simplecov"
|
59
|
+
spec.add_development_dependency "terminal-notifier-guard" if /Darwin/.match?(`uname -a`.strip)
|
60
|
+
spec.add_development_dependency "yard", ">= 0.9.24"
|
61
|
+
|
62
|
+
spec.add_dependency "activesupport"
|
63
|
+
end
|
data/lib/easy_time.rb
ADDED
@@ -0,0 +1,381 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'time'
|
4
|
+
require 'date'
|
5
|
+
require 'active_support'
|
6
|
+
require 'active_support/duration'
|
7
|
+
require 'active_support/time_with_zone'
|
8
|
+
require 'active_support/core_ext/numeric/time'
|
9
|
+
|
10
|
+
require 'easy_time/version'
|
11
|
+
require 'easy_time/convert'
|
12
|
+
|
13
|
+
# Are you tired of having to deal with many kinds of date and time objects?
|
14
|
+
#
|
15
|
+
# Are you frustrated that comparing timestamps from different systems yields
|
16
|
+
# incorrect results? _(Were you surprised to learn that, despite really good
|
17
|
+
# time sync sources, many systems aren't actually synced all that closely in
|
18
|
+
# time?)_
|
19
|
+
#
|
20
|
+
# Well, then, give EasyTime a try!
|
21
|
+
#
|
22
|
+
# `EasyTime` accepts most of the well-known date and time objects, including
|
23
|
+
# `RFC2822`, `HTTPDate`, `XMLSchema`, and `ISO8601` strings and provides
|
24
|
+
# comparisons that have an adjustable tolerance. With `EasyTime` methods, you
|
25
|
+
# can reliably compare two timestamps and determine which one is "newer", "older"
|
26
|
+
# or the "same" withing a configurable tolerance. The default comparison
|
27
|
+
# tolerance is 1.minute.
|
28
|
+
#
|
29
|
+
# In other words, if you have a time-stamp from an `ActiveRecord` object that is
|
30
|
+
# a few seconds different from a related object obtained from a 3rd-party system,
|
31
|
+
# (eg: AWS S3), then logically, from an application perspective, these two
|
32
|
+
# objects could be considered having the "same" time-stamp.
|
33
|
+
#
|
34
|
+
# This is quite useful when one is trying to keep state synchronized between
|
35
|
+
# different systems. How does one know if an object is "newer" or "older" than
|
36
|
+
# that from another system? If the system time from the connected systems varies
|
37
|
+
# by a few or more seconds, then comparisons needs to have some tolerance.
|
38
|
+
#
|
39
|
+
# Having a tolerant comparison makes "newness" and "oldness" checks easier to
|
40
|
+
# manage across systems with possibly varying time sources.
|
41
|
+
#
|
42
|
+
# `EasyTime` objects are just like Time objects, except:
|
43
|
+
#
|
44
|
+
# - they auto-convert most date and time objects to Time objects
|
45
|
+
# - they provide configurable tolerant comparisons between two time objects
|
46
|
+
#
|
47
|
+
# Even if you decide to set the configurable comparison tolerance to zero
|
48
|
+
# _(which disables it)_, the auto-type conversion of most date and time objects
|
49
|
+
# makes time and date comparisons and arithmetic very easy.
|
50
|
+
#
|
51
|
+
# Finally, this module adds an new instance method to the familiar date and
|
52
|
+
# time classes, to easily convert from the object to the corresponding
|
53
|
+
# `EasyTime` object:
|
54
|
+
#
|
55
|
+
# time.easy_time
|
56
|
+
#
|
57
|
+
# The conversion to an `EasyTime` can also be provided with a tolerance value:
|
58
|
+
#
|
59
|
+
# time.easy_time(tolerance: 5.seconds)
|
60
|
+
#
|
61
|
+
# These are the currently known date and time classes the values of which will
|
62
|
+
# be automatically converted to an `EasyTime` value with tolerant comparisons:
|
63
|
+
#
|
64
|
+
# Date
|
65
|
+
# Time
|
66
|
+
# EasyTime
|
67
|
+
# DateTime
|
68
|
+
# ActiveSupport::Duration
|
69
|
+
# ActiveSupport::TimeWithZone
|
70
|
+
# String
|
71
|
+
#
|
72
|
+
# The String values are examined and parsed into a `Time` value. If a string
|
73
|
+
# cannot be parsed, the `new` and `convert` methods return a nil.
|
74
|
+
#
|
75
|
+
class EasyTime
|
76
|
+
include Comparable
|
77
|
+
|
78
|
+
# we define a default tolerance below. This causes time value differences
|
79
|
+
# less than this to be considered "equal". This allows for time comparisons
|
80
|
+
# between values from different systems where the clock sync might not be
|
81
|
+
# very accurate.
|
82
|
+
#
|
83
|
+
# If this default tolerance is not desired, it can be overridden with an
|
84
|
+
# explicit tolerance setting in the singleton class instance:
|
85
|
+
#
|
86
|
+
# EasyTime.comparison_tolerance = 0
|
87
|
+
|
88
|
+
DEFAULT_TIME_COMPARISON_TOLERANCE = 1.minute
|
89
|
+
|
90
|
+
class << self
|
91
|
+
# @example These comparison methods observe the comparison tolerance
|
92
|
+
#
|
93
|
+
# EasyTime.newer?(t1, t2, tolerance: nil) # => true if t1 > t2
|
94
|
+
# EasyTime.older?(t1, t2, tolerance: nil) # => true if t1 < t2
|
95
|
+
# EasyTime.same?(t1, t2, tolerance: nil) # => true if t1 == t2
|
96
|
+
# EasyTime.compare(t1, t2, tolerance: nil) # => -1, 0, 1 (or nil)
|
97
|
+
#
|
98
|
+
# By default, the `tolerance` is nil, which means that any previously
|
99
|
+
# configured instance comparison tolerance value is used, if it is set,
|
100
|
+
# otherwise, the class comparison tolerance is used, if it set, otherwise
|
101
|
+
# the default `DEFAULT_TIME_COMPARISON_TOLERANCE` is used.
|
102
|
+
|
103
|
+
# @param time1 [Date,Time,DateTime,EasyTime,Duration,String,Array<Integer>] a time value
|
104
|
+
# @param time2 [Date,Time,DateTime,EasyTime,Duration,String,Array<Integer>] another time value
|
105
|
+
# @param tolerance [Integer] seconds of tolerance _(optional)_
|
106
|
+
# @return [Boolean] true if `time1` > `time2`, using a tolerant comparison
|
107
|
+
|
108
|
+
def newer?(time1, time2, tolerance: nil)
|
109
|
+
compare(time1, time2, tolerance: tolerance).positive?
|
110
|
+
end
|
111
|
+
|
112
|
+
# @param time1 [Date,Time,DateTime,EasyTime,Duration,String,Array<Integer>] a time value
|
113
|
+
# @param time2 [Date,Time,DateTime,EasyTime,Duration,String,Array<Integer>] another time value
|
114
|
+
# @param tolerance [Integer] seconds of tolerance _(optional)_
|
115
|
+
# @return [Boolean] true if `time1` > `time2`, using a tolerant comparison
|
116
|
+
|
117
|
+
def older?(time1, time2, tolerance: nil)
|
118
|
+
compare(time1, time2, tolerance: tolerance).negative?
|
119
|
+
end
|
120
|
+
|
121
|
+
# @param time1 [Date,Time,DateTime,EasyTime,Duration,String,Array<Integer>] a time value
|
122
|
+
# @param time2 [Date,Time,DateTime,EasyTime,Duration,String,Array<Integer>] another time value
|
123
|
+
# @param tolerance [Integer] seconds of tolerance _(optional)_
|
124
|
+
# @return [Boolean] true if `time1` > `time2`, using a tolerant comparison
|
125
|
+
|
126
|
+
def same?(time1, time2, tolerance: nil)
|
127
|
+
compare(time1, time2, tolerance: tolerance).zero?
|
128
|
+
end
|
129
|
+
|
130
|
+
|
131
|
+
# @overload between?(time1, t_min, t_max, tolerance: nil)
|
132
|
+
# @param time1 [Date,Time,DateTime,EasyTime,Duration,String,Array<Integer>] a time value
|
133
|
+
# @param t_min [Date,Time,DateTime,EasyTime,Duration,String,Array<Integer>] the minimum time
|
134
|
+
# @param t_max [Date,Time,DateTime,EasyTime,Duration,String,Array<Integer>] the maximum time
|
135
|
+
# @return [Boolean] true if `t_min <= time1 <= t_max`, using tolerant comparisons
|
136
|
+
#
|
137
|
+
# @overload between?(time1, time_range, tolerance: nil)
|
138
|
+
# @param time1 [Date,Time,DateTime,EasyTime,Duration,String,Array<Integer>] a time value
|
139
|
+
# @param time_range [Range] a range `(t_min..t_max)` of time values
|
140
|
+
# @return [Boolean] true if `time_range.min <= time1 <= time_range.max`, using tolerant comparisons
|
141
|
+
|
142
|
+
def between?(time1, t_arg, t_max=nil, tolerance: nil)
|
143
|
+
if t_arg.is_a?(Range)
|
144
|
+
t_min = t_arg.min
|
145
|
+
t_max = t_arg.max
|
146
|
+
else
|
147
|
+
t_min = t_arg
|
148
|
+
end
|
149
|
+
compare(time1, t_min, tolerance: tolerance) >= 0 &&
|
150
|
+
compare(time1, t_max, tolerance: tolerance) <= 0
|
151
|
+
end
|
152
|
+
|
153
|
+
# @param time1 [Date,Time,DateTime,EasyTime,Duration,String,Array<Integer>] a time value
|
154
|
+
# @param time2 [Date,Time,DateTime,EasyTime,Duration,String,Array<Integer>] another time value
|
155
|
+
# @param tolerance [Integer] seconds of tolerance _(optional)_
|
156
|
+
# @return [Integer] one of [-1, 0, 1] if `time1` <, ==, or > than `time2`,
|
157
|
+
# or nil if `time2` cannot be converted to a `Time` value.
|
158
|
+
|
159
|
+
def compare(time1, time2, tolerance: nil)
|
160
|
+
new(time1, tolerance: tolerance) <=> time2
|
161
|
+
end
|
162
|
+
|
163
|
+
attr_writer :comparison_tolerance
|
164
|
+
|
165
|
+
# Class methods to set the class-level comparison tolerance
|
166
|
+
#
|
167
|
+
# @example
|
168
|
+
# EasyTime.comparison_tolerance = 0 # turns off any tolerance
|
169
|
+
# EasyTime.comparison_tolerance = 5.seconds # makes times within 5 seconds the "same"
|
170
|
+
# EasyTime.comparison_tolerance = 1.minute # the default
|
171
|
+
|
172
|
+
# @return [Integer] the number of seconds of tolerance to use for "equality" tests
|
173
|
+
def comparison_tolerance
|
174
|
+
@tolerance || DEFAULT_TIME_COMPARISON_TOLERANCE
|
175
|
+
end
|
176
|
+
|
177
|
+
# @param time_string [String] a time string in one of the many known Time string formats
|
178
|
+
# @return [EasyTime]
|
179
|
+
def parse(time_string)
|
180
|
+
new(parse_string(time_string))
|
181
|
+
end
|
182
|
+
|
183
|
+
def method_missing(name, *args, &block)
|
184
|
+
if Time.respond_to?(name)
|
185
|
+
value = Time.send(name, *args, &block)
|
186
|
+
is_a_time?(value) ? new(value) : value
|
187
|
+
else
|
188
|
+
super
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def respond_to_missing?(name, include_all=false)
|
193
|
+
Time.respond_to?(name, include_all)
|
194
|
+
end
|
195
|
+
|
196
|
+
# @param value [Anything] value to test as a time-like object
|
197
|
+
# @return [Boolean] true if value is one the known Time classes, or responds to :acts_like_time?
|
198
|
+
def is_a_time?(value)
|
199
|
+
case value
|
200
|
+
when Integer, ActiveSupport::Duration
|
201
|
+
false
|
202
|
+
when Date, Time, DateTime, ActiveSupport::TimeWithZone, EasyTime
|
203
|
+
true
|
204
|
+
else
|
205
|
+
value.respond_to?(:acts_like_time?) && value.acts_like_time?
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
attr_accessor :time
|
211
|
+
attr_reader :other_time
|
212
|
+
attr_writer :comparison_tolerance
|
213
|
+
|
214
|
+
delegate :to_s, :inspect, to: :time
|
215
|
+
|
216
|
+
def initialize(*time, tolerance: nil)
|
217
|
+
@time = time.presence && convert(time.size == 1 ? time.first : time)
|
218
|
+
@comparison_tolerance = tolerance
|
219
|
+
end
|
220
|
+
|
221
|
+
# if there is no instance value, default to the class value
|
222
|
+
def comparison_tolerance
|
223
|
+
@comparison_tolerance || self.class.comparison_tolerance
|
224
|
+
end
|
225
|
+
|
226
|
+
# returns a _new_ EasyTime value with the tolerance set to value
|
227
|
+
#
|
228
|
+
# @example Example:
|
229
|
+
#
|
230
|
+
# t1 = EasyTime.new(some_time)
|
231
|
+
# t1.with_tolerance(2.seconds) <= some_other_time
|
232
|
+
#
|
233
|
+
# @param value [Integer] a number of seconds to use as the comparison tolerance
|
234
|
+
# @return [EasyTime] a new EasyTime value with the given tolerance
|
235
|
+
|
236
|
+
def with_tolerance(value)
|
237
|
+
dup.tap { |time| time.comparison_tolerance = value }
|
238
|
+
end
|
239
|
+
|
240
|
+
# @example Comparison examples
|
241
|
+
# time1 = EasyTime.new(a_time, tolerance: nil)
|
242
|
+
# time1.newer?(time2)
|
243
|
+
# time1.older?(time2)
|
244
|
+
# time1.same?(time2)
|
245
|
+
# time1.compare(time2) # => -1, 0, 1
|
246
|
+
|
247
|
+
# @param time2 [String,Date,Time,DateTime,Duration,Array<Integer>] another time value
|
248
|
+
# @return [Boolean] true if `self` > `time2`
|
249
|
+
|
250
|
+
def newer?(time2)
|
251
|
+
self > time2
|
252
|
+
end
|
253
|
+
|
254
|
+
# @param time2 [String,Date,Time,DateTime,Duration,Array<Integer>] another time value
|
255
|
+
# @return [Boolean] true if `self` < `time2`
|
256
|
+
|
257
|
+
def older?(time2)
|
258
|
+
self < time2
|
259
|
+
end
|
260
|
+
|
261
|
+
# @param time2 [String,Date,Time,DateTime,Duration,Array<Integer>] another time value
|
262
|
+
# @return [Boolean] true if `self` == `time2`
|
263
|
+
|
264
|
+
def same?(time2)
|
265
|
+
self == time2
|
266
|
+
end
|
267
|
+
alias eql? same?
|
268
|
+
|
269
|
+
# @param time2 [String,Date,Time,DateTime,Duration,Array<Integer>] another time value
|
270
|
+
# @return [Boolean] true if `self` != `time2`
|
271
|
+
|
272
|
+
def different?(time2)
|
273
|
+
self != time2
|
274
|
+
end
|
275
|
+
|
276
|
+
# @param time2 [String,Date,Time,DateTime,Duration,Array<Integer>] another time value
|
277
|
+
# @return [Integer] one of the values: [-1, 0, 1] if `self` [<, ==, >] `time2`,
|
278
|
+
# or nil if `time2` cannot be converted to a `Time` value
|
279
|
+
|
280
|
+
def compare(time2, tolerance: nil)
|
281
|
+
self.comparison_tolerance = tolerance if tolerance
|
282
|
+
self <=> time2
|
283
|
+
end
|
284
|
+
|
285
|
+
# compare with automatic type-conversion and tolerance
|
286
|
+
# @return [Integer] one of [-1, 0, 1] or nil
|
287
|
+
|
288
|
+
def <=>(other)
|
289
|
+
diff = self - other # note: this has a side-effect of setting @other_time
|
290
|
+
if diff && diff.abs.to_i <= comparison_tolerance.to_i
|
291
|
+
0
|
292
|
+
elsif diff
|
293
|
+
time <=> other_time
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
# compare a time against a min and max date pair, or against a time Range value.
|
298
|
+
# @overload between?(t_min, t_max, tolerance: nil)
|
299
|
+
# @param t_min [Date,Time,DateTime,EasyTime,Duration,String,Array<Integer>] the minimum time
|
300
|
+
# @param t_max [Date,Time,DateTime,EasyTime,Duration,String,Array<Integer>] the maximum time
|
301
|
+
# @param tolerance [Integer] the optional amount of seconds of tolerance to use in the comparison
|
302
|
+
# @return [Boolean] true if `t_min <= self.time <= t_max`, using tolerant comparisons
|
303
|
+
#
|
304
|
+
# @overload between?(time_range, tolerance: nil)
|
305
|
+
# @param time_range [Range] a range `(t_min..t_max)` of time values
|
306
|
+
# @param tolerance [Integer] the optional amount of seconds of tolerance to use in the comparison
|
307
|
+
# @return [Boolean] true if `time_range.min <= self.time <= time_range.max`, using tolerant comparisons
|
308
|
+
|
309
|
+
def between?(t_arg, t_max = nil)
|
310
|
+
if t_arg.is_a?(Range)
|
311
|
+
t_min = t_arg.min
|
312
|
+
t_max = t_arg.max
|
313
|
+
else
|
314
|
+
t_min = t_arg
|
315
|
+
end
|
316
|
+
compare(t_min) >= 0 && compare(t_max) <= 0
|
317
|
+
end
|
318
|
+
|
319
|
+
# @param duration [Integer] seconds to add to the EasyTime value
|
320
|
+
# @return [EasyTime] updated date and time value
|
321
|
+
def +(duration)
|
322
|
+
dup.tap { |eztime| eztime.time += duration }
|
323
|
+
end
|
324
|
+
|
325
|
+
# Subtract a value from an EasyTime. If the value is an integer, it is treated
|
326
|
+
# as seconds. If the value is any of the Date, DateTime, Time, EasyTime, or a String-
|
327
|
+
# formatted date/time, it is subtracted from the EasyTime value resulting in an integer
|
328
|
+
# duration.
|
329
|
+
# @param other [Date,Time,DateTime,EasyTime,Duration,String,Integer]
|
330
|
+
# a date/time value, a duration, or an Integer
|
331
|
+
# @return [EasyTime,Integer] updated time _(time - duration)_ or duration _(time - time)_
|
332
|
+
def -(other)
|
333
|
+
@other_time = convert(other, false)
|
334
|
+
if is_a_time?(other_time)
|
335
|
+
time - other_time
|
336
|
+
elsif other_time
|
337
|
+
dup.tap { |eztime| eztime.time -= other_time }
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
def acts_like_time?
|
342
|
+
true
|
343
|
+
end
|
344
|
+
|
345
|
+
private
|
346
|
+
|
347
|
+
def convert(datetime, coerce = true)
|
348
|
+
self.class.convert(datetime, coerce)
|
349
|
+
end
|
350
|
+
|
351
|
+
# intercept any time methods so they can wrap the time-like result in a new EasyTime object.
|
352
|
+
def method_missing(name, *args, &block)
|
353
|
+
if time.respond_to?(name)
|
354
|
+
value = time.send(name, *args, &block)
|
355
|
+
is_a_time?(value) ? dup.tap { |eztime| eztime.time = value } : value
|
356
|
+
else
|
357
|
+
super
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
def respond_to_missing?(method_name, include_all=false)
|
362
|
+
time.respond_to?(name, include_all)
|
363
|
+
end
|
364
|
+
|
365
|
+
def is_a_time?(value)
|
366
|
+
self.class.is_a_time?(value)
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
# Extend the known date and time classes _(including EasyTime itself!)_
|
371
|
+
module EasyTimeExtensions
|
372
|
+
def easy_time(tolerance: nil)
|
373
|
+
EasyTime.new(self, tolerance: tolerance)
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
[EasyTime, Date, Time, DateTime, ActiveSupport::Duration, ActiveSupport::TimeWithZone, String].each do |klass|
|
378
|
+
klass.include(EasyTimeExtensions)
|
379
|
+
end
|
380
|
+
|
381
|
+
# end of EasyTime
|