MySQLの日時フィールドと夏時間—「余分な」時間を参照するにはどうすればよいですか?


88

アメリカ/ニューヨークのタイムゾーンを使用しています。秋には、1時間「フォールバック」します。つまり、午前2時に実質的に1時間「回復」します。移行ポイントでは、次のことが起こります。

01:59:00 -04:00で
、1分後に
01:00:00 -05:00になります。

したがって、単に「1:30 am」と言っただけでは、1:30が最初に回転するのか2番目に回転するのかについてはあいまいです。MySQLデータベースにスケジュールデータを保存しようとしていますが、時間を適切に保存する方法を判断できません。

ここに問題があります:
「2009-11-01 00:30:00」は2009-11-01 00:30:00 -04:00
として内部的に格納されます「2009-11-01 01:30:00」は内部的に格納されます2009-11-01 01:30:00 -05:00

これは問題なく、かなり期待されています。しかし、どのようにして01:30:00 -04:00に何かを保存しますか?ドキュメントには、私はそれが正当に無視されていオフセットを指定しようとしたとき、それに応じて、オフセットとを指定するための任意のサポートは表示されません。

私が考えた唯一の解決策は、夏時間を使用しないタイムゾーンにサーバーを設定し、スクリプトで必要な変換を行うことです(これにはPHPを使用しています)。しかし、それは必要であるように思われません。

提案をありがとう。


MySQLやPHPについて、首尾一貫した答えを作成するのに十分な知識はありませんが、UTCとの間の変換に何らかの関係があると思います。
マークランサム

2
内部的にはすべてUTCとして保存されています。
Eli

4
このトピックについて、web.ivy.net /〜carton / rant / MySQL-timezones.txtが興味深い記事であることがわかりました。
micahwittman、2009年

良いリンク、micahwittman-とても役に立ちました。
アーロン

すばらしい質問。よくある問題。
Vardumper 2016

回答:


47

MySQLの日付タイプは、率直に言って、壊れており、格納することはできませんすべてのシステムがUTCまたはGMT-5のように、一定のオフセットのタイムゾーンに設定されていない限り、正しく回。(MySQL 5.0.45を使用しています)

これは、サマータイムが終了する前の1時間は保存できないためです。日付をどのように入力しても、すべての日付関数はこれらの時刻を切り替え後の1時間のように扱います。

私のシステムのタイムゾーンはAmerica/New_Yorkです。1257051600(2009年11月1日、日曜日06:00:00 +0100)を保存してみましょう。

ここでは、独自のINTERVAL構文を使用しています。

SELECT UNIX_TIMESTAMP('2009-11-01 00:00:00' + INTERVAL 3599 SECOND); # 1257051599
SELECT UNIX_TIMESTAMP('2009-11-01 00:00:00' + INTERVAL 3600 SECOND); # 1257055200

SELECT UNIX_TIMESTAMP('2009-11-01 01:00:00' - INTERVAL 1 SECOND); # 1257051599
SELECT UNIX_TIMESTAMP('2009-11-01 01:00:00' - INTERVAL 0 SECOND); # 1257055200

FROM_UNIXTIME()正確な時刻を返さなくても。

SELECT UNIX_TIMESTAMP(FROM_UNIXTIME(1257051599)); # 1257051599
SELECT UNIX_TIMESTAMP(FROM_UNIXTIME(1257051600)); # 1257055200

奇妙なことに、DATETIME 、DSTが開始されたとき(たとえば2009-03-08 02:59:59)の「失われた」時間内に(文字列形式でのみ!)時間を格納して返します。しかし、MySQL関数でこれらの日付を使用するのは危険です。

SELECT UNIX_TIMESTAMP('2009-03-08 01:59:59'); # 1236495599
SELECT UNIX_TIMESTAMP('2009-03-08 02:00:00'); # 1236495600
# ...
SELECT UNIX_TIMESTAMP('2009-03-08 02:59:59'); # 1236495600
SELECT UNIX_TIMESTAMP('2009-03-08 03:00:00'); # 1236495600

