We had a lot of trouble even believing this problem existed, but we finally narrowed it down to a reproducible test. This problem does not exhibit itself under Linux, but does show up under Windows.
When running Rails 0.14.3 (and 0.14.2, and possibly 0.14.1), under Ruby 1.8.3 (and also under Ruby 1.8.2), against Oracle (10g, but I don't think the problem is with the Oracle version), on Windows XP (maybe more versions) we see an ArgumentError exception ("time out of range") when trying to assign string date values to an AR date field prior to 1/1/1970 (the Unix epoch). This does NOT happen on Linux when all code and software versions are otherwise identical. We have tested this on 2 separate Windows systems (not ghosted copies -- long-running developer systems with different install and configuration histories) and multiple Linux systems of varying distributions.
The details:
Here is the Oracle table definition for our Party class:
create table party
(
id number(12) not null,
lock_version number(12) default 1 not null,
category varchar(30) not null,
name varchar(30) not null,
middle_name varchar(30),
surname varchar(30),
suffix varchar(10),
birthdate date,
social_security_number varchar(9),
created varchar(200),
updated varchar(200)
) tablespace coredata;
Here is the test case we use to expose the problem in question. This test runs successfully on Linux, but fails consistently on Windows.
def setup
@party = Party.new
@party.name = "Joe"
@party.surname = "Blow"
@party.id = 1000
@PARTy.category = 'Staff'
end
def test_epoch_time
@party.birthdate = '1969-01-01'
@party.save
@party = Party.find(1000)
assert_equal Date.new(1969,1,1), @party.birthdate
end
Line #40 (see test trace below) is the @party.save line, where we get an ArgumentError from down in the bowels of AR's oci_adapter.rb.
Here is a hook we wrote to allow us to inspect what's going on in cast_to_date_or_time() (this is active in the test trace below):
# Fix bug in date casting on a nil
class OCIColumn
alias :original_cast_to_date_or_time :cast_to_date_or_time
def cast_to_date_or_time(value)
STDERR.puts "Date cast [#{value}] [#{value.class}]"
original_cast_to_date_or_time(value)
end
end
And here is the trace of a test run including the test in question. Note that the diagnostics for the failing test indicate that value is a String when going into oci_adapter.rb's cast_to_date_or_time()
Microsoft Windows XP [Version 5.1.2600]
(C) Copyright 1985-2001 Microsoft Corp.
C:\egghead\phoenix\rails>rake
c:/ruby/bin/ruby -Ilib;test "c:/ruby/lib/ruby/gems/1.8/gems/rake-0.6.2/lib/rake/rake_test_loader.rb" "test/unit/accountability_cat_test.rb" "test/unit/accountability_test.rb" "test/unit/address_test.rb" "test/unit/date_test.rb" "test/unit/helper_test.rb" "test/unit/lazy_hash_test.rb" "test/unit/login_property_test.rb" "test/unit/login_test.rb" "test/unit/module_privilege_test.rb" "test/unit/phone_test.rb" "test/unit/privilege_test.rb"
User [sydennis]
Using Oracle
Loaded suite c:/ruby/lib/ruby/gems/1.8/gems/rake-0.6.2/lib/rake/rake_test_loader
Started
.....................Date cast [2005-01-01] [Date]
Date cast [Tue Nov 08 16:18:39 Central Standard Time 2005] [Time]
....................Date cast [1969-01-01] [String]
E................Date cast [Tue Nov 08 16:18:43 Central Standard Time 2005] [Time]
Date cast [Sun May 07 17:18:41 Central Standard Time 2006] [Time]
.Date cast [Sun May 07 17:18:43 Central Standard Time 2006] [Time]
.Date cast [Sun May 07 17:18:41 Central Standard Time 2006] [Time]
.Date cast [Tue Nov 08 16:18:43 Central Standard Time 2005] [Time]
Date cast [Tue Nov 08 16:18:43 Central Standard Time 2005] [Time]
....Date cast [Mon May 08 17:18:44 Central Standard Time 2006] [Time]
Date cast [Sun May 07 17:18:41 Central Standard Time 2006] [Time]
Date cast [Mon May 08 17:18:44 Central Standard Time 2006] [Time]
Date cast [Sun May 07 17:18:41 Central Standard Time 2006] [Time]
...Date cast [Sun May 07 17:18:41 Central Standard Time 2006] [Time]
.Date cast [Tue Nov 08 16:18:44 Central Standard Time 2005] [Time]
.Date cast [Sun May 07 17:18:41 Central Standard Time 2006] [Time]
Date cast [Sun May 07 17:18:41 Central Standard Time 2006] [Time]
....................
Finished in 7.485 seconds.
1) Error:
test_epoch_time(DateTest):
ArgumentError: time out of range
c:/ruby/lib/ruby/gems/1.8/gems/activerecord-1.13.0/lib/active_record/connection_adapters/oci_adapter.rb:120:in `local'
c:/ruby/lib/ruby/gems/1.8/gems/activerecord-1.13.0/lib/active_record/connection_adapters/oci_adapter.rb:120:in `cast_to_time'
c:/ruby/lib/ruby/gems/1.8/gems/activerecord-1.13.0/lib/active_record/connection_adapters/oci_adapter.rb:113:in `original_cast_to_date_or_time'
C:/egghead/phoenix/rails/config/../lib/active_record_ext.rb:81:in `cast_to_date_or_time'
c:/ruby/lib/ruby/gems/1.8/gems/activerecord-1.13.0/lib/active_record/connection_adapters/oci_adapter.rb:95:in `type_cast'
c:/ruby/lib/ruby/gems/1.8/gems/activerecord-1.13.0/lib/active_record/base.rb:1510:in `read_attribute'
c:/ruby/lib/ruby/gems/1.8/gems/activerecord-1.13.0/lib/active_record/base.rb:1748:in `clone_attribute_value'
c:/ruby/lib/ruby/gems/1.8/gems/activerecord-1.13.0/lib/active_record/base.rb:1742:in `clone_attributes'
c:/ruby/lib/ruby/gems/1.8/gems/activerecord-1.13.0/lib/active_record/callbacks.rb:335:in `inject'
c:/ruby/lib/ruby/gems/1.8/gems/activerecord-1.13.0/lib/active_record/base.rb:1741:in `clone_attributes'
c:/ruby/lib/ruby/gems/1.8/gems/activerecord-1.13.0/lib/active_record/base.rb:1344:in `attributes'
c:/ruby/lib/ruby/gems/1.8/gems/activerecord-1.13.0/lib/active_record/base.rb:1626:in `attributes_with_quotes_pre_oci'
c:/ruby/lib/ruby/gems/1.8/gems/activerecord-1.13.0/lib/active_record/connection_adapters/oci_adapter.rb:45:in `attributes_with_quotes'
c:/ruby/lib/ruby/gems/1.8/gems/activerecord-1.13.0/lib/active_record/base.rb:1718:in `quoted_column_names'
c:/ruby/lib/ruby/gems/1.8/gems/activerecord-1.13.0/lib/active_record/base.rb:1448:in `create_without_callbacks'
c:/ruby/lib/ruby/gems/1.8/gems/activerecord-1.13.0/lib/active_record/callbacks.rb:261:in `create_without_timestamps'
c:/ruby/lib/ruby/gems/1.8/gems/activerecord-1.13.0/lib/active_record/timestamp.rb:30:in `create'
c:/ruby/lib/ruby/gems/1.8/gems/activerecord-1.13.0/lib/active_record/base.rb:1431:in `create_or_update_without_callbacks'
c:/ruby/lib/ruby/gems/1.8/gems/activerecord-1.13.0/lib/active_record/callbacks.rb:249:in `create_or_update'
c:/ruby/lib/ruby/gems/1.8/gems/activerecord-1.13.0/lib/active_record/base.rb:1231:in `save_without_validation'
c:/ruby/lib/ruby/gems/1.8/gems/activerecord-1.13.0/lib/active_record/validations.rb:687:in `save_without_transactions'
c:/ruby/lib/ruby/gems/1.8/gems/activerecord-1.13.0/lib/active_record/transactions.rb:126:in `save'
c:/ruby/lib/ruby/gems/1.8/gems/activerecord-1.13.0/lib/active_record/connection_adapters/abstract/database_statements.rb:51:in `transaction'
c:/ruby/lib/ruby/gems/1.8/gems/activerecord-1.13.0/lib/active_record/transactions.rb:91:in `transaction'
c:/ruby/lib/ruby/gems/1.8/gems/activerecord-1.13.0/lib/active_record/transactions.rb:118:in `transaction'
c:/ruby/lib/ruby/gems/1.8/gems/activerecord-1.13.0/lib/active_record/transactions.rb:126:in `save'
./test/unit/date_test.rb:40:in `test_epoch_time'
90 tests, 371 assertions, 0 failures, 1 errors
c:/ruby/bin/ruby -Ilib;test "c:/ruby/lib/ruby/gems/1.8/gems/rake-0.6.2/lib/rake/rake_test_loader.rb" "test/functional/accountability_controller_test.rb" "test/functional/centernet_controller_test.rb" "test/functional/contacts_controller_test.rb" "test/functional/demographics_controller_test.rb" "test/functional/helper_test.rb" "test/functional/login_controller_test.rb" "test/functional/search_controller_test.rb"
User [sydennis]
Using Oracle
Loaded suite c:/ruby/lib/ruby/gems/1.8/gems/rake-0.6.2/lib/rake/rake_test_loader
Started
..........
Finished in 1.329 seconds.
10 tests, 29 assertions, 0 failures, 0 errors
rake aborted!
Test failures
C:\egghead\phoenix\rails>
We are stuck now on why the oci_adapter's date/time logic would have varying behaviors from one OS to the next. We'll continue to beat on this, but I wanted to get some visibility in case someone has a quick explanation why there might be such a problem, or just in case someone else is seeing this behavior.