SimpleDateFormat с установленным часовым поясом получает правильное значение, но неверную зону - PullRequest
0 голосов
/ 15 ноября 2018

У меня есть простой тест в приложении Spring, для которого по умолчанию установлен часовой пояс UTC:

@SpringBootApplication
public class MemberIntegrationApp {

    @Autowired
    private TimeZoneProperties timeZoneProperties;

    @PostConstruct
    void started() {
        TimeZone.setDefault(TimeZone.getTimeZone(timeZoneProperties.getAppDefault()));  // which is UTC
    }

    public static void main(String[] args) {
        SpringApplication.run(MemberIntegrationApp.class, args);
    }

}

И этот простой тест: ( Класс теста помечен @SpringBootTest для загрузки конфигурации в основной класс, и @SpringRunner также применяется )

/**
 * Test the effect of setting timezone
 */
@Test
public void testTimezoneSettingOnSimpleDateFormat() throws ParseException {
    SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    String d = "2018-08-08 12:34:56";
    log.info("Trying to parse the date string: {}", d);
    Date result = f.parse(d);
    log.info("The result should be 12:34 UTC: {}", result);

    f.setTimeZone(TimeZone.getTimeZone("UTC"));
    result = f.parse(d);
    log.info("The result should be 12:34 UTC: {}", result);

    f.setTimeZone(TimeZone.getTimeZone("Europe/Madrid"));
    result = f.parse(d);
    log.info("The result should be 10:34 CEST: {}", result);
    log.info("Now the offset(depre): {}", result.getTimezoneOffset());
}

У меня есть вывод:

Trying to parse the date string: 2018-08-08 12:34:56
The result should be 12:34 UTC: Wed Aug 08 12:34:56 UTC 2018
The result should be 12:34 UTC: Wed Aug 08 12:34:56 UTC 2018
The result should be 10:34 CEST: Wed Aug 08 10:34:56 UTC 2018
Now the offset(depre): 0

Теперь, почему четвертая строка имеет правильное значение, а часовой пояс неправильный? Это должно быть Europe/Madrid. И смещение (которое устарело в Java 8, хорошо, я могу простить это), оно должно быть +0200, а не 0.

Это UTC, потому что при преобразовании в строку в log.info(), slf4j мешает ???? Или что? Я так не думаю, потому что System.out.println() тоже дает мне UTC.

Я знаю, что должен использовать OffsetDateTime, но это наследие, и мы пока не можем изменить все поля даты на это. Я хочу знать, почему Java неправильно его проанализировала.

Каково влияние Timezone.getDefault() при разборе с SimpleDateFormat? А что это за f.getTimezone()? Они, кажется, действуют в другой части процесса .....

Я задаю этот вопрос, потому что внутри Джексон использует SimpleDateFormat для обработки строки даты / даты форматирования. Влияет ли конфигурация на ObjectMapper на SimpleDateFormat, который использует маппер?

Ответы [ 3 ]

0 голосов
/ 16 ноября 2018

Спасибо за ответ; в ОП я думал об этой строке, но не правильно сказал:

 log.info("The result should be 14:34 CEST: {}", result);

Я подумал, что это похоже на «Итак, я хочу, чтобы это был Мадрид, поэтому выходной результат - часовой пояс Мадрида», но наоборот:

Часовой пояс устройства форматирования будет входной датой / часовым поясом строки, а часовой пояс по умолчанию (если не изменен, то JVM, если изменено, значение Timezone.getDefault() будет выходным результатом ( часовой пояс даты / строки). На основе этих двух форматер выполнит преобразование.

И Spring / Jackson внутренне использует SimpleDateFormat для выполнения сериализации / десериализации JSON / Object, поэтому это будет правилом и для Spring

И, как я проверяю, spring.jackson.time-zone и mapper.setTimezone() будут переопределены JsonFormat(timezone = "xxx") на полях. То есть spring.jackson.time-zone является более общим и применяется ко всем полям Date, которым требуется часовой пояс "input", а JsonFormat(timezone = "xxx") является более конкретным и переопределяет предыдущий. Я думаю, spring.jackson.dateformat и @JsonFormat(pattern = "xx") имеют такие же отношения, но я не проверял.

Графический:

enter image description here

Я пишу этот тест, чтобы продемонстрировать это:

/**
 * Test the effect of setting timezone on a {@link SimpleDateFormat}. Apparently,
 * <code>f.setTimezone()</code> sets the input timezone, and default timezone sets
 * the output timezone.
 *
 */
@Test
public void testTimezoneSettingOnSimpleDateFormat() throws ParseException {
    /* *********** test parsing *********** */
    log.info("********** test parsing **********");
    SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    String d = "2018-08-08 12:34:56";

    log.info("Trying to parse the date string: {}", d);
    Date result = f.parse(d);
    log.info("The result should be 12:34 UTC: {}", result);

    f.setTimeZone(TimeZone.getTimeZone("UTC"));
    result = f.parse(d);
    log.info("The result should be 12:34 UTC: {}", result);

    f.setTimeZone(TimeZone.getTimeZone("Europe/Madrid"));
    result = f.parse(d);
    log.info("The result should be 10:34 UTC: {}", result);

    /* ********** test formatting ********** */
    log.info("********** test formatting **********");
    // given
    SimpleDateFormat f2 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
    // construct a date to represent this moment
    OffsetDateTime now = OffsetDateTime.of(2018, 11, 16, 10, 22, 22, 0, ZoneOffset.of("+0100"));
    TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai")); // GMT+8, so Madrid+7
    // when you construct a date without timezone, it will be the timezone of system/default!
    Date nowDate = new Date(now.toEpochSecond() * 1000);
    log.info("The constructed date is: {}", nowDate); // Fri Nov 16 17:22:22 CST 2018

    // now formatter timezone is Madrid
    f2.setTimeZone(TimeZone.getTimeZone("Europe/Madrid"));
    // now default timezone is Asia/Shanghai

    // when
    String result2 = f2.format(nowDate);

    // then
    log.info("The result should be 10:22: {}", result2); // 2018-11-16T10:22:22+01:00


    log.info("Conclusion: the formatter's timezone sets the timezone of input; the application/default " +
            "timezone sets the timezone of output. ");
}
0 голосов
/ 26 мая 2019
public static Instant getInstantNow() {
        Clock utcClock = Clock.systemUTC();
        //ZoneId myTZ = ZoneId.of("Brazil/East");       
        return Instant.now(utcClock).minusSeconds(10800);   
        //Instant in = Instant.now(utcClock);
        //return in.atZone(myTZ);   
    }

    public static LocalDateTime getLocalDateTimeNow() {
        ZonedDateTime nowBrasil = ZonedDateTime.now(ZoneId.of("Brazil/East"));
        return LocalDateTime.from(nowBrasil);
    }

    public static LocalDate getLocalDateNow() {
        ZonedDateTime nowBrasil = ZonedDateTime.now(ZoneId.of("Brazil/East"));
        return LocalDate.from(nowBrasil);
    }
0 голосов
/ 15 ноября 2018

Я не думаю, что это ошибка, а скорее неправильная интерпретация строк:

    f.setTimeZone(TimeZone.getTimeZone("Europe/Madrid"));
    result = f.parse(d);
    log.info("The result should be 10:34 CEST: {}", result);

Что это значит?

Сначала вы устанавливаете часовой пояс, сообщая парсеру, что собираетесь анализировать время в зоне Европы / Мадрида.

Тогда вы показываете это. Он не может угадать, в каком часовом поясе вы его хотите, поэтому он отображает его в часовом поясе по умолчанию, в вашем случае UTC.


Обратите внимание, что:

  • это на самом деле 10:34 в UTC, а в Мадриде 12:34, а не наоборот.
  • Date.getTimezoneOffset() - это смещение между UTC и часовым поясом по умолчанию (таким образом, 0 в вашем случае), не имеет ничего общего с часовым поясом, который вы использовали для настройки анализатора. Более того, он устарел начиная с Java 1.1, вы больше не должны его использовать.

Для отображения значения даты в другом часовом поясе можно использовать SimpleDateFormat.format(), например:

    f.setTimeZone(TimeZone.getTimeZone("UTC"));
    log.info("UTC {}", f.format(new Date()));
    f.setTimeZone(TimeZone.getTimeZone("Europe/Madrid"));
    log.info("Europe/Madrid {}", f.format(new Date()));
...