10

私は奇妙な行動に出くわし、好奇心旺盛で、まだ満足のいく説明がありません.

簡単にするために、気付いた症状を次のコードに減らしました。

import java.text.SimpleDateFormat;
import java.util.GregorianCalendar;

public class CalendarTest {
    public static void main(String[] args) {
        System.out.println(new SimpleDateFormat().getCalendar());
        System.out.println(new GregorianCalendar());
    }
}

このコードを実行すると、次のような出力が得られます。

java.util.GregorianCalendar[time=-1274641455755,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="アメリカ/ロサンゼルス",offset=-28800000,dstSavings=3600000, useDaylight=true,transitions=185,lastRule=java.util.SimpleTimeZone[id=America/Los_Angeles,offset=-28800000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8 ,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1 ,YEAR=1929,MONTH=7,WEEK_OF_YEAR=32,WEEK_OF_MONTH=2,DAY_OF_MONTH=10,DAY_OF_YEAR=222,DAY_OF_WEEK=7,DAY_OF_WEEK_IN_MONTH=2,AM_PM=1,HOUR=8,HOUR_OF_DAY=20,MINUTE=55,SECOND =44,MILLISECOND=245,ZONE_OFFSET=-28800000,DST_OFFSET=0]
java.util.GregorianCalendar[time=1249962944248,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="アメリカ/ロサンゼルス",offset=-28800000,dstSavings=3600000,useDaylight =true,transitions=185,lastRule=java.util.SimpleTimeZone[id=アメリカ/ロサンゼルス,offset=-28800000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8, startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1, YEAR=2009,MONTH=7,WEEK_OF_YEAR=33,WEEK_OF_MONTH=3,DAY_OF_MONTH=10,DAY_OF_YEAR=222,DAY_OF_WEEK=2,DAY_OF_WEEK_IN_MONTH=2,AM_PM=1,HOUR=8,HOUR_OF_DAY=20,MINUTE=55,SECOND= 44,MILLISECOND=248,ZONE_OFFSET=-28800000,DST_OFFSET=3600000]

"yyyy-MM-dd"( SimpleDateFormatのような有効な書式文字列を指定すると、同じことが起こります。)

恐ろしい非ラッピング行を許してください, しかし、それは2つを比較する最も簡単な方法です. 2/3 ほどスクロールすると、カレンダーの YEAR 値がそれぞれ 1929 年と 2009 年であることがわかります。(年の週、曜日、DST オフセットなど、他にもいくつかの違いがあります。) どちらも明らかに GregorianCalendar のインスタンスですが、それらが異なる理由は不可解です。

私が言えることから、フォーマッタは、渡された Date オブジェクトをフォーマットするときに正確な結果を生成します。明らかに、正しい基準年よりも正しい機能性の方が重要ですが、それでもこの不一致は当惑させられます。現在の年を取得するためだけに、新しい日付フォーマッタでカレンダーを設定する必要があるとは思いません...

Java 5 (OS X 10.4、PowerPC) および Java 6 (OS X 10.6、Intel) を搭載した Mac でこれをテストしましたが、結果は同じでした。これは Java ライブラリ API であるため、すべてのプラットフォームで同じように動作すると想定しています。ここで何が起こっているかについての洞察はありますか?

(注:このSOの質問は多少関連していますが、同じではありません。)


編集:

以下のすべての回答は、この動作の説明に役立ちました。SimpleDateFormatの Javadoc には、実際にこれがある程度文書化されていることがわかります。

「省略された年パターン ("y" または "yy") で解析するために、SimpleDateFormat は、ある世紀に関連する省略された年を解釈する必要があります。これは、SimpleDateFormat インスタンスの時刻の 80 年前と 20 年後の日付を調整することによって行われます。創造された。"

そのため、解析対象の日付の年にこだわる代わりに、デフォルトで内部カレンダーを 80 年戻します。その部分自体は文書化されていませんが、それについて知っていると、すべての部分がうまく収まります。

4

5 に答える 5

6

トムが「シリアライゼーションと関係がある」と言う理由はわかりませんが、彼は正しい行を持っています:

private void initializeDefaultCentury() {
    calendar.setTime( new Date() );
    calendar.add( Calendar.YEAR, -80 );
    parseAmbiguousDatesAsAfter(calendar.getTime());
}

これは SimpleDateFormat.java の 813 行目で、プロセスが非常に遅れています。その時点までは、年は (日付部分の残りの部分と同様に) 正しいので、80 ずつ減分されます。

あはは!

への呼び出しparseAmbiguousDatesAsAfter()は、以下を呼び出すのと同じプライベート関数ですset2DigitYearStart()

/* Define one-century window into which to disambiguate dates using
 * two-digit years.
 */
private void parseAmbiguousDatesAsAfter(Date startDate) {
    defaultCenturyStart = startDate;
    calendar.setTime(startDate);
    defaultCenturyStartYear = calendar.get(Calendar.YEAR);
}

/**
 * Sets the 100-year period 2-digit years will be interpreted as being in
 * to begin on the date the user specifies.
 *
 * @param startDate During parsing, two digit years will be placed in the range
 * <code>startDate</code> to <code>startDate + 100 years</code>.
 * @see #get2DigitYearStart
 * @since 1.2
 */
public void set2DigitYearStart(Date startDate) {
    parseAmbiguousDatesAsAfter(startDate);
}

今、何が起こっているのかがわかります。「リンゴとオレンジ」についてのピーターのコメントは正しかった!SimpleDateFormat の年は、「デフォルトの世紀」の最初の年であり、2 桁の年文字列 (たとえば、「1/12/14」) が解釈される範囲です。http://java.sun.com/j2se/1.4.2/docs/api/java/text/SimpleDateFormat.html#get2DigitYearStart%28%29を参照してください:

したがって、明快さよりも「効率」が勝って、SimpleDateFormat の年は、現在の年ではなく、「2 桁の年が解析される 100 年の期間の開始」を格納するために使用されます!

/ありがとう、これは楽しかったです -- そして最後に jdk ソースをインストールすることができました (パーティションに合計 4GB のスペースしかありません)。

于 2009-08-11T06:09:06.367 に答える
2

SimpleDateFormat には変更可能な内部状態があります。これが、疫病のようにそれを避ける理由です (私はJoda Timeをお勧めします)。この内部カレンダーはおそらく日付を解析するプロセスで使用されますが、日付を解析する前に特に何かに初期化される理由はありません。

説明するコードを次に示します。

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.GregorianCalendar;

public class DateTest {
    public static void main(String[] args) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
        System.out.println("sdf cal: " + simpleDateFormat.getCalendar());
        System.out.println("new cal: " + new GregorianCalendar());
        System.out.println("new date: " + simpleDateFormat.format(new Date()));
        System.out.println("sdf cal: " + simpleDateFormat.getCalendar());
    }
}
于 2009-08-11T06:05:01.383 に答える
2

あなたは内部の行動を調査しています。これが公開された API の外にある場合は、未定義のものを見ていることになり、気にする必要はありません。

それ以外では、1929 年は、いつ 2 桁の年を 20xx ではなく 19xx であると解釈するかを検討するために使用されると思います。

于 2009-08-11T05:58:04.393 に答える
1

SimpleDateFormat を見ると、シリアル化に関係しているようです。

/* Initialize the fields we use to disambiguate ambiguous years. Separate
 * so we can call it from readObject().
 */
private void initializeDefaultCentury() {
    calendar.setTime( new Date() );
    calendar.add( Calendar.YEAR, -80 );
    parseAmbiguousDatesAsAfter(calendar.getTime());
}
于 2009-08-11T04:49:12.020 に答える
0
System.out.println(new SimpleDateFormat().getCalendar());
System.out.println(new GregorianCalendar());

上記のコードを比較すると、リンゴとナシが比較されます

1つ目は、文字列を日付に、またはその逆に解析するツールを提供します。2つ目は、日付を操作できるDateUtilityです。

が同様の出力を提供する必要がある理由は実際にはありません。

以下と比較してください

System.out.println(new String() );
System.out.println(new Date().toString() );

両方の行で文字列が出力されますが、論理的には同じ結果は期待できません。

于 2009-08-11T05:27:50.647 に答える