Reputation: 4577
With the exception of when clocks roll back an hour, It is possible to programmatically determine the timezone, given a country-code and a list of times at which the timezone changes.
For example, if you know that on the last Sunday of every March at 1am and October at 2am the clocks change in the UK, then given a historic or future "local" time you can determine the UTC time.
My question is, are there any built-in methods in Java that can determine this information - not just for the current time but also for historic times?
Upvotes: 0
Views: 274
Reputation: 48
It's not that simple, because timezones's rules (might/can/do) change all the time.
... if you know that on the last Sunday of every March at 1am and October at 2am the clocks change in the UK
That's not true for all historical dates. In the 70's you'll notice that some DST changes occured in the middle of March, not in the last Sunday - like in 1974 and 1975, when DST started at March 17th and 16th, respectively. Also note that it started at 2 AM, not 1 AM (only in 1981 it was changed to start at 1 AM).
Anyway, Java contains all the timezone data from IANA (including historical data), so you just need to use the built-in classes and the API does all the math:
// London timezone
ZoneId londonTimezone = ZoneId.of("Europe/London");
// the historical local date/time I want to check (March 16th 1975, 3 AM)
LocalDateTime localDt = LocalDateTime.of(1975, 3, 16, 3, 0, 0);
// convert to UK timezone (1975-03-16T03:00+01:00)
ZonedDateTime londonDt = localDt.atZone(londonTimezone);
// convert to UTC (1975-03-16T02:00:00Z)
Instant instant = londonDt.toInstant();
In the code above, I've created a local time of March 16th 1975 at 3 AM (so, after the DST transition, that occurred at 2 AM).
When I converted that to London timezone, the result is 1975-03-16T03:00+01:00
- the offset is +01:00 (one hour ahead UTC), because DST is in effect. Converting that to UTC, the result is 1975-03-16T02:00:00Z
.
With the exception of when clocks roll back an hour...
Actually, there's a way to resolve this. Using London's DST transition in 1975, DST ended in October 26th: at 3 AM clocks were set 1 hour back to 2 AM, so all the local times between 2 AM and 2:59 AM existed twice. How to resolve this ambiguity?
// London timezone
ZoneId londonTimezone = ZoneId.of("Europe/London");
// October 26th 1975, 2 AM - the ambiguous local time in London, due to DST end
LocalDateTime localDt = LocalDateTime.of(1975, 10, 26, 2, 0, 0);
// convert to UK timezone (default is the local time in DST: 1975-10-26T02:00+01:00)
ZonedDateTime londonDt = localDt.atZone(londonTimezone);
// get the date in DST (1975-10-26T02:00+01:00)
System.out.println(londonDt.withEarlierOffsetAtOverlap());
// get the date after DST ends (1975-10-26T02:00Z)
System.out.println(londonDt.withLaterOffsetAtOverlap());
The methods withEarlierOffsetAtOverlap()
and withLaterOffsetAtOverlap()
return, respectively, the correspondent date/time before and after DST ends, resolving the ambiguity.
Each of these methods return a new ZonedDateTime
instance (each one corresponding to a different UTC instant), and you can call toInstant()
on them to get the correspond UTC instant.
Always use those 2 methods to get the date/time before and after a transition. Don't try to add or subtract one hour, because not all transitions changes the clock by 1 hour:
It is possible to programmatically determine the timezone, given a country-code and a list of times at which the timezone changes
It's possible, but remind that the same country can have more than one timezone, specially big countries such USA (4 zones in the continent, plus Alaska and Hawai), Russia (more than 10), Brazil (4 - actually it has more because some states have DST but others don't), etc.
Anyway, given a country code, you can check this file and get all the timezones of that country. Then, for each timezone, you run a code similar to this to compare all the dates when a transition occurs:
ZoneId londonTimezone = ZoneId.of("Europe/London");
ZoneRules rules = londonTimezone.getRules();
// get all the transitions (dates when the offset changes)
rules.getTransitions().forEach(t -> {
// UTC instant when the change occurs
Instant instant = t.getInstant();
// local date/time before the transition
LocalDateTime before = t.getDateTimeBefore();
// UTC offset before the transition
ZoneOffset offsetBefore = t.getOffsetBefore();
// local date/time after the transition
LocalDateTime after = t.getDateTimeAfter();
// UTC offset afger the transition
ZoneOffset offsetAfter = t.getOffsetAfter();
// is GAP (clock shifts forward - local times are skipped)
boolean gap = t.isGap();
// is overlap (clock shifts backwards - local times can happen twice)
boolean overlap = t.isOverlap();
// *** You can use the data above to check if the transition date matches your list***
});
Some zones have transition rules instead of just transitions, so you must also check the transition rules list:
rules.getTransitionRules().forEach(rule -> {
// get a transition for specific year
ZoneOffsetTransition transition = rule.createTransition(2018);
// use the transition the same as above (getInstant(), getDateTimeBefore(), etc)
});
Upvotes: 2