简体   繁体   中英

Postgresql: Convert a date string '2016-01-01 00:00:00' to a datetime with specific timezone

I'm in a tricky situation. A client's database timezone was configured for America/Chicago instead of UTC.

From the app, we ask customers to enter useful dates, and sadly those dates are stored 'as-is', so if they entered '2001-01-01 00:00:00' in the text input, that same value will be stored in the DB, and we are ignoring the customer's timezone. We save that info separately.

The table column is of type TIMEZONETZ. So Postgresql will add the America/Chicago timezone offset at the end: Eg '2001-01-01 00:00:00-02'. Naturally, most of the customers are not in Chicago.

The difficult part, is that, even knowing the customer's timezone, it's really hard to run calculations on the DB given that the datetime was not correctly pre-processed before storing it into the DB.

My attempted solution, is finding a way to extract the datetime string from the column value, and re-convert it to a date with the right timezone. Eg (pseudo-code):

// This is psuedo code
SELECT DATETIME((SELECT date_string(mycolumn) FROM mytable),  
TIMEZONE('America/Managua'));

Which would be equivalent in PHP:
$customerInput = '2016-01-01 00:00:00';
$format = 'Y-m-d H:i:s';
$wrongDateStoredInDb = DateTime::createFromFormat($format, $customerInput, new DateTimezone('America/Chicago'));

// In order to fix that date, I'd extract the dateString and create a new DateTime but passing the correct timezone info.

$customerTimezone = new Timezone('America/Bogota');
$customerInput = $wrongDateStoredInDb->format($format); // Assuming we didn't have it already.
$actualDateTime = DateTime::createFromFormat($format, $customerInput, $customerTimezone);

With that kind of information, I'd be able to run calculations on date ranges, with the correct values, eg:

// Pseudo-code
SELECT * FROM myTable WHERE fix_date_time(columnWithInvalidDate, `correctTimezone`)::timestamp > `sometimestamp`;

I've read the Postgresql docs, and I've tried everything I could, but nothing seems to work.

Any suggestion is more than welcomed!

So you say that you have a timestamptz column. That is not stored as a string, but as an "instant" in microseconds since the epoch. But when you do an INSERT and give a string, Postgres converts your string into a time value automatically, before storing it. It's been assuming the strings you give it are in Chicago time, since that's the default timezone.

Now you want to reinterpret those times as being in the user's time zone instead. To do that, you can put them back into strings (in Chicago time), and then parse them again but with a different time zone.

Suppose you have data like this:

CREATE TABLE t (id int primary key, ts timestamptz, tz text);

SET TIMEZONE='America/Chicago';
INSERT INTO t
VALUES
(1, '2015-01-01 12:00:00', 'America/Managua'),
(2, '2015-01-01 12:00:00', 'America/Los_Angeles')
;

Then this will give you new times that are what the user really meant:

SET TIMEZONE='America/Chicago';
SELECT  ts::text::timestamp AT TIME ZONE tz FROM t;

To break it down: ts::text stringifies the value into Chicago time, then we re-parse it, but into a bare timestamp with no timezone information yet. Then we attach a time zone---not Chicago time, but the user's own timezone.

From here you should be able to handle fixing the bad rows (and not the new ones, if you've already changed the server's default timezone).

One caveat is that if a user entered a time and then changed their timezone, there is no way to recover that, so this will interpret that old time incorrectly.

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