要点:1年のたびに保存および取得する必要がある場合、いくつかの望ましくないオプションがあります。

  1. システムのタイムゾーンをGMT +一定のオフセットに設定します。例:UTC
  2. 日付をINTとして保存する(Aaronが発見したように、TIMESTAMPは信頼性さえありません)

  3. DATETIMEタイプに一定のオフセットタイムゾーンがあるとしましょう。たとえば、にいる場合は、MySQLの外でAmerica/New_York日付をGMT-5に変換し、DATETIMEとして保存します(これは必須であることがわかります。Aaronの回答を参照してください)。次に、MySQLの日付/時刻関数を使用する場合は十分に注意する必要があります。一部の値はシステムのタイムゾーンであると想定し、他の値(特に時間算術関数)は「タイムゾーンに依存しない」(時間はUTCであるかのように動作する場合がある)ためです。

Aaronと私は、自動生成TIMESTAMP列も壊れていると思います。両方2009-11-01 01:30 -04002009-11-01 01:30 -0500曖昧として格納されます2009-11-01 01:30


このmrclayにご協力いただきありがとうございます。ここで状況を非常に正確に概説しました。
アーロン

オプション3は、DST機能が追加される前に関数が実装されたように見えるため、実際には時間計算に対してより安全であるようです。例:TIMEDIFF( '2009-11-01 02:30:00'、 '2009-11-01 00:30:00')は2:00を返しますが、これはUTCには適切ですが、America / New_Yorkでは3時間です。離れて。
スティーブクレイ、

1
-1:MySQLの日付/時刻関数がタイムゾーンに依存しないDATETIME型で動作するという誤りを犯しました。あなたはUNIX_TIMSTAMPに渡している引数は、そのためです。UNIX_TIMESTAMPは、セッションタイムゾーンのコンテキストでこれをUTCに変換しようとするだけです。そのタイムゾーンのDSTルールのコンテキストで追加を実行することはありません。select '2009-11-01 00:00:00' + INTERVAL 3600 SECOND;'2009-11-01 01:00:00'
kbro 2016

@kbro OK、問題はまだ残っています。セッションtzがの場合、America/New_York1257051600を保存する方法がありません。
Steve Clay

75

私の目的のためにそれを理解しました。私が学んだことを要約します(申し訳ありませんが、これらのメモは冗長です。これらは他の何よりも私の将来の紹介用です)。

私は私の以前のコメントの一つに言ったことに反して、DATETIMEおよびTIMESTAMPフィールドがない動作が異なります。TIMESTAMPフィールド(ドキュメントに示されている)は、「YYYY-MM-DD hh:mm:ss」形式で送信したものをすべて受け取り、現在のタイムゾーンからUTC時間に変換します。データを取得するたびに、その逆が透過的に行われます。DATETIMEフィールドはこの変換を行いません。あなたが送ったものは何でも受け取り、直接保存します。

DATETIMEとTIMESTAMPのどちらのフィールドタイプも、DSTを監視するタイムゾーンにデータを正確に格納できません。「2009-11-01 01:30:00」を保存する場合、フィールドには、必要な1:30 amのバージョン(-04:00または-05:00バージョン)を区別する方法がありません。

では、データをDST以外のタイムゾーン(UTCなど)に保存する必要があります。説明する理由により、TIMESTAMPフィールドはこのデータを正確に処理できません。システムがDSTタイムゾーンに設定されている場合、TIMESTAMPに入力したものは、取得したものと異なる場合があります。すでにUTCに変換したデータを送信しても、データはローカルタイムゾーンにあると見なされ、さらにUTCに変換されます。ローカルタイムゾーンがDSTを遵守している場合、このTIMESTAMP強制によるローカルからUTC、バックからローカルへのラウンドトリップは不可逆です( "2009-11-01 01:30:00"が2つの異なる時間にマップされるため)。

