michal.kreuzman
michal.kreuzman

Reputation: 12410

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.

Upvotes: 50

Views: 41740

Answers (6)

Arvind Kumar Avinash
Arvind Kumar Avinash

Reputation: 79245

java.time

Shown below is a notice on the Joda-Time Home Page:

Note that from Java SE 8 onwards, users are asked to migrate to java.time (JSR-310) - a core part of the JDK which replaces this project.

Solution using modern date-time API

ZonedDateTime zdt = ZonedDateTime.of(
        LocalDate.of(2011, Month.MARCH, 27),
        LocalTime.of(2, 0),
        ZoneId.of("Europe/Prague") // Replace it as per your requirement
); // printing it outputs 2011-03-27T03:00+02:00[Europe/Prague]

In 2011, DST started on 27 March for European Time when the time was moved forward from 01:59 to 03:00 i.e., on this date, the clock did not show the time 02:00 in your time zone. Check this illustration to learn more.

As you can see in the above example, ZonedDateTime smartly adjusted the output to 2011-03-27T03:00+02:00[Europe/Prague].

Demo:

import java.time.*;

public class Main {
    public static void main(String[] args) {
        ZonedDateTime zdt = ZonedDateTime.of(
                LocalDate.of(2011, Month.MARCH, 27),
                LocalTime.of(2, 0),
                ZoneId.of("Europe/Prague")
        );
        System.out.println(zdt);

        // If you want to convert to a different zone, use withZoneSameInstant
        ZonedDateTime zdt2 = zdt.withZoneSameInstant(ZoneId.of("Etc/UTC"));
        System.out.println(zdt2);

        // However, there is another way to do it for UTC. You can obtain an Instant
        // from a ZonedDateTime. An Instant is a point in time independent of any
        // time zone. But for practical purposes, we need a reference time zone.
        // UTC is used as the reference.
        Instant instant = zdt.toInstant();
        System.out.println(instant);
    }
}

Output:

2011-03-27T03:00+02:00[Europe/Prague]
2011-03-27T01:00Z[Etc/UTC]
2011-03-27T01:00:00Z

Online Demo

Learn more about the modern Date-Time API from Trail: Date Time.

Upvotes: 1

Jorge Washington
Jorge Washington

Reputation: 11

Update to jodatime 2.1 and use LocalDate.parse():

DateTimeFormatter formatter = DateTimeFormat.forPattern("dd/MM/yyyy");
LocalDate.parse(date, formatter);

Upvotes: 0

Crushnik
Crushnik

Reputation: 448

If you need to parse date from string:

final DateTimeZone dtz = DateTimeZone.getDefault(); //DateTimeZone.forID("Europe/Warsaw")
LocalDateTime ldt = new LocalDateTime("1946-04-14", dtz);
if (dtz.isLocalDateTimeGap(ldt)){
    ldt = ldt.plusHours(1);
}
DateTime date = ldt.toDateTime();
Date date = date.toDate();

Worked for me perfectly. Maybe somebody will need it.

Upvotes: 3

Jeremy Huiskamp
Jeremy Huiskamp

Reputation: 5294

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...

Upvotes: 8

andersoj
andersoj

Reputation: 22894

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

Upvotes: 38

josh3736
josh3736

Reputation: 144992

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);

Upvotes: 13

Related Questions