web-dev-qa-db-ja.com

SQL DATEのタイムゾーンとJava.sql.Date

SQL DATEデータ型の動作とJava.sql.Dateの動作に少し混乱しています。たとえば、次のステートメントを見てください。

select cast(? as date)           -- in most databases
select cast(? as date) from dual -- in Oracle

Javaでステートメントを準備して実行しましょう

PreparedStatement stmt = connection.prepareStatement(sql);
stmt.setDate(1, new Java.sql.Date(0)); // GMT 1970-01-01 00:00:00
ResultSet rs = stmt.executeQuery();
rs.next();

// I live in Zurich, which is CET, not GMT. So the following prints -3600000, 
// which is CET 1970-01-01 00:00:00
// ... or   GMT 1969-12-31 23:00:00
System.out.println(rs.getDate(1).getTime());

つまり、ステートメントにバインドするGMTタイムスタンプが、取得したCETタイムスタンプになります。タイムゾーンが追加されるステップとその理由は何ですか?

注意:

  • これらのデータベースのいずれにも当てはまることが確認されています。

    DB2、ダービー、H2、HSQLDB、Ingres、MySQL、Oracle、Postgres、SQL Server、Sybase ASE、Sybase SQL Anywhere

  • 私はこれがSQLiteに対してfalseであることに気付きました(実際にはDATEデータ型はありません)
  • Java.sql.Timestampの代わりにJava.sql.Dateを使用する場合、これらはすべて無関係です。
  • これは同様の質問ですが、この質問には答えていません: Java.util.Date vs Java.sql.Date
36
Lukas Eder

JDBC仕様 は、タイムゾーンに関する詳細を定義しません。それにもかかわらず、私たちのほとんどは、JDBCタイムゾーンの不一致に対処しなければならないことの苦痛を知っています。すべての StackOverflowの質問 を見てください!

最終的に、日付/時刻データベースタイプのタイムゾーンの処理は、データベースサーバー、JDBCドライバー、およびその間のすべてに集約されます。 JDBCドライバーのバグに翻弄されています。 PostgreSQLが修正した バージョン8.3のバグ ここで

Calendarオブジェクトに渡されるStatement.getTime、.getDate、および.getTimestampメソッドは、タイムゾーンを間違った方向に回転させていました。

new Date(0)を使用して新しい日付を作成すると(Oracle JavaSE _Java.sql.Date_ を使用していると断言しましょう)、日付が作成されます

指定されたミリ秒の時間値を使用します。指定されたミリ秒値に時間情報が含まれる場合、ドライバーは時間コンポーネントをゼロに対応するデフォルトのタイムゾーン(アプリケーションを実行する仮想マシンのJava仮想マシン)のタイムゾーン)の時間に設定しますGMT。

したがって、new Date(0)はGMTを使用する必要があります。

ResultSet.getDate(int) を呼び出すと、JDBC実装を実行しています。 JDBC仕様では、JDBC実装がタイムゾーンの詳細をどのように処理するかは規定されていません。そのため、実装の容赦があります。 Oracle 11g _Oracle.sql.DATE_ JavaDocを見ると、Oracle DBはタイムゾーン情報を保存していないようです。したがって、独自の変換を実行して日付を_Java.sql.Date_に変換します。 Oracle DBの経験はありませんが、JDBC実装はサーバーとローカルJVMのタイムゾーン設定を使用して_Oracle.sql.DATE_から_Java.sql.Date_への変換を行っていると思います。


SQLiteを除き、複数のRDBMS実装がタイムゾーンを正しく処理することに言及しています。 JDBCドライバーに日付値を送信するとき、およびJDBCドライバーから日付値を取得するときの H2 および SQLite の動作を見てみましょう。

H2 JDBCドライバー PrepStmt.setDate(int, Date)ValueDate.get(Date) を使用します。これは DateTimeUtils.dateValueFromDate(long) を呼び出しますタイムゾーンの変換を行います。

これを使用して SQLite JDBCドライバーPrepStmt.setDate(int, Date) 呼び出し PrepStmt.setObject(int, Object) を実行し、タイムゾーン変換を行いません。

H2 JDBCドライバー JdbcResultSet.getDate(int)get(columnIndex).getDate()を返します。 get(int) はH2を返します Value は指定された列に対して。列タイプはDATEであるため、H2は ValueDate を使用します。 ValueDate.getDate()DateTimeUtils.convertDateValueToDate(long) を呼び出します。これにより、タイムゾーン変換後に最終的に_Java.sql.Date_が作成されます。

この SQLite JDBCドライバー を使用すると、 RS.getDate(int) コードがはるかに簡単になります。データベースに保存されているlong日付値を使用して_Java.sql.Date_を返すだけです。

そのため、H2 JDBCドライバーは、日付を使用したタイムゾーン変換の処理についてスマートである一方、SQLite JDBCドライバーはスマートではないことがわかります(この決定はスマートではないということではなく、SQLiteの設計決定によく合うかもしれません)。言及した他のRDBMS JDBCドライバーのソースを追いかけると、ほとんどの人がH2と同様の方法で日付とタイムゾーンに近づいていることに気付くでしょう。

JDBC仕様ではタイムゾーン処理の詳細は説明されていませんが、RDBMSおよびJDBC実装設計者がタイムゾーンを考慮し、適切に処理することは理にかなっています。特に、世界の市場で製品を市場に出したい場合。これらのデザイナーはかなり賢く、具体的な仕様がなくても、ほとんどの人がこれを正しく行っていることは驚くことではありません。


このMicrosoft SQL Serverブログ SQL Server 2008のタイムゾーンデータの使用 を見つけました。これは、タイムゾーンが物事を複雑にする方法を説明しています。

タイムゾーンは複雑な領域であり、各アプリケーションは、プログラムをよりユーザーフレンドリーにするために、タイムゾーンデータをどのように処理するかを検討する必要があります。

残念ながら、タイムゾーンの名前と値に関する現在の国際標準機関はありません。各システムは、独自に選択したシステムを使用する必要があり、国際標準が確立されるまで、SQL Serverが提供することは不可能であり、最終的には解決するよりも多くの問題が発生します。

38
Dan Cruz

変換を行うのはjdbcドライバーです。 Dateオブジェクトをdb/wire形式で受け入れられる形式に変換する必要があり、その形式にタイムゾーンが含まれていない場合、日付を解釈するときにローカルマシンのタイムゾーン設定がデフォルトになる傾向があります。したがって、指定するドライバーのリストを考えると、最も可能性の高いシナリオは、日付をGMT 1970-1-1 00:00:00に設定しますが、ステートメントに設定した解釈日はCET 1970-1-1でした1:00:00。日付は日付部分のみであるため、1970-1-1(タイムゾーンなし)がサーバーに送信され、エコーバックされます。ドライバーが日付を取得し、日付としてアクセスすると、1970-1-1が表示され、ローカルタイムゾーン、つまりCET 1970-1-1 00:00:00またはGMT 1969-12で再度解釈されます。 -31 23:00:00。したがって、元の日付と比較して1時間「失われた」ことになります。

11
philwb

Java.util.DateとOracle/MySQL Dateオブジェクトはどちらも、場所に関係なく、ある時点の単なる表現です。これは、GMT 1970-01-01 00:00:00から「エポック」以降のミリ/ナノ秒数として内部的に保存される可能性が非常に高いことを意味します。

結果セットから読み取る場合、「rs.getDate()」を呼び出すと、結果セットに、特定の時点を含む内部データを取得して、Java Dateオブジェクトに変換するように指示します。日付オブジェクトはローカルマシン上に作成されるため、JavaはチューリッヒのCETであるローカルタイムゾーンを選択します。

あなたが見ている違いは表現の違いであり、時間の違いではありません。

5
Rolf

クラス_Java.sql.Date_は、SQL DATEに対応します。SQLDATEは、時間またはタイムゾーン情報を保存しません。これを達成する方法は、 javadoc のように日付を「正規化」することです。

SQL DATEの定義に準拠するには、Java.sql.Dateインスタンスによってラップされたミリ秒値を、インスタンスが関連付けられている特定のタイムゾーンで時間、分、秒、ミリ秒をゼロに設定して「正規化」する必要があります。

これは、UTC + 1で作業してデータベースにDATEを要求すると、準拠する実装が観察したとおりに動作することを意味します。問題の日付に対応するミリ秒値at 00:00:00 UTC + 1そもそもデータがデータベースに到達した方法とは無関係です。

データベースドライバーは、必要に応じて、この動作をオプションで変更できる場合があります。

一方、_Java.sql.Date_をデータベースに渡すと、ドライバーは既定のタイムゾーンを使用して、ミリ秒値から日付と時刻のコンポーネントを分離します。 _0_を使用し、UTC + Xを使用している場合、日付はX> = 0の場合は1970-01-01、X <0の場合は1969-12-31になります。


サイドノート: Date(long) constructor のドキュメントが実装と異なることを見るのは奇妙です。 javadocはこう言います:

指定されたミリ秒値に時間情報が含まれる場合、ドライバーは時間コンポーネントをゼロに対応するデフォルトのタイムゾーン(アプリケーションを実行する仮想マシンのJava仮想マシン)のタイムゾーン)の時間に設定しますGMT。

ただし、実際にOpenJDKに実装されているのは次のとおりです。

_public Date(long date) {
    // If the millisecond date value contains time info, mask it out.
    super(date);

}
_

どうやらこの「マスキング」は実装されていません。同様に、指定された動作が適切に指定されていないため、例えば1970-01-01 00:00:00 GMT-6 = 1970-01-01 06:00:00 GMTは1970-01-01 00:00:00 GMT = 1969-12-31 18:00にマッピングされます: 00 GMT-6、または1970-01-01 18:00:00 GMT-6?

4
Joni

getDateを受け入れるCalendarのオーバーロードがあります。これを使用して問題を解決できますか?または、Calendarオブジェクトを使用して情報を変換できます。

つまり、検索が問題であると仮定します。

Calendar gmt = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
PreparedStatement stmt = connection.prepareStatement(sql);
stmt.setDate(1, new Date(0)); // I assume this is always GMT
ResultSet rs = stmt.executeQuery();
rs.next();

//This will output 0 as expected
System.out.println(rs.getDate(1, gmt).getTime());

または、ストレージが問題であると想定します。

Calendar gmt = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
PreparedStatement stmt = connection.prepareStatement(sql);
gmt.setTimeInMillis(0) ;
stmt.setDate(1, gmt.getTime()); //getTime returns a Date object
ResultSet rs = stmt.executeQuery();
rs.next();

//This will output 0 as expected
System.out.println(rs.getDate(1).getTime());

私はこれのためのテスト環境を持っていないので、正しい仮定がどれであるかを見つけなければなりません。また、getTimeは現在のロケールを返すと信じています。そうでない場合、クイックタイムゾーン変換を行う方法を調べる必要があります。

2
Guvante

タイムゾーンの問題は複雑すぎると思います。準備されたステートメントに日付/時刻を渡すと、日付/時刻情報のみがデータベースサーバーに送信されます。事実上の標準として、データベースサーバーとjvmのタイムゾーンの違いは操作しません。その後、結果セットから日付/時刻を読み取ったとき。データベースから日時情報を読み取り、jvmタイムゾーンに同じ日時を作成します。

0