Skip to content

Commit

Permalink
Merge pull request #20 from davesag/15_new_rspec_matcher
Browse files Browse the repository at this point in the history
Enhanced rspec matchers and validator.
  • Loading branch information
davesag committed May 13, 2015
2 parents 44acc11 + a415872 commit a269d9b
Show file tree
Hide file tree
Showing 9 changed files with 118 additions and 32 deletions.
25 changes: 20 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ A collection of useful utilities for projects that have to deal with dates, time
## Features

1. A base method called `is_zulu_time?`,
2. An `rspec` matcher called `be_zulu_time`,
2. `rspec` matchers `be_zulu_time`, `be_an_iso_formatted_date`, and `be_an_iso_formatted_time`
3. An `ActiveModel` validator called `zulu_time`, and
4. An `ActiveModel::Serializer` helper method called `enforce_zulu_time`.

Expand All @@ -18,7 +18,7 @@ Each feature can be required individually so you can use the `rspec` matcher, `A
1. `Ruby`, `Bundler`, etc. The usual suspects. (Tested against Ruby 2.0.0 and up)
2. `rspec` if you `require 'datetime_helper/rspec'`
3. `active_model` if you `require 'datetime_helper/active_model'`
4. `active_model_serializers` if you `require 'datetime_helper/active_model_serialiser'`
4. `active_model_serializer` if you `require 'datetime_helper/active_model_serialiser'`

## TL;DR

Expand All @@ -36,10 +36,13 @@ Put this in your `Gemfile`
gem 'datetime_helper'
```

### Testing a string to see if it is a Zulu Time formatted string
### Basic Zulu Time checking

You can also use this to test that a `DateTime`, or `Time`, are at `UTC+0`, or
that a `String` is formatted in correct Zulu Time format.

```ruby
DatetimeHelper.is_zulu_time? "some_string"
DatetimeHelper.is_zulu_time? something
```

### Using the `be_zulu_time` matcher in your `RSpec` tests
Expand All @@ -60,6 +63,15 @@ And put this in your `rspec` tests.
it {expect(subject[:deleted_at]).to be_zulu_time}
```

This can be used to expect that a `DateTime`, or `Time`, are at `UTC+0`, or that a `String` is formatted in Zulu Time.

#### ISO Times and Dates

Similarly to the above you can also test Time and Date strings with

* `be_an_iso_formatted_time`, and
* `be_an_iso_formatted_date`

### Validating `ActiveModel` fields to ensure they hold UTC+0 `datetime` data

First be sure you `require 'datetime_helper/active_model'`
Expand All @@ -72,7 +84,9 @@ include DatetimeHelper::Validators
validates :updated_at, zulu_time: true
```

This will verify that a `Time` is supplied at `UTC+0`, or that a `DateTime` has `.zone == "+00:00"`.
This will verify that a `Time` is supplied at `UTC+0`,
or that a `DateTime` has `.zone == "+00:00"`,
or that a `String` is in Zulu Time format.

### Enforcing `ActiveModel::Serializer` Zulu Time string formats

Expand Down Expand Up @@ -128,3 +142,4 @@ The `Datetime Helper` is © 2015 Westfield Labs and is available for use under t
|`0.0.2`| Added the `ActiveModel` validator |
|`0.0.3`| Added the `ActiveModel::Serializer` helper |
|`1.0.0`| Cleaned up for first official release |
|`1.0.1`| Enhanced matchers, and validator |
23 changes: 20 additions & 3 deletions lib/datetime_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,25 @@ module DatetimeHelper

ZULU_TIME_PATTERN = /^(\d{4})-([0-1][0-9])-([0-3]\d{1})T([0-2]\d{1}):([0-5]\d{1}):([0-5]\d{1})(\.[0-9]{1,3})?Z$/

def self.is_zulu_time?(time_string)
!(time_string =~ ZULU_TIME_PATTERN).nil?
end
class << self
def is_zulu_time?(something)
return is_zulu_time_string?(something) if something.is_a? String
return is_zulu_time_datetime?(something) if something.is_a? DateTime
return is_zulu_time_time?(something) if something.is_a? Time
false
end

def is_zulu_time_string?(time_string)
!(time_string =~ ZULU_TIME_PATTERN).nil?
end

def is_zulu_time_datetime?(datetime)
datetime.zone == "+00:00"
end

def is_zulu_time_time?(time)
time.utc?
end

end
end
17 changes: 17 additions & 0 deletions lib/datetime_helper/matchers/iso_date_and_time_rspec_matchers.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module DatetimeHelper
module Matchers

RSpec::Matchers.define :be_an_iso_formatted_time do |expected|
match do |time|
!(time =~ /\d{2}:\d{2}/).nil?
end
end

