Reputation: 3256
I have an issue with parsing local zoned time strings (e.g."12:00:00"
). Some remote service provides data with local time strings. Service's local time zone changes from time to time. Sometimes service's clock have an inaccuracy (up to several seconds). My clock also might not be well calibrated. But what we know well, is the fact that the data on the server is always the most recent.
Here is the explaining code
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")));
}
Let the total error don't exceed 10 seconds. How to determine the exact time of the server data in 100% of cases?
Upvotes: 2
Views: 150
Reputation: 328923
One way would be to first adjust the server time to reflect an "accurate" time (i.e. adjusted by a few seconds up or down to match the correct seconds of minutes).
You can then calculate the difference of time between you server and local time.
It could look like:
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);
}
Upvotes: 2