web-dev-qa-db-ja.com

Javaを使用してカレンダーのTimeZonesを処理する方法

アプリケーションから取得したタイムスタンプ値があります。ユーザーは、任意のローカルタイムゾーンに参加できます。

この日付は、指定された時間が常にGMTであると想定するWebServiceに使用されるため、ユーザーのパラメーターを(EST)から(GMT)に変換する必要があります。キッカーは次のとおりです。ユーザーは自分のTZに気付いていません。彼はWSに送信する作成日を入力するので、必要なのは次のとおりです。

ユーザー入力: 5/1/2008 6:12 PM(EST)
WSへのパラメーターは次のとおりである必要があります:5/1/2008 6:12 PM(GMT)

TimeStampsは常にデフォルトで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分]

91
Jorge Valois

お返事ありがとうございます。さらなる調査の後、私は正しい答えを得ました。 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)

29
Jorge Valois
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」)を渡す場合の出力です。

デバッグ-入力カレンダーに日付があります[木10月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を処理したい場合があります。

61
matt b

日付はWebサービスに関連して使用されると言うので、ある時点でそれが文字列にシリアル化されると仮定します。

この場合、DateFormatクラスの 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"));
12

タイムスタンプが元のシステムのタイムゾーンに設定されているようです。

これは非推奨ですが、動作するはずです:

cal.setTimeInMillis(ts_.getTime() - ts_.getTimezoneOffset());

非推奨ではない方法は、

Calendar.get(Calendar.ZONE_OFFSET) + Calendar.get(Calendar.DST_OFFSET)) / (60 * 1000)

しかし、システムはそれがどのタイムゾーンにあるかを知っているので、それはクライアント側で行われる必要があります。

8
Skip Head

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;
}
7
Helpa

DateおよびTimestampオブジェクトはタイムゾーンを無視します。それらは、エポック以降の特定の秒数を表し、その瞬間の特定の解釈を時間と日としてコミットしません。タイムゾーンは、GregorianCalendar(このタスクには直接必要ありません)およびSimpleDateFormatにのみ画像を入力します。これらは、個別のフィールドと日付の間で変換するタイムゾーンオフセットが必要です(またはlong)値。

OPの問題は処理の開始直後です。ユーザーは時間を入力しますが、これはあいまいであり、ローカルの非GMTタイムゾーンで解釈されます。この時点で、値は "6:12 EST"であり、 "11.12 GMT"またはその他のタイムゾーンとして簡単に印刷できますが、 changeto "6.12 GMT"に移動します。

"06:12" "HH:MM"として解析するSimpleDateFormatを作成する方法はありません(デフォルト現地のタイムゾーンへ)代わりにデフォルトのUTC SimpleDateFormatは、それ自体ではあまりにもスマートすぎます。

ただし、SimpleDateFormatインスタンスを入力に明示的に配置すると、正しいタイムゾーンを使用するように確信させることができます。受信した(そして適切に検証された)固定文字列を追加するだけです "06 :12 "を解析" 06:12 GMT "として" HH:MM z "

GregorianCalendarフィールドを明示的に設定したり、タイムゾーンと夏時間のオフセットを取得して使用する必要はありません。

実際の問題は、デフォルトがローカルタイムゾーンに設定されている入力、デフォルトがUTCに設定されている入力、および明示的にタイムゾーンを明示的に指定する必要がある入力を分離することです。

6
user412090

過去に私のために働いていたのは、ユーザーのタイムゾーンとGMTの間のオフセット(ミリ秒単位)を決定することでした。オフセットを取得したら、どちらのタイムゾーンでも適切な時間を取得するために(変換の進行方法に応じて)単純に加算/減算できます。通常はCalendarオブジェクトのミリ秒フィールドを設定することでこれを実現しますが、タイムスタンプオブジェクトに簡単に適用できると確信しています。これがオフセットを取得するために使用するコードです

int offset = TimeZone.getTimeZone(timezoneId).getRawOffset();

timezoneIdは、ユーザーのタイムゾーンのID(ESTなど)です。

4
Adam

Java.time

最新のアプローチでは、Java.timeクラスを使用します。これは、Javaの初期バージョンにバンドルされている面倒なレガシー日時クラスに取って代わるものです。

Java.sql.Timestampクラスは、これらのレガシークラスの1つです。不要になりました。代わりに、JDBC 4.2以降を使用して、Instantまたは他のJava.timeクラスをデータベースで直接使用します。

Instant クラスは、 UTC のタイムライン上のモーメントを ナノ秒小数部の最大9桁)で表します。

Instant instant = myResultSet.getObject( … , Instant.class ) ; 

既存のTimestampと相互運用する必要がある場合は、古いクラスに追加された新しい変換メソッドを介して、すぐにJava.timeに変換します。

Instant instant = myTimestamp.toInstant() ;

別のタイムゾーンに調整するには、タイムゾーンをZoneIdオブジェクトとして指定します。 continent/regionAmerica/Montreal 、またはAfrica/Casablancaなどの 適切なタイムゾーン名Pacific/Aucklandの形式で指定します。 ESTISTなどの3〜4文字の擬似ゾーンは、_(nottrueタイムゾーンであり、標準化されておらず、一意(!)でもないため、使用しないでください。

ZoneId z = ZoneId.of( "America/Montreal" ) ;

Instantに適用して、ZonedDateTimeオブジェクトを生成します。

ZonedDateTime zdt = instant.atZone( z ) ;

ユーザーに表示する文字列を生成するには、DateTimeFormatterのStack Overflowを検索して、多くの議論と例を見つけます。

あなたの質問は、ユーザーのデータ入力から日時オブジェクトまで、他の方向に向かうことです。一般に、データ入力を日付と時刻の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を意味する場合は、UTCの定数ZoneOffset.UTCを使用して、オフセット付きのOffsetDateTimeを作成します。

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.DateCalendar 、& SimpleDateFormat などの厄介な古い legacy date-timeクラスに取って代わります。

Joda-Time プロジェクトは、現在 メンテナンスモード であり、 Java.time クラスへの移行を推奨しています。

詳細については、 Oracle Tutorial を参照してください。また、Stack Overflowで多くの例と説明を検索してください。指定は JSR 31 です。

Java.timeクラスはどこで入手できますか?

  • Java SE 8Java SE 9 、およびそれ以降
    • ビルトイン。
    • 実装がバンドルされた標準Java AP​​Iの一部。
    • Java 9は、いくつかのマイナーな機能と修正を追加します。
  • Java SE 6 および Java SE 7
    • Java.time機能の多くは、 ThreeTen-Backport のJava 6および7にバックポートされています。
  • Android
    • Androidの後のバージョンは、Java.timeクラスの実装をバンドルします。
    • 以前のAndroidの場合、 ThreeTenABP プロジェクトはThreeTen-Backport(上記)に適合します。 ThreeTenABPの使用方法... を参照してください。 。

ThreeTen-Extra プロジェクトは、追加のクラスでJava.timeを拡張します。このプロジェクトは、Java.timeに将来追加される可能性のある証明の場です。 IntervalYearWeekYearQuartermore などの便利なクラスがあります。

1
Basil Bourque