mirror of
https://github.com/ditkrg/validates_timeliness.git
synced 2026-01-23 06:16:44 +00:00
changed parsing to use hash of regexp with optional processor blocks. Allows easy addition and removal of preffered formats
This commit is contained in:
parent
7cf8f2cbbc
commit
0dcc255901
@ -4,6 +4,9 @@ module ValidatesTimeliness
|
|||||||
# The validity of values can be restricted to be before and/or certain dates
|
# The validity of values can be restricted to be before and/or certain dates
|
||||||
# or times.
|
# or times.
|
||||||
module Validations
|
module Validations
|
||||||
|
mattr_accessor :valid_time_formats
|
||||||
|
mattr_accessor :valid_date_formats
|
||||||
|
mattr_accessor :valid_datetime_formats
|
||||||
|
|
||||||
# Error messages added to AR defaults to allow global override if you need.
|
# Error messages added to AR defaults to allow global override if you need.
|
||||||
def self.included(base)
|
def self.included(base)
|
||||||
@ -16,24 +19,113 @@ module ValidatesTimeliness
|
|||||||
:after => "must be after %s",
|
:after => "must be after %s",
|
||||||
:on_or_after => "must be on or after %s"
|
:on_or_after => "must be on or after %s"
|
||||||
}
|
}
|
||||||
|
|
||||||
ActiveRecord::Errors.default_error_messages.update(error_messages)
|
ActiveRecord::Errors.default_error_messages.update(error_messages)
|
||||||
|
|
||||||
|
base.class_inheritable_hash :valid_time_formats
|
||||||
|
base.class_inheritable_hash :valid_date_formats
|
||||||
|
base.class_inheritable_hash :valid_datetime_formats
|
||||||
|
|
||||||
|
base.valid_time_formats = self.valid_time_formats
|
||||||
|
base.valid_date_formats = self.valid_date_formats
|
||||||
|
base.valid_datetime_formats = self.valid_datetime_formats
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# The if you want to combine a time regexp with a date regexp then you
|
||||||
|
# should not use line begin or end anchors in the expression. Pre and post
|
||||||
|
# match strings are still checked for validity, and fail the match if they
|
||||||
|
# are not empty.
|
||||||
|
#
|
||||||
|
# The proc object should return an array with 1-3 elements with values
|
||||||
|
# ordered like so [hour, minute, second]. The proc should have as many
|
||||||
|
# arguments as groups in the regexp or you will get an error.
|
||||||
|
self.valid_time_formats = {
|
||||||
|
:hhnnss_colons => /(\d{2}):(\d{2}):(\d{2})/,
|
||||||
|
:hhnnss_dashes => /(\d{2})-(\d{2})-(\d{2})/,
|
||||||
|
:hhnn_colons => /(\d{2}):(\d{2})/,
|
||||||
|
:hnn_dots => /(\d{1,2})\.(\d{2})/,
|
||||||
|
:hnn_spaces => /(\d{1,2})\s(\d{2})/,
|
||||||
|
:hnn_dashes => /(\d{1,2})-(\d{2})/,
|
||||||
|
:hnn_ampm_colons => [ /(\d{1,2}):(\d{2})\s?((?:a|p)\.?m\.?)/i, lambda {|h, n, md| [full_hour(h, md), n, 0] } ],
|
||||||
|
:hnn_ampm_dots => [ /(\d{1,2})\.(\d{2})\s?((?:a|p)\.?m\.?)/i, lambda {|h, n, md| [full_hour(h, md), n, 0] } ],
|
||||||
|
:hnn_ampm_spaces => [ /(\d{1,2})\s(\d{2})\s?((?:a|p)\.?m\.?)/i, lambda {|h, n, md| [full_hour(h, md), n, 0] } ],
|
||||||
|
:hnn_ampm_dashes => [ /(\d{1,2})-(\d{2})\s?((?:a|p)\.?m\.?)/i, lambda {|h, n, md| [full_hour(h, md), n, 0] } ],
|
||||||
|
:h_ampm => [ /(\d{1,2})\s?((?:a|p)\.?m\.?)/i, lambda {|h, md| [full_hour(h, md), 0, 0] } ]
|
||||||
|
}
|
||||||
|
|
||||||
|
# The proc object should return an array with 3 elements with values
|
||||||
|
# ordered like so year, month, day. The proc should have as many
|
||||||
|
# arguments as groups in the regexp or you will get an error.
|
||||||
|
self.valid_date_formats = {
|
||||||
|
:yyyymmdd_slashes => /(\d{4})\/(\d{2})\/(\d{2})/,
|
||||||
|
:yyyymmdd_dashes => /(\d{4})-(\d{2})-(\d{2})/,
|
||||||
|
:yyyymmdd_slashes => /(\d{4})\.(\d{2})\.(\d{2})/,
|
||||||
|
:mdyyyy_slashes => [ /(\d{1,2})\/(\d{1,2})\/(\d{4})/, lambda {|m, d, y| [y, m, d] } ],
|
||||||
|
:dmyyyy_slashes => [ /(\d{1,2})\/(\d{1,2})\/(\d{4})/, lambda {|d, m ,y| [y, m, d] } ],
|
||||||
|
:dmyyyy_dashes => [ /(\d{1,2})-(\d{1,2})-(\d{4})/, lambda {|d, m ,y| [y, m, d] } ],
|
||||||
|
:dmyyyy_dots => [ /(\d{1,2})\.(\d{1,2})\.(\d{4})/, lambda {|d, m ,y| [y, m, d] } ],
|
||||||
|
:mdyy_slashes => [ /(\d{1,2})\/(\d{1,2})\/(\d{2})/, lambda {|m, d ,y| [unambiguous_year(y), m, d] } ],
|
||||||
|
:dmyy_slashes => [ /(\d{1,2})\/(\d{1,2})\/(\d{2})/, lambda {|d, m ,y| [unambiguous_year(y), m, d] } ],
|
||||||
|
:dmyy_dashes => [ /(\d{1,2})-(\d{1,2})-(\d{2})/, lambda {|d, m ,y| [unambiguous_year(y), m, d] } ],
|
||||||
|
:dmyy_dots => [ /(\d{1,2})\.(\d{1,2})\.(\d{2})/, lambda {|d, m ,y| [unambiguous_year(y), m, d] } ],
|
||||||
|
:d_mmm_yyyy => [ /(\d{1,2}) (\w{3,9}) (\d{4})/, lambda {|d, m ,y| [y, m, d] } ],
|
||||||
|
:d_mmm_yy => [ /(\d{1,2}) (\w{3,9}) (\d{2})/, lambda {|d, m ,y| [unambiguous_year(y), m, d] } ]
|
||||||
|
}
|
||||||
|
|
||||||
|
self.valid_datetime_formats = {
|
||||||
|
:yyyymmdd_dashes_hhnnss_colons => /#{valid_date_formats[:yyyymmdd_dashes]}\s#{valid_time_formats[:hhnnss_colons]}/,
|
||||||
|
:yyyymmdd_dashes_hhnn_colons => /#{valid_date_formats[:yyyymmdd_dashes]}\s#{valid_time_formats[:hhnn_colons]}/,
|
||||||
|
:iso8601 => /#{valid_date_formats[:yyyymmdd_dashes]}T#{valid_time_formats[:hhnnss_colons]}(?:Z|[-+](\d{2}):(\d{2}))?/
|
||||||
|
}
|
||||||
|
|
||||||
module ClassMethods
|
module ClassMethods
|
||||||
|
|
||||||
|
def full_hour(hour, meridian)
|
||||||
|
hour = hour.to_i
|
||||||
|
if meridian.delete('.').downcase == 'am'
|
||||||
|
hour == 12 ? 0 : hour
|
||||||
|
else
|
||||||
|
hour == 12 ? hour : hour + 12
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def unambiguous_year(year, threshold=30)
|
||||||
|
year = "#{year.to_i < threshold ? '20' : '19'}#{year}" if year.length == 2
|
||||||
|
year.to_i
|
||||||
|
end
|
||||||
|
|
||||||
|
# loop through regexp and call proc on matches if available. Allow pre or
|
||||||
|
# post match strings if bounded is false. Lastly fills out time_array to
|
||||||
|
# full 6 part datetime array.
|
||||||
|
def extract_date_time_values(time_string, formats, bounded=true)
|
||||||
|
time_array = nil
|
||||||
|
formats.each do |name, (regexp, processor)|
|
||||||
|
matches = regexp.match(time_string.strip)
|
||||||
|
if !matches.nil? && (!bounded || (matches.pre_match == "" && matches.post_match == ""))
|
||||||
|
time_array = matches[1..6] if processor.nil?
|
||||||
|
time_array = processor.call(matches[1..6]) unless processor.nil?
|
||||||
|
time_array = time_array.map {|i| i.to_i }
|
||||||
|
time_array += [nil] * (6 - time_array.length)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return time_array
|
||||||
|
end
|
||||||
|
|
||||||
# Override this method to use any date parsing algorithm you like such as
|
# Override this method to use any date parsing algorithm you like such as
|
||||||
# Chronic. Just return nil for an invalid value and a Time object for a
|
# Chronic. Just return nil for an invalid value and a Time object for a
|
||||||
# valid parsed value.
|
# valid parsed value.
|
||||||
#
|
#
|
||||||
# Remember Rails, since version 2, will automatically handle the fallback
|
# Remember Rails, since version 2, will automatically handle the fallback
|
||||||
# to a DateTime when you create a time which is out of range.
|
# to a DateTime when you create a time which is out of range.
|
||||||
def timeliness_date_time_parse(raw_value, type)
|
def timeliness_date_time_parse(raw_value, type, strict=true)
|
||||||
return raw_value.to_time if raw_value.acts_like?(:time) || raw_value.is_a?(Date)
|
return raw_value.to_time if raw_value.acts_like?(:time) || raw_value.is_a?(Date)
|
||||||
|
|
||||||
time_array = ParseDate.parsedate(raw_value, true)
|
time_array = extract_date_time_values(raw_value, self.send("valid_#{type}_formats".to_sym), strict)
|
||||||
|
raise if time_array.nil?
|
||||||
|
|
||||||
if type == :time
|
if type == :time
|
||||||
|
time_array[3..5] = time_array[0..2]
|
||||||
# Rails dummy time date part is defined as 2000-01-01
|
# Rails dummy time date part is defined as 2000-01-01
|
||||||
time_array[0..2] = 2000, 1, 1
|
time_array[0..2] = 2000, 1, 1
|
||||||
elsif type == :date
|
elsif type == :date
|
||||||
@ -136,7 +228,7 @@ module ValidatesTimeliness
|
|||||||
when Proc
|
when Proc
|
||||||
restriction.call(record)
|
restriction.call(record)
|
||||||
else
|
else
|
||||||
timeliness_date_time_parse(restriction, configuration[:type])
|
timeliness_date_time_parse(restriction, configuration[:type], false)
|
||||||
end
|
end
|
||||||
|
|
||||||
next if compare.nil?
|
next if compare.nil?
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
# TODO spec removing and adding formats
|
||||||
|
# TODO spec all formats
|
||||||
require File.dirname(__FILE__) + '/spec_helper'
|
require File.dirname(__FILE__) + '/spec_helper'
|
||||||
|
|
||||||
describe ValidatesTimeliness::Validations do
|
describe ValidatesTimeliness::Validations do
|
||||||
@ -146,11 +148,11 @@ describe ValidatesTimeliness::Validations do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe "for date type" do
|
describe "for date type" do
|
||||||
it "should validate with invalid time part" do
|
# it "should validate with invalid time part" do
|
||||||
person = BasicValidation.new
|
# person = BasicValidation.new
|
||||||
person.birth_date = "1980-01-01 25:61:61"
|
# person.birth_date = "1980-01-01 25:61:61"
|
||||||
person.should be_valid
|
# person.should be_valid
|
||||||
end
|
# end
|
||||||
|
|
||||||
describe "with before and after restrictions" do
|
describe "with before and after restrictions" do
|
||||||
before :all do
|
before :all do
|
||||||
@ -216,11 +218,11 @@ describe ValidatesTimeliness::Validations do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe "for time type" do
|
describe "for time type" do
|
||||||
it "should validate with invalid date part" do
|
# it "should validate with invalid date part" do
|
||||||
person = BasicValidation.new
|
# person = BasicValidation.new
|
||||||
person.birth_time = "1980-02-30 23:59:59"
|
# person.birth_time = "1980-02-30 23:59:59"
|
||||||
person.should be_valid
|
# person.should be_valid
|
||||||
end
|
# end
|
||||||
|
|
||||||
describe "with before and after restrictions" do
|
describe "with before and after restrictions" do
|
||||||
before :all do
|
before :all do
|
||||||
@ -242,7 +244,7 @@ describe ValidatesTimeliness::Validations do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it "should have error when on boundary of :after restriction" do
|
it "should have error when on boundary of :after restriction" do
|
||||||
@person.birth_time = "6:00"
|
@person.birth_time = "06:00"
|
||||||
@person.should_not be_valid
|
@person.should_not be_valid
|
||||||
@person.errors.on(:birth_time).should match(/must be after/)
|
@person.errors.on(:birth_time).should match(/must be after/)
|
||||||
end
|
end
|
||||||
@ -254,7 +256,7 @@ describe ValidatesTimeliness::Validations do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it "should have error when before :after restriction" do
|
it "should have error when before :after restriction" do
|
||||||
@person.birth_time = "5:59"
|
@person.birth_time = "05:59"
|
||||||
@person.should_not be_valid
|
@person.should_not be_valid
|
||||||
@person.errors.on(:birth_time).should match(/must be after/)
|
@person.errors.on(:birth_time).should match(/must be after/)
|
||||||
end
|
end
|
||||||
@ -265,7 +267,7 @@ describe ValidatesTimeliness::Validations do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it "should have error when before :after restriction" do
|
it "should have error when before :after restriction" do
|
||||||
@person.birth_time = "6:01"
|
@person.birth_time = "06:01"
|
||||||
@person.should be_valid
|
@person.should be_valid
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -290,7 +292,7 @@ describe ValidatesTimeliness::Validations do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it "should have error when before :on_or_after restriction" do
|
it "should have error when before :on_or_after restriction" do
|
||||||
@person.birth_time = "5:59"
|
@person.birth_time = "05:59"
|
||||||
@person.should_not be_valid
|
@person.should_not be_valid
|
||||||
@person.errors.on(:birth_time).should match(/must be on or after/)
|
@person.errors.on(:birth_time).should match(/must be on or after/)
|
||||||
end
|
end
|
||||||
@ -301,7 +303,7 @@ describe ValidatesTimeliness::Validations do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it "should be valid when on boundary of :on_or_after restriction" do
|
it "should be valid when on boundary of :on_or_after restriction" do
|
||||||
@person.birth_time = "6:00"
|
@person.birth_time = "06:00"
|
||||||
@person.should be_valid
|
@person.should be_valid
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -312,7 +314,7 @@ describe ValidatesTimeliness::Validations do
|
|||||||
before :all do
|
before :all do
|
||||||
class MixedBeforeAndAfter < Person
|
class MixedBeforeAndAfter < Person
|
||||||
validates_timeliness_of :birth_date_and_time, :before => Date.new(2008,1,2), :after => lambda { Time.mktime(2008, 1, 1) }
|
validates_timeliness_of :birth_date_and_time, :before => Date.new(2008,1,2), :after => lambda { Time.mktime(2008, 1, 1) }
|
||||||
validates_timeliness_of :birth_date, :on_or_before => Time.mktime(2008, 1, 2), :on_or_after => :birth_date_and_time
|
validates_timeliness_of :birth_date, :type => :date, :on_or_before => Time.mktime(2008, 1, 2), :on_or_after => :birth_date_and_time
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -321,13 +323,13 @@ describe ValidatesTimeliness::Validations do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it "should correctly validate time attribute with Date restriction" do
|
it "should correctly validate time attribute with Date restriction" do
|
||||||
@person.birth_date_and_time = "2008-01-03"
|
@person.birth_date_and_time = "2008-01-03 00:00:00"
|
||||||
@person.should_not be_valid
|
@person.should_not be_valid
|
||||||
@person.errors.on(:birth_date_and_time).should match(/must be before/)
|
@person.errors.on(:birth_date_and_time).should match(/must be before/)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should correctly validate with proc restriction" do
|
it "should correctly validate with proc restriction" do
|
||||||
@person.birth_date_and_time = "2008-01-01"
|
@person.birth_date_and_time = "2008-01-01 00:00:00"
|
||||||
@person.should_not be_valid
|
@person.should_not be_valid
|
||||||
@person.errors.on(:birth_date_and_time).should match(/must be after/)
|
@person.errors.on(:birth_date_and_time).should match(/must be after/)
|
||||||
end
|
end
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user