How to handle jodatime Illegal instant due to time zone offset transition
I want to set up joda DateTime
to today at 2 AM (see sample code below). But I'm getting this exception:
Exception in thread "main" org.joda.time.IllegalFieldValueException: Value 2 for hourOfDay is not supported: Illegal instant due to time zone offset transition: 2011-03-27T02:52:05.239 (Europe/Prague)
at org.joda.time.chrono.ZonedChronology$ZonedDateTimeField.set(ZonedChronology.java:469)
at org.joda.time.MutableDateTime.setHourOfDay(MutableDateTime.java:702)
What is the correct way to the handle exception above or to create a DateTime
at a particular hour of day?
Sample code:
MutableDateTime now = new MutableDateTime();
now.setHourOfDay(2);
now.setMinuteOfHour(0);
now.setSecondOfMinute(0);
now.setMillisOfSecond(0);
DateTime myDate = now.toDateTime();
Thanks.
It seems like you're trying to get from a specific local time to a DateTime
instance and you want that to be robust against daylight savings. Try this... (note I'm in US/Eastern, so our transition date was 13 Mar 11; I had to find the right date to get the exception you got today. Updated my code below for CET, which transitions today.) The insight here is that Joda provides LocalDateTime
to let you reason about a local wall-clock setting and whether it's legal in your timezone or not. In this case, I just add an hour if the time doesn't exist (your application has to decide if this is the right policy.)
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.LocalDateTime;
class TestTz {
public static void main(String[] args)
{
final DateTimeZone dtz = DateTimeZone.forID("CET");
LocalDateTime ldt = new LocalDateTime(dtz)
.withYear(2011)
.withMonthOfYear(3)
.withDayOfMonth(27)
.withHourOfDay(2);
// this is just here to illustrate I'm solving the problem;
// don't need in operational code
try {
DateTime myDateBorken = ldt.toDateTime(dtz);
} catch (IllegalArgumentException iae) {
System.out.println("Sure enough, invalid instant due to time zone offset transition!");
}
if (dtz.isLocalDateTimeGap(ldt)) {
ldt = ldt.withHourOfDay(3);
}
DateTime myDate = ldt.toDateTime(dtz);
System.out.println("No problem: "+myDate);
}
}
This code produces:
Sure enough, invalid instant due to time zone offset transition! No problem: 2011-03-27T03:00:00.000+02:00
CET switches to DST (summer time) on the last Sunday in March, which happens to be today. The time went from 1:59:59 to 3:00:00 – there's no 2, hence the exception.
You should use UTC instead of local time to avoid this kind of time zone issue.
MutableDateTime now = new MutableDateTime(DateTimeZone.UTC);
I think a lot of the time, you will want joda to fix this automatically for you. You will often not know the correct way to fix a gap date because the size of the gap depends on the zone and the year (although of course it's usually an hour).
One example of this is if you are parsing a timestamp that comes from a source you don't control; eg, the network. If the sender of the timestamp has outdated zone files, this could happen. (If you have outdated zone files, you're pretty much screwed).
Here's a way to do that, which, granted, is slightly more complicated. I've made it work in joda 1.6 as well as 2.x, since we happen to be stuck on 1.6 in our environment.
If you're building a date from some other inputs as in your question, you can start with a UTC date or a LocalDate
as suggested above, and then adapt this to automatically fix your offset. The special sauce is in DateTimeZone.convertLocalToUTC
Dangerous:
public DateTime parse(String str) {
formatter.parseDateTime(gapdate)
}
Safe:
public DateTime parse(String str) {
// separate date from zone; you may need to adjust the pattern,
// depending on what input formats you support
String[] parts = str.split("(?=[-+])");
String datepart = parts[0];
DateTimeZone zone = (parts.length == 2) ?
DateTimeZone.forID(parts[1]) : formatter.getZone();
// parsing in utc is safe, there are no gaps
// parsing a LocalDate would also be appropriate,
// but joda 1.6 doesn't support that
DateTime utc = formatter.withZone(DateTimeZone.UTC).parseDateTime(datepart);
// false means don't be strict, joda will nudge the result forward by the
// size of the gap. The method is somewhat confusingly named, we're
// actually going from UTC to local
long millis = zone.convertLocalToUTC(utc.getMillis(), false);
return new DateTime(millis, zone);
}
I've tested this in the eastern and western hemispheres as well as the Lord Howe Island zone, which happens to have a half hour dst.
It would be kind of nice if joda formatters would support a setStrict(boolean) that would have them take care of this for you...