Skip to content

PG::TextDecoder::Date fails to handle dates within the 10-day Gregorian calendar adjustment period #725

Description

@dmolesUC

Since Postgres 10, Postgres has used the Gregorian calendar for all dates, including dates before the Gregorian calendar was in use.

Ruby claims to, by default, uses the Gregorian calendar for 1582-10-15 and later, and the Julian calendar otherwise; however, this is only valid for dates 1582-10-04 and earlier. Dates using the default Date::ITALY epoch cannot be created between 1582-10-05 and 1582-10-14, and attempting to do so raises invalid date (Date::Error).

Because PG::TextDecoder::Date uses the default Date initializer, attempting to read a date in this range also raises this error.

Steps to reproduce:

  1. create an ActiveRecord model with a date field:

    class CreateDateRecords < ActiveRecord::Migration[8.1]
      def change
        create_table :date_records do |t|
          t.date :some_date
          t.timestamps
          t.check_constraint "num_nonnulls(from_date, to_date, from_year, to_year) >= 1"
        end
      end
    end
    
    class DateRecord < ApplicationRecord; end
  2. create and persist an instance of this model with the date field set to a value in this range:

    date_value = Date.new(1582, 10, 10, Date::GREGORIAN)
    record = DateRecord.create!(date_field: date_value)
  3. load the record from the database:

    reloaded = DateRecord.find_by(id: record.id)

Expected:

  • record is reloaded
  • date value matches original value

Actual:

  • reloading fails with a stack trace like:

    Date::Error:
      invalid date
    # /Users/dmoles/.rvm/gems/ruby-4.0.3/gems/pg-1.6.3-arm64-darwin/lib/pg/text_decoder/date.rb:14:in 'Date#initialize'
    # /Users/dmoles/.rvm/gems/ruby-4.0.3/gems/pg-1.6.3-arm64-darwin/lib/pg/text_decoder/date.rb:14:in 'PG::TextDecoder::Date#decode'
    # /Users/dmoles/.rvm/gems/ruby-4.0.3/gems/activerecord-8.1.3/lib/active_record/connection_adapters/postgresql/database_statements.rb:191:in 'PG::Result#values'
    # /Users/dmoles/.rvm/gems/ruby-4.0.3/gems/activerecord-8.1.3/lib/active_record/connection_adapters/postgresql/database_statements.rb:191:in 'ActiveRecord::ConnectionAdapters::PostgreSQL::DatabaseStatements#cast_result'
    

Proposed fix:

The most correct fix, in terms of matching the Postgres documentation, would be to modify PG::TextDecoder::Date to pass Date::GREGORIAN explicitly, and have all dates returned from Postgres us the Gregorian epoch.

This would be a change in behavior for dates 1582-10-04 and earlier, though, and might surprise some users -- for instance, saving and reloading the naive Julian value Date.new(1582, 10, 3) would return a Date that would not be equal to the original.

A less correct but more compatible fix would be to only pass start = Date::GREGORIAN for dates in the invalid range, possibly in a rescue block.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions