49

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.

Kirby
  • 15,127
  • 10
  • 89
  • 104
michal.kreuzman
  • 12,170
  • 10
  • 58
  • 70
  • relevant: http://joda-interest.219941.n2.nabble.com/Question-re-Daylight-Savings-Time-td5297106.html – andersoj Mar 27 '11 at 18:05
  • It always seems to me that these questions are much more complicated than they need to be. Internally, Joda represents instants as POSIX-style UTC long integers. Since this time format is a simple cumulative count of milliseconds and is never adjusted for DST, using it internally for datetimes always dispenses with DST conversion problems. The issue with DST is amortized into the conversion functions that convert the instant into a locally formatted time-zone aware strings when it needs to be displayed to the user. That approach seems much more straightforward to me. – scottb Jul 12 '13 at 05:20
  • look at my answer http://stackoverflow.com/questions/17665921/recommended-use-for-joda-times-datemidnight/29230727#29230727 – shareef Mar 24 '15 at 10:57

5 Answers5

38

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
andersoj
  • 22,406
  • 7
  • 62
  • 73
  • 21
    Adding an hour is not always the right thing to do. There are zones in the world that use half hours for daylight savings and there have been zones in the past that have used 2 hours. There are probably also some other one-off jumps of different sizes in history. DateTimeZone.getOffsetFromLocal(long) allows you to use the zone database to get the correct fix. – Jeremy Huiskamp Apr 09 '13 at 07:08
  • Yup, that's true. I was focused on noting the source of the problem... The resolution to this issue is specific to the kind of computation you're doing, the application context, and the specific timezone. – andersoj Apr 09 '13 at 10:39
  • if (dtz.isLocalDateTimeGap(ldt)){ ldt = ldt.plusHours(1); } – Liam Mazy May 04 '17 at 06:50
13

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);
josh3736
  • 139,160
  • 33
  • 216
  • 263
  • Then, the chosen answer may cause problems? – falsarella Sep 25 '12 at 19:58
  • @falsarella: I'm not sure what you're trying to ask. – josh3736 Sep 25 '12 at 20:02
  • You said that CET has summer time too. So, the answer from @andersoj (see above) will cause problems too! So, it will not resolve the OP's (and mine) situation. Having that pointed, the only right answer is yours, using UTC date time zone. – falsarella Sep 26 '12 at 11:29
  • 1
    The OP implied their intent was to set a local time specifically. While you are right, doing things from UTC avoids the idiosyncrasies of local offsets moving around, it doesn't answer the question of how to do something at 2am in MY time zone. – andersoj Apr 09 '13 at 10:53
8

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

Jeremy Huiskamp
  • 5,186
  • 5
  • 26
  • 19
3

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.

Kirby
  • 15,127
  • 10
  • 89
  • 104
Crushnik
  • 408
  • 5
  • 16
0

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

DateTimeFormatter formatter = DateTimeFormat.forPattern("dd/MM/yyyy");
LocalDate.parse(date, formatter);
Styx
  • 9,863
  • 8
  • 43
  • 53