Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions ext/pg_text_decoder.c
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ static ID s_id_utc;
static ID s_id_getlocal;
static ID s_id_BigDecimal;

static VALUE s_Date;
static VALUE s_Date_GREGORIAN; /* Date::GREGORIAN */
static VALUE s_IPAddr;
static VALUE s_vmasks4;
static VALUE s_vmasks6;
Expand Down Expand Up @@ -937,6 +939,51 @@ pg_text_dec_inet(t_pg_coder *conv, const char *val, int len, int tuple, int fiel
return ip;
}

/*
* Document-class: PG::TextDecoder::Date < PG::SimpleDecoder
*
* This is a decoder class for conversion of PostgreSQL date type to Ruby Date values.
*
* Dates are expected in ISO format (+YYYY-MM-DD+), which is PostgreSQL's default DateStyle.
* Other formats, BC dates, years beyond 9999 and the special values +infinity+ and +-infinity+
* are returned as String.
*
* As soon as this class is used, it requires the ruby standard library 'date'.
*/
static VALUE
pg_text_dec_date(t_pg_coder *conv, const char *val, int len, int tuple, int field, int enc_idx)
{
if (len == 10
&& isdigit(val[0]) && isdigit(val[1]) && isdigit(val[2]) && isdigit(val[3])
&& val[4] == '-' && isdigit(val[5]) && isdigit(val[6])
&& val[7] == '-' && isdigit(val[8]) && isdigit(val[9])
) {
int year = str2_to_int(val) * 100 + str2_to_int(val + 2);
int mon = str2_to_int(val + 5);
int day = str2_to_int(val + 8);

return rb_funcall(s_Date, s_id_new, 4, INT2NUM(year), INT2NUM(mon), INT2NUM(day), s_Date_GREGORIAN);
}

return pg_text_dec_string(conv, val, len, tuple, field, enc_idx);
}

/* called per autoload when TextDecoder::Date is used */
static VALUE
init_pg_text_decoder_date(VALUE rb_mPG_TextDecoder)
{
rb_require("date");
rb_gc_register_address(&s_Date);
rb_gc_register_address(&s_Date_GREGORIAN);
s_Date = rb_const_get(rb_cObject, rb_intern("Date"));
s_Date_GREGORIAN = rb_const_get(s_Date, rb_intern("GREGORIAN"));

/* dummy = rb_define_class_under( rb_mPG_TextDecoder, "Date", rb_cPG_SimpleDecoder ); */
pg_define_coder( "Date", pg_text_dec_date, rb_cPG_SimpleDecoder, rb_mPG_TextDecoder );

return Qnil;
}

/* called per autoload when TextDecoder::Inet is used */
static VALUE
init_pg_text_decoder_inet(VALUE rb_mPG_TextDecoder)
Expand Down Expand Up @@ -982,6 +1029,7 @@ init_pg_text_decoder(void)

/* This module encapsulates all decoder classes with text input format */
rb_mPG_TextDecoder = rb_define_module_under( rb_mPG, "TextDecoder" );
rb_define_private_method(rb_singleton_class(rb_mPG_TextDecoder), "init_date", init_pg_text_decoder_date, 0);
rb_define_private_method(rb_singleton_class(rb_mPG_TextDecoder), "init_inet", init_pg_text_decoder_inet, 0);
rb_define_private_method(rb_singleton_class(rb_mPG_TextDecoder), "init_numeric", init_pg_text_decoder_numeric, 0);

Expand Down
16 changes: 2 additions & 14 deletions lib/pg/text_decoder/date.rb
Original file line number Diff line number Diff line change
@@ -1,21 +1,9 @@
# -*- ruby -*-
# frozen_string_literal: true

require 'date'

module PG
module TextDecoder
# This is a decoder class for conversion of PostgreSQL date type to Ruby Date values.
#
# As soon as this class is used, it requires the ruby standard library 'date'.
class Date < SimpleDecoder
def decode(string, tuple=nil, field=nil)
if string =~ /\A(\d{4})-(\d\d)-(\d\d)\z/
::Date.new $1.to_i, $2.to_i, $3.to_i, ::Date::GREGORIAN
else
string
end
end
end
# Init C part of the decoder
init_date
end
end # module PG
29 changes: 29 additions & 0 deletions spec/pg/type_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,35 @@ def textdec_timestamptz_decode_should_fail(str)
end
end

context 'dates' do
let(:textdec_date) { PG::TextDecoder::Date.new }

it 'decodes ISO dates' do
expect( textdec_date.decode('2016-01-02') ).to eq( Date.new(2016, 1, 2) )
expect( textdec_date.decode('0001-12-31') ).to eq( Date.new(1, 12, 31, Date::GREGORIAN) )
end
it 'decodes dates in the proleptic gregorian calendar' do
expect( textdec_date.decode('1581-12-15').start ).to eq( Date::GREGORIAN )
end
it 'returns special values, BC dates and years beyond 9999 as string' do
%w[infinity -infinity].each do |value|
expect( textdec_date.decode(value) ).to eq( value )
end
expect( textdec_date.decode('0044-03-15 BC') ).to eq( '0044-03-15 BC' )
expect( textdec_date.decode('10000-01-01') ).to eq( '10000-01-01' )
end
it 'returns dates in other formats as string' do
%w[01/02/2016 02.01.2016 2016-1-02 2016-01-2 2016/01/02 2016-01ª0].each do |value|
expect( textdec_date.decode(value) ).to eq( value )
end
expect( textdec_date.decode(' 2016-01-02') ).to eq( ' 2016-01-02' )
expect( textdec_date.decode('2016-01-02 ') ).to eq( '2016-01-02 ' )
end
it 'raises on invalid dates' do
expect{ textdec_date.decode('2016-02-30') }.to raise_error( Date::Error )
end
end

context 'identifier quotation' do
it 'should build an array out of an quoted identifier string' do
quoted_type = PG::TextDecoder::Identifier.new
Expand Down
Loading