Project

easy_time

0.0
The project is in a healthy, maintained state
A class that wraps the Time class and makes it easy to work with most known time values, including various time strings, automatically converting them to Time values, and perform tolerant comparisons. Several time classes, and the String class, are extended with the ".easy_time" method to perform an auto-conversion. A tolerant comparison allows for times from differing systems to be compared, even when the systems are out of sync, using the relationship operators and methods like "newer?", "older?", "same?" and "between?". A tolerant comparison for equality is where the difference of two values is less than the tolerance value (1 minute by default). The tolerance can be configured, even set to zero. Finally, all of the Time class and instance methods are available on the EasyTime class and instances.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies
 Project Readme

EasyTime

Gem Version

CircleCI

Are you tired of having to deal with many kinds of date and time objects?

Are you frustrated that comparing timestamps from different systems yields incorrect results? (Were you surprised to learn that, despite really good time sync sources, many systems aren't actually synced all that closely in time?)

Well, then, give EasyTime a try!

EasyTime accepts most of the well-known date and time objects, including RFC2822, HTTPDate, XMLSchema, ISO8601, as well as ActiveSupport::TimeWithZone strings and provides comparisons that have an adjustable tolerance. With EasyTime methods, you can reliably compare two timestamps and determine which one is "newer", "older" or the "same" withing a configurable tolerance. The default comparison tolerance is 1.minute.

In other words, if you have a time-stamp from an ActiveRecord object that is a few seconds different from a related object obtained from a 3rd-party system, (eg: AWS S3), then logically, from an application perspective, these two objects could be considered having the "same" time-stamp.

This is quite useful when one is trying to keep state synchronized between different systems. How does one know if an object is "newer" or "older" than that from another system? If the system time from the connected systems varies by a few or more seconds, then comparisons needs to have some tolerance.

Having a tolerant comparison makes "newness" and "oldness" checks easier to manage across systems with possibly varying time sources.

EasyTime objects are just like Time objects, except:

  • they auto-convert most date and time objects to Time objects
  • they provide configurable tolerant comparisons between two time objects

Even if you decide to set the configurable comparison tolerance to zero (disabling it), the auto-type conversion of most date and time objects is very useful all by itself.

Finally, this module adds a new instance method to the familiar date and time classes, to easily convert from the object to the corresponding EasyTime object:

obj.easy_time

For example, this is pretty cool:

'2010-09-08 07:06:05 +04:00'.easy_time # => EasyTime instance

So it is just as easy to convert an ISO8601 time string as it is to convert a created_at value from an ActiveRecord object:

rec.created_at.easy_time # => EasyTime instance

resp = RestClient.get service_uri + '/get_time'
time = JSON.parse(resp.body)["date_time"].easy_time

These are the currently known date and time classes the values of which will be automatically converted, enabling tolerant ("easy") comparisons:

Date
Time
DateTime
ActiveSupport::Duration
ActiveSupport::TimeWithZone

Installation

Add this line to your application's Gemfile:

gem 'easy_time'

or, add this to your application's *-gemspec.rb file:

add.dependency 'easy_time'

And then execute:

$ bundle install

Or install it yourself as:

$ gem install easy_time

Usage

require 'easy_time'

Class Methods

There are class methods and instance methods.

Creating a EasyTime Value

EasyTime.new(time)

This creates a new EasyTime value, which is essentially a wrapped Time value.

The new method uses the convert method to convert several known date and time value types into a Time value.

EasyTime.convert(timestr)

Auto-Conversion Date and Time Classes

These are the currently known class values that can be converted:

String                       # => Time.parse(string)
EasyTime                     # => EasyTime.time
Time                         # => Time
Date                         # => Time.iso8601(Date.iso8601)
DateTime                     # => Time.iso8601(DateTime.iso8601)
[yyyy,mm,dd,...]             # => Time.new(yyyy,mm,dd,...)
ActiveSupport::TimeWithZone  # => value.to_time (eg: 3.weeks.ago)
ActiveSupport::Duration      # => Time.now + duration (eg: 2.months)

Most of the date and time classes have built-in ISO8601 formatters, which allows easy conversion using the Time.iso8601 parser.

The EasyTime and Time values are passed through unchanged.

The ActiveSupport::TimeWithZone value is converted to a Time.

The ActiveSupport::Duration value is converted to a Time by adding it to Time.now.

A String value is parsed with the convert method.

Time Strings

Most modern time format strings are recognized: ISO8601, RFD2822, HTTPDate, XMLSchema, and even some natural date and time formats. Many systems use an ISO8601 string, which has date, time, and timezone components, with each variant having some variations.

With EasyTime, you don't have to know which time string format to use, you can just ask EasyTime to convert it. If it can't, you'll get an ArgumentError exception.

EasyTime Comparison Class Methods

One advantage of EasyTime values over the other time objects is that EasyTime can perform tolerant-comparisons, where the tolerance can be specified by you (the developer).

These are the EasyTime class methods for comparison:

EasyTime.newer?(t1, t2)   # => true if t1 > t2
EasyTime.older?(t1, t2)   # => true if t1 < t2
EasyTime.same?(t1, t2)    # => true if t1 == t2 with tolerance
EasyTime.compare(t1, t2)  # => t1 <=> t2 => -1, 0, 1, or nil with tolerance

Each comparison method auto-converts both time arguments into a Time value, and then compares them with tolerance. This means that if the absolute difference between t1 and t2 is less than the configured tolerance value, then the two values will be considered equal (the same).

If the two values are not tolerantly-equal, then the two values are compared with <=>, which normally returns one of three values: [-1, 0, 1]. However, since a tolerant-equality (or sameness) was already tested and ruled out, the comparison using <=> will only ever return -1 or 1.

Finally, if either time value is nil, or cannot be converted to a Time value, the result of any comparison method is also nil.

There are other methods that make use of the tolerant comparison:

EasyTime.between?(t1, t_min, t_max) # => true if t1 >= t_min and t1 <= t_max

or

EasyTime.between?(t1, time_range)   # => true if t1 is within the time_range

Easy Time Arithmetic

These class methods make it easy to do arithmetic on time values and strings.

EasyTime.add(time1, duration)     # => new EasyTime value

EasyTime.subtract(time1, time2)   # => duration

EasyTime.subtract(time, duration) # => new EasyTime value

In the above examples, time1 can be any of the usual Date and Time classes, including a time string value.

Configuring the Comparison Tolerance

As described above, the comparison tolerance value can be configured to something other than the default of DEFAULT_TIME_COMPARISON_TOLERANCE, which is currently set to 1.minute.

EasyTime.tolerance = 15.seconds # set an appropriate tolerance for our app

The above will set the tolerance value to 15 seconds. This configuration is applied across all instances of the class.

EasyTime Instance Methods

Once an EasyTime value has been created, it is basically a wrapped Time value where the comparison methods are overridden in order to apply the comparison tolerance value.

These are the instance methods:

t1 < t2   # => true if t1 is older than t2
t1 > t2   # => true if t1 is newer than t2
t1 <= t2  # => true if t1 is the same or older as t2
t1 >= t2  # => true if t1 is the same or newer as t2
t1 != t2  # => true if t1 is not the same as t2 (with tolerance)
t1 == t2  # => true if t1 is the same as t2 (with tolerance)

On each of the above, the t2 value will be automatically converted to a Time value, and tolerantly compared against t1.

In addition to the infix operators, there are the comparison instance methods:

t1.newer?(t2)   # => true if t1 is newer (>) than t2
t1.older?(t2)   # => true if t1 is older (<) then t2
t1.same?(t2)    # => true if t1 is equal (==) to t2
t1.compare(t2)  # => [-1, 0, 1] from t1 <=> t2

and

t1.between?(t_min, t_max)    #=> true if t_min >= t1 <= t_max
t1.between?([t_min..t_max])  # => true if t1 is in the range

In all the above instance methods, t2, t_min, or t_max may be any known date or time value, which is automatically converted into a Time value for the tolerant comparison.

Arithmetic operators with auto-conversion are also supported:

t1 + duration
t1 - t2
t1 - duration

The t2 value can be a date, time or even a time string. In which case, it is converted to a Time value, and the subtraction is performed between two times, with a duration result.

When arithmetic is performed with a duration value, then the result is a new updated EasyTime value.

Auto-Conversion Of Comparison Values

EasyTime values can be compared with tolerance against any other time value, including time strings, using instance methods. The receiver object must be an EasyTime object in order to enable the auto-conversion of the other time object.

The values provided on any comparison operator or method are converted to a Time value using the same convert routine. See the description of the acceptable date and time types above.

Comparison Tolerance Instance Methods

As given above, the comparison tolerance value used on the instance methods is that previously configured either on the instance, or at the class level.

It is possible, however, to dynamically provide comparison tolerance when creating a new EasyTime value, or with the with_tolerance method.

In addition to the class methods for comparison tolerance, it is possible to configure the comparison tolerance for each instance.

The new method accepts an optional tolerance: keyword value:

t1 = EasyTime.new(record.created_at, tolerance: 20.seconds)
t1.newer?(another_time) # => compared with a tolerance of 20 seconds

The comparison tolerance can also be set dynamically using .with_tolerance:

t1 = EasyTime.new(record.updated_at)
t1.with_tolerance(1.second) > another_time
t1.with_tolerance(2.seconds).newer?(another_time)

The .with_tolerance method creates a new EasyTime value with the specified tolerance, leaving the original value and configured tolerance intact.

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install.

Testing

There is a complete set of tests in the spec directory. To run all the tests:

bundle exec rake spec

To run all the tests, with simplecov code coverage analysis:

bundle exec rake spec:coverage

Then, to review the code coverage:

open coverage/index.html

Additional Documentation

The easy_time gem has been developed using yard markdown, so it is fairly simple to review the gem API documentation:

bundle exec yard
open doc/index.html

Contributing

If you wish to contribute, please fork the repo, create a new branch for your suggested improvement, and create a pull request based off of your forked repo branch. Please do not make changes in the version.

Bug reports and pull requests are welcome on GitHub at https://github.com/aks/easy_time.

License

The gem is available as open source under the terms of the MIT License.

Author

Alan K. Stebbens <aks@stebbens.org>