DATETIMEを使用すると、任意のタイムゾーンでデータを保存でき、送信したデータを確実に取り戻すことができます(TIMESTAMPフィールドが強制する損失の多い往復変換に強制されることはありません)。したがって、解決策はDATETIMEフィールドを使用して、フィールドに保存する前に、システムのタイムゾーンから、保存先のDST以外のゾーンに変換することです(UTCがおそらく最良のオプションだと思います)。これにより、変換ロジックをスクリプト言語に組み込んで、 "2009-11-01 01:30:00 -04:00"または "" 2009-11-01 01:30:に相当するUTCを明示的に保存できます。 00 -05:00」。

注意すべきもう1つの重要な点は、日付をDST TZに格納した場合、MySQLの日付/時刻演算関数はDSTの境界で正しく機能しないことです。したがって、UTCで保存する理由はなおさらです。

一言で言えば、私は今これを行います:

データベースからデータを取得する場合:

正確なUnixタイムスタンプを取得するために、データベースのデータをMySQLの外部のUTCとして明示的に解釈します。これには、PHPのstrtotime()関数またはそのDateTimeクラスを使用します。MySQLのCONVERT_TZ()またはUNIX_TIMESTAMP()関数を使用してMySQL内で確実に実行することはできません。CONVERT_TZは、あいまいな問題のある 'YYYY-MM-DD hh:mm:ss'値のみを出力し、UNIX_TIMESTAMP()は入力はシステムのタイムゾーンであり、データが実際に格納されたタイムゾーン(UTC)ではありません。

データをデータベースに保存する場合:

MySQL以外で希望する正確なUTC時間に日付を変換します。例:PHPのDateTimeクラスでは、「2009-11-01 1:30:00 EST」と「2009-11-01 1:30:00 EDT」を区別して指定し、それをUTCに変換して正しいUTC時間を保存できます。 DATETIMEフィールドに。

ふew。皆様のご意見、ご協力に感謝いたします。うまくいけば、これにより他の誰かが将来の頭痛の種を救うことができます。

ところで、私はこれをMySQL 5.0.22および5.0.27で見ています


13

micahwittmanのリンクは、これらのMySQLの制限に対する最も実用的な解決策だと思います。接続時にセッションのタイムゾーンをUTCに設定します。

SET SESSION time_zone = '+0:00'

次に、Unixタイムスタンプを送信するだけで、すべてが正常に行われます。


このアドバイスはうまくいきます。指定されたステートメントを使用してプール内のすべての接続を初期化すると、問題は解決します。
スノーインディー2014年

4

変更されたレコードとデータウェアハウスへのETLを追跡するために(つまり:)でTIMESTAMP列を使用するので、このスレッドは私をおかしくしました。On UPDATE CURRENT_TIMESTAMPrecordTimestamp timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP

この場合、誰かが不思議に思った場合、TIMESTAMP正しく動作し、TIMESTAMPUNIXタイムスタンプに変換することで、2つの類似した日付を区別できます。

select TestFact.*, UNIX_TIMESTAMP(recordTimestamp) from TestFact;

id  recordTimestamp         UNIX_TIMESTAMP(recordTimestamp)
1   2012-11-04 01:00:10.0   1352005210
2   2012-11-04 01:00:10.0   1352008810

3

しかし、01:30:00 -04:00に何かを保存するにはどうすればよいですか?

次のようにUTCに変換できます。

SELECT CONVERT_TZ('2009-11-29 01:30:00','-04:00','+00:00');


さらに、日付をTIMESTAMPフィールドとして保存します。これは常にUTCで保存され、UTCは夏/冬時間を認識しません。

CONVERT_TZを使用してUTCから現地時間に変換できます。

SELECT CONVERT_TZ(UTC_TIMESTAMP(),'+00:00','SYSTEM');

「+00:00」はUTC、fromタイムゾーン、「SYSTEM」はMySQLが実行されるOSのローカルタイムゾーンです。


