Java を使用してカレンダーのタイムゾーンを処理するにはどうすればよいですか?
質問
アプリケーションから取得したタイムスタンプ値があります。ユーザーは、任意のローカル タイムゾーンに存在できます。
この日付は、指定された時刻が常に GMT であると想定する Web サービスで使用されるため、ユーザーのパラメータをたとえば (EST) から (GMT) に変換する必要があります。キッカーは次のとおりです。ユーザーは自分の TZ に気づいていません。彼は WS に送信したい作成日を入力するので、必要なのは次のとおりです。
ユーザーは次のように入力します: 2008 年 5 月 1 日午後 6 時 12 分 (EST)
WS へのパラメータは次のようにする必要があります。:2008 年 5 月 1 日午後 6 時 12 分 (グリニッジ標準時)
デフォルトでは、タイムスタンプは常に GMT であることになっているのはわかっていますが、パラメーターを送信するとき、TS からカレンダーを作成したにもかかわらず (GMT であるはずです)、ユーザーが GMT にいない限り、時間は常にオフになります。私には何が欠けているのでしょうか?
Timestamp issuedDate = (Timestamp) getACPValue(inputs_, "issuedDate");
Calendar issueDate = convertTimestampToJavaCalendar(issuedDate);
...
private static java.util.Calendar convertTimestampToJavaCalendar(Timestamp ts_) {
java.util.Calendar cal = java.util.Calendar.getInstance(
GMT_TIMEZONE, EN_US_LOCALE);
cal.setTimeInMillis(ts_.getTime());
return cal;
}
前のコードでは、結果として次のようになります (読みやすいように短い形式)。
[2008年5月1日午後11時12分]
解決 2
ご回答いただきありがとうございます。さらなる調査の後、私は正しい答えを得ました。 Skip Headで述べたように、アプリケーションから取得したTimeStampedはユーザーのTimeZoneに合わせて調整されていました。ユーザーが6:12 PM(EST)を入力した場合、2:12 PM(GMT)になります。必要なのは、ユーザーが入力した時間がWebServerリクエストに送信した時間になるように、変換を元に戻す方法でした。これがどのように達成されたかです:
// Get TimeZone of user
TimeZone currentTimeZone = sc_.getTimeZone();
Calendar currentDt = new GregorianCalendar(currentTimeZone, EN_US_LOCALE);
// Get the Offset from GMT taking DST into account
int gmtOffset = currentTimeZone.getOffset(
currentDt.get(Calendar.ERA),
currentDt.get(Calendar.YEAR),
currentDt.get(Calendar.MONTH),
currentDt.get(Calendar.DAY_OF_MONTH),
currentDt.get(Calendar.DAY_OF_WEEK),
currentDt.get(Calendar.MILLISECOND));
// convert to hours
gmtOffset = gmtOffset / (60*60*1000);
System.out.println("Current User's TimeZone: " + currentTimeZone.getID());
System.out.println("Current Offset from GMT (in hrs):" + gmtOffset);
// Get TS from User Input
Timestamp issuedDate = (Timestamp) getACPValue(inputs_, "issuedDate");
System.out.println("TS from ACP: " + issuedDate);
// Set TS into Calendar
Calendar issueDate = convertTimestampToJavaCalendar(issuedDate);
// Adjust for GMT (note the offset negation)
issueDate.add(Calendar.HOUR_OF_DAY, -gmtOffset);
System.out.println("Calendar Date converted from TS using GMT and US_EN Locale: "
+ DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT)
.format(issueDate.getTime()));
コードの出力は次のとおりです:(ユーザーが5/1/2008 6:12 PM(EST)を入力しました
現在のユーザーのタイムゾーン:EST
GMTからの現在のオフセット(時間単位):-4(通常-5、DST調整を除く)
ACPからのTS:2008-05-01 14:12:00.0
GMTおよびUS_ENロケールを使用してTSから変換されたカレンダー日付:5/1/08 6:12 PM(GMT)
他のヒント
public static Calendar convertToGmt(Calendar cal) {
Date date = cal.getTime();
TimeZone tz = cal.getTimeZone();
log.debug("input calendar has date [" + date + "]");
//Returns the number of milliseconds since January 1, 1970, 00:00:00 GMT
long msFromEpochGmt = date.getTime();
//gives you the current offset in ms from GMT at the current date
int offsetFromUTC = tz.getOffset(msFromEpochGmt);
log.debug("offset is " + offsetFromUTC);
//create a new calendar in GMT timezone, set to this date and add the offset
Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
gmtCal.setTime(date);
gmtCal.add(Calendar.MILLISECOND, offsetFromUTC);
log.debug("Created GMT cal with date [" + gmtCal.getTime() + "]");
return gmtCal;
}
次の場所に現在の時刻( Calendar.getInstance()
からの" 12:09:05 EDT")を渡した場合の出力は次のとおりです。
DEBUG-入力カレンダーには日付があります[Thu Oct 23 12:09:05 EDT 2008]
DEBUG-オフセットは-14400000
DEBUG-日付付きのGMT calを作成[木10月23日08:09:05 EDT 2008]
12:09:05 GMTは8:09:05 EDTです。
ここで紛らわしいのは、 Calendar.getTime()
が現在のタイムゾーンで Date
を返すことと、タイムゾーンを変更する方法がないことです。カレンダーを作成し、基になる日付もロールします。 Webサービスが使用するパラメーターのタイプに応じて、エポックからのミリ秒単位でWSを処理したい場合があります。
日付はWebサービスに関連して使用されると言うので、ある時点で文字列にシリアル化されていると思います。
この場合は、 setTimeZoneメソッド。これにより、タイムスタンプの印刷時に使用されるタイムゾーンが決まります。
簡単な例:
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
Calendar cal = Calendar.getInstance();
String timestamp = formatter.format(cal.getTime());
Joda Time で解決できます:
Date utcDate = new Date(timezoneFrom.convertLocalToUTC(date.getTime(), false));
Date localDate = new Date(timezoneTo.convertUTCToLocal(utcDate.getTime()));
Java 8:
LocalDateTime localDateTime = LocalDateTime.parse("2007-12-03T10:15:30");
ZonedDateTime fromDateTime = localDateTime.atZone(
ZoneId.of("America/Toronto"));
ZonedDateTime toDateTime = fromDateTime.withZoneSameInstant(
ZoneId.of("Canada/Newfoundland"));
タイムスタンプが元のシステムのタイムゾーンに設定されているようです。
これは非推奨ですが、動作するはずです:
cal.setTimeInMillis(ts_.getTime() - ts_.getTimezoneOffset());
非推奨の方法は使用することです
Calendar.get(Calendar.ZONE_OFFSET) + Calendar.get(Calendar.DST_OFFSET)) / (60 * 1000)
しかし、システムはそれがどのタイムゾーンにあるかを知っているので、それはクライアント側で行われる必要があります。
1つのtimeZoneから別のtimeZoneに変換する方法(おそらく動作します:))。
/**
* Adapt calendar to client time zone.
* @param calendar - adapting calendar
* @param timeZone - client time zone
* @return adapt calendar to client time zone
*/
public static Calendar convertCalendar(final Calendar calendar, final TimeZone timeZone) {
Calendar ret = new GregorianCalendar(timeZone);
ret.setTimeInMillis(calendar.getTimeInMillis() +
timeZone.getOffset(calendar.getTimeInMillis()) -
TimeZone.getDefault().getOffset(calendar.getTimeInMillis()));
ret.getTime();
return ret;
}
Date および Timestamp オブジェクトはタイムゾーンを無視します:それらはエポックからの特定の秒数を表し、その瞬間の特定の解釈を時間と日としてコミットしません。 タイムゾーンは、 GregorianCalendar (このタスクには直接必要ありません)および SimpleDateFormat にのみ画像を入力します。これらは、個別のフィールドと日付(または long )値。
OPの問題は処理の開始直後です。ユーザーは時間を入力しますが、これはあいまいであり、ローカルの非GMTタイムゾーンで解釈されます。この時点で、値は" 6:12 EST" です。これは、" 11.12 GMT" または他のタイムゾーンとして簡単に印刷できますが、 を" 6.12 GMT" に変更します。
" 06:12" を" HH:MM" として解析する SimpleDateFormat を作成する方法はありません(デフォルトはローカルタイムゾーン)デフォルトではなくUTC SimpleDateFormat は、あまりにもスマートすぎます。
ただし、入力に明示的に配置した場合、 SimpleDateFormat インスタンスに正しいタイムゾーンを使用するように説得することができます。受信した(そして適切に検証された)固定文字列を追加するだけです&quot ; 06:12" で" 06:12 GMT" を" HH:MM z" として解析します。
GregorianCalendar フィールドを明示的に設定したり、タイムゾーンと夏時間のオフセットを取得して使用する必要はありません。
実際の問題は、デフォルトのローカルタイムゾーンの入力、デフォルトのUTCの入力、および実際に明示的なタイムゾーンの指示を必要とする入力を分離することです。
過去に私にとって有効だったのは、ユーザーのタイムゾーンとGMTの間のオフセット(ミリ秒単位)を決定することでした。オフセットを取得したら、どちらのタイムゾーンでも適切な時間を取得するために、単純に加算/減算(変換の進行方法に応じて)できます。通常はCalendarオブジェクトのミリ秒フィールドを設定することでこれを実現しますが、タイムスタンプオブジェクトに簡単に適用できると確信しています。オフセットを取得するために使用するコードは次のとおりです
int offset = TimeZone.getTimeZone(timezoneId).getRawOffset();
timezoneIdは、ユーザーのタイムゾーンのID(ESTなど)です。
java.time
最新のアプローチでは、 java.time このクラスは、Java の初期バージョンにバンドルされていた、面倒な従来の日時クラスに取って代わりました。
の java.sql.Timestamp
class は従来のクラスの 1 つです。不要になりました。代わりに使用してください Instant
または、JDBC 4.2 以降を使用して他の java.time クラスをデータベースに直接接続します。
の Instant
クラスはタイムライン上の瞬間を表します。 UTC の解像度で ナノ秒 (小数点以下 9 桁まで)。
Instant instant = myResultSet.getObject( … , Instant.class ) ;
既存のシステムと相互運用する必要がある場合は、 Timestamp
, 、古いクラスに追加された新しい変換メソッドを介して、すぐに java.time に変換されます。
Instant instant = myTimestamp.toInstant() ;
別のタイムゾーンに調整するには、タイムゾーンを次のように指定します。 ZoneId
物体。を指定してください 適切なタイムゾーン名 の形式で continent/region
, 、 のような America/Montreal
, Africa/Casablanca
, 、 または Pacific/Auckland
. 。次のような 3 ~ 4 文字の疑似ゾーンは決して使用しないでください。 EST
または IST
彼らはあるとして ない 本当のタイムゾーンは標準化されておらず、一意でもありません(!)。
ZoneId z = ZoneId.of( "America/Montreal" ) ;
に申請してください Instant
を生産する ZonedDateTime
物体。
ZonedDateTime zdt = instant.atZone( z ) ;
ユーザーに表示する文字列を生成するには、Stack Overflow で検索します。 DateTimeFormatter
多くの議論や例を見つけることができます。
あなたの質問は実際には、ユーザーのデータ入力から日付/時刻オブジェクトまで、逆の方向に進むことについてです。一般に、データ入力を日付と時刻の 2 つの部分に分割するのが最善です。
LocalDate ld = LocalDate.parse( dateInput , DateTimeFormatter.ofPattern( "M/d/uuuu" , Locale.US ) ) ;
LocalTime lt = LocalTime.parse( timeInput , DateTimeFormatter.ofPattern( "H:m a" , Locale.US ) ) ;
あなたの質問は明確ではありません。ユーザーが入力した日付と時刻を UTC であると解釈しますか?それとも別のタイムゾーンでしょうか?
UTC を意味する場合は、 OffsetDateTime
UTC の定数を使用したオフセットを使用し、 ZoneOffset.UTC
.
OffsetDateTime odt = OffsetDateTime.of( ld , lt , ZoneOffset.UTC ) ;
別のタイムゾーンを意味する場合は、タイムゾーンオブジェクトと組み合わせてください。 ZoneId
. 。しかし、どのタイムゾーンでしょうか?デフォルトのタイムゾーンが検出される場合があります。または、重要な場合は、ユーザーに確認してユーザーの意図を確認する必要があります。
ZonedDateTime zdt = ZonedDateTime.of( ld , lt , z ) ;
定義上常に UTC である単純なオブジェクトを取得するには、 Instant
.
Instant instant = odt.toInstant() ;
…または…
Instant instant = zdt.toInstant() ;
データベースに送信します。
myPreparedStatement.setObject( … , instant ) ;
java.timeについて
の java.time フレームワークは Java 8 以降に組み込まれています。これらのクラスは、面倒な古いクラスに取って代わります。 遺産 日時クラスなど java.util.Date
, Calendar
, & SimpleDateFormat
.
の ジョーダタイム プロジェクト、現在進行中 メンテナンスモード, への移行をアドバイスします。 java.time クラス。
詳細については、を参照してください。 オラクルのチュートリアル. 。また、Stack Overflow で検索すると、多くの例や説明が得られます。仕様は JSR 310.
java.time クラスはどこで入手できますか?
- Java SE8, Java SE9, 、 以降
- 内蔵。
- 実装がバンドルされている標準 Java API の一部。
- Java 9 では、いくつかのマイナーな機能と修正が追加されています。
- Java SE6 そして Java SE 7
- java.time 機能の多くは、Java 6 および 7 にバックポートされています。 ThreeTen バックポート.
- アンドロイド
- Android の新しいバージョンには、java.time クラスの実装がバンドルされています。
- 以前の Android の場合、 スリーテンABP プロジェクトが適応する ThreeTen バックポート (上記の通り)。見る ThreeTenABPの使い方….
の ThreeTen-Extra プロジェクトは追加のクラスで java.time を拡張します。このプロジェクトは、将来 java.time に追加される可能性があるための実験場です。ここで次のような便利なクラスを見つけることができます。 Interval
, YearWeek
, YearQuarter
, 、 そして もっと.