I have this scenario that I would like to see if there is a better solution:
I receive a timestamp from system A, in the format of "yyyy-MM-dd-HH-mm-ss", without timezone but they can fix it to be UTC.
And then I need to SSH into system B to get a "last sync time", which could be in the past, and it does not return any time zone, not even the year (from a backup product), and here is an example:
"Sync'ed-as-of time: Sun Nov 3 01:13"
System B is a backup appliance that runs some proprietary software, and there is an internal scheduler which wakes up every 15 minutes to check if there is any data that need to be synced from the source. If the previous sync is still running, it will let the current one finish without taking further action. This "last sync time" will be updated after each sync, so it should not lag behind the current system time more than a couple of hours in the worst scenario.
I need to compare these two timestamps to make sure "last sync time" on B is later than A. If not I need to wait and retry the query until B is later than A to proceed to the next step.
I have the java program sitting on a Linux box that when request coming in from A, it queries B for the "last sync time", and after that immediately issues a "date" command to B to get the current system time, which returns the current system time in the format of "EEE MMM dd kk:mm:ss z yyyy", e.g.,
"Fri Jun 14 21:07:07 EDT 2019",
so the program can apply the year, and the timezone of this output to complete the timestamp of the "last sync time". The program does handle the case the "last snync time" and "date" output are spread across a year boundary.
It works fine except when the summer daylight savings time ends, and clock rolls back (2am back to 1am ), there is an one hour window the time will be ambiguous:
For example, I get a 1:20am timestmap from system A (converted from UTC already to local), and then when I get a 1:30am from system B, I cannot tell if B is later than A. It could be the 1:30 before the clock change. The current timezone of system B, does not clarify which timezone the "last sync time" was in.
We asked the user to switch all of the systems to UTC time, so we do not need to worry about the clock adjustment. But that has too much impact to the environment.
One alternative is that I handle that one hour specially and wait until the "last sync time" timestamp goes beyond 2am. To do that I will need to check in the code whether this is the specific hour every time I do the timestamp comparison, which I think is not optimal. But I cannot think of a better way.
Any suggestion is greatly appreciated. And if this is the way to go, is there a good Java library to figure out the day light saving switching date? Thanks a million!
I see no way that you can be completely sure. “Sun Nov 3” might be in 2002, 2013 or 2019 or even further back in history. One way would be if you can assume that the last sync time is not more than, say, a few years before the current system B system time that you get (and also not after that time), and report an error if you can detect that it is not within those few years.
The trick when summer time (daylight saving time) ends is always to assume the earlier time if there is any ambiguity. In this way when you detect that sync time is later than the timestamp from A, this is true no matter how the ambiguous sync time is interpreted. It also implies that you will wait until the last sync time goes after 2 AM as you suggested. I wouldn’t expect this to be prohibitively expensive in terms of comparison, but if you need to be sure, you need to make your own measurements and judgement.
final DateTimeFormatter timestampFormatter
= DateTimeFormatter.ofPattern("uuuu-MM-dd-HH-mm-ss");
final ZoneId systemBTimeZone = ZoneId.of("America/New_York");
final String syncFormatPattern = "'Sync''ed-as-of time:' EEE MMM d HH:mm";
final DateTimeFormatter currentSystemBTiemFormatter = new DateTimeFormatterBuilder()
.appendPattern("EEE MMM dd HH:mm:ss ")
.appendZoneText(TextStyle.SHORT, Collections.singleton(systemBTimeZone))
.appendPattern(" yyyy")
.toFormatter(Locale.US);
final Period maxSyncAge = Period.ofYears(2);
String systemATimestampString = "2019-11-03-06-05-55";
String lastSyncMsg = "Sync'ed-as-of time: Sun Nov 3 01:13";
String currentSystemBTime = "Sun Nov 03 01:13:07 EDT 2019";
OffsetDateTime systemATimestamp = LocalDateTime.parse(systemATimestampString, timestampFormatter)
.atOffset(ZoneOffset.UTC);
ZonedDateTime maxLastSyncTime
= ZonedDateTime.parse(currentSystemBTime, currentSystemBTiemFormatter);
ZonedDateTime minLatSyncTime = maxLastSyncTime.minus(maxSyncAge);
int candidateYear = maxLastSyncTime.getYear();
ZonedDateTime syncTime;
while (true) {
DateTimeFormatter syncFormatter = new DateTimeFormatterBuilder()
.appendPattern(syncFormatPattern)
.parseDefaulting(ChronoField.YEAR, candidateYear)
.toFormatter(Locale.US);
try {
syncTime = LocalDateTime.parse(lastSyncMsg, syncFormatter)
.atZone(systemBTimeZone)
.withEarlierOffsetAtOverlap();
if (syncTime.isBefore(minLatSyncTime) || syncTime.isAfter(maxLastSyncTime)) {
throw new IllegalStateException("Last sync time is out of range");
}
break;
} catch (DateTimeParseException dtpe) {
// Ignore; try next earlier year
}
candidateYear--;
}
System.out.println("Last sync time: " + syncTime);
System.out.println("Timestamp from system A: " + systemATimestamp);
System.out.println("Is OK? " + syncTime.toOffsetDateTime().isAfter(systemATimestamp));
As the snippet stands, the output from it is:
Last sync time: 2019-11-03T01:13-04:00[America/New_York] Timestamp from system A: 2019-11-03T06:05:55Z Is OK? false
You can see that it has chosen to interpret the last sync time of 01:13 as daylight time, the offset is -04:00, which causes the check to fail. The time might also have been in standard time, offset -05:00, in which case the UTC equivalent would have been 06:13, and the check would have succeeded. But as discussed, to be on the safe side, we prefer wait until the sync time goes after 02:00 and gets unambiguous again.
The while loop keeps parsing the string assuming different years until it hits a year where the day of week matches.