web-dev-qa-db-ja.com

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

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

01:59:00 -04:00
それから1分後:
01:00:00-05:00

したがって、「午前1時30分」と単純に言うと、1時30分が1回目を指しているのか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に保存できますか? documentation はオフセットの指定をサポートしていません。したがって、オフセットを指定しようとしても、無視されます。

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

提案をありがとう。

82
Aaron

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

これは、夏時間が終了する前の1時間は何も保存できないであるためです。日付の入力方法に関係なく、すべての日付関数は、これらの時刻を切り替え後の1時間にあるかのように扱います。

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

独自の区間構文を使用しています:

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

奇妙なことに、DATETIMEwillは、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として保存します(アーロンが発見したように、TIMESTAMPは信頼性さえありません)

  3. DATETIME型には、一定のオフセットタイムゾーンがあります。例えば。 America/New_Yorkにいる場合は、MySQLの外部で日付をGMT-5に変換し、DATETIMEとして保存します(これは必須であることがわかります:アーロンの答え)。次に、MySQLの日付/時刻関数を使用する場合は細心の注意を払う必要があります。一部の値はシステムのタイムゾーンのものであると仮定するものもあれば、他の(特に時間算術関数)は「タイムゾーンに依存しない」(時刻がUTCであるかのように動作する).

アーロンと私は、自動生成のTIMESTAMP列も壊れているのではないかと疑っています。 2009-11-01 01:30 -04002009-11-01 01:30 -0500の両方が、あいまいな2009-11-01 01:30として保存されます。

42
Steve Clay

私の目的のためにそれを把握しました。私が学んだことを要約します(申し訳ありませんが、これらのメモは冗長です。これらは、将来の紹介に役立つものです。).

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

DATETIMEとTIMESTAMPのどちらのフィールドタイプも、DSTを監視するタイムゾーンにデータを正確に格納できません。 「2009-11-01 01:30:00」を保存する場合、フィールドには、希望する午前1:30のバージョン、つまり-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クラスを使用します。 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 EST」を指定し、UTCに変換して正しいUTC時間を保存できます。 DATETIMEフィールドに。

ふう。皆様のご意見とご協力ありがとうございました。うまくいけば、他の誰かが将来の頭痛の種を救うことを願っています。

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

66
Aaron

Micahwittmanの link は、これらのMySQLの制限に対する最も実用的なソリューションを持っていると思います。接続時にセッションタイムゾーンをUTCに設定します。

SET SESSION time_zone = '+0:00'

次に、Unixのタイムスタンプを送信するだけで、すべて問題ありません。

12
Steve Clay

TIMESTAMP列とOn UPDATE CURRENT_TIMESTAMP(例:recordTimestamp timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP)変更されたレコードとETLをデータウェアハウスに追跡します。

この場合、誰かが疑問に思う場合は、TIMESTAMPが正しく動作し、TIMESTAMPをUNIXタイムスタンプに変換することにより、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
4
Manuel Darveau

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;
2
Arpan Jain

私はページの訪問数を記録し、その数をグラフに表示する作業をしていました(Flot jQueryプラグインを使用)。テーブルにテストデータを入力し、すべてが正常に見えましたが、グラフの最後にx軸のラベルに従ってポイントが1日ずれていることに気付きました。調査後、2015年10月25日のビューカウントがデータベースから2回取得され、Flotに渡されることに気付きました。
しばらくの間、コード内のバグを探した後、この日付が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

次に、min_dateで始まり、max_dateで終わるforループで、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
}

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

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

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

注:これは5年前のスレッドであり、これがOPの質問への回答ではないことは知っていますが、このページに遭遇して問題の解決策を探している私のような人々を助けるかもしれません記載されています

1
Lukas