mirror of
https://github.com/ditkrg/validates_timeliness.git
synced 2026-01-24 14:56:43 +00:00
moved extact_date_time_values method in to Formats and added specs
changed Formats to a class
This commit is contained in:
parent
137ee152e2
commit
5983622ac8
223
README
223
README
@ -3,72 +3,231 @@
|
|||||||
* Source: http://github.com/adzap/validates_timeliness
|
* Source: http://github.com/adzap/validates_timeliness
|
||||||
* Tickets: http://adzap.lighthouseapp.com/projects/14111-validates_timeliness
|
* Tickets: http://adzap.lighthouseapp.com/projects/14111-validates_timeliness
|
||||||
|
|
||||||
|
== DESCRIPTION:
|
||||||
== DESCRIPTION
|
|
||||||
|
|
||||||
Validate dates, times and datetimes for Rails 2.x. Plays nicely with new
|
Validate dates, times and datetimes for Rails 2.x. Plays nicely with new
|
||||||
features such as automatic timezone handling and dirty attributes. Allows
|
features such as automatic timezone handling and dirty attributes. Allows
|
||||||
date/time atttributes to behave like other attribute types by allowing you to
|
date/time atttributes to behave like other attribute types by allowing you to
|
||||||
review the raw entered value before it is converted.
|
review the raw entered value before it is converted.
|
||||||
|
|
||||||
|
Allows you add custom formats or remove defaults easily. You can also just
|
||||||
|
another date parser altogther in conjuction with the plugin.
|
||||||
|
|
||||||
== FEATURES/PROBLEMS
|
|
||||||
|
== FEATURES:
|
||||||
|
|
||||||
* Adds ActiveRecord validation for dates, times and datetimes
|
* Adds ActiveRecord validation for dates, times and datetimes
|
||||||
|
|
||||||
* Adds better transparency of date/time attributes restoring ability to view
|
* Add or remove date/time formats to customize validation
|
||||||
raw value before type casting, which was lost in Rails 2.1 to get the better
|
|
||||||
timezone features.
|
|
||||||
|
|
||||||
|
* Create new formats using very simple date/time format patterns
|
||||||
|
|
||||||
|
* Adds better transparency of date/time attributes restoring ability to view
|
||||||
|
raw value before type casting, which was lost in Rails 2.1.
|
||||||
|
|
||||||
* Allows pluggable date and time parsing with other parsers of your choosing (eg Chronic)
|
* Allows pluggable date and time parsing with other parsers of your choosing (eg Chronic)
|
||||||
|
|
||||||
* Respects timezone features of both Rails 2.0 and 2.1.
|
* Respects new timezone features of Rails 2.1.
|
||||||
|
|
||||||
== INSTALLATION
|
|
||||||
|
== INSTALLATION:
|
||||||
|
|
||||||
Rails 2.1
|
Rails 2.1
|
||||||
./script/plugin git://github.com/adzap/validates_timeliness
|
./script/plugin git://github.com/adzap/validates_timeliness
|
||||||
|
|
||||||
Rails 2.0
|
Rails 2.0
|
||||||
|
# TODO: best practice for git with Rails 2.0?
|
||||||
|
|
||||||
== USAGE
|
|
||||||
|
== USAGE:
|
||||||
|
|
||||||
To validate a model with a date, time or datetime attribute you just use the
|
To validate a model with a date, time or datetime attribute you just use the
|
||||||
validation method
|
validation method
|
||||||
|
|
||||||
class Person < ActiveRecord::Base
|
class Person < ActiveRecord::Base
|
||||||
validates_timeliness_of :date_of_birth
|
validates_date :date_of_birth
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
But for a more semantic validation method you can use the specific validation
|
The list of validation methods available are as follows:
|
||||||
for the attribute type. The type based validation methods are
|
|
||||||
|
|
||||||
* validates_date
|
* validates_date - validate value as date
|
||||||
|
|
||||||
* validates_time
|
* validates_time - validate value as time only i.e. '12:20pm'
|
||||||
|
|
||||||
* validates_datetime
|
* validates_datetime - validate value as a full date and time
|
||||||
|
|
||||||
If the value for a date attribute contains a time, it will be ignored when
|
The validation method take the usual options plus some specific ones to restrict
|
||||||
validating. The same is true to time attributes which ignore the date.
|
the valid range of dates or times allowed
|
||||||
|
|
||||||
The default valid formats for a date or datetime field are those supported by
|
Temporal options:
|
||||||
the Ruby ParseDate library
|
|
||||||
(http://www.ruby-doc.org/docs/ProgrammingRuby/html/lib_standard.html#ParseDate.parsedate).
|
:before - Attribute must be before this value to be valid
|
||||||
|
:on_or_before - Attribute must be equal to or before this value to be valid
|
||||||
|
:after - Attribute must be after this value to be valid
|
||||||
|
:on_or_after - Attribute must be equal to or after this value to be valid
|
||||||
|
|
||||||
|
Regular validation options
|
||||||
|
:allow_nil - Allow a nil value to be valid
|
||||||
|
:allow_blank - Allows a nil or empty string value to be valid
|
||||||
|
|
||||||
The validation method has the usual options plus some specific ones to restrict
|
The temporal options can 4 different value types:
|
||||||
the valid range
|
|
||||||
|
|
||||||
Options:
|
* String date or time value
|
||||||
- allow_nil
|
* Date, Time, or DateTime object value
|
||||||
- allow_blank
|
* Proc or lambda object
|
||||||
- before
|
* A symbol matching the method name in the model
|
||||||
- on_or_before
|
|
||||||
- after
|
If a Time object value is compared to a date attribute using a temporal option,
|
||||||
- on_or_after
|
both values are compared as dates. The rule is that the values are compared as
|
||||||
- type
|
the same type as the validation method type. So validates_date means all values
|
||||||
|
are compared as dates.
|
||||||
|
|
||||||
|
== EXAMPLES:
|
||||||
|
|
||||||
|
validates_date :date_of_birth, :after => '1900-01-01'
|
||||||
|
|
||||||
|
validates_date :date_of_birth, :on_or_after => '1900-01-01'
|
||||||
|
|
||||||
|
validates_date :date_of_birth, :before => Proc.new { Time.now } # only part is used
|
||||||
|
|
||||||
|
validates_time :breakfast_time, :before => '12:00pm'
|
||||||
|
|
||||||
|
validates_time :breakfast_time, :on_or_after => '6:00am'
|
||||||
|
|
||||||
|
validates_datetime :appointment_date, :before => Proc.new { 1.week.from_now }
|
||||||
|
|
||||||
|
validates_datetime :appointment_date, :after => :last_appointment_date
|
||||||
|
|
||||||
|
|
||||||
MUCH MORE DOCS TO COME
|
== DATE/TIME FORMATS:
|
||||||
|
|
||||||
|
So what formats does the plugin allow. Well there are default formats which can
|
||||||
|
be added to easily using the plugins format rules. Also formats can be easily
|
||||||
|
removed without hacking the plugin at all.
|
||||||
|
|
||||||
|
Below are the default formats. If you think they are easy to read then you will
|
||||||
|
be happy to know that is exactly the format you can use to define your own if
|
||||||
|
you want. No regular expressions or hacking plugin methods.
|
||||||
|
|
||||||
|
Time formats:
|
||||||
|
hh:nn:ss => 01:23:59
|
||||||
|
hh-nn-ss => 01:23:59
|
||||||
|
h:nn => 1:23 or 01:23
|
||||||
|
h.nn => 1.23 or 01.23
|
||||||
|
h nn => 1 23 or 01 23
|
||||||
|
h-nn => 1-23 or 01-23
|
||||||
|
h:nn_ampm => 1:23am or 1:23 am or 01:23am
|
||||||
|
h.nn_ampm
|
||||||
|
h nn_ampm
|
||||||
|
h-nn_ampm
|
||||||
|
h_ampm
|
||||||
|
|
||||||
|
NOTE: Any time format without a ampm token or meridian is considered in 24 hour time.
|
||||||
|
|
||||||
|
Date formats:
|
||||||
|
yyyy/mm/dd
|
||||||
|
yyyy-mm-dd
|
||||||
|
yyyy.mm.dd
|
||||||
|
m/d/yy
|
||||||
|
d/m/yy
|
||||||
|
m\d\yy
|
||||||
|
d\m\yy
|
||||||
|
d-m-yy
|
||||||
|
d.m.yy
|
||||||
|
d mmm yy
|
||||||
|
|
||||||
|
Datetime formats:
|
||||||
|
m/d/yy h:nn:ss
|
||||||
|
m/d/yy h:nn
|
||||||
|
m/d/yy h:nn_ampm
|
||||||
|
d/m/yy hh:nn:ss
|
||||||
|
d/m/yy h:nn
|
||||||
|
d/m/yy h:nn_ampm
|
||||||
|
yyyy-mm-dd hh:nn:ss
|
||||||
|
yyyy-mm-dd h:nn
|
||||||
|
ddd mmm d hh:nn:ss zo yyyy # Ruby time string
|
||||||
|
yyyy-mm-ddThh:nn:ss(?:Z|zo) # ISO 8601
|
||||||
|
|
||||||
|
Here is what each format token means:
|
||||||
|
|
||||||
|
Format tokens:
|
||||||
|
y = year
|
||||||
|
m = month
|
||||||
|
d = day
|
||||||
|
h = hour
|
||||||
|
n = minute
|
||||||
|
s = second
|
||||||
|
u = micro-seconds
|
||||||
|
ampm = meridian (am or pm) with or without dots (e.g. am, a.m, or a.m.)
|
||||||
|
_ = optional space
|
||||||
|
tz = Timezone abbreviation (e.g. UTC, GMT, PST, EST)
|
||||||
|
zo = Timezone offset (e.g. +10:00, -08:00, +1000)
|
||||||
|
|
||||||
|
All other characters are considered literal. You can embed regular expressions
|
||||||
|
in the format but no gurantees that it will remain intact. If you avoid the use
|
||||||
|
of any token characters and regexp dots or backslashes as special characters
|
||||||
|
in the regexp, it may well work as expected. For special characters use
|
||||||
|
POSIX character clsses for safety. See the ISO 8601 datetime for en example of
|
||||||
|
of an embedded regular expression.
|
||||||
|
|
||||||
|
Repeating tokens:
|
||||||
|
x = 1 or 2 digits for unit (e.g. 'h' means an hour can be '9' or '09')
|
||||||
|
xx = 2 digits exactly for unit (e.g. 'hh' means an hour can only be '09')
|
||||||
|
|
||||||
|
Special Cases:
|
||||||
|
yy = 2 or 4 digit year
|
||||||
|
yyyyy = exactly 4 digit year
|
||||||
|
mmm = month long name (e.g. 'Jul' or 'July')
|
||||||
|
ddd = Day name of 3 to 9 letters (e.g. Wed or Wednesday)
|
||||||
|
u = microseconds matches 1 to 3 digits
|
||||||
|
|
||||||
|
For the technically minded (well you are developers), these formats are compiled
|
||||||
|
into regular expressions at runtime so don't add any extra overhead than using
|
||||||
|
regular expressions directly. So, no, it won't make your app slow!
|
||||||
|
|
||||||
|
|
||||||
|
== CUSTOMISING FORMATS:
|
||||||
|
|
||||||
|
I hear you say "Thats greats but I don't want X format to be valid". Well to
|
||||||
|
remove a format stick this in an initializer file or environment.rb
|
||||||
|
|
||||||
|
ValidatesTimeliness::Formats.remove_formats(:date, 'm\d\yy')
|
||||||
|
|
||||||
|
Done! That format is no longer considered valid. Easy!
|
||||||
|
|
||||||
|
Ok, now I hear you say "Well I have format that I want to use but you don't have it".
|
||||||
|
Ahh, then add it yourself. Again stick this in an initializer file or environment.rb.
|
||||||
|
|
||||||
|
ValidatesTimeliness::Formats.add_formats(:time, "d o'clock")
|
||||||
|
|
||||||
|
Now '10 o'clock' will be a valid format. So easy, no more whingeing!
|
||||||
|
|
||||||
|
|
||||||
|
== EXTERNAL PARSER:
|
||||||
|
|
||||||
|
I mentioned earlier that you could use a pluggable or alternative parser such
|
||||||
|
as Chronic instead the in built one. So you need some super fancy stuff that the
|
||||||
|
custom formats can't handle then be my guest and override it. This is an example
|
||||||
|
of using Chronis instead. Put this into a file in the lib directory.
|
||||||
|
|
||||||
|
class ActiveRecord::Base
|
||||||
|
|
||||||
|
def self.timeliness_date_time_parse(raw_value, type)
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
== CREDITS:
|
||||||
|
|
||||||
|
* Adam Meehan (http://duckpunching.com/)
|
||||||
|
|
||||||
|
* Jonathan Viney (http://workingwithrails.com/person/4985-jonathan-viney)
|
||||||
|
For his validates_date_time plugin which I have used up till now and which
|
||||||
|
influenced some of the design and I borrowed a small amount of code from it.
|
||||||
|
|
||||||
|
|
||||||
|
== LICENSE:
|
||||||
|
|
||||||
|
Copyright (c) 2008 Adam Meehan, released under the MIT license
|
||||||
|
|||||||
@ -17,3 +17,5 @@ ActionView::Helpers::InstanceTag.send(:include, ValidatesTimeliness::InstanceTag
|
|||||||
Time.send(:include, ValidatesTimeliness::CoreExtensions::Time)
|
Time.send(:include, ValidatesTimeliness::CoreExtensions::Time)
|
||||||
Date.send(:include, ValidatesTimeliness::CoreExtensions::Date)
|
Date.send(:include, ValidatesTimeliness::CoreExtensions::Date)
|
||||||
DateTime.send(:include, ValidatesTimeliness::CoreExtensions::DateTime)
|
DateTime.send(:include, ValidatesTimeliness::CoreExtensions::DateTime)
|
||||||
|
|
||||||
|
ValidatesTimeliness::Formats.compile_format_expressions
|
||||||
|
|||||||
@ -10,17 +10,17 @@ module ValidatesTimeliness
|
|||||||
# Formats can be added or removed to customize the set of valid date or time
|
# Formats can be added or removed to customize the set of valid date or time
|
||||||
# string values.
|
# string values.
|
||||||
#
|
#
|
||||||
module Formats
|
class Formats
|
||||||
mattr_accessor :time_formats
|
cattr_accessor :time_formats
|
||||||
mattr_accessor :date_formats
|
cattr_accessor :date_formats
|
||||||
mattr_accessor :datetime_formats
|
cattr_accessor :datetime_formats
|
||||||
|
|
||||||
mattr_accessor :time_expressions
|
cattr_accessor :time_expressions
|
||||||
mattr_accessor :date_expressions
|
cattr_accessor :date_expressions
|
||||||
mattr_accessor :datetime_expressions
|
cattr_accessor :datetime_expressions
|
||||||
|
|
||||||
mattr_accessor :format_tokens
|
cattr_accessor :format_tokens
|
||||||
mattr_accessor :format_proc_args
|
cattr_accessor :format_proc_args
|
||||||
|
|
||||||
# Format tokens:
|
# Format tokens:
|
||||||
# y = year
|
# y = year
|
||||||
@ -33,7 +33,7 @@ module ValidatesTimeliness
|
|||||||
# ampm = meridian (am or pm) with or without dots (e.g. am, a.m, or a.m.)
|
# ampm = meridian (am or pm) with or without dots (e.g. am, a.m, or a.m.)
|
||||||
# _ = optional space
|
# _ = optional space
|
||||||
# tz = Timezone abbreviation (e.g. UTC, GMT, PST, EST)
|
# tz = Timezone abbreviation (e.g. UTC, GMT, PST, EST)
|
||||||
# zo = Timezone offset (e.g. +10:00, -08:00)
|
# zo = Timezone offset (e.g. +10:00, -08:00, +1000)
|
||||||
#
|
#
|
||||||
# All other characters are considered literal. You can embed regexp in the
|
# All other characters are considered literal. You can embed regexp in the
|
||||||
# format but no gurantees that it will remain intact. If you avoid the use
|
# format but no gurantees that it will remain intact. If you avoid the use
|
||||||
@ -148,98 +148,118 @@ module ValidatesTimeliness
|
|||||||
:meridian => [nil, 'md', nil]
|
:meridian => [nil, 'md', nil]
|
||||||
}
|
}
|
||||||
|
|
||||||
# Compile formats into validation regexps and format procs
|
class << self
|
||||||
def self.format_expression_generator(string_format)
|
|
||||||
regexp = string_format.dup
|
# Compile formats into validation regexps and format procs
|
||||||
order = {}
|
def format_expression_generator(string_format)
|
||||||
regexp.gsub!(/([\.\\])/, '\\\\\1') # escapes dots and backslashes ]/
|
regexp = string_format.dup
|
||||||
|
order = {}
|
||||||
format_tokens.each do |token|
|
regexp.gsub!(/([\.\\])/, '\\\\\1') # escapes dots and backslashes ]/
|
||||||
token_name = token.keys.first
|
|
||||||
token_regexp, regexp_str, arg_key = *token.values.first
|
|
||||||
|
|
||||||
# hack for lack of look-behinds. If has a capture group then is
|
format_tokens.each do |token|
|
||||||
# considered an anchor to put straight back in the regexp string.
|
token_name = token.keys.first
|
||||||
regexp.gsub!(token_regexp) {|m| "#{$1}" + regexp_str }
|
token_regexp, regexp_str, arg_key = *token.values.first
|
||||||
order[arg_key] = $~.begin(0) if $~ && !arg_key.nil?
|
|
||||||
end
|
# hack for lack of look-behinds. If has a capture group then is
|
||||||
|
# considered an anchor to put straight back in the regexp string.
|
||||||
|
regexp.gsub!(token_regexp) {|m| "#{$1}" + regexp_str }
|
||||||
|
order[arg_key] = $~.begin(0) if $~ && !arg_key.nil?
|
||||||
|
end
|
||||||
|
|
||||||
return Regexp.new(regexp), format_proc(order)
|
return Regexp.new(regexp), format_proc(order)
|
||||||
rescue
|
rescue
|
||||||
puts "The following format regular expression failed to compile: #{regexp}\n from format #{string_format}."
|
puts "The following format regular expression failed to compile: #{regexp}\n from format #{string_format}."
|
||||||
raise
|
raise
|
||||||
end
|
|
||||||
|
|
||||||
# Generates a proc which when executed maps the regexp capture groups to a
|
|
||||||
# proc argument based on order captured. A time array is built using the proc
|
|
||||||
# argument in the position indicated by the first element of the proc arg
|
|
||||||
# array.
|
|
||||||
#
|
|
||||||
# Examples:
|
|
||||||
#
|
|
||||||
# 'yyyy-mm-dd hh:nn' => lambda {|y,m,d,h,n| md||=0; [unambiguous_year(y),month_index(m),d,full_hour(h,md),n,nil,nil].map {|t| t.to_i unless t.nil? } }
|
|
||||||
# 'dd/mm/yyyy h:nn_ampm' => lambda {|d,m,y,h,n,md| md||=0; [unambiguous_year(y),month_index(m),d,full_hour(h,md),n,nil,nil].map {|t| t.to_i unless t.nil? } }
|
|
||||||
#
|
|
||||||
def self.format_proc(order)
|
|
||||||
arg_map = format_proc_args
|
|
||||||
args = order.invert.sort.map {|p| arg_map[p[1]][1] }
|
|
||||||
arr = [nil] * 7
|
|
||||||
order.keys.each {|k| i = arg_map[k][0]; arr[i] = arg_map[k][2] unless i.nil? }
|
|
||||||
proc_string = "lambda {|#{args.join(',')}| md||=nil; [#{arr.map {|i| i.nil? ? 'nil' : i }.join(',')}].map {|t| t.to_i unless t.nil? } }"
|
|
||||||
eval proc_string
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.compile_formats(formats)
|
|
||||||
formats.collect { |format|
|
|
||||||
regexp, format_proc = format_expression_generator(format)
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.compile_format_expressions
|
|
||||||
@@time_expressions = compile_formats(@@time_formats)
|
|
||||||
@@date_expressions = compile_formats(@@date_formats)
|
|
||||||
@@datetime_expressions = compile_formats(@@datetime_formats)
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.remove_formats(type, *remove_formats)
|
|
||||||
remove_formats.each do |format|
|
|
||||||
unless self.send("#{type}_formats").delete(format)
|
|
||||||
raise "Format #{format} not found in #{type} formats"
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
compile_format_expressions
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.add_formats(type, *add_formats)
|
|
||||||
formats = self.send("#{type}_formats")
|
|
||||||
|
|
||||||
add_formats.each do |format|
|
# Generates a proc which when executed maps the regexp capture groups to a
|
||||||
if formats.include?(format)
|
# proc argument based on order captured. A time array is built using the proc
|
||||||
raise "Format #{format} is already included in #{type} formats"
|
# argument in the position indicated by the first element of the proc arg
|
||||||
|
# array.
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
#
|
||||||
|
# 'yyyy-mm-dd hh:nn' => lambda {|y,m,d,h,n| md||=0; [unambiguous_year(y),month_index(m),d,full_hour(h,md),n,nil,nil].map {|t| t.to_i unless t.nil? } }
|
||||||
|
# 'dd/mm/yyyy h:nn_ampm' => lambda {|d,m,y,h,n,md| md||=0; [unambiguous_year(y),month_index(m),d,full_hour(h,md),n,nil,nil].map {|t| t.to_i unless t.nil? } }
|
||||||
|
#
|
||||||
|
def format_proc(order)
|
||||||
|
arg_map = format_proc_args
|
||||||
|
args = order.invert.sort.map {|p| arg_map[p[1]][1] }
|
||||||
|
arr = [nil] * 7
|
||||||
|
order.keys.each {|k| i = arg_map[k][0]; arr[i] = arg_map[k][2] unless i.nil? }
|
||||||
|
proc_string = "lambda {|#{args.join(',')}| md||=nil; [#{arr.map {|i| i.nil? ? 'nil' : i }.join(',')}].map {|t| t.to_i unless t.nil? } }"
|
||||||
|
eval proc_string
|
||||||
|
end
|
||||||
|
|
||||||
|
def compile_formats(formats)
|
||||||
|
formats.collect { |format|
|
||||||
|
regexp, format_proc = format_expression_generator(format)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def compile_format_expressions
|
||||||
|
@@time_expressions = compile_formats(@@time_formats)
|
||||||
|
@@date_expressions = compile_formats(@@date_formats)
|
||||||
|
@@datetime_expressions = compile_formats(@@datetime_formats)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Loop through format expressions for type and call proc on matches. Allow
|
||||||
|
# pre or post match strings to exist if strict is false.
|
||||||
|
# Returns 7 part datetime array.
|
||||||
|
def extract_date_time_values(time_string, type, strict=true)
|
||||||
|
expressions = self.send("#{type}_expressions")
|
||||||
|
time_array = nil
|
||||||
|
expressions.each do |(regexp, processor)|
|
||||||
|
regexp = strict || type == :datetime ? /\A#{regexp}\Z/ : (type == :date ? /\A#{regexp}\s?/ : /\s?#{regexp}\Z/)
|
||||||
|
if matches = regexp.match(time_string.strip)
|
||||||
|
time_array = processor.call(*matches[1..7])
|
||||||
|
break
|
||||||
|
end
|
||||||
end
|
end
|
||||||
formats << format
|
return time_array
|
||||||
end
|
end
|
||||||
compile_format_expressions
|
|
||||||
end
|
def remove_formats(type, *remove_formats)
|
||||||
|
remove_formats.each do |format|
|
||||||
def self.full_hour(hour, meridian)
|
unless self.send("#{type}_formats").delete(format)
|
||||||
hour = hour.to_i
|
raise "Format #{format} not found in #{type} formats"
|
||||||
return hour if meridian.nil?
|
end
|
||||||
if meridian.delete('.').downcase == 'am'
|
end
|
||||||
hour == 12 ? 0 : hour
|
compile_format_expressions
|
||||||
else
|
end
|
||||||
hour == 12 ? hour : hour + 12
|
|
||||||
|
def add_formats(type, *add_formats)
|
||||||
|
formats = self.send("#{type}_formats")
|
||||||
|
|
||||||
|
add_formats.each do |format|
|
||||||
|
if formats.include?(format)
|
||||||
|
raise "Format #{format} is already included in #{type} formats"
|
||||||
|
end
|
||||||
|
formats << format
|
||||||
|
end
|
||||||
|
compile_format_expressions
|
||||||
|
end
|
||||||
|
|
||||||
|
def full_hour(hour, meridian)
|
||||||
|
hour = hour.to_i
|
||||||
|
return hour if meridian.nil?
|
||||||
|
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
|
||||||
|
|
||||||
|
def month_index(month)
|
||||||
|
return month.to_i if month.to_i.nonzero?
|
||||||
|
Date::ABBR_MONTHNAMES.index(month.capitalize) || Date::MONTHNAMES.index(month.capitalize)
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def self.unambiguous_year(year, threshold=30)
|
|
||||||
year = "#{year.to_i < threshold ? '20' : '19'}#{year}" if year.length == 2
|
|
||||||
year.to_i
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.month_index(month)
|
|
||||||
return month.to_i if month.to_i.nonzero?
|
|
||||||
Date::ABBR_MONTHNAMES.index(month.capitalize) || Date::MONTHNAMES.index(month.capitalize)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -4,7 +4,7 @@ module ValidatesTimeliness
|
|||||||
# or times.
|
# or times.
|
||||||
module Validations
|
module Validations
|
||||||
|
|
||||||
# Error messages added to AR defaults to allow global override if you need.
|
# Error messages added to AR defaults to allow global override.
|
||||||
def self.included(base)
|
def self.included(base)
|
||||||
base.extend ClassMethods
|
base.extend ClassMethods
|
||||||
|
|
||||||
@ -16,25 +16,9 @@ module ValidatesTimeliness
|
|||||||
: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)
|
||||||
ValidatesTimeliness::Formats.compile_format_expressions
|
|
||||||
end
|
end
|
||||||
|
|
||||||
module ClassMethods
|
module ClassMethods
|
||||||
# loop through format regexps and call proc on matches if available. Allow
|
|
||||||
# pre or post match strings if bounded is false. Lastly fills out
|
|
||||||
# time_array to full 7 part datetime array.
|
|
||||||
def extract_date_time_values(time_string, type, bounded=true)
|
|
||||||
expressions = ValidatesTimeliness::Formats.send("#{type}_expressions")
|
|
||||||
time_array = nil
|
|
||||||
expressions.each do |(regexp, processor)|
|
|
||||||
matches = regexp.match(time_string.strip)
|
|
||||||
if !matches.nil? && (!bounded || (matches.pre_match == "" && matches.post_match == ""))
|
|
||||||
time_array = processor.call(*matches[1..7])
|
|
||||||
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
|
||||||
@ -45,7 +29,7 @@ module ValidatesTimeliness
|
|||||||
def timeliness_date_time_parse(raw_value, type, strict=true)
|
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 = extract_date_time_values(raw_value, type, strict)
|
time_array = ValidatesTimeliness::Formats.extract_date_time_values(raw_value, type, strict)
|
||||||
raise if time_array.nil?
|
raise if time_array.nil?
|
||||||
|
|
||||||
if type == :time
|
if type == :time
|
||||||
|
|||||||
@ -136,6 +136,34 @@ describe ValidatesTimeliness::Formats do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "extracting values" do
|
||||||
|
|
||||||
|
it "should return time array from date string" do
|
||||||
|
time_array = formats.extract_date_time_values('12:13:14', :time, true)
|
||||||
|
time_array.should == [nil,nil,nil,12,13,14,nil]
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should return date array from time string" do
|
||||||
|
time_array = formats.extract_date_time_values('2000-02-01', :date, true)
|
||||||
|
time_array.should == [2000,2,1,nil,nil,nil,nil]
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should return datetime array from string value" do
|
||||||
|
time_array = formats.extract_date_time_values('2000-02-01 12:13:14', :datetime, true)
|
||||||
|
time_array.should == [2000,2,1,12,13,14,nil]
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should ignore time when extracting date and strict is false" do
|
||||||
|
time_array = formats.extract_date_time_values('2000-02-01 12:12', :date, false)
|
||||||
|
time_array.should == [2000,2,1,nil,nil,nil,nil]
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should ignore date when extracting time and strict is false" do
|
||||||
|
time_array = formats.extract_date_time_values('2000-02-01 12:12', :time, false)
|
||||||
|
time_array.should == [nil,nil,nil,12,12,nil,nil]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "removing formats" do
|
describe "removing formats" do
|
||||||
before do
|
before do
|
||||||
formats.compile_format_expressions
|
formats.compile_format_expressions
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user