doctrine/dbal

Oracle `datetimetz` incorrectly parsed due to disagreement between `OraclePlatform`->`getDateTimeTzFormatString()` and Oracle driver's `InitializeSession`

wayne530 opened this issue · 0 comments

Bug Report

Q A
Version all versions >= 3.7.0 (I did not check prior versions)

Summary

This issue was documented in #2332 however simply because a workaround exists, I would not consider the issue "fixed" as everyone using datetimetz will encounter this issue when using Oracle and as a result, will have many corrupted datetimetz values that need to be corrected even after the workaround is discovered and applied. tl;dr This is a major issue with a relatively trivial fix so rather than rely on a workaround (which requires digging through your issues to find), how about just fixing it in all actively maintained version branches?

Current behaviour

Timestamps that have a negative offset from GMT, for example PST (UTC-8) or PDT (UTC-7) are incorrectly parsed as positive offsets from GMT. For example, datetimetz values with a -08:00 offset are stored in Oracle as +08:00.

How to reproduce

Reproducing this issue is quite straightforward. The Oracle InitializeSession middleware sets NLS_TIMESTAMP_TZ_FORMAT to YYYY-MM-DD HH24:MI:SS TZH:TZM:

                $connection->exec(
                    'ALTER SESSION SET'
                        . " NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS'"
                        . " NLS_TIME_FORMAT = 'HH24:MI:SS'"
                        . " NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS'"
                        . " NLS_TIMESTAMP_TZ_FORMAT = 'YYYY-MM-DD HH24:MI:SS TZH:TZM'"
                        . " NLS_NUMERIC_CHARACTERS = '.,'",
                );

while OraclePlatform->getDateTimeTzFormatString() uses:

    public function getDateTimeTzFormatString(): string
    {
        return 'Y-m-d H:i:sP';
    }

Thus, if I wish set set a datetimetz from the current datetime in my location at the time of this writing, which is 2024-07-15T11:16:00-07:00, I would take the DateTime instance and format it to a string using the format provided by OraclePlatform, or 2024-07-15 11:16:00-07:00.

Connect to an Oracle database using credentials that allow you to create tables and insert. Initialize the session as the Oracle middleware would:

ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS' NLS_TIME_FORMAT = 'HH24:MI:SS' NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS' NLS_TIMESTAMP_TZ_FORMAT = 'YYYY-MM-DD HH24:MI:SS TZH:TZM' NLS_NUMERIC_CHARACTERS = '.,';

Create a table with a single column that is a TIMESTAMP WITH TIME ZONE type:

CREATE TABLE test (created_at TIMESTAMP WITH TIME ZONE);

Insert a row into the table and provide the string formatted datetimetz as OraclePlatform would provide:

INSERT INTO test (created_at) VALUES ('2024-07-15 11:16:00-07:00');

Now view the stored row:

SELECT * FROM test;

Expected output:

2024-07-15 11:16:00 -07:00

Actual output:

2024-07-15 11:16:00 +07:00

Expected behaviour

datetimetz values should be stored with the correct UTC offset. Hopefully this is not controversial.

The fix

The fix is quite straightforward. Make OraclePlatform->getDateTimeTzFormatString() and Oracle middleware's InitializeSession agree on a datetimetz format, whether that is with OR without a space. For example, leave OraclePlatform as is and simply update InitializeSession as follows:

                $connection->exec(
                    'ALTER SESSION SET'
                        . " NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS'"
                        . " NLS_TIME_FORMAT = 'HH24:MI:SS'"
                        . " NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS'"
                        . " NLS_TIMESTAMP_TZ_FORMAT = 'YYYY-MM-DD HH24:MI:SSTZH:TZM'"
                        . " NLS_NUMERIC_CHARACTERS = '.,'",
                );