web-dev-qa-db-ja.com

MySQLのDATETIMEへの挿入:ISO :: 8601形式を使用しても安全ですか?

私たちのプロジェクトでは、Zend Framework Modelジェネレーターを使用しています。これは、DB(MySQL)に格納されているプロパティをDATETIMEフィールドとして設定するために次のようなものを生成します。

public function setObjectDatetime($data) {
  if (! $data instanceof Zend_Date) { ... some conversion code ... }
  $this->objectDatetime = $data->toString(Zend_Date::ISO_8601);
}

したがって、ISO :: 8601形式の文字列(たとえば、「2012-06-15T18:33:00 + 03:00」)は、実際にはプロパティとして保存されるものです。

このモデルをsaveしてMySQL(バージョン5.5.16)に渡そうとすると、問題が発生します。警告が発生しますが、対応する行が挿入/更新され、正しい結果が得られます。問題の原因がMySQLであり、一部のドライバーの動作ではないことを確認するのは簡単です。次のようなクエリを発行するだけです...

UPDATE table_name SET datetime_field = '2012-06-15T18:33:00+03:00' WHERE id = 1;

...そして結果は1 row affected, 1 warningとなり、

1264 | Out of range value for column 'dt' at row 1

警告(SHOW WARNINGSで表示)。

驚いたことに、phpMyAdminは警告をまったく表示しません。そして、すべてのサーバー側コードがこのクエリを確実なものとして処理しました。 )

したがって、問題は、モデルに保存するものを別の文字列形式(たとえば、「YY-MM-dd HH:mm:ss」など)に本当に再フォーマットする必要があるのか​​、それとも修正されるMySQLの奇妙な動作だけなのか遅かれ早かれ?

17
raina77ow

この質問に対する短い答えは「いいえ、安全ではありません」のようです。この結論は、MySQL Shellを使用した一連の実験に従っています。それでも、もっと「理論的な」答えをいただければ幸いです...

どうやらMySQLエンジンは、(デフォルトでは)_sql_mode_がSTRICT_ALL_TABLESに設定されていても、Datetimeリテラルとして受け入れることがかなり自由です。

INSERT INTO t(dt) VALUES('2012-01,03.04:05@06'); -- Query OK, 1 row affected

さらに、文字列が短すぎる場合は、ゼロが埋め込まれます...しかし、驚きがあるかもしれません:

INSERT INTO t(dt) VALUES('2012011'); -- 2020-12-01 01:00:00 is what's inserted

悲しいことに、文字列が長すぎる(最後の解析可能な数字の後に空白以外のものが続く場合)は、strictモードでは無効な値と見なされます。

_mysql> INSERT INTO t(dt) VALUES('2012-06-27T05:25Z');
ERROR 1292 (22007): Incorrect datetime value: '2012-06-27T05:25Z' for column 'dt' at row 1
mysql> INSERT INTO t(dt) VALUES('2012-06-27T05:25');
Query OK, 1 row affected (0.10 sec)
_

従来のモードでは、解析はさらに緩和されますが、より正確ではありません。さらに、strictモードで正しくないと見なされる文字列は、「サイレント警告」を表示しますが、操作は成功します。

_mysql> INSERT INTO t(dt) VALUES('2012-06-27T05:25Z');
Query OK, 1 row affected, 1 warning (0.10 sec)

mysql> SHOW WARNINGS;
+---------+------+---------------------------------------------+
| Warning | 1264 | Out of range value for column 'dt' at row 1 |
+---------+------+---------------------------------------------+

mysql> SELECT dt FROM t;
+---------------------+
| dt                  |
+---------------------+
| 2012-06-27 05:25:00 |
+---------------------+
_

結論として、日付(および日時)が常に「正規化された」形式でDBに送信されるように、DAL関連のコードを書き直す必要がありました。 Zend_Dbの開発者ではなく、なぜ私たちがそうする必要があるのでしょうか。しかし、それは別の話だと思います。 )

16
raina77ow

私が知る限り、時間オフセット情報(ISO 8601文字列の最後の+03:00)をMySQLの日付または時間タイプに格納する方法はないので、自分で解決策を見つける必要があります。

考えられる1つのアプローチは、ISO 8601文字列を分割し、char(5)列にオフセットを格納することですが、確かに操作が多少困難になります。オフセットをTime列に格納すると、日付/時刻の操作が少し簡単になると思います。

[〜#〜]編集[〜#〜]
MySQLのドキュメントで this を偶然見つけました。

4
Brian Driscoll

デフォルトでは、MySQLは日時にISO9075形式を使用します

1番目と2番目の引数の可能な値は、いくつかの可能なフォーマット文字列になります(使用される指定子については、DATE_FORMAT()関数の説明の表を参照してください)。 ISO形式は、ISO 8601ではなく、ISO 9075を参照します。

http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_get-format

3
akazakou