设置时区的 SimpleDateFormat 获得正确的值但区域错误
Posted
技术标签:
【中文标题】设置时区的 SimpleDateFormat 获得正确的值但区域错误【英文标题】:SimpleDateFormat with Timezone set gets correct value but wrong zone 【发布时间】:2019-04-18 17:28:45 【问题描述】:我在 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
?
【问题讨论】:
我建议你避免使用SimpleDateFormat
类。它不仅过时了,而且出了名的麻烦。今天我们在java.time
, the modern Java date and time API 中做得更好。一方面,java.time 在时区之间的转换方面不那么令人困惑。
是的……但不是我,而是 Spring/Jackson……你知道的。
FasterXML/jackson-modules-java8?
【参考方案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);
【讨论】:
所以你建议使用 Java 8 Time。完全正确。我想我可能有一天会改变......但Spring似乎在杰克逊使用SimpleDateFormatter,这是我在这里模拟的目的......我不确定......【参考方案2】:感谢您的回复;在 OP 中,我想到了这一行,但写错了:
log.info("The result should be 14:34 CEST: ", result);
我认为这就像“所以我希望它是马德里,所以输出是马德里时区”,但恰恰相反:
格式化程序的时区将是输入日期/字符串的时区,而默认时区(如果不更改,则为JVM的时区,如果更改,则Timezone.getDefault()
的值将是输出结果( date/string) 的时区。根据这两个,格式化程序将进行转换。
而且,Spring/Jackson 内部使用SimpleDateFormat
进行 JSON/Object 序列化/反序列化,所以这也是 Spring 的规则
而且,正如我测试的那样,spring.jackson.time-zone
和 mapper.setTimezone()
将被字段上的JsonFormat(timezone = "xxx")
覆盖。也就是说spring.jackson.time-zone
更通用,适用于需要“输入”时区的所有Date字段,JsonFormat(timezone = "xxx")
更具体,覆盖前者。我猜spring.jackson.dateformat
和@JsonFormat(pattern = "xx")
有相同的关系,但我没有测试。
图形化:
我写这个测试来证明这一点:
/**
* 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】:我不认为这是一个错误,而是对线条的误解:
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()));
【讨论】:
是的,我错了,你明白了;所以这就像input(as f.getTimezone()) ----> f ---->date(as default timezone)
,而不是相反。就是在解析的时候f.setTimezone()
建立字符串的输入时区,默认时区建立输出日期时区。那么,在 Spring Boot 中这样做的时候,是一样的吗?格式化怎么样?
那么,在第一种情况下,我们知道如果没有设置时区,格式化程序会将UTC作为预设,对吧?
是的。要输出不同时区的日期,请参阅我的更新
要理解的关键点是Date
确实是一个时间点,因此没有固有的时区或偏移量。只有当您想解析或打印日期时,您才需要想知道您在哪个区域
对,Date
是一个时间点,它的 getTimezoneOffset()
方法自 Java 1.1 以来已被弃用,带有指向 Calendar
的指针,这是 Java 8 之前的正确工具 java.time
API…以上是关于设置时区的 SimpleDateFormat 获得正确的值但区域错误的主要内容,如果未能解决你的问题,请参考以下文章
Calendar日期类详解SimpleDateFormat时区Date夏令时常用方法,日期差获取当前时间
如果手机时区已更改,SimpleDateFormat setTimeZone无效
遇到的问题---java---使用SimpleDateFormat进行时区加8小时后出现24点的数据,导致时间异常无法入库