设置了时区的SimpleDateFormat获取正确的值,但时区错误

时间:2018-11-15 15:07:35

标签: java spring-boot datetime java-8 jackson-databind

我在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错误地解析了它。

使用SimpleDateFormat进行解析时Timezone.getDefault()有什么作用? f.getTimezone()是什么?他们似乎在流程的不同部分起作用。..

我问这个问题,因为杰克逊内部使用SimpleDateFormat处理日期字符串/格式日期。 ObjectMapper上的配置会影响映射器使用的SimpleDateFormat吗?

3 个答案:

答案 0 :(得分:4)

我不认为这是一个错误,而是对行的误解:

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

是什么意思?

您首先设置一个时区,告诉解析器您将要解析欧洲/马德里时区的时间。

然后显示它。它无法猜测您想要哪个时区,因此它会以默认时区(以您的情况为UTC)显示它。


请注意:

  • 实际上在UTC是10:34,而在马德里是12:34,而不是相反。
  • Date.getTimezoneOffset()offset between UTC and the default timezone(在您的情况下为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()));

答案 1 :(得分:1)

感谢您的回复;在OP中,我在想这条线,但是把它弄错了:

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

我认为这就像“所以我希望它成为马德里,所以输出是马德里时区”,但恰恰相反:

格式化程序的时区将为输入日期/字符串的时区,而默认时区(如果未更改,则为JVM的时区,如果更改,则为Timezone.getDefault()的值,将作为输出结果(日期/字符串)的时区。格式化程序将基于这两个时区进行转换。

而且,Spring / Jackson内部使用SimpleDateFormat进行JSON /对象序列化/反序列化,因此这也是Spring的规则

而且,根据我的测试,spring.jackson.time-zonemapper.setTimezone()在字段上将被JsonFormat(timezone = "xxx")覆盖。也就是说,spring.jackson.time-zone是更通用的,适用于需要“输入”时区的Date的所有字段,而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. ");
}

答案 2 :(得分:1)

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