SimpleDateFormat се държи непоследователно

Вижте следната част от кода:

String timeString = "1980-01-01T14:00:00+0300";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
Date date2 = sdf.parse(timeString);

// sdf.getCalendar().get(Calendar.ZONE_OFFSET);
System.out.println(sdf.format(date2));

сега съм в държава, която има +2h компенсиране, +1 лятно часово време (в момента). Ако пусна този код такъв, какъвто е, той ще се отпечата

1980-01-01T13:00:00+0200

Ако разкоментирам реда, питащ за отместването на календара, изходът на програмата е

1980-01-01T14:00:00+0300

Някаква идея защо се случва това и как мога да получа последователен резултат?

За да избегна още неясни неща: Тъй като боравя с някакъв наследен код, java 8 не е опция. И да, ключовият момент тук е ЗАЩО, а не какви са заобиколните решения? И има 2 ЗАЩО:

  1. Защо преминавам +0300 TZ и по подразбиране получавам +0200? (SimpleDateFormat винаги трябва да използва TimeZone.getDefault, освен ако не е посочено друго).
  2. Защо дава различен отговор, само защото се обаждам на getter в неговия календарен екземпляр.

person Eduard Korenschi    schedule 03.07.2015    source източник
comment
какво несъвместимо има в него? когато не използвате отместване, ви дава датата такава, каквато е, а когато използвате, тя се променя според часовата зона.   -  person Raman Shrivastava    schedule 03.07.2015
comment
@RamanShrivastava Еднократното извикване на getter не трябва да променя изхода на функцията за форматиране.   -  person wonderb0lt    schedule 03.07.2015


Отговори (3)


Проблемът е, че Calendar#get не е прост инструмент за получаване, изглежда така:

public int  get(int field)
{
    complete();
    return internalGet(field);
}

където complete прави това, според Javadoc:

Попълва всички незададени полета в полетата на календара. Първо, методът computeTime() се извиква, ако стойността на времето (милисекунда отместване от епохата) не е изчислена от стойностите на календарното поле. След това се извиква методът computeFields() за изчисляване на всички стойности на полетата на календара.

Потърсих източника тук, но кодът е същият и за най-новата версия на Java.

person meskobalazs    schedule 03.07.2015
comment
Да, знаех го. Но това просто го прави, защото го прави. На мен определено ми прилича на бъг. - person Eduard Korenschi; 03.07.2015
comment
Бих казал, че това е спорно дизайнерско решение. Има причина Java 8 да има нов API за дата и час :) - person meskobalazs; 03.07.2015
comment
Този отговор би бил идеален с малък фрагмент как OP може да промени кода си, за да избегне complete() да наруши своя код (напр. задаване на някои специфични полета в екземпляра на календара и т.н.) - person wonderb0lt; 03.07.2015
comment
Това не го обяснява. SimpleDateFormat.format() всъщност също извиква Calendar.get(). - person gogognome; 03.07.2015

sdf.parse променя отместването на зоната на вътрешния календар на форматиращия на +0300

    System.out.println(sdf.getCalendar());
    sdf.parse(timeString);
    System.out.println(sdf.getCalendar());

можете да видите разликата в края на изходните редове

... ,ZONE_OFFSET=7200000,DST_OFFSET=0]
... ,ZONE_OFFSET=10800000,DST_OFFSET=0]

sdf.getCalendar().get(Calendar.ZONE_OFFSET); възстановява отместването на зоната на календара обратно към текущата часова зона

person Evgeniy Dorofeev    schedule 03.07.2015
comment
Въпросът е: защо го прави? Моето предположение е следното: Календарът има препратка към обект на часова зона. При анализ на датата не може да се определи часова зона от +0300. Той просто съхранява отместване за 3 часа в полето ZONE_OFFSET на календара. Методът get() изчислява вътрешно всички полета за календар, когато все още не е направил това за всички полета, включително полето ZONE_OFFSET. Календарът използва известната му часова зона, за да изчисли отместването и определя различно отместване. - person gogognome; 03.07.2015
comment
Да, предполагам, че това е най-точният отговор. Всъщност, където съм сега, както също написах в началния пост, съм в +2 офсет, +1 dst зона. Това не може да бъде представено по никакъв начин в шаблон SimpleDateFormat. Ето защо го комбинираме като +3. Но все пак някъде има грешка в цялата вътрешна обработка, която извършват Calendar и SimpleDateFormat, тъй като поведението на трансформиране на (+3 +0) в (+2 +1) или оставянето му както е било трябва да е последователно и да не се засяга чрез получаване. Тъй като не променям sdf по никакъв начин след създаването на публикация, sdf.format(sdf.parse(txt)) винаги трябва да бъде txt. - person Eduard Korenschi; 03.07.2015

SimpleDateFormate използва календар, за да създаде датата. Обектът за дата се създава с клеймо за време, което се изчислява в java.util.GregorianCalendar.computeTime(). Там в ред 2789 се използва първоначалната часова зона (която е зададена java.text.SimpleDateFormat.initializeCalendar(Locale)). Ето защо виждате грешната часова зона.

Когато извикате get(Calendar.ZONE_OFFSET), се извиква метод java.util.GregorianCalendar.computeFields(), който използва часовата зона, зададена първоначално.

Предполагам, че това е бъг.

person hinneLinks    schedule 03.07.2015