问题
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