validates_date_time 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +61 -0
- data/MIT-LICENSE +20 -0
- data/README.markdown +94 -0
- data/Rakefile +37 -0
- data/VERSION +1 -0
- data/init.rb +1 -0
- data/lib/multiparameter_attributes.rb +47 -0
- data/lib/parser.rb +130 -0
- data/lib/validates_date_time.rb +150 -0
- data/test/abstract_unit.rb +59 -0
- data/test/database.yml +6 -0
- data/test/date_test.rb +133 -0
- data/test/date_time_test.rb +77 -0
- data/test/fixtures/en.yml +12 -0
- data/test/fixtures/people.yml +4 -0
- data/test/fixtures/person.rb +13 -0
- data/test/schema.rb +16 -0
- data/test/time_test.rb +103 -0
- data/validates_date_time.gemspec +63 -0
- metadata +88 -0
data/CHANGELOG
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
[5 March 2009]
|
|
2
|
+
|
|
3
|
+
* Add support for I18N [Roman Shterenzon]
|
|
4
|
+
|
|
5
|
+
[18 February 2009]
|
|
6
|
+
|
|
7
|
+
* Fix errors caused by assigning values that don't respond to #strip.
|
|
8
|
+
|
|
9
|
+
[26 May 2008]
|
|
10
|
+
|
|
11
|
+
* Fix incorrect validation of 24 hour times including meridian, eg: 18:20 AM.
|
|
12
|
+
|
|
13
|
+
[2 May 2008]
|
|
14
|
+
|
|
15
|
+
* Permenently remove old ActiveRecord::Validations::DateTime namespace.
|
|
16
|
+
|
|
17
|
+
[19 March 2008]
|
|
18
|
+
|
|
19
|
+
* Fix README typo.
|
|
20
|
+
|
|
21
|
+
[11 February 2008]
|
|
22
|
+
|
|
23
|
+
* Make :after add an error if the date is the same as the restriction.
|
|
24
|
+
|
|
25
|
+
[7 January 2008]
|
|
26
|
+
|
|
27
|
+
* Add support for ISO8601 formats.
|
|
28
|
+
|
|
29
|
+
[14 December 2007]
|
|
30
|
+
|
|
31
|
+
* Allow 1 through 6 digits for microseconds.
|
|
32
|
+
|
|
33
|
+
[11 December 2007]
|
|
34
|
+
|
|
35
|
+
* Parse microseconds from time and datetime strings.
|
|
36
|
+
|
|
37
|
+
[28 September 2007]
|
|
38
|
+
|
|
39
|
+
* Preserve ActiveRecord::Validations::DateTime.us_date_format for compatibility.
|
|
40
|
+
|
|
41
|
+
[27 September 2007]
|
|
42
|
+
|
|
43
|
+
* Improve the handling of multiparameter attributes.
|
|
44
|
+
|
|
45
|
+
* BACKWARDS INCOMPATIBLE: Change namespace from ActiveRecord::Validations::DateTime to ValidatesDateTime.
|
|
46
|
+
|
|
47
|
+
[19 September 2007]
|
|
48
|
+
|
|
49
|
+
* Almost a complete rewrite! API unchanged. Let me know if there are any issues.
|
|
50
|
+
|
|
51
|
+
[2 Sep 2007]
|
|
52
|
+
|
|
53
|
+
* Allow date and time formats like "February 5, 2006" and "September 01, 2007 06:10". [Mark A. Lane]
|
|
54
|
+
|
|
55
|
+
[7 Jun 07]
|
|
56
|
+
|
|
57
|
+
* Datetime restrictions should be parsed as datetimes [Adam Meehan]
|
|
58
|
+
|
|
59
|
+
[Earlier]
|
|
60
|
+
|
|
61
|
+
* Remove unused colon separated date format (dd:mm:yy) from parsing
|
data/MIT-LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright (c) 2006 Jonathan Viney
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
validates_date_time
|
|
2
|
+
===================
|
|
3
|
+
This plugin adds the ability to do stricter date and time checking with ActiveRecord.
|
|
4
|
+
|
|
5
|
+
Install
|
|
6
|
+
=======
|
|
7
|
+
./script/plugin install git://github.com/nickstenning/validates_date_time.git
|
|
8
|
+
|
|
9
|
+
Instructions
|
|
10
|
+
============
|
|
11
|
+
The validators can be used to parse strings into Date and Time objects as well as restrict
|
|
12
|
+
an attribute based on other dates or times.
|
|
13
|
+
class Person < ActiveRecord::Base
|
|
14
|
+
validates_date :date_of_birth
|
|
15
|
+
validates_time :time_of_birth
|
|
16
|
+
validates_date_time :date_and_time_of_birth
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
Use `:allow_nil` to allow the value to be blank.
|
|
20
|
+
class Person < ActiveRecord::Base
|
|
21
|
+
validates_date :date_of_birth, :allow_nil => true
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
Supported formats
|
|
25
|
+
=================
|
|
26
|
+
The default for the plugin is to expect dates in day/month/year format. If you are in the
|
|
27
|
+
US, you will want to change the default to month/day/year by placing the following in config/environment.rb
|
|
28
|
+
ValidatesDateTime.us_date_format = true
|
|
29
|
+
|
|
30
|
+
Date format examples:
|
|
31
|
+
- 2006-01-01
|
|
32
|
+
- 1 Jan 06
|
|
33
|
+
- 1 Jan 2006
|
|
34
|
+
- 10/1/06
|
|
35
|
+
- 1/1/2006
|
|
36
|
+
|
|
37
|
+
Time format examples:
|
|
38
|
+
- 1pm
|
|
39
|
+
- 10:11
|
|
40
|
+
- 12:30pm
|
|
41
|
+
- 8am
|
|
42
|
+
|
|
43
|
+
Datetime format examples:
|
|
44
|
+
- 1 Jan 2006 2pm
|
|
45
|
+
- 31/1/06 8:30am
|
|
46
|
+
|
|
47
|
+
Examples
|
|
48
|
+
========
|
|
49
|
+
If an attribute value can not be parsed correctly, an error is added:
|
|
50
|
+
p = Person.new
|
|
51
|
+
p.date_of_birth = "1 Jan 2006"
|
|
52
|
+
p.time_of_birth = "5am"
|
|
53
|
+
p.save # true
|
|
54
|
+
|
|
55
|
+
p.date_of_birth = "30 Feb 2006"
|
|
56
|
+
p.save # false, 30 feb is invalid for obvious reasons
|
|
57
|
+
|
|
58
|
+
p.date_of_birth = "java is better than ruby"
|
|
59
|
+
p.save # false
|
|
60
|
+
|
|
61
|
+
In the final example, as I'm sure you are aware, the record failed to save not only
|
|
62
|
+
because "java is better than ruby" is an invalid date, but more importantly, because the statement is blatantly false. ;)
|
|
63
|
+
|
|
64
|
+
Restricting date and time ranges
|
|
65
|
+
================================
|
|
66
|
+
Using the `:before` and `:after` options you can restrict a date or time value based on other attribute values
|
|
67
|
+
and predefined values. You can pass as many value to :before or :after as you like.
|
|
68
|
+
class Person
|
|
69
|
+
validates_date :date_of_birth, :before => [:date_of_death, Proc.new { 1.day.from_now_to_date}], :after => '1 Jan 1900'
|
|
70
|
+
validates_date :date_of_death, :before => Proc.new { 1.day.from_now.to_date }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
p = Person.new
|
|
74
|
+
p.date_of_birth = '1800-01-01'
|
|
75
|
+
p.save # false
|
|
76
|
+
p.errors[:date_of_birth] # must be after 1 Jan 1900
|
|
77
|
+
|
|
78
|
+
p.date_of_death = Date.new(2010, 1, 1)
|
|
79
|
+
p.save # false
|
|
80
|
+
p.errors[:date_of_death] # must be before <1 day from now>
|
|
81
|
+
|
|
82
|
+
p.date_of_birth = '1960-03-02'
|
|
83
|
+
p.date_of_death = '2003-06-07'
|
|
84
|
+
p.save # true
|
|
85
|
+
|
|
86
|
+
You can customise the error messages for dates or times that fall outside the required range. The boundary date will be substituted in for %s. Eg:
|
|
87
|
+
class Person
|
|
88
|
+
validates_date :date_of_birth, :after => Date.new(1900, 1, 1), :before => Proc.new { 1.day.from_now.to_date }, :before_message => 'Ensure it is before %s', :after_message => 'Ensure it is after %s'
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
Author
|
|
92
|
+
======
|
|
93
|
+
If you find this plugin useful, please consider a donation to [show your support](http://www.paypal.com/cgi-bin/webscr?cmd=_send-money)!
|
|
94
|
+
Suggestions, comments, problems are all welcome. You'll find me at jonathan.viney@gmail.com
|
data/Rakefile
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
require 'rake'
|
|
2
|
+
require 'rake/testtask'
|
|
3
|
+
require 'rake/rdoctask'
|
|
4
|
+
|
|
5
|
+
begin
|
|
6
|
+
require 'jeweler'
|
|
7
|
+
Jeweler::Tasks.new do |gemspec|
|
|
8
|
+
gemspec.name = "validates_date_time"
|
|
9
|
+
gemspec.summary = "A Rails plugin adds the ability to validate dates and times with ActiveRecord."
|
|
10
|
+
gemspec.description = "A Rails plugin that adds an ActiveRecord validation helper to do range and start/end date checking in."
|
|
11
|
+
gemspec.email = ["jonathan.viney@gmail.com", "nick@whiteink.com"]
|
|
12
|
+
gemspec.homepage = "http://github.com/nickstenning/validates_date_time"
|
|
13
|
+
gemspec.authors = ["Jonathan Viney", "Nick Stenning"]
|
|
14
|
+
end
|
|
15
|
+
rescue LoadError
|
|
16
|
+
puts "Jeweler not available. Install it with: gem install jeweler"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
desc 'Default: run unit tests.'
|
|
21
|
+
task :default => :test
|
|
22
|
+
|
|
23
|
+
desc 'Test the validates_date_time plugin.'
|
|
24
|
+
Rake::TestTask.new(:test) do |t|
|
|
25
|
+
t.libs << 'lib'
|
|
26
|
+
t.pattern = 'test/**/*_test.rb'
|
|
27
|
+
t.verbose = true
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
desc 'Generate documentation for the validates_date_time plugin.'
|
|
31
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
|
32
|
+
rdoc.rdoc_dir = 'rdoc'
|
|
33
|
+
rdoc.title = 'validates_date_time'
|
|
34
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
|
35
|
+
rdoc.rdoc_files.include('README')
|
|
36
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
|
37
|
+
end
|
data/VERSION
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1.0.0
|
data/init.rb
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require File.dirname(__FILE__) + '/lib/validates_date_time'
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
module ValidatesDateTime
|
|
2
|
+
module MultiparameterAttributes
|
|
3
|
+
def self.included(base)
|
|
4
|
+
base.alias_method_chain :execute_callstack_for_multiparameter_attributes, :temporal_error_handling
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def execute_callstack_for_multiparameter_attributes_with_temporal_error_handling(callstack)
|
|
8
|
+
errors = []
|
|
9
|
+
callstack.each do |name, values|
|
|
10
|
+
klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
|
|
11
|
+
|
|
12
|
+
if values.empty?
|
|
13
|
+
send("#{name}=", nil)
|
|
14
|
+
else
|
|
15
|
+
column = column_for_attribute(name)
|
|
16
|
+
|
|
17
|
+
if [:date, :time, :datetime].include?(column.type)
|
|
18
|
+
values = values.map(&:to_s)
|
|
19
|
+
|
|
20
|
+
result = case column.type
|
|
21
|
+
when :date
|
|
22
|
+
extract_date_from_multiparameter_attributes(values)
|
|
23
|
+
when :time
|
|
24
|
+
extract_time_from_multiparameter_attributes(values)
|
|
25
|
+
when :datetime
|
|
26
|
+
date_values, time_values = values.slice!(0, 3), values
|
|
27
|
+
extract_date_from_multiparameter_attributes(date_values) + " " + extract_time_from_multiparameter_attributes(time_values)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
send("#{name}=", result)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
unless errors.empty?
|
|
35
|
+
raise ActiveRecord::MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def extract_date_from_multiparameter_attributes(values)
|
|
40
|
+
[values[0], *values.slice(1, 2).map { |s| s.rjust(2, "0") }].join("-")
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def extract_time_from_multiparameter_attributes(values)
|
|
44
|
+
values.last(3).map { |s| s.rjust(2, "0") }.join(":")
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
data/lib/parser.rb
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
module ActiveRecord
|
|
2
|
+
module ConnectionAdapters
|
|
3
|
+
class Column
|
|
4
|
+
class << self
|
|
5
|
+
def string_to_date(value)
|
|
6
|
+
return if value.blank?
|
|
7
|
+
return value if value.is_a?(Date)
|
|
8
|
+
return value.to_date if value.is_a?(Time) || value.is_a?(DateTime)
|
|
9
|
+
|
|
10
|
+
year, month, day = case value.to_s.strip
|
|
11
|
+
# 22/1/06, 22\1\06 or 22.1.06
|
|
12
|
+
when /\A(\d{1,2})[\\\/\.-](\d{1,2})[\\\/\.-](\d{2}|\d{4})\Z/
|
|
13
|
+
ValidatesDateTime.us_date_format ? [$3, $1, $2] : [$3, $2, $1]
|
|
14
|
+
# 22 Feb 06 or 1 jun 2001
|
|
15
|
+
when /\A(\d{1,2}) (\w{3,9}) (\d{2}|\d{4})\Z/
|
|
16
|
+
[$3, $2, $1]
|
|
17
|
+
# July 1 2005
|
|
18
|
+
when /\A(\w{3,9}) (\d{1,2})\,? (\d{2}|\d{4})\Z/
|
|
19
|
+
[$3, $1, $2]
|
|
20
|
+
# 2006-01-01
|
|
21
|
+
when /\A(\d{4})-(\d{2})-(\d{2})\Z/
|
|
22
|
+
[$1, $2, $3]
|
|
23
|
+
# 2006-01-01T10:10:10+13:00
|
|
24
|
+
when /\A(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})\Z/
|
|
25
|
+
[$1, $2, $3]
|
|
26
|
+
# Not a valid date string
|
|
27
|
+
else
|
|
28
|
+
return nil
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
Date.new(unambiguous_year(year), month_index(month), day.to_i) rescue nil
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def string_to_dummy_time(value)
|
|
35
|
+
return if value.blank?
|
|
36
|
+
return value if value.is_a?(Time) || value.is_a?(DateTime)
|
|
37
|
+
return value.to_time(ActiveRecord::Base.default_timezone) if value.is_a?(Date)
|
|
38
|
+
|
|
39
|
+
hour, minute, second, microsecond = case value.to_s.strip
|
|
40
|
+
# 12 hours with minute and second
|
|
41
|
+
when /\A(\d{1,2})[\. :](\d{2})[\. :](\d{2})\s?(am|pm)\Z/i
|
|
42
|
+
[full_hour($1, $4), $2, $3]
|
|
43
|
+
# 12 hour with minute: 7.30pm, 11:20am, 2 20PM
|
|
44
|
+
when /\A(\d{1,2})[\. :](\d{2})\s?(am|pm)\Z/i
|
|
45
|
+
return nil unless $1.to_i <= 12
|
|
46
|
+
[full_hour($1, $3), $2]
|
|
47
|
+
# 12 hour without minute: 2pm, 11Am, 7 pm
|
|
48
|
+
when /\A(\d{1,2})\s?(am|pm)\Z/i
|
|
49
|
+
[full_hour($1, $2)]
|
|
50
|
+
# 24 hour: 22:30, 03.10, 12 30
|
|
51
|
+
when /\A(\d{2})[\. :](\d{2})([\. :](\d{2})(\.(\d{1,6}))?)?\Z/
|
|
52
|
+
[$1, $2, $4, $6]
|
|
53
|
+
# 2006-01-01T10:10:10+13:00
|
|
54
|
+
when /\A(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})\Z/
|
|
55
|
+
[$4, $5, $6]
|
|
56
|
+
# Not a valid time string
|
|
57
|
+
else
|
|
58
|
+
return nil
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
Time.send(ActiveRecord::Base.default_timezone, 2000, 1, 1, hour.to_i, minute.to_i, second.to_i, microsecond.to_i) rescue nil
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def string_to_time(value)
|
|
65
|
+
return if value.blank?
|
|
66
|
+
return value if value.is_a?(Time) || value.is_a?(DateTime)
|
|
67
|
+
return value.to_time(ActiveRecord::Base.default_timezone) if value.is_a?(Date)
|
|
68
|
+
|
|
69
|
+
value = value.to_s.strip
|
|
70
|
+
|
|
71
|
+
if value =~ /\A(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})\Z/
|
|
72
|
+
time_array = [$1, $2, $3, $4, $5, $6].map!(&:to_i)
|
|
73
|
+
else
|
|
74
|
+
# The basic approach is to attempt to parse a date from the front of the string, splitting on spaces.
|
|
75
|
+
# Once a date has been parsed, a time is extracted from the rest of the string.
|
|
76
|
+
split_index = date = nil
|
|
77
|
+
loop do
|
|
78
|
+
split_index = value.index(' ', split_index ? split_index + 1 : 0)
|
|
79
|
+
|
|
80
|
+
if split_index.nil? or date = string_to_date(value.first(split_index))
|
|
81
|
+
break
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
return if date.nil?
|
|
86
|
+
|
|
87
|
+
time = string_to_dummy_time(value.last(value.size - split_index))
|
|
88
|
+
return if time.nil?
|
|
89
|
+
|
|
90
|
+
time_array = [date.year, date.month, date.day, time.hour, time.min, time.sec, time.usec]
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# From schema_definitions.rb
|
|
94
|
+
begin
|
|
95
|
+
Time.send(ActiveRecord::Base.default_timezone, *time_array)
|
|
96
|
+
rescue
|
|
97
|
+
zone_offset = if Base.default_timezone == :local then DateTime.now.offset else 0 end
|
|
98
|
+
# Append zero calendar reform start to account for dates skipped by calendar reform
|
|
99
|
+
DateTime.new(*time_array[0..5] << zone_offset << 0) rescue nil
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def full_hour(hour, meridian)
|
|
104
|
+
hour = hour.to_i
|
|
105
|
+
if meridian.strip.downcase == 'am'
|
|
106
|
+
hour == 12 ? 0 : hour
|
|
107
|
+
else
|
|
108
|
+
hour == 12 ? hour : hour + 12
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def month_index(month)
|
|
113
|
+
return month.to_i if month.to_i.nonzero?
|
|
114
|
+
Date::ABBR_MONTHNAMES.index(month.capitalize) || Date::MONTHNAMES.index(month.capitalize)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Extract a 4-digit year from a 2-digit year.
|
|
118
|
+
# If the number is less than 20, assume year 20#{number}
|
|
119
|
+
# otherwise use 19#{number}. Ignore if already 4 digits.
|
|
120
|
+
#
|
|
121
|
+
# Eg:
|
|
122
|
+
# 10 => 2010, 60 => 1960, 00 => 2000, 1963 => 1963
|
|
123
|
+
def unambiguous_year(year)
|
|
124
|
+
year = "#{year.to_i < 20 ? '20' : '19'}#{year}" if year.length == 2
|
|
125
|
+
year.to_i
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
require File.dirname(__FILE__) + '/parser'
|
|
2
|
+
require File.dirname(__FILE__) + '/multiparameter_attributes'
|
|
3
|
+
|
|
4
|
+
module ValidatesDateTime
|
|
5
|
+
def self.included(base)
|
|
6
|
+
base.class_eval do
|
|
7
|
+
extend ClassMethods
|
|
8
|
+
include MultiparameterAttributes
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
mattr_accessor :us_date_format
|
|
13
|
+
self.us_date_format = false
|
|
14
|
+
|
|
15
|
+
DEFAULT_TEMPORAL_VALIDATION_OPTIONS = {
|
|
16
|
+
:before_message => "must be before %s",
|
|
17
|
+
:after_message => "must be after %s",
|
|
18
|
+
:on => :save
|
|
19
|
+
}.freeze
|
|
20
|
+
|
|
21
|
+
class ValidatesDateTimeRestriction < Struct.new(:raw_value, :parse_method)
|
|
22
|
+
def value(record)
|
|
23
|
+
@last_value = case raw_value
|
|
24
|
+
when Symbol
|
|
25
|
+
record.send(raw_value)
|
|
26
|
+
when Proc
|
|
27
|
+
raw_value.call(record)
|
|
28
|
+
else
|
|
29
|
+
raw_value
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
@last_value = parse(@last_value)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def parse(string)
|
|
36
|
+
ActiveRecord::ConnectionAdapters::Column.send("string_to_#{parse_method}", string)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def last_value
|
|
40
|
+
@last_value
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def to_s
|
|
44
|
+
if raw_value.is_a?(Symbol)
|
|
45
|
+
raw_value.to_s.humanize
|
|
46
|
+
else
|
|
47
|
+
@last_value.to_s
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
module ClassMethods
|
|
54
|
+
def validates_date(*args)
|
|
55
|
+
options = temporal_validation_options({ :message => "is an invalid date" }, args)
|
|
56
|
+
attr_names = args
|
|
57
|
+
|
|
58
|
+
# We must remove this from the configuration that is passed to validates_each because
|
|
59
|
+
# we want to have our own definition of nil that uses the before_type_cast value
|
|
60
|
+
allow_nil = options.delete(:allow_nil)
|
|
61
|
+
|
|
62
|
+
prepare_restrictions(options, :date)
|
|
63
|
+
|
|
64
|
+
validates_each(attr_names, options) do |record, attr_name, value|
|
|
65
|
+
raw_value = record.send("#{attr_name}_before_type_cast")
|
|
66
|
+
|
|
67
|
+
# If value that is unable to be parsed, and a blank value where allow_nil is not set are both invalid
|
|
68
|
+
if (!raw_value.blank? and !value) || (raw_value.blank? and !allow_nil)
|
|
69
|
+
record.errors.add(attr_name, options[:message])
|
|
70
|
+
elsif value
|
|
71
|
+
validate_before_and_after_restrictions(record, attr_name, value, options)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def validates_time(*args)
|
|
77
|
+
options = temporal_validation_options({ :message => "is an invalid time" }, args)
|
|
78
|
+
attr_names = args
|
|
79
|
+
|
|
80
|
+
allow_nil = options.delete(:allow_nil)
|
|
81
|
+
prepare_restrictions(options, :dummy_time)
|
|
82
|
+
|
|
83
|
+
validates_each(attr_names, options) do |record, attr_name, value|
|
|
84
|
+
raw_value = record.send("#{attr_name}_before_type_cast")
|
|
85
|
+
|
|
86
|
+
if (!raw_value.blank? and !value) || (raw_value.blank? and !allow_nil)
|
|
87
|
+
record.errors.add(attr_name, options[:message])
|
|
88
|
+
elsif value
|
|
89
|
+
validate_before_and_after_restrictions(record, attr_name, value, options)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def validates_date_time(*args)
|
|
95
|
+
options = temporal_validation_options({ :message => "is an invalid date time" }, args)
|
|
96
|
+
attr_names = args
|
|
97
|
+
|
|
98
|
+
allow_nil = options.delete(:allow_nil)
|
|
99
|
+
prepare_restrictions(options, :time)
|
|
100
|
+
|
|
101
|
+
validates_each(attr_names, options) do |record, attr_name, value|
|
|
102
|
+
raw_value = record.send("#{attr_name}_before_type_cast")
|
|
103
|
+
|
|
104
|
+
if (!raw_value.blank? and !value) || (raw_value.blank? and !allow_nil)
|
|
105
|
+
record.errors.add(attr_name, options[:message])
|
|
106
|
+
elsif value
|
|
107
|
+
validate_before_and_after_restrictions(record, attr_name, value, options)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
private
|
|
113
|
+
def validate_before_and_after_restrictions(record, attr_name, value, options)
|
|
114
|
+
if options[:before]
|
|
115
|
+
options[:before].each do |r|
|
|
116
|
+
if r.value(record) and value >= r.last_value
|
|
117
|
+
record.errors.add(attr_name, :before, :value => r, :default => options[:before_message] % r)
|
|
118
|
+
break
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
if options[:after]
|
|
124
|
+
options[:after].each do |r|
|
|
125
|
+
if r.value(record) and value <= r.last_value
|
|
126
|
+
record.errors.add(attr_name, :after, :value => r, :default => options[:after_message] % r)
|
|
127
|
+
break
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def prepare_restrictions(options, parse_method)
|
|
134
|
+
options[:before] = [*options[:before]].compact.map { |r| ValidatesDateTimeRestriction.new(r, parse_method) }
|
|
135
|
+
options[:after] = [*options[:after]].compact.map { |r| ValidatesDateTimeRestriction.new(r, parse_method) }
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def temporal_validation_options(options, args)
|
|
139
|
+
returning options do
|
|
140
|
+
options.reverse_merge!(DEFAULT_TEMPORAL_VALIDATION_OPTIONS)
|
|
141
|
+
options.update(args.pop) if args.last.is_a?(Hash)
|
|
142
|
+
options.assert_valid_keys :message, :before_message, :after_message, :before, :after, :if, :on, :allow_nil
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
class ActiveRecord::Base
|
|
149
|
+
include ValidatesDateTime
|
|
150
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
require 'test/unit'
|
|
2
|
+
|
|
3
|
+
begin
|
|
4
|
+
require File.dirname(__FILE__) + '/../../../../config/boot'
|
|
5
|
+
Rails::Initializer.run
|
|
6
|
+
rescue LoadError
|
|
7
|
+
require 'rubygems'
|
|
8
|
+
gem 'activerecord', '=2.2.2'
|
|
9
|
+
require 'activerecord'
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Search for fixtures first
|
|
13
|
+
fixture_path = File.dirname(__FILE__) + '/fixtures/'
|
|
14
|
+
ActiveSupport::Dependencies.load_paths.insert(0, fixture_path)
|
|
15
|
+
|
|
16
|
+
require 'active_record/fixtures'
|
|
17
|
+
|
|
18
|
+
require File.expand_path(File.dirname(__FILE__) + '/../lib/validates_date_time')
|
|
19
|
+
|
|
20
|
+
ActiveRecord::Base.configurations = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
|
|
21
|
+
ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + '/debug.log')
|
|
22
|
+
ActiveRecord::Base.establish_connection(ENV['DB'] || 'mysql')
|
|
23
|
+
|
|
24
|
+
load(File.dirname(__FILE__) + '/schema.rb')
|
|
25
|
+
|
|
26
|
+
Test::Unit::TestCase.fixture_path = File.dirname(__FILE__) + '/fixtures/'
|
|
27
|
+
|
|
28
|
+
class Test::Unit::TestCase #:nodoc:
|
|
29
|
+
self.use_transactional_fixtures = true
|
|
30
|
+
self.use_instantiated_fixtures = false
|
|
31
|
+
|
|
32
|
+
fixtures :people
|
|
33
|
+
|
|
34
|
+
def p
|
|
35
|
+
people(:jonathan)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def assert_update_and_equal(expected, attributes = {})
|
|
39
|
+
assert p.update_attributes!(attributes), "#{attributes.inspect} should be valid"
|
|
40
|
+
assert_equal expected, p.send(attributes.keys.first).to_s
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def assert_update_and_match(expected, attributes = {})
|
|
44
|
+
assert p.update_attributes(attributes), "#{attributes.inspect} should be valid"
|
|
45
|
+
assert_match expected, p.send(attributes.keys.first).to_s
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def assert_invalid_and_errors_match(expected, attributes = {})
|
|
49
|
+
assert !p.update_attributes(attributes)
|
|
50
|
+
assert_match expected, p.errors.full_messages.join
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def with_us_date_format(&block)
|
|
54
|
+
ValidatesDateTime.us_date_format = true
|
|
55
|
+
yield
|
|
56
|
+
ensure
|
|
57
|
+
ValidatesDateTime.us_date_format = false
|
|
58
|
+
end
|
|
59
|
+
end
|
data/test/database.yml
ADDED
data/test/date_test.rb
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
require File.dirname(__FILE__) + '/abstract_unit'
|
|
2
|
+
|
|
3
|
+
class DateTest < Test::Unit::TestCase
|
|
4
|
+
def test_valid_when_nil
|
|
5
|
+
assert p.update_attributes!(:date_of_birth => nil, :date_of_death => nil)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def test_date_required
|
|
9
|
+
assert_invalid_and_errors_match /invalid/, :required_date => ""
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Test 1/1/06 format
|
|
13
|
+
def test_first_format
|
|
14
|
+
{ '1/1/01' => '2001-01-01', '29/10/2005' => '2005-10-29', '8\12\63' => '1963-12-08',
|
|
15
|
+
'07/06/2006' => '2006-06-07', '11\1\06' => '2006-01-11', '10.6.05' => '2005-06-10' }.each do |value, result|
|
|
16
|
+
assert_update_and_equal result, :date_of_birth => value
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Test 1 Jan 06 and 1 January 06 formats
|
|
21
|
+
def test_second_format
|
|
22
|
+
{ '19 Mar 60' => '1960-03-19', '22 dec 1985' => '1985-12-22',
|
|
23
|
+
'24 August 00' => '2000-08-24', '25 December 1960' => '1960-12-25'}.each do |value, result|
|
|
24
|
+
assert_update_and_equal result, :date_of_birth => value
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Test February 4 2006 formats
|
|
29
|
+
def test_third_format
|
|
30
|
+
{ 'february 4 06' => '2006-02-04', 'DECember 25 1850' => '1850-12-25', 'February 5, 2006' => '2006-02-05' }.each do |value, result|
|
|
31
|
+
assert_update_and_equal result, :date_of_birth => value
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def test_iso_format
|
|
36
|
+
{ '2006-01-01' => '2006-01-01', '1900-04-22' => '1900-04-22',
|
|
37
|
+
'2008-03-04T20:33:41' => '2008-03-04' }.each do |value, result|
|
|
38
|
+
assert_update_and_equal result, :date_of_birth => value
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def test_format_without_validation
|
|
43
|
+
assert_equal Date.new(2005, 12, 11).to_s, Person.new(:date_of_birth => "11/12/05").date_of_birth.to_s
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def test_invalid_formats
|
|
47
|
+
['aksjhdaksjhd', 'meow', 'chocolate',
|
|
48
|
+
'221 jan 05', '21 JAN 001', '1 Jaw 00', '1 Febrarary 2003', '30/2/06',
|
|
49
|
+
'1/2/3/4', '11/22/33', '10/10/990', '189 /1 /9', '12\ f m', 100].each do |value|
|
|
50
|
+
assert_invalid_and_errors_match /invalid/, :date_of_birth => value
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def test_date_objects
|
|
55
|
+
assert_update_and_equal '1963-04-05', :date_of_birth => Date.new(1963, 4, 5)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def test_before_and_after
|
|
59
|
+
p.update_attributes!(:date_of_death => '1950-01-01')
|
|
60
|
+
assert_invalid_and_errors_match /before/, :date_of_death => (Date.today + 1).to_s
|
|
61
|
+
assert_invalid_and_errors_match /before/, :date_of_death => Date.new(2030, 1, 1)
|
|
62
|
+
|
|
63
|
+
p.update_attributes!(:date_of_birth => '1950-01-01', :date_of_death => nil)
|
|
64
|
+
assert_invalid_and_errors_match /after/, :date_of_death => '1950-01-01'
|
|
65
|
+
assert p.update_attributes!(:date_of_death => Date.new(1951, 1, 1))
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def test_before_and_after_with_custom_message
|
|
69
|
+
assert_invalid_and_errors_match /avant/, :date_of_arrival => 2.years.from_now, :date_of_departure => 2.years.ago
|
|
70
|
+
assert_invalid_and_errors_match /apres/, :date_of_arrival => '1792-03-03'
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def test_before_and_after_with_i18n
|
|
74
|
+
original_load_path = I18n.load_path
|
|
75
|
+
|
|
76
|
+
I18n.load_path = [File.join(File.dirname(__FILE__),'fixtures/en.yml')]
|
|
77
|
+
I18n.reload!
|
|
78
|
+
|
|
79
|
+
assert_invalid_and_errors_match /i18n_before/, :date_of_death => (Date.today + 1).to_s
|
|
80
|
+
assert_invalid_and_errors_match /i18n_after/, :date_of_birth => (Date.today + 1).to_s
|
|
81
|
+
ensure
|
|
82
|
+
I18n.load_path = original_load_path
|
|
83
|
+
I18n.reload!
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def test_dates_with_unknown_year
|
|
87
|
+
assert p.update_attributes!(:date_of_birth => '9999-12-11')
|
|
88
|
+
assert p.update_attributes!(:date_of_birth => Date.new(9999, 1, 1))
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def test_us_date_format
|
|
92
|
+
with_us_date_format do
|
|
93
|
+
{'1/31/06' => '2006-01-31', '28 Feb 01' => '2001-02-28',
|
|
94
|
+
'10/10/80' => '1980-10-10', 'July 4 1960' => '1960-07-04',
|
|
95
|
+
'2006-03-20' => '2006-03-20'}.each do |value, result|
|
|
96
|
+
assert_update_and_equal result, :date_of_birth => value
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def test_blank
|
|
102
|
+
assert p.update_attributes!(:date_of_birth => " ")
|
|
103
|
+
assert_nil p.date_of_birth
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def test_conversion_of_restriction_result
|
|
107
|
+
assert_invalid_and_errors_match /Date of birth/, :date_of_death => Date.new(2001, 1, 1), :date_of_birth => Date.new(2005, 1, 1)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def test_multi_parameter_attribute_assignment_with_valid_date
|
|
111
|
+
assert_nothing_raised do
|
|
112
|
+
p.update_attributes!('date_of_birth(1i)' => '2006', 'date_of_birth(2i)' => '2', 'date_of_birth(3i)' => '10')
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
assert_equal Date.new(2006, 2, 10), p.date_of_birth
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def test_multi_parameter_attribute_assignment_with_invalid_date
|
|
119
|
+
assert_nothing_raised do
|
|
120
|
+
assert !p.update_attributes('date_of_birth(1i)' => '2006', 'date_of_birth(2i)' => '2', 'date_of_birth(3i)' => '30')
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
assert p.errors[:date_of_birth]
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def test_incomplete_multi_parameter_attribute_assignment
|
|
127
|
+
assert_nothing_raised do
|
|
128
|
+
assert !p.update_attributes('date_of_birth(1i)' => '2006', 'date_of_birth(2i)' => '1', 'date_of_birth(3i)' => '')
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
assert p.errors[:date_of_birth]
|
|
132
|
+
end
|
|
133
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
require File.dirname(__FILE__) + '/abstract_unit'
|
|
2
|
+
|
|
3
|
+
class DateTimeTest < Test::Unit::TestCase
|
|
4
|
+
def test_various_formats
|
|
5
|
+
formats = {
|
|
6
|
+
'2006-01-01 01:01:01' => /Jan 01 01:01:01 [\+-]?[\w ]+ 2006/,
|
|
7
|
+
'2/2/06 7pm' => /Feb 02 19:00:00 [\+-]?[\w ]+ 2006/,
|
|
8
|
+
'10 AUG 04 6.23am' => /Aug 10 06:23:00 [\+-]?[\w ]+ 2004/,
|
|
9
|
+
'6 June 1981 10 10' => /Jun 06 10:10:00 [\+-]?[\w ]+ 1981/,
|
|
10
|
+
'September 01, 2007 06:10' => /Sep 01 06:10:00 [\+-]?[\w ]+ 2007/,
|
|
11
|
+
'2007-02-23T12:03:28' => /Feb 23 12:03:28 [\+-]?[\w ]+ 2007/
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
formats.each do |value, result|
|
|
15
|
+
assert_update_and_match result, :date_and_time_of_birth => value
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
with_us_date_format do
|
|
19
|
+
formats.each do |value, result|
|
|
20
|
+
assert_update_and_match result, :date_and_time_of_birth => value
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def test_date_time_with_microseconds
|
|
26
|
+
assert_update_and_match /Mar 20 09:22:50 [\+-]?[\w ]+ 2007/, :date_and_time_of_birth => "20 Mar 07 09:22:50.987654"
|
|
27
|
+
assert_equal 987654, p.date_and_time_of_birth.usec
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def test_invalid_formats
|
|
31
|
+
['29 Feb 06 1am', '1 Jan 06', '7pm', 6].each do |value|
|
|
32
|
+
assert_invalid_and_errors_match /date time/, :date_and_time_of_birth => value
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def test_before_and_after_restrictions_parsed_as_date_times
|
|
37
|
+
assert_invalid_and_errors_match /before/, :date_and_time_of_birth => '2008-01-02 00:00:00'
|
|
38
|
+
assert p.update_attributes!(:date_and_time_of_birth => '2008-01-01 01:01:00')
|
|
39
|
+
|
|
40
|
+
assert_invalid_and_errors_match /after/, :date_and_time_of_birth => '1981-01-01 01:00am'
|
|
41
|
+
assert p.update_attributes!(:date_and_time_of_birth => '1981-01-01 01:02am')
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def test_multi_parameter_attribute_assignment_with_valid_date_times
|
|
45
|
+
assert_nothing_raised do
|
|
46
|
+
p.update_attributes!('date_and_time_of_birth(1i)' => '2006', 'date_and_time_of_birth(2i)' => '2', 'date_and_time_of_birth(3i)' => '20',
|
|
47
|
+
'date_and_time_of_birth(4i)' => '23', 'date_and_time_of_birth(5i)' => '10', 'date_and_time_of_birth(6i)' => '40')
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
assert_equal Time.local(2006, 2, 20, 23, 10, 40), p.date_and_time_of_birth
|
|
51
|
+
|
|
52
|
+
# Without second parameter
|
|
53
|
+
assert_nothing_raised do
|
|
54
|
+
p.update_attributes!('date_and_time_of_birth(1i)' => '2004', 'date_and_time_of_birth(2i)' => '3', 'date_and_time_of_birth(3i)' => '14',
|
|
55
|
+
'date_and_time_of_birth(4i)' => '22', 'date_and_time_of_birth(5i)' => '20')
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
assert_equal Time.local(2004, 3, 14, 22, 20), p.date_and_time_of_birth
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def test_multi_parameter_attribute_assignment_with_invalid_date_time
|
|
62
|
+
assert_nothing_raised do
|
|
63
|
+
assert !p.update_attributes('date_and_time_of_birth(1i)' => '2006', 'date_and_time_of_birth(2i)' => '2', 'time_of_birth(3i)' => '10',
|
|
64
|
+
'date_and_time_of_birth(4i)' => '30', 'date_and_time_of_birth(5i)' => '88', 'date_and_time_of_birth(6i)' => '100')
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
assert p.errors[:date_and_time_of_birth]
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def test_incomplete_multi_parameter_attribute_assignment
|
|
71
|
+
assert_nothing_raised do
|
|
72
|
+
assert !p.update_attributes('date_and_time_of_birth(1i)' => '2006', 'date_and_time_of_birth(2i)' => '1')
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
assert p.errors[:date_and_time_of_birth]
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
class Person < ActiveRecord::Base
|
|
2
|
+
validates_date :date_of_birth, :allow_nil => true
|
|
3
|
+
validates_date :date_of_death, :allow_nil => true, :before => Proc.new { 1.day.from_now }, :after => :date_of_birth
|
|
4
|
+
|
|
5
|
+
validates_date :date_of_arrival, :allow_nil => true, :before => :date_of_departure, :after => '1 Jan 1800', :before_message => "avant %s", :after_message => "apres %s"
|
|
6
|
+
|
|
7
|
+
validates_time :time_of_birth, :allow_nil => true, :before => [Proc.new { Time.now }]
|
|
8
|
+
validates_time :time_of_death, :allow_nil => true, :after => [:time_of_birth, '7pm'], :before => [Proc.new { 10.years.from_now }]
|
|
9
|
+
|
|
10
|
+
validates_date_time :date_and_time_of_birth, :allow_nil => true, :before => '2008-01-01 01:01:01', :after => '1981-01-01 01:01am'
|
|
11
|
+
|
|
12
|
+
validates_date :required_date
|
|
13
|
+
end
|
data/test/schema.rb
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
ActiveRecord::Schema.define :version => 0 do
|
|
2
|
+
create_table :people, :force => true do |t|
|
|
3
|
+
t.column :date_of_birth, :date
|
|
4
|
+
t.column :date_of_death, :date
|
|
5
|
+
|
|
6
|
+
t.column :date_of_arrival, :date
|
|
7
|
+
t.column :date_of_departure, :date
|
|
8
|
+
|
|
9
|
+
t.column :time_of_birth, :time
|
|
10
|
+
t.column :time_of_death, :time
|
|
11
|
+
|
|
12
|
+
t.column :date_and_time_of_birth, :datetime
|
|
13
|
+
|
|
14
|
+
t.column :required_date, :date
|
|
15
|
+
end
|
|
16
|
+
end
|
data/test/time_test.rb
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
require File.dirname(__FILE__) + '/abstract_unit'
|
|
2
|
+
|
|
3
|
+
class TimeTest < Test::Unit::TestCase
|
|
4
|
+
def test_valid_when_nil
|
|
5
|
+
assert p.update_attributes!(:time_of_birth => nil, :time_of_death => nil, :time_of_death => nil)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def test_with_seconds
|
|
9
|
+
{ '03:45:22' => /03:45:22/, '09:10:27' => /09:10:27/ }.each do |value, result|
|
|
10
|
+
assert_update_and_match result, :time_of_birth => value
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def test_12_hour_with_minute
|
|
15
|
+
{ '7.20pm' => /19:20:00/, ' 1:33 AM' => /01:33:00/, '11 28am' => /11:28:00/ }.each do |value, result|
|
|
16
|
+
assert_update_and_match result, :time_of_birth => value
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def test_12_hour_without_minute
|
|
21
|
+
{ '11 am' => /11:00:00/, '7PM ' => /19:00:00/, ' 1Am' => /01:00:00/, '12pm' => /12:00:00/, '12.00pm' => /12:00:00/, '12am' => /00:00:00/ }.each do |value, result|
|
|
22
|
+
assert_update_and_match result, :time_of_birth => value
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def test_24_hour
|
|
27
|
+
{ '22:00' => /22:00:00/, '10 23' => /10:23:00/, '01 01' => /01:01:00/ }.each do |value, result|
|
|
28
|
+
assert_update_and_match result, :time_of_birth => value
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def test_24_hour_with_microseconds
|
|
33
|
+
assert_update_and_match /12:23:56/, :time_of_birth => "12:23:56.169732"
|
|
34
|
+
assert_equal 169732, p.time_of_birth.usec
|
|
35
|
+
|
|
36
|
+
assert_update_and_match /12:23:56/, :time_of_birth => "12:23:56.15"
|
|
37
|
+
assert_equal 15, p.time_of_birth.usec
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def test_iso8601
|
|
41
|
+
assert_update_and_match /12:03:28/, :time_of_birth => "2008-02-23T12:03:28"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def test_24_hour_with_invalid_microseconds
|
|
45
|
+
assert_invalid_and_errors_match /invalid/, :time_of_birth => "12:23:11.1234567"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def test_time_objects
|
|
49
|
+
{ Time.gm(2006, 2, 2, 22, 30) => /22:30:00/, '2pm' => /14:00:00/, Time.gm(2006, 2, 2, 1, 3) => /01:03:00/ }.each do |value, result|
|
|
50
|
+
assert_update_and_match result, :time_of_birth => value
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def test_invalid_formats
|
|
55
|
+
['1 PPM', 'lunchtime', '8..30', 'chocolate', '29am', '18:20 AM', '18:20 PM', 2].each do |value|
|
|
56
|
+
assert !p.update_attributes(:time_of_birth => value)
|
|
57
|
+
end
|
|
58
|
+
assert_match /time/, p.errors[:time_of_birth]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def test_after
|
|
62
|
+
assert_invalid_and_errors_match /must be after/, :time_of_death => '6pm'
|
|
63
|
+
|
|
64
|
+
assert p.update_attributes!(:time_of_death => '8pm')
|
|
65
|
+
assert p.update_attributes!(:time_of_death => nil, :time_of_birth => Time.gm(2001, 1, 1, 9))
|
|
66
|
+
|
|
67
|
+
assert_invalid_and_errors_match /must be after/, :time_of_death => '7am'
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def test_before
|
|
71
|
+
assert_invalid_and_errors_match /must be before/, :time_of_birth => Time.now + 1.day
|
|
72
|
+
assert p.update_attributes!(:time_of_birth => Time.now - 1)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def test_blank
|
|
76
|
+
assert p.update_attributes!(:time_of_birth => " ")
|
|
77
|
+
assert_nil p.time_of_birth
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def test_multi_parameter_attribute_assignment_with_valid_time
|
|
81
|
+
assert_nothing_raised do
|
|
82
|
+
p.update_attributes!('time_of_birth(1i)' => '3', 'time_of_birth(2i)' => '2', 'time_of_birth(3i)' => '10')
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
assert_equal Time.local(2000, 1, 1, 3, 2, 10), p.time_of_birth
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def test_multi_parameter_attribute_assignment_with_invalid_time
|
|
89
|
+
assert_nothing_raised do
|
|
90
|
+
assert !p.update_attributes('time_of_birth(1i)' => '23', 'time_of_birth(2i)' => '2', 'time_of_birth(3i)' => '77')
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
assert p.errors[:time_of_birth]
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def test_incomplete_multi_parameter_attribute_assignment
|
|
97
|
+
assert_nothing_raised do
|
|
98
|
+
assert !p.update_attributes('time_of_birth(1i)' => '10')
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
assert p.errors[:time_of_birth]
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Generated by jeweler
|
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
|
4
|
+
# -*- encoding: utf-8 -*-
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |s|
|
|
7
|
+
s.name = %q{validates_date_time}
|
|
8
|
+
s.version = "1.0.0"
|
|
9
|
+
|
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
|
11
|
+
s.authors = ["Jonathan Viney", "Nick Stenning"]
|
|
12
|
+
s.date = %q{2010-04-07}
|
|
13
|
+
s.description = %q{A Rails plugin that adds an ActiveRecord validation helper to do range and start/end date checking in.}
|
|
14
|
+
s.email = ["jonathan.viney@gmail.com", "nick@whiteink.com"]
|
|
15
|
+
s.extra_rdoc_files = [
|
|
16
|
+
"README.markdown"
|
|
17
|
+
]
|
|
18
|
+
s.files = [
|
|
19
|
+
"CHANGELOG",
|
|
20
|
+
"MIT-LICENSE",
|
|
21
|
+
"README.markdown",
|
|
22
|
+
"Rakefile",
|
|
23
|
+
"VERSION",
|
|
24
|
+
"init.rb",
|
|
25
|
+
"lib/multiparameter_attributes.rb",
|
|
26
|
+
"lib/parser.rb",
|
|
27
|
+
"lib/validates_date_time.rb",
|
|
28
|
+
"test/abstract_unit.rb",
|
|
29
|
+
"test/database.yml",
|
|
30
|
+
"test/date_test.rb",
|
|
31
|
+
"test/date_time_test.rb",
|
|
32
|
+
"test/fixtures/en.yml",
|
|
33
|
+
"test/fixtures/people.yml",
|
|
34
|
+
"test/fixtures/person.rb",
|
|
35
|
+
"test/schema.rb",
|
|
36
|
+
"test/time_test.rb",
|
|
37
|
+
"validates_date_time.gemspec"
|
|
38
|
+
]
|
|
39
|
+
s.homepage = %q{http://github.com/nickstenning/validates_date_time}
|
|
40
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
|
41
|
+
s.require_paths = ["lib"]
|
|
42
|
+
s.rubygems_version = %q{1.3.6}
|
|
43
|
+
s.summary = %q{A Rails plugin adds the ability to validate dates and times with ActiveRecord.}
|
|
44
|
+
s.test_files = [
|
|
45
|
+
"test/abstract_unit.rb",
|
|
46
|
+
"test/date_test.rb",
|
|
47
|
+
"test/date_time_test.rb",
|
|
48
|
+
"test/fixtures/person.rb",
|
|
49
|
+
"test/schema.rb",
|
|
50
|
+
"test/time_test.rb"
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
if s.respond_to? :specification_version then
|
|
54
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
|
55
|
+
s.specification_version = 3
|
|
56
|
+
|
|
57
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
|
58
|
+
else
|
|
59
|
+
end
|
|
60
|
+
else
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
metadata
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: validates_date_time
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
prerelease: false
|
|
5
|
+
segments:
|
|
6
|
+
- 1
|
|
7
|
+
- 0
|
|
8
|
+
- 0
|
|
9
|
+
version: 1.0.0
|
|
10
|
+
platform: ruby
|
|
11
|
+
authors:
|
|
12
|
+
- Jonathan Viney
|
|
13
|
+
- Nick Stenning
|
|
14
|
+
autorequire:
|
|
15
|
+
bindir: bin
|
|
16
|
+
cert_chain: []
|
|
17
|
+
|
|
18
|
+
date: 2010-04-07 00:00:00 -04:00
|
|
19
|
+
default_executable:
|
|
20
|
+
dependencies: []
|
|
21
|
+
|
|
22
|
+
description: A Rails plugin that adds an ActiveRecord validation helper to do range and start/end date checking in.
|
|
23
|
+
email:
|
|
24
|
+
- jonathan.viney@gmail.com
|
|
25
|
+
- nick@whiteink.com
|
|
26
|
+
executables: []
|
|
27
|
+
|
|
28
|
+
extensions: []
|
|
29
|
+
|
|
30
|
+
extra_rdoc_files:
|
|
31
|
+
- README.markdown
|
|
32
|
+
files:
|
|
33
|
+
- CHANGELOG
|
|
34
|
+
- MIT-LICENSE
|
|
35
|
+
- README.markdown
|
|
36
|
+
- Rakefile
|
|
37
|
+
- VERSION
|
|
38
|
+
- init.rb
|
|
39
|
+
- lib/multiparameter_attributes.rb
|
|
40
|
+
- lib/parser.rb
|
|
41
|
+
- lib/validates_date_time.rb
|
|
42
|
+
- test/abstract_unit.rb
|
|
43
|
+
- test/database.yml
|
|
44
|
+
- test/date_test.rb
|
|
45
|
+
- test/date_time_test.rb
|
|
46
|
+
- test/fixtures/en.yml
|
|
47
|
+
- test/fixtures/people.yml
|
|
48
|
+
- test/fixtures/person.rb
|
|
49
|
+
- test/schema.rb
|
|
50
|
+
- test/time_test.rb
|
|
51
|
+
- validates_date_time.gemspec
|
|
52
|
+
has_rdoc: true
|
|
53
|
+
homepage: http://github.com/nickstenning/validates_date_time
|
|
54
|
+
licenses: []
|
|
55
|
+
|
|
56
|
+
post_install_message:
|
|
57
|
+
rdoc_options:
|
|
58
|
+
- --charset=UTF-8
|
|
59
|
+
require_paths:
|
|
60
|
+
- lib
|
|
61
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
62
|
+
requirements:
|
|
63
|
+
- - ">="
|
|
64
|
+
- !ruby/object:Gem::Version
|
|
65
|
+
segments:
|
|
66
|
+
- 0
|
|
67
|
+
version: "0"
|
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
69
|
+
requirements:
|
|
70
|
+
- - ">="
|
|
71
|
+
- !ruby/object:Gem::Version
|
|
72
|
+
segments:
|
|
73
|
+
- 0
|
|
74
|
+
version: "0"
|
|
75
|
+
requirements: []
|
|
76
|
+
|
|
77
|
+
rubyforge_project:
|
|
78
|
+
rubygems_version: 1.3.6
|
|
79
|
+
signing_key:
|
|
80
|
+
specification_version: 3
|
|
81
|
+
summary: A Rails plugin adds the ability to validate dates and times with ActiveRecord.
|
|
82
|
+
test_files:
|
|
83
|
+
- test/abstract_unit.rb
|
|
84
|
+
- test/date_test.rb
|
|
85
|
+
- test/date_time_test.rb
|
|
86
|
+
- test/fixtures/person.rb
|
|
87
|
+
- test/schema.rb
|
|
88
|
+
- test/time_test.rb
|