RSpec::Matchers.define :be_an_iso_formatted_date do |expected|
match do |date|
!(date =~ /\d{4}\-\d{2}\-\d{2}/).nil?
end
end

end
end
1 change: 1 addition & 0 deletions lib/datetime_helper/rspec.rb
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
require_relative "../datetime_helper"
require_relative "matchers/zulu_time_rspec_matcher"
require_relative "matchers/iso_date_and_time_rspec_matchers"
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,10 @@ module Validators
class ZuluTimeValidator < ActiveModel::EachValidator

def validate_each(record, attribute, value)
return if value.is_a? DateTime and utc_datetime? value
return if value.is_a? Time and utc_time? value

return if DatetimeHelper.is_zulu_time? value
record.errors[attribute] << (options[:message] || 'is not in UTC+0 (Zulu Time)')
end

private

def utc_datetime?(value)
value.zone == "+00:00"
end

def utc_time?(value)
value.utc?
end

end
end
end
2 changes: 1 addition & 1 deletion lib/datetime_helper/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module DatetimeHelper
VERSION = "1.0.0"
VERSION = "1.0.1"
end
27 changes: 27 additions & 0 deletions spec/matchers/iso_date_and_time_rspec_matcher_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
require_relative '../spec_helper'

describe 'iso_formatted' do
describe 'be_an_iso_formatted_time' do
context 'given a valid time string' do
let(:valid_time_string) { "11:00" }
it { expect(valid_time_string).to be_an_iso_formatted_time }
end

context 'given an invalid time string' do
let(:invalid_time_string) { "This is not a time string" }
it { expect(invalid_time_string).to_not be_an_iso_formatted_time }
end
end

describe 'be_an_iso_formatted_date' do
context 'given a valid date string' do
let(:valid_time_string) { "2015-11-30" }
it { expect(valid_time_string).to be_an_iso_formatted_date }
end

context 'given an invalid date string' do
let(:invalid_time_string) { "This is not a date string" }
it { expect(invalid_time_string).to_not be_an_iso_formatted_date }
end
end
end
20 changes: 20 additions & 0 deletions spec/matchers/zulu_time_rspec_matcher_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,24 @@
let(:invalid_time_string) { "This is not a zulu time string" }
it { expect(invalid_time_string).to_not be_zulu_time }
end

context 'given a datetime that is at UTC0' do
let(:valid_datetime) { DateTime.now.new_offset(0) }
it { expect(valid_datetime).to be_zulu_time }
end

context 'given a datetime that is not at UTC0' do
let(:invalid_datetime) { DateTime.now.new_offset("+05:30") }
it { expect(invalid_datetime).to_not be_zulu_time }
end

context 'given a time that is at UTC0' do
let(:valid_time) { Time.now.utc }
it { expect(valid_time).to be_zulu_time }
end

context 'given a time that is not at UTC0' do
let(:invalid_time) { Time.now.getlocal("+05:30") }
it { expect(invalid_time).to_not be_zulu_time }
end
end
21 changes: 11 additions & 10 deletions spec/validators/zulu_time_active_model_validator_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,6 @@
let(:validator) { described_class.new({attributes: [:updated_at]})}
let(:model) { double('model') }

# if, by chance, you happen to be running these tests
# on a machine set to UTC+0 then the local time tests
# won't really be a valid test.
let(:already_in_utc) { Time.now.utc == Time.now.getlocal }

before :each do
allow(model).to receive_message_chain('errors').and_return([])
allow(model.errors).to receive_message_chain('[]').and_return({})
Expand All @@ -24,10 +19,9 @@
end
end

context "given a local Time" do
let(:invalid_time) { Time.now.getlocal }
context "given a non UTC+0 Time" do
let(:invalid_time) { Time.now.getlocal("+05:30") }
it "is not accepted as valid" do
pending "Local Time is UTC so skipping test" if already_in_utc
expect(model.errors[]).to receive('<<')
validator.validate_each(model, 'updated_at', invalid_time)
end
Expand All @@ -42,14 +36,21 @@
end

context "given a local DateTime" do
let(:invalid_time) { DateTime.now }
let(:invalid_time) { DateTime.now.new_offset("+05:30") }
it "is not accepted as valid" do
pending "Local Time is UTC so skipping test" if already_in_utc
expect(model.errors[]).to receive('<<')
validator.validate_each(model, 'updated_at', invalid_time)
end
end

context "given a zulu time string" do
let(:valid_time) { Time.now.utc.iso8601 }
it "is accepted as valid" do
expect(model.errors[]).to_not receive('<<')
validator.validate_each(model, 'updated_at', valid_time)
end
end

context "given a String instead of a Time or DateTime" do
let(:nonsense) { "this makes no sense" }
it "is not accepted as valid" do
Expand Down

0 comments on commit a269d9b

Please sign in to comment.