ご返信ありがとうございます。私が知ることができるように、ドキュメントが言っているにもかかわらず、TIMESTAMPフィールドとDatetimeフィールドは同じように動作します:データはUTCで保存されますが、入力は現地時間であると想定し、自動的にUTCに変換します-変換するとUTCの最初のデータベースには、私がそれを行ったという考えがなく、4(またはDSTかどうかに応じて5)時間追加されます。したがって、問題は残ります:2009-11-01 01:30:00 -04:00を入力として指定するにはどうすればよいですか?
アーロン

さて、私の混乱のほとんどの原因は、UNIX_TIMESTAMP()関数が、TIMESTAMPまたはDATETIMEフィールドからデータをプルしているかどうかに関係なく、常に現在のタイムゾーンに関連する日付パラメーターを解釈するという事実であることがわかりました。今考えてみると、これは理にかなっています。後でもっと更新します。
アーロン

2

Mysqlは本質的に、mysql dbのtime_zone_nameテーブルを使用してこの問題を解決します。CRUD中にCONVERT_TZを使用して、夏時間を気にすることなく日時を更新します。

SELECT
  CONVERT_TZ('2019-04-01 00:00:00','Europe/London','UTC') AS time1,
  CONVERT_TZ('2019-03-01 00:00:00','Europe/London','UTC') AS time2;

1

ページの訪問数のログを記録し、その数をグラフに表示していました(Flot jQueryプラグインを使用)。テーブルにテストデータを入力したところ、すべて問題なく見えましたが、グラフの最後で、x軸のラベルに従ってポイントが1日ずれていることに気付きました。調べたところ、2015-10-25日の視聴回数がデータベースから2回取得され、Flotに渡されたため、この日付以降、毎日1日右に移動しました。
コードのバグをしばらく探したところ、この日付がDSTが行われる日付であることに気付きました。それから私はこのSOページに来ました...
...しかし、提案された解決策は私が必要とするもののやり過ぎであったか、または他の欠点がありました。あいまいなタイムスタンプを区別できないことについてはあまり心配していません。 1日あたりのレコードをカウントして表示するだけです。

まず、日付範囲を取得します。

SELECT 
    DATE(MIN(created_timestamp)) AS min_date, 
    DATE(MAX(created_timestamp)) AS max_date 
FROM page_display_log
WHERE item_id = :item_id

次に、forループで、で始まり、min_dateで終わりmax_date、1日ずつ(60*60*24)、カウントを取得しています。

for( $day = $min_date_timestamp; $day <= $max_date_timestamp; $day += 60 * 60 * 24 ) {
    $query = "
        SELECT COUNT(*) AS count_per_day
        FROM page_display_log
        WHERE 
            item_id = :item_id AND
            ( 
                created_timestamp BETWEEN 
                '" . date( "Y-m-d 00:00:00", $day ) . "' AND
                '" . date( "Y-m-d 23:59:59", $day ) . "'
            )
    ";
    //execute query and do stuff with the result
}

私の最終的かつ迅速な解決私の問題は、このでした:

$min_date_timestamp += 60 * 60 * 2; // To avoid DST problems
for( $day = $min_date_timestamp; $day <= $max_da.....

だから私はその日の初めにループを見つめているのでなく、2時間後に見つめています。タイムスタンプの実際の時刻に関係なく、その日の00:00:00から23:59:59までのレコードをデータベースに明示的に要求しているため、日は同じで、正しいカウントを取得しています。そして時間が1時間ジャンプするとき、私はまだ正しい日にいます。

注:これは5年前のスレッドであり、OPの質問に対する回答ではないことはわかっていますが、このページに遭遇した私のような人々が、説明した問題の解決策を探すのに役立つ場合があります。


おそらく実際の質問には関係ありませんが、これは恐ろしく非効率的であり、誰もそれをコピーするべきではありません!代わりに、のような単一のクエリを発行します
ドゥーイン

"SELECT CAST(created_timestamp AS date) day,COUNT(*) WHERE item_id=:item_id AND (created_timestamp BETWEEN '".date("Y-m-d 00:00:00", $min_date_timestamp)."' AND '".date("Y-m-d 23:59:59", $max_date_timestamp)."') GROUP BY day ORDER BY day";
Doin '22年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.