我在解析本地分区时间字符串时遇到问题(例如 "12:00:00"
)。某些远程服务提供具有本地时间字符串的数据。服务的本地时区会不时更改。有时服务的时钟不准确(长达几秒钟)。我的时钟也可能没有很好地校准。但是我们很清楚的是,服务器上的数据始终是最新的。 这是解释代码
private static ZoneOffset parseOffset(Clock now, String serviceLocal) {
LocalTime lt = LocalTime.parse(serviceLocal);
for (long s = HOURS.toSeconds(12); s > -HOURS.toSeconds(12); s -= MINUTES.toSeconds(30)) {
final ZoneOffset offset = ZoneOffset.ofTotalSeconds((int) s);
final LocalDate ld = LocalDate.now(now);
final ZonedDateTime zdt = ZonedDateTime.of(ld, lt, offset);
final long seconds = zdt.getLong(ChronoField.INSTANT_SECONDS);
if (seconds > now.instant().minusSeconds(MINUTES.toSeconds(30)).getEpochSecond()
&& seconds <= now.instant().getEpochSecond()) {
return offset;
}
}
throw new DateTimeException(String.format("Can't determine time zone of "%s". Current UTC time is "%s".",
serviceLocal, now.instant()));
}
/**
* This test passes. Detected offset is "GMT+6:00".
*/
@Test
public void shouldParseOffset_1() {
// given
String serviceLocal = "23:59:59";
Clock systemTime = Clock.fixed(Instant.parse("2016-02-25T18:00:00Z"), ZoneOffset.UTC);
// when
ZoneOffset offset = parseOffset(systemTime, serviceLocal);
// then
assertThat(offset, equalTo(ZoneOffset.of("+06:00")));
}
/**
* This test fails. As I said before data is always latest. It is easy to
* guess that some of systems (dedicated service or local) clock is not well
* calibrated. So zone offset should be still equal to "GMT+6:00".
*/
@Test
public void shouldParseOffset_2() {
// given
String serviceLocal = "00:00:01";
Clock systemTime = Clock.fixed(Instant.parse("2016-02-25T18:00:00Z"), ZoneOffset.UTC);
// when
ZoneOffset offset = parseOffset(systemTime, serviceLocal);
// then
assertThat(offset, equalTo(ZoneOffset.of("+06:00")));
}
让总误差不超过 10 秒。如何确定100%情况下服务器数据的确切时间?
一种方法是首先调整服务器时间以反映"准确"时间(即向上或向下调整几秒钟以匹配正确的分钟秒数)。
然后,您可以计算服务器和本地时间之间的时间差。
它可能看起来像:
public static ZoneOffset parseOffset(Clock now, String serviceLocal) {
LocalTime serverTime = LocalTime.parse(serviceLocal);
LocalTime localTime = LocalTime.now(now);
LocalTime adjustedServerTime = getAccurateServerTime(serverTime, localTime, 10);
int seconds = getSecondsOffset(localTime, adjustedServerTime);
return ZoneOffset.ofTotalSeconds(seconds);
}
/**
*
* @param actual the time on the server (may be inaccurate by 10 seconds)
* @param accurate the accurate local time , may be a in a different time zone
* @param maxSecondsInaccuracy the maximum inaccuracy of the time on the server in seconds
* @return the server time, adjusted for seconds inaccuracy
*/
private static LocalTime getAccurateServerTime(LocalTime actual, LocalTime accurate, int maxSecondsInaccuracy) {
int actualSeconds = actual.getSecond();
int accurateSeconds = accurate.getSecond();
if (Math.abs(actualSeconds - accurateSeconds) < maxSecondsInaccuracy) {
return actual.withSecond(accurateSeconds);
} else { //not in the same minute
if (actualSeconds < accurateSeconds) {
return actual.minusMinutes(1).withSecond(accurateSeconds);
} else {
return actual.plusMinutes(1).withSecond(accurateSeconds);
}
}
}
/**
* @return the offset between the two times, in seconds, between -12 and +12
*/
private static int getSecondsOffset(LocalTime target, LocalTime serverTime) {
Duration d = Duration.between(target, serverTime);
long minutes = d.toMinutes();
//limit offset to -12/+12
if (minutes < -12 * 60) minutes = minutes + 24 * 60;
if (minutes > 12 * 60) minutes = minutes - 24 * 60;
return (int) (minutes * 60);
}