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


92

アプリケーションから取得したタイムスタンプ値があります。ユーザーは、任意の特定のローカルTimeZoneにいることができます。

この日付は、指定された時間が常に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]


2
なぜタイムゾーンを変更するだけで日付と時刻を一緒に変換しないのですか?
スペンサーコルモス2008年

1
あるタイムゾーンにあるJavaの日付を取得し、その日付を別のタイムゾーンで取得するのは本当に大変です。IE、午後5時のEDTを受け取り、午後5時のPDTを取得します。
mtyson 2012年

回答:


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

現在時刻(「12:09:05 EDT」からCalendar.getInstance())を渡した場合の出力は次のとおりです。

デバッグ-入力カレンダーには日付があります[2008年10月23日12:09:05 EDT 2008]
デバッグ-オフセットは-14400000
デバッグ-日付付きのGMT calを作成しました[2008年10月23日08:09:05 EDT]

12:09:05 GMTは8:09:05 EDTです。

ここで混乱Calendar.getTime()するのDateは、現在のタイムゾーンでa を返すことと、カレンダーのタイムゾーンを変更して基になる日付もロールバックする方法がないことです。Webサービスが取るパラメーターのタイプによっては、エポックからのミリ秒単位でのWS取引を希望する場合があります。


11
あなたはoffsetFromUTCそれを足すのではなく、引くべきではありませんか?例を使用すると、12:09 GMTが8:09 EDT(true)で、ユーザーが「12:09 EDT」と入力すると、アルゴリズムは「16:09 GMT」を出力するはずです。
DzinX 2012

29

ご返信ありがとうございます。さらなる調査の後、私は正しい答えを得ました。Skip Headが述べたように、アプリケーションから取得したTimeStampedは、ユーザーのTimeZoneに合わせて調整されていました。したがって、ユーザーが午後6時12分(EST)と入力した場合、午後2時12分(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()));

コードの出力は次のとおりです(ユーザーが2008年5月1日、午後6時12分(EST)に入力)

現在のユーザーのTimeZone:
GMTからのEST 現在のオフセット(時間単位):-4(通常は-5、DST調整は除く)
TS ACPから:2008-05-01 14:12:00.0
GMTとUS_ENロケールを使用してTSから変換されたカレンダー日付:2008年5月1日午後6時12分(GMT)


20

日付は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());

4
SDFを解放しませんでした。独自のタイムゾーンがあり、カレンダーのTimeZoneを変更しても効果がないように見えるのではないかと疑問に思いました。
Chris.Jenkins、2012年

UTCはAvailableIDs()ではないのではTimeZone.getTimeZone(「UTC」)は、神は知っている...有効でない理由
childno͡.de

TimeZone.getTimeZone( "UTC")は私のマシンで利用できます。JVMに依存していますか?
CodeClimber 2012

2
setTimeZone()メソッドによる素晴らしいアイデア。あなたは私に多くの頭痛の種を救いました、本当にありがとう!
ボグダンズラック2013年

1
ちょうど私が必要なもの!フォーマッターでサーバーのタイムゾーンを設定し、それをカレンダー形式に変換するときに心配する必要はありません。最善の方法!getTimeZoneの場合、ETDにはEx: "GMT-4:00"という形式を使用する必要があるかもしれません
Michael Kern

12

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"));

ただし、Hibernate 4を使用している場合は注意してください。Hibernate4は、サイド依存関係と追加の構成なしでは直接互換性がありません。ただし、これは3番目のバージョンのアプローチを使用するのが最も速くて簡単でした。
Aubergine 2013

8

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

これは非推奨ですが、機能するはずです。

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

非推奨されない方法は使用することです

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

ただし、そのシステムはどのタイムゾーンにあるかを認識しているため、クライアント側で行う必要があります。


7

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

6

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

OPの問題は彼の処理の最初にあります。ユーザーは時間を入力しますが、それはあいまいであり、それらはローカルの非GMTタイムゾーンで解釈されます。この時点で値は"6:12 EST"であり、"11.12 GMT"またはその他のタイムゾーンとして簡単に出力できますが、"6.12 GMT"変更されることはありません。

「06:12」「HH:MM」(デフォルトはローカルタイムゾーン)として解析するSimpleDateFormatをデフォルトでUTCに設定する方法はありません。SimpleDateFormatは、それだけでは少しスマートすぎます。

しかし、あなたがどんな説得できるのSimpleDateFormatあなたが入力で明示的にそれを置く場合は、右のタイムゾーンを使用するようにインスタンスを:ちょうど受け取った(かつ適切に検証さ)に固定された文字列を追加し、「6時12分」解析するために「6時12分GMTを」など"HH:MM z"

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

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


4

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

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

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


9
生のオフセットを使用すると、DSTは無視されます。
Werner Lehmann、

1
正確に言えば、この方法では半年分が正しくありません。最悪のエラー。
ダヌビアンセーラー2013

1

java.time

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

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

このInstantクラスは、ナノ秒の分解能(小数点以下9桁まで)でUTCのタイムライン上の瞬間を表します。

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

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

Instant instant = myTimestamp.toInstant() ;

別のタイムゾーンに調整するには、タイムゾーンをZoneIdオブジェクトとして指定します。指定適切なタイムゾーン名をの形式でcontinent/region、のようなAmerica/MontrealAfrica/CasablancaまたはPacific/Auckland。決してなど3-4 letter擬似ゾーンを使用しないESTか、ISTそのままではない真の時間帯ではなく、標準化、さらには一意ではありません(!)。

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

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

ZonedDateTime zdt = instant.atZone( z ) ;

ユーザーに表示する文字列を生成するには、スタックオーバーフローを検索し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を意味する場合は、UTC OffsetDateTimeの定数を使用して、オフセット付きのを作成します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.DateCalendar、& SimpleDateFormat

ジョダタイムプロジェクトは、今でメンテナンスモードへの移行をアドバイスjava.timeのクラス。

詳細については、Oracleチュートリアルを参照してください。スタックオーバーフローで多くの例と説明を検索してください。仕様はJSR 310です。

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

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

ThreeTen-エクストラプロジェクトでは、追加のクラスでjava.timeを拡張します。このプロジェクトは、java.timeに将来追加される可能性があることを証明する場です。あなたはここにいくつかの有用なクラスのような見つけることがIntervalYearWeekYearQuarter、および多くを

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.