Date and time validation plugin for ActiveModel and Rails. Supports multiple ORMs and allows custom date/time formats.
Go to file
2008-07-23 12:43:42 +10:00
lib added extra check to see if time zone attributes in enabled 2008-07-23 12:43:42 +10:00
spec added define_write_method with simple parse and cace for dates, times and datetimes when time zone conversion is off and default for Rails 2.0 2008-07-23 12:42:03 +10:00
benchmark.rb added more benchmarks for special cases 2008-07-23 12:35:06 +10:00
CHANGELOG added changelog and license 2008-07-19 17:56:51 +10:00
init.rb added specs for validation 2008-05-03 15:52:40 +10:00
MIT-LICENSE added changelog and license 2008-07-19 17:56:51 +10:00
Rakefile added default task 2008-07-08 10:37:54 +10:00
README added ignore_datetime_restriction_errors class option and docs 2008-07-21 13:02:21 +10:00

= validates_timeliness

  * Source:  http://github.com/adzap/validates_timeliness
  * Tickets: http://adzap.lighthouseapp.com/projects/14111-validates_timeliness

== DESCRIPTION:

Validate dates, times and datetimes for Rails 2.x. Plays nicely with new 
features such as automatic timezone handling and dirty attributes. Allows 
date/time atttributes to behave like other attribute types by allowing you to
review the raw entered value before it is converted.

Allows you add custom formats or remove defaults easily. You can also just use
another date parser altogther in conjuction with the plugin.


== FEATURES:

* Adds ActiveRecord validation for dates, times and datetimes

* Add or remove date/time formats to customize validation

* 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)

* Respects new timezone features of Rails 2.1.


== INSTALLATION:

Rails 2.1
  ./script/plugin git://github.com/adzap/validates_timeliness

Rails 2.0
  # TODO: best practice for git with Rails 2.0? 

  
== USAGE:

To validate a model with a date, time or datetime attribute you just use the 
validation method

  class Person < ActiveRecord::Base
    validates_date :date_of_birth

  end
  
The list of validation methods available are as follows:

  * validates_date     - validate value as date
  
  * validates_time     - validate value as time only i.e. '12:20pm'
  
  * validates_datetime - validate value as a full date and time
  
The validation methods take the usual options plus some specific ones to restrict
the valid range of dates or times allowed

  Temporal options:  
    :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

  Message options: - Use these to override the default error messages
    :invalid_datetime_message
    :before_message
    :on_or_before_message
    :after_message
    :on_or_after_message

The temporal options can take 4 different value types:

  * String date or time value
  * Date, Time, or DateTime object value
  * Proc or lambda object
  * A symbol matching the method name in the model  

When an attribute value is compared to temporal options, they are compared as 
the same type as the validation method type. So validates_date means all values
are compared as dates.

== EXAMPLES:

  validates_date :date_of_birth :before => Proc.new { 18.years.ago },
                                :before_message => "must be at least 18 years old"
  
  validates_time :breakfast_time, :on_or_after => '6:00am',
                                  :before => :second_breakfast_time,
                                  :on_or_after_message => 'must after opening time',
                                  :allow_nil => true

  validates_datetime :appointment_date, :before => Proc.new { 1.week.from_now }
 

=== 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 duck punching (monkey patching) the plugin.

  Time formats:  
    hh:nn:ss
    hh-nn-ss
    h:nn
    h.nn
    h nn
    h-nn
    h:nn_ampm
    h.nn_ampm 
    h nn_ampm
    h-nn_ampm
    h_ampm
  
  NOTE: Any time format without a meridian value or ampm token is considered 
        in 24 hour time.
  
  Date formats:  
    yyyy/mm/dd
    yyyy-mm-dd
    yyyy.mm.dd
    m/d/yy  OR  d/m/yy
    m\d\yy  OR  d\m\yy
    d-m-yy
    d.m.yy
    d mmm yy
  
  NOTE: To use non-US date formats see US/EURO FORMATS section
  
  Datetime formats:  
    m/d/yy h:nn:ss   OR  d/m/yy hh:nn:ss
    m/d/yy h:nn      OR  d/m/yy h:nn
    m/d/yy h:nn_ampm OR  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

  NOTE: To use non-US date formats see US/EURO FORMATS section

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)

  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

All other characters are considered literal. 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!

To see all defined formats look in the lib/validates_timeliness/formats.rb.

=== US/EURO FORMATS

The perenial problem for non-US develops or applications not primarily for the
US, is the US date format of m/d/yy. This is ambiguous with the European format
of d/my/yy. By default the plugin uses the US formats as this is the Ruby default
when it does date interpretation, and is in keeping PoLS (principle of least
surprise). 

To switch to using the European (or Rest of The World) formats put this in an
initializer or environment.rb

  ValidatesTimeliness::Formats.remove_us_formats  

Now '01/02/2000' will be parsed as 1st February 2000, instead of 2nd January 2000.

=== 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!

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.

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 value. So easy, no more whingeing!

Because formats are evaluated in order, adding a format which may be ambiguous 
with an existing format, will mean your format is ignored. If you need to make
your new format higher precedence than an existing format, you can include the 
before option like so

    ValidatesTimeliness::Formats.add_formats(:time, 'ss:nn:hh', :before => 'hh:nn:ss')

Now a time of '59:30:23' will be interpreted as 11:30:59 pm. This option saves
you adding a new one and deleting an old one to get it to work.

=== EXTERNAL PARSER:

I mentioned earlier that you could use a pluggable or alternative parser such
as Chronic instead of the in built one. So if you need some super fancy stuff that 
the plugin 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.parse_date_time_with_chronic(raw_value, type, strict=true)
      # you can ignore strict with Chronic
      
      # Times without meridian are assumed to be in 24 hour format
      options = { :ambiguous_time_range => :none }
      
      # Attribute of type :time will have a dummy date of 2000-01-01 which is
      # per Rails convention
      options[:now] = Time.local(2000, 1, 1) if type == :time
      Chronic.parse(raw_value)
    end

  end

=== TEMPORAL RESTRICTION ERRORS

When using the validation temporal options there are times when the restriction
value itself may be invalid. Normally this will add an error to the model such as
'restriction :before value was invalid'. These can be annoying if you are using 
other attributes in the model as restrictions, since hopefully those attributes
used are also validated and will have errors themselves which will stop the 
record from saving. In these situations you turn them off.

To turn them off globally:

  ActiveRecord::Base.ignore_datetime_restriction_errors = true

To switch them on or off per model:

  class Person < ActiveRecord::Base
    self.ignore_datetime_restriction_errors = false
  end


== CREDITS:

* Adam Meehan (adam.meehan@gmail.com, http://duckpunching.com/)

* Jonathan Viney (http://workingwithrails.com/person/4985-jonathan-viney)
  For his validates_date_time plugin which I have used before this plugin and
  which influenced some of the design and I borrowed a little of code from it.


== LICENSE:

Copyright (c) 2008 Adam Meehan, released under the MIT license