简体   繁体   中英

postgresql to_timestamp accepts invalid dates by design

I'm trying to validate strings to timestamps from several CSVs and simply casting them to timestamptz will fail due to the impossibility of forcing an unique datetime format:

select '10/31/2010'::timestamp --fail due to "different datestyle" 
select '31/10/2010'::timestamp --works

I thought to_timestamp() would do the trick, but something like:

select to_timestamp('31/02/2014 14:30', 'DD/MM/YYYY HH24:MI'); 

will return "2014-03-03 14:30:00-05" instead of throwing an exception

So I thought of using this approach , that reverts back the output to text using to_char and comparing it with the original input, but a mask like 'DD/MM/YYYY HH24:MI' will cast "06/03/2014 0 :06" to "06/03/2014 00 :06", the strings are different!

CREATE OR REPLACE FUNCTION to_timestamp_safe(IN p_date text, IN p_format text, OUT r_date timestamp without time zone)
  RETURNS timestamp without time zone AS
$BODY$

    BEGIN
      r_date = TO_TIMESTAMP(p_date, p_format);
       IF TO_CHAR(r_date, p_format) != p_date THEN
        RAISE EXCEPTION 'Input date % does not match output date %', p_date, r_date;
      END IF;
    END;
    $BODY$
  LANGUAGE plpgsql VOLATILE
  COST 100;

The following example fails where it should work:

select to_timestamp_safe ('06/03/2014 0:06', 'DD/MM/YYYY HH24:MI');

output:

ERROR: Input date 06/03/2014 0:06 does not match output date 2014-03-06 00:06:00
SQL state: P0001

Is there a smart way of safely validate strings to timestamptz without the above pitfalls?

The FM modifier did the trick (thanks @ErwinBrandstetter) and it's indeed kind of a general solution because we came up with this idea of creating a profile for each type of csv and the date format mask can be stored there for the columns that are date/time, most part of the columns go to an hstore column anyway and we need to keep their types stored somewhere. So I was able to create something like this, where _colmask is the datetime format mask that may or may not have a FM modifier. Then I simply perform this function against my staging table (looping for each column)

CREATE OR REPLACE FUNCTION validate_column_datatype(_colvalue text, _colname text, _type text,  _colmask text)
  RETURNS void AS
$BODY$
BEGIN

declare
    -- error stack
    _returned_sqlstate text := null;
    _column_name text := null;
    _constraint_name text := null;
    _pg_datatype_name text := null; 
    _message_text text := null;
    _table_name text := null;
    _schema_name text := null;
    _pg_exception_detail text := null;
    _pg_exception_hint text := null;
    _pg_exception_context text := null;

    BEGIN


    IF _type = 'timestamptz'  then    
          IF TO_CHAR(TO_TIMESTAMP(_colvalue, _colmask), _colmask) != _colvalue THEN
        RAISE EXCEPTION 'Input date % does not match output date', _colvalue;
          END IF;

    ELSEIF _type = 'timestamp'  then
          IF TO_CHAR(TO_TIMESTAMP(_colvalue, _colmask), _colmask) != _colvalue THEN
        RAISE EXCEPTION 'Input date % does not match output date', _colvalue;
          END IF;

    ELSEIF _type = 'numeric'  then
        perform _colvalue::numeric;   

    ELSEIF _type = 'integer'  then
        perform _colvalue::integer;    

   -- other types

        END IF;   

-- exception occurs      
    EXCEPTION WHEN OTHERS THEN
         get stacked diagnostics 
        _returned_sqlstate      = RETURNED_SQLSTATE,
        _column_name        = COLUMN_NAME,
        _constraint_name    = CONSTRAINT_NAME, 
        _pg_datatype_name   = PG_DATATYPE_NAME, 
        _message_text       = MESSAGE_TEXT,
        _table_name         = TABLE_NAME,
        _schema_name        = SCHEMA_NAME,
        _pg_exception_detail    = PG_EXCEPTION_DETAIL,
        _pg_exception_hint      = PG_EXCEPTION_HINT,
        _pg_exception_context   = PG_EXCEPTION_CONTEXT;

        _message_text :=  -- write something meaningful
        _pg_exception_detail = -- write something meaningful
        _pg_exception_hint := -- write something meaningful

        -- log to something

    END;

END;
$BODY$
  LANGUAGE plpgsql VOLATILE
  COST 100; 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM