web-dev-qa-db-ja.com

SimpleDateFormat解析でタイムゾーンが失われる

コード:

 SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss z");
    sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
    System.out.println(new Date());
    try {
        String d = sdf.format(new Date());
        System.out.println(d);
        System.out.println(sdf.parse(d));
    } catch (Exception e) {
        e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
    }

出力:

Thu Aug 08 17:26:32 GMT+08:00 2013
2013.08.08 09:26:32 GMT
Thu Aug 08 17:26:32 GMT+08:00 2013

format()DateをGMTに正しくフォーマットしますが、parse()はGMTの詳細を失います。 substring()を使用してこれを回避できることはわかっていますが、この現象の根底にある理由は何ですか?

これは重複した質問です これには答えがありません。

編集:別の方法で質問をしましょう、Dateオブジェクトを取得して常にGMTになるようにする方法は何ですか?

53
Achow

私が必要なのはこれだけでした:

SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));

SimpleDateFormat sdfLocal = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");

try {
    String d = sdf.format(new Date());
    System.out.println(d);
    System.out.println(sdfLocal.parse(d));
} catch (Exception e) {
    e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
}

出力:少し疑わしいが、日付のみを一致させたい

2013.08.08 11:01:08
Thu Aug 08 11:01:08 GMT+08:00 2013
51
Achow

彼の問題に対するOPの解決策は、彼が言うように、疑わしい出力を持っています。そのコードはまだ時間の表現について混乱を示しています。この混乱を解消し、間違った時間にならないコードを作成するには、彼がしたことのこの拡張を検討してください。

public static void _testDateFormatting() {
    SimpleDateFormat sdfGMT1 = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");
    sdfGMT1.setTimeZone(TimeZone.getTimeZone("GMT"));
    SimpleDateFormat sdfGMT2 = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss z");
    sdfGMT2.setTimeZone(TimeZone.getTimeZone("GMT"));

    SimpleDateFormat sdfLocal1 = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");
    SimpleDateFormat sdfLocal2 = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss z");

    try {
        Date d = new Date();
        String s1 = d.toString();
        String s2 = sdfLocal1.format(d);
        // Store s3 or s4 in database.
        String s3 = sdfGMT1.format(d);
        String s4 = sdfGMT2.format(d);
        // Retrieve s3 or s4 from database, using LOCAL sdf.
        String s5 = sdfLocal1.parse(s3).toString();
        //EXCEPTION String s6 = sdfLocal2.parse(s3).toString();
        String s7 = sdfLocal1.parse(s4).toString();
        String s8 = sdfLocal2.parse(s4).toString();
        // Retrieve s3 from database, using GMT sdf.
        // Note that this is the SAME sdf that created s3.
        Date d2 = sdfGMT1.parse(s3);
        String s9 = d2.toString();
        String s10 = sdfGMT1.format(d2);
        String s11 = sdfLocal2.format(d2);
    } catch (Exception e) {
        e.printStackTrace();
    }       
}

デバッガーで値を調べる:

s1  "Mon Sep 07 06:11:53 EDT 2015" (id=831698113128)    
s2  "2015.09.07 06:11:53" (id=831698114048) 
s3  "2015.09.07 10:11:53" (id=831698114968) 
s4  "2015.09.07 10:11:53 GMT+00:00" (id=831698116112)   
s5  "Mon Sep 07 10:11:53 EDT 2015" (id=831698116944)    
s6  -- omitted, gave parse exception    
s7  "Mon Sep 07 10:11:53 EDT 2015" (id=831698118680)    
s8  "Mon Sep 07 06:11:53 EDT 2015" (id=831698119584)    
s9  "Mon Sep 07 06:11:53 EDT 2015" (id=831698120392)    
s10 "2015.09.07 10:11:53" (id=831698121312) 
s11 "2015.09.07 06:11:53 EDT" (id=831698122256) 

sdf2とsdfLocal2にはタイムゾーンが含まれているため、実際に何が起こっているかを確認できます。 s1およびs2は、ゾーンEDTの06:11:53にあります。 s3およびs4は、ゾーンGMTの10:11:53にあり、元のEDT時間に相当します。一貫性のためにGMTを使用しているデータベースにs3またはs4を保存し、異なるタイムゾーンを保存せずに世界中のどこからでも時間を確保できると想像してください。

s5はGMT時間を解析しますが、現地時間として扱います。そのため、GMT時間である「10:11:53」と表示されますが、ローカル時間では10:11:53であると考えられます。良くない。

s7はGMT時間を解析しますが、文字列内のGMTを無視するため、依然として現地時間として扱います。

s8は機能します。文字列にGMTが含まれるようになり、ローカルゾーンパーサーはそれを使用して、あるタイムゾーンから別のタイムゾーンに変換します。

ここで、ゾーンを保存したくない場合、s3を解析できるようにしたいが、ローカル時間として表示したいとします。答えは保存されたのと同じタイムゾーンを使用して解析する-ですので、作成されたのと同じsdf、sdfGMT1を使用します。 s9、s10、およびs11は、すべて元の時刻を表しています。それらはすべて「正しい」ものです。つまり、d2 == d1です。それはあなたがそれをどのように表示したいかという問題にすぎません。 DBに保存されているもの(GMT時間)を表示する場合は、GMT sdfを使用してフォーマットする必要があります。これはs10です。

文字列に「GMT」と明示的に保存したくない場合、およびGMT形式で表示したい場合の最終的な解決策は次のとおりです。

public static void _testDateFormatting() {
    SimpleDateFormat sdfGMT1 = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");
    sdfGMT1.setTimeZone(TimeZone.getTimeZone("GMT"));

    try {
        Date d = new Date();
        String s3 = sdfGMT1.format(d);
        // Store s3 in DB.
        // ...
        // Retrieve s3 from database, using GMT sdf.
        Date d2 = sdfGMT1.parse(s3);
        String s10 = sdfGMT1.format(d2);
    } catch (Exception e) {
        e.printStackTrace();
    }       
}
8
ToolmakerSteve

tl; dr

dateオブジェクトを取得して、常にGMTになるようにする方法は何ですか?

Instant.now() 

詳細

現在、Java.timeクラスに取って代わられている、面倒でわかりにくい古い日時クラスを使用しています。

Instant = UTC

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

Instant instant = Instant.now() ; // Current moment in UTC.

ISO 8601

このデータをテキストとして交換するには、標準の ISO 8601 形式のみを使用してください。これらの形式は、明確で、機械で処理しやすく、多くの文化で人が読みやすいように設計されています。

Java.timeクラスは、文字列を解析および生成するときにデフォルトで標準形式を使用します。

String output = instant.toString() ;  

2017-01-23T12:34:56.123456789Z

タイムゾーン

特定の地域の実時間に表示されているのと同じ瞬間を見たい場合は、ZoneIdを適用してZonedDateTimeを取得します。

適切なタイムゾーン名 を、 continent/regionAmerica/Montreal 、またはAfrica/CasablancaなどのPacific/Auckland = /の形式で指定します。ESTISTなどのように、not真のタイムゾーン。標準化されておらず、一意でもありません(!)。

ZoneId z = ZoneId.of( "Asia/Singapore" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;  // Same simultaneous moment, same point on the timeline.

この コードはIdeOne.comで公開されています

Asia/Singapore のタイムゾーンは現在、UTCからのオフセットが+08:00であるため、8時間の違いに注意してください。同じ瞬間、異なる壁時計時刻。

instant.toString():2017-01-23T12:34:56.123456789Z

zdt.toString():2017-01-23T20:34:56.123456789 + 08:00 [アジア/シンガポール]

変換する

従来のJava.util.Dateクラスを避けてください。ただし、必要な場合は変換できます。古いクラスに追加された新しいメソッドに注目してください。

Java.util.Date date = Date.fromInstant( instant ) ;

…逆に…

Instant instant = myJavaUtilDate.toInstant() ;

日付のみ

日付のみの場合は、LocalDateを使用します。

LocalDate ld = zdt.toLocalDate() ;

Java.timeについて

Java.time フレームワークはJava 8以降に組み込まれています。これらのクラスは、厄介な古い legacyJava.util.DateCalendar 、& SimpleDateFormat

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

詳細については、 Oracleチュートリアル を参照してください。多くの例と説明についてはStack Overflowを検索してください。仕様は JSR 310 です。

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

ThreeTen-Extra プロジェクトは、追加のクラスでJava.timeを拡張します。このプロジェクトは、Java.timeに将来追加される可能性のある証明の場です。 IntervalYearWeekYearQuarter 、および more

6
Basil Bourque