私はこれをたくさんデバッグし、ようやくmysqlシェルで再現することに成功しました。
フィールドタイプtimestamp
のmysqlデータベースにタイムスタンプを保存しています。私はFROM_UNIXTIME()
を使用してスクリプトからそれらを更新し、それらを選択するときにUNIX_TIMESTAMP()
を使用します。
_SET time_zone =
_を使用した接続でタイムゾーンを設定しない場合、正常に動作します。しかし、タイムゾーンを設定すると、次のことが起こります。
UNIX_TIMESTAMP()
は依然として正しい結果を提供しています。UPDATE table SET field = FROM_UNIXTIME(..)
は、DBに誤った値を設定します。動作していないのはFROM_UNIXTIME()であることを知っています。接続固有のタイムゾーンなしで別の接続を開くと、再度更新するまで誤った値が表示されるためです。
1時間の差があるという事実は、夏時間の問題かもしれないと思います。ベルリンには夏時間があり、バンコクにはないので(私の知る限り)。
これは、mysqlシェルの変更されていないログで、この動作を再現しました。
サーバーのタイムゾーンは、Asia/Bangkok(CIT)です。
_$ mysql -uroot -p timezonetest
Enter password:
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 530
Server version: 5.7.16-0ubuntu0.16.10.1 (Ubuntu)
Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> SELECT @@system_time_zone;
+--------------------+
| @@system_time_zone |
+--------------------+
| ICT |
+--------------------+
1 row in set (0.00 sec)
mysql> describe test;
+------------+-----------+------+-----+-------------------+-----------------------------+
| Field | Type | Null | Key | Default | Extra |
+------------+-----------+------+-----+-------------------+-----------------------------+
| payment_id | int(11) | NO | PRI | NULL | auto_increment |
| begins_at | timestamp | NO | | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
+------------+-----------+------+-----+-------------------+-----------------------------+
2 rows in set (0.00 sec)
mysql> select * from test;
+------------+---------------------+
| payment_id | begins_at |
+------------+---------------------+
| 338840 | 2013-10-27 08:15:33 |
+------------+---------------------+
1 row in set (0.00 sec)
mysql> select unix_timestamp(begins_at) from test where payment_id = 338840;
+---------------------------+
| unix_timestamp(begins_at) |
+---------------------------+
| 1382836533 |
+---------------------------+
1 row in set (0.00 sec)
mysql> set time_zone = 'CET';
Query OK, 0 rows affected (0.00 sec)
mysql> select * from test;
+------------+---------------------+
| payment_id | begins_at |
+------------+---------------------+
| 338840 | 2013-10-27 02:15:33 |
+------------+---------------------+
1 row in set (0.00 sec)
mysql> select unix_timestamp(begins_at) from test where payment_id = 338840;
+---------------------------+
| unix_timestamp(begins_at) |
+---------------------------+
| 1382836533 |
+---------------------------+
1 row in set (0.00 sec)
mysql> update test set begins_at = from_unixtime(1382836533) where payment_id = 338840;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> select unix_timestamp(begins_at) from test where payment_id = 338840;
+---------------------------+
| unix_timestamp(begins_at) |
+---------------------------+
| 1382832933 |
+---------------------------+
1 row in set (0.00 sec)
mysql> select * from test;
+------------+---------------------+
| payment_id | begins_at |
+------------+---------------------+
| 338840 | 2013-10-27 02:15:33 |
+------------+---------------------+
1 row in set (0.00 sec)
mysql>
_
別のログ:
_ mysql> describe test;
+------------+-----------+------+-----+-------------------+-----------------------------+
| Field | Type | Null | Key | Default | Extra |
+------------+-----------+------+-----+-------------------+-----------------------------+
| payment_id | int(11) | NO | PRI | NULL | auto_increment |
| begins_at | timestamp | NO | | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
+------------+-----------+------+-----+-------------------+-----------------------------+
2 rows in set (0.00 sec)
mysql> select * from test;
+------------+---------------------+
| payment_id | begins_at |
+------------+---------------------+
| 338840 | 2013-10-27 02:15:33 |
+------------+---------------------+
1 row in set (0.00 sec)
mysql> set time_zone = 'Europe/Berlin';
Query OK, 0 rows affected (0.00 sec)
mysql> select unix_timestamp(begins_at) from test;
+---------------------------+
| unix_timestamp(begins_at) |
+---------------------------+
| 1382832933 |
+---------------------------+
1 row in set (0.00 sec)
mysql> update test set begins_at = from_unixtime(1382836533);
Query OK, 0 rows affected (0.00 sec)
Rows matched: 1 Changed: 0 Warnings: 0
mysql> select unix_timestamp(begins_at) from test;
+---------------------------+
| unix_timestamp(begins_at) |
+---------------------------+
| 1382832933 |
+---------------------------+
1 row in set (0.00 sec)
mysql> select * from test;
+------------+---------------------+
| payment_id | begins_at |
+------------+---------------------+
| 338840 | 2013-10-27 02:15:33 |
+------------+---------------------+
1 row in set (0.00 sec)
mysql> set time_zone = 'Asia/Bangkok';
Query OK, 0 rows affected (0.00 sec)
mysql> select * from test;
+------------+---------------------+
| payment_id | begins_at |
+------------+---------------------+
| 338840 | 2013-10-27 07:15:33 |
+------------+---------------------+
1 row in set (0.00 sec)
mysql> select unix_timestamp(begins_at) from test;
+---------------------------+
| unix_timestamp(begins_at) |
+---------------------------+
| 1382832933 |
+---------------------------+
1 row in set (0.00 sec)
mysql> update test set begins_at = from_unixtime(1382836533);
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> select unix_timestamp(begins_at) from test;
+---------------------------+
| unix_timestamp(begins_at) |
+---------------------------+
| 1382836533 |
+---------------------------+
1 row in set (0.00 sec)
mysql> select * from test;
+------------+---------------------+
| payment_id | begins_at |
+------------+---------------------+
| 338840 | 2013-10-27 08:15:33 |
+------------+---------------------+
1 row in set (0.00 sec)
mysql>
_
MySQLは正しく動作しています–テストは無効です。
DSTを使用してタイムゾーンを往復する場合、遷移が発生してもロスレスコンバージョンは発生しません。問題のタイムスタンプは、「CET」および「ヨーロッパ/ベルリン」のDST移行中に発生します。
アジア/バンコクには2つの壁時計時刻があり、ヨーロッパ/ベルリンの1つの壁時計時刻に対応しています。
_mysql> SELECT CONVERT_TZ('2013-10-27 08:15:33','Asia/Bangkok','Europe/Berlin');
+------------------------------------------------------------------+
| CONVERT_TZ('2013-10-27 08:15:33','Asia/Bangkok','Europe/Berlin') |
+------------------------------------------------------------------+
| 2013-10-27 02:15:33 |
+------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> SELECT CONVERT_TZ('2013-10-27 07:15:33','Asia/Bangkok','Europe/Berlin');
+------------------------------------------------------------------+
| CONVERT_TZ('2013-10-27 07:15:33','Asia/Bangkok','Europe/Berlin') |
+------------------------------------------------------------------+
| 2013-10-27 02:15:33 |
+------------------------------------------------------------------+
1 row in set (0.00 sec)
_
UTCに変換してこれを確認してください...
_mysql> select convert_tz('2013-10-27 02:59:59','Europe/Berlin','UTC');
+---------------------------------------------------------+
| convert_tz('2013-10-27 02:59:59','Europe/Berlin','UTC') |
+---------------------------------------------------------+
| 2013-10-27 00:59:59 |
+---------------------------------------------------------+
1 row in set (0.00 sec)
_
2秒後...
_mysql> select convert_tz('2013-10-27 03:01:01','Europe/Berlin','UTC');
+---------------------------------------------------------+
| convert_tz('2013-10-27 03:01:01','Europe/Berlin','UTC') |
+---------------------------------------------------------+
| 2013-10-27 02:01:01 |
+---------------------------------------------------------+
1 row in set (0.00 sec)
_
... 1時間2秒後です。
または、裏返します。
_mysql> SET @@time_zone = 'CET';
mysql> SELECT FROM_UNIXTIME(1382825733) AS zero,
FROM_UNIXTIME(1382825733 + 3600) AS one,
FROM_UNIXTIME(1382825733 + 3600 + 3600) as two,
FROM_UNIXTIME(1382825733 + 3600 + 3600 + 3600) as three,
FROM_UNIXTIME(1382825733 + 3600 + 3600 + 3600 + 3600) as four;
+---------------------+---------------------+---------------------+---------------------+---------------------+
| zero | one | two | three | four |
+---------------------+---------------------+---------------------+---------------------+---------------------+
| 2013-10-27 00:15:33 | 2013-10-27 01:15:33 | 2013-10-27 02:15:33 | 2013-10-27 02:15:33 | 2013-10-27 03:15:33 |
+---------------------+---------------------+---------------------+---------------------+---------------------+
^^ ... wait, what? .. ^^
1 row in set (0.00 sec)
_
移行時間中にあいまいな値でタイムゾーン変換を行う場合、変換はロスレスではありません。
タイムスタンプの操作は、エンドツーエンドでUTCにする必要があります。 FROM_UNIXTIME()
またはUNIX_TIMESTAMP()
の使用は、どちらか一方のネイティブUTC値で機能しますが、値は引き続きセッションのタイムゾーン(またはセッションの場合はサーバーのタイムゾーン)との間で変換されます。タイムゾーンは設定されていません)反対側-TIMESTAMP
列の値(途中でUTCとして保存され、セッションのタイムゾーンとの間で変換されます)に向かう途中またはからの途中。
これが、サーバークロックが常にUTCを使用する必要がある理由の1つです。