SimpleDateFormat with Timezone set gets correct value but wrong zone

烈酒焚心 提交于 2019-12-07 08:04:10

问题


I have a simple test in a Spring application, which has default timezone set to 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);
    }

}

And, this simple test: (The test class is annotated with @SpringBootTest to load the configuration in main class and @SpringRunner is applied, too)

/**
 * 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());
}

I have output:

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

Now, why the fourth line has the value correct, but the timezone is wrong? It should be Europe/Madrid. And the offset(which is deprecated in Java 8, OK I can forgive it), it should be +0200, not 0.

It is UTC because when converting to string in log.info(), slf4j is interferring???? Or what? I don't think so because System.out.println() gives me UTC too.

I know I should use OffsetDateTime, but it is legacy and we cannot change all fields of date to that, for now. I want to know why Java parsed it wrongly.

What is the effect of Timezone.getDefault() when parsing with SimpleDateFormat? And what is that of f.getTimezone()? They seem to act in different part of the process.....

I ask this question, because internally Jackson uses SimpleDateFormat to process date string/formatting dates. Does the config on an ObjectMapper affect the SimpleDateFormat that the mapper uses?


回答1:


I don't think it's a bug, but rather a misinterpretation of the lines:

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

What does it mean ?

You first set a time zone, telling the parser you are about to parse a time in Europe/Madrid zone.

Then you display it. It cannot guess in which time zone you want it, so it displays it in the default time zone, UTC in your case.


Note that:

  • it's actually 10:34 in UTC while it's 12:34 in Madrid, and not the other way round.
  • Date.getTimezoneOffset() is the offset between UTC and the default timezone (thus 0 in your case), nothing to do with the time zone you used to configure the parser. Moreover it is deprecated since java 1.1, you should not really use it anymore.

To display a date value in different time zone, SimpleDateFormat.format() can be used, for instance:

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



回答2:


Thanks for the reply; in the OP I was thinking of this line but put it wrong:

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

I thought it is like "So I want it to be Madrid so the output is Madrid timezone", but is the opposite:

The timezone of the formatter will be the input date/string's timezone, while the default timezone(if not changed, that of the JVM, if changed, the value of Timezone.getDefault(), will be the output result(date/string)'s timezone. Based on these two, the formatter will do the conversion.

And, Spring/Jackson internally uses SimpleDateFormat to do JSON/Object serialization/deserialization, so it will be the rule for Spring, too

And, as I test, spring.jackson.time-zone and mapper.setTimezone() will be overridden by JsonFormat(timezone = "xxx") on the fields. That is to say, the spring.jackson.time-zone is more general and applies to all fields of Date who needs an "input" timezone, and JsonFormat(timezone = "xxx") is more specific and overrides the former one. I guess spring.jackson.dateformat and @JsonFormat(pattern = "xx") have the same relationship but I haven't tested.

Graphically:

I write this test to demonstrate this:

/**
 * 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. ");
}



回答3:


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


来源:https://stackoverflow.com/questions/53322376/simpledateformat-with-timezone-set-gets-correct-value-but-wrong-zone

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!