JavaおよびMySQLを使用して、サードパーティが開発したデータベースと相互運用するコードを記述しようとしています。このデータベースには、DATETIME
フィールドにタイムスタンプを格納するフィールドがありますUTC日付として。データベースとクライアントの両方が実行されるサーバーのタイムゾーンが非UTCゾーン(Europe/London
)に設定されているため、デフォルトでは、タイムスタンプはローカル時間であるかのように誤って読み取られます。UTCとして読み取るコードを記述しようとしています。
私はここでいくつかの同様の質問を読みましたが、どれも私にとってうまくいく答えがありません:
残念ながら、サーバー設定を変更できないため、接続の「time_zone」変数を使用してデータベースサーバーをUTCに設定し、オプションのCalendar
パラメータをResultSet.getTimestamp
に設定して日付を取得しようとしましたが、これは結果には影響しません。これが私のコードです:
private static final Calendar UTCCALENDAR = Calendar.getInstance (TimeZone.getTimeZone (ZoneOffset.UTC));
public Date getDate ()
{
try (Connection c = dataSource.getConnection ();
PreparedStatement s = c
.prepareStatement ("select datefield from dbmail_datefield where physmessage_id=?"))
{
fixTimeZone (c);
s.setLong (1, getPhysId ());
try (ResultSet rs = s.executeQuery ())
{
if (!rs.next ()) return null;
return new Date (rs.getTimestamp(1,UTCCALENDAR).getTime ()); // do not use SQL timestamp object, as it fucks up comparisons!
}
}
catch (SQLException e)
{
throw new MailAccessException ("Error accessing dbmail database", e);
}
}
private void fixTimeZone (Connection c)
{
try (Statement s = c.createStatement ())
{
s.executeUpdate ("set time_zone='+00:00'");
}
catch (SQLException e)
{
throw new MailAccessException ("Unable to set SQL connection time zone to UTC", e);
}
}
私が読み込もうとしているデータベースフィールドには、次のように値が格納されています。
mysql> select * from dbmail_datefield where physmessage_id=494539;
+----------------+--------+---------------------+
| physmessage_id | id | datefield |
+----------------+--------+---------------------+
| 494539 | 494520 | 2015-04-16 10:30:30 |
+----------------+--------+---------------------+
しかし、残念ながら、結果はUTCではなくBSTとして表示されます。
Java.lang.AssertionError: expected:<Thu Apr 16 11:30:30 BST 2015> but was:<Thu Apr 16 10:30:30 BST 2015>
あなたのクライアントのgetDate()
コードは、それがどこまでも正しいように見えます。また、MySQL Connector/J JDBCドライバーが日付テーブルに保存されているをUTC日付として扱うようにして、誤ったタイムゾーン変換を回避する必要があると思います。つまり、クライアントセッションのタイムゾーンと、JDBC getTimestamp
の呼び出しに使用するカレンダーに加えて、有効なサーバーのタイムゾーンを設定します。
失敗したアサーションで取得した値と、エラーの方向を確認します。
expected:<Thu Apr 16 11:30:30 BST 2015> but was:<Thu Apr 16 10:30:30 BST 2015>
返されたのは10:30 BST、つまり9:30 GMTです。これは、GMT日付として解析する前に、テーブルの10:30をBST値として扱い、誤ってGMTに変換するデータベースと一致しています。これは、誤ってBSTに変換されるGMT値の方向です。
JDBCは時間をローカルゾーンに変換する必要があるため、これはJDBC固有の問題である可能性があります。 (MySQL C APIが対応しないのは、おそらくCの従来の時間型がJavaのようにゾーンに対応していないためです。)また、変換するゾーンを知る必要がありますfromも同様です。 MySQL TIMESTAMP
タイプは常にUTCとして保存されます。しかし、それはDATETIME
タイプについては述べられていません。 I thinkこれは、MySQLがDATETIME
列の値をサーバーのタイムゾーンにあるものとして解釈することを意味します。 BSTに設定されているとおっしゃっていましたが、これはアサーションエラーメッセージに示されているシフトの方向と一致しています。
time_zone
設定したセッション変数は、MySQLサーバーにクライアントマシンのタイムゾーンを伝えますが、サーバーが独自のタイムゾーンをどのように認識するかには影響しません。 serverTimezone
JDBC接続プロパティ でオーバーライドできます。接続で、serverTimezone
をUTCに設定し、useLegacyDatetimeCode
がオフになっていることを確認します。 (それが機能しない場合は、他のゾーン関連のプロパティを確認してください。)データベースと同じカレンダーフィールド値を持つ日付がUTCとして渡されるかどうかを確認します。
これにより、データベース内の他のDATETIME
値の解釈が変わることに注意してください。これらの値はすべて、現在(JDBC接続のコンテキストでは)UTC日付のように見えます。それが正しいかどうかは、最初にどのように入力されたかに依存します。クライアントコードは希望どおりの動作をしますが、サーバーレベルでサーバーのタイムゾーンをUTCに設定せずに、このシステム全体が完全に一貫して動作できるかどうかはわかりません。基本的に、ゾーンがUTCに設定されていない場合、必要な動作に完全に設定されておらず、その周りを把握しています。
タイムゾーンを使用する場合は、列をUTCとして読み取ることができます。
ZonedDateTime zdt = ZonedDateTime.of(rs.getTimestamp(1).toLocalDateTime(), ZoneOffset.UTC);
次に、使用したい任意のタイムゾーンに変更できます。
zdt = zdt.withZoneSameInstant(ZoneId.of(
TARGET_ZONE));
日付のみを読み、ゾーンはまったく気にしない場合は、次のようにします。
LocalDateTime ldt = rs.getTimestamp(1).toLocalDateTime()
タイムゾーンなしでLocalDateTimeを取得します。
Java.util.Dateを返す必要がある場合:
Date.from(ldt.atZone(ZoneOffset.UTC).toInstant());
私の見解では、MySQLにGMTを使用し、データベースではなくアプリケーションコードですべての現地時間の問題を処理するように指示することが最善の策です。データベース内の値は常にGMT、完全な停止であり、明確です。あなたが言うように、夏時間(夏時間)の調整により、人間にとっては2つの異なる時間について、データベースで同じ値になる可能性があります。
これにより、データベースの移植性も向上します。北アメリカに移動し、MySQLを(たとえば)中央時間に設定して使い始めると、データベースの値が突然数時間移動したように見えます。 MySQLがマシンのゾーンにスレーブ化されているかどうかを確認することを考えずに、私が継承したデータベースでサーバーの現地時間を使用していたデータベースに問題がありました。
long t = 1351382400000; // the timestamp in UTC
String insert = "INSERT INTO my_table (timestamp) VALUES (?)";
PreparedStatement stmt = db.prepareStatement(insert);
Java.sql.Timestamp date = new Timestamp(t);
stmt.setTimestamp(1, date);
stmt.executeUpdate();
.....
TimeZone timezone = TimeZone.getTimeZone("MyTimeZoneId");
Calendar cal = Java.util.Calendar.getInstance(timezone);
String select = "SELECT timestamp FROM my_table";
// some code omitted....
ResultSet rs = stmt.executeQuery();
while (rs.next()) {
Java.sql.Timestamp ts = rs.getTimestamp(1);
cal.setTimeInMillis(ts.getTime());
System.out.println("date in db: " + cal.getTime());
}
たぶん、次のようにJodaTimeを使用できます。
private static final Calendar UTCCALENDAR = Calendar.getInstance (TimeZone.getTimeZone (ZoneOffset .UTC));
public Date getDate ()
{
try (Connection c = dataSource.getConnection ();
PreparedStatement s = c
.prepareStatement ("select datefield from dbmail_datefield where physmessage_id=?"))
{
s.setLong (1, getPhysId ());
try (ResultSet rs = s.executeQuery ())
{
if (!rs.next ()) return null;
DateTime dt = new LocalDateTime(rs.getTimestamp(1,UTCCALENDAR).getTime ()).toDateTime(DateTimeZone.forID("Europe/London"));
return dt.toDate(); }
}
catch (SQLException e)
{
throw new MailAccessException ("Error accessing dbmail database", e);
}
}
編集:
Java.util.DateはTimeZoneに依存しません。メソッドtoDateTimeはTimeZoneとDSTを処理するので、気にする必要はありません
次のコード:
public static void main(String[] args) {
// 29/March/2015 1:05 UTC
DateTime now = new DateTime(2015, 3,29,1,5,DateTimeZone.UTC);
// Pre DST 29/March/2015 0:30 UTC
DateTime preDst = new DateTime(2015, 3,29,0,30,DateTimeZone.UTC);
System.out.println("1:05 UTC:"+now);
System.out.println("0:30 UTC:"+preDst);
DateTimeZone europeDTZ = DateTimeZone.forID("Europe/London");
DateTime europeLondon = now.toDateTime(europeDTZ);
System.out.println("1:05 UTC as Europe/London:"+europeLondon);
DateTime europeLondonPreDst = preDst.toDateTime(europeDTZ);
System.out.println("0:30 UTC as Europe/London:"+europeLondonPreDst);
}
印刷されます:
1:05 UTC:2015-03-29T01:05:00.000Z
0:30 UTC:2015-03-29T00:30:00.000Z
1:05 UTC as Europe/London:2015-03-29T02:05:00.000+01:00
0:30 UTC as Europe/London:2015-03-29T00:30:00.000Z
JodaTimeがDSTを処理するのを確認できる場合。