Hot questions for Using Joda-Time in dst

Question:

Today our Brazilian users are generating plenty of crash reports for us. I've tracked it down to this code, which throws a Joda exception:

import org.joda.time.DateTime;
import org.joda.time.DateTimeUtils;
import org.joda.time.DateTimeZone;
import org.joda.time.LocalTime;

public class ScratchSpace {

    public static void main(String[] args) {
        // force Joda to act like we are in Sao Paolo on 2015-10-18
        DateTimeUtils.setCurrentMillisFixed(1445185758078L); // 2015-10-18T18:29
        DateTimeZone.setDefault(DateTimeZone.forID("America/Sao_Paulo"));

        // most of users have offset == 0, but it could be any number of millis from 0 to 86_400_000-1 (millis in day) 
        int offset = 0;

        // local time at start of day + offset millis  
        final LocalTime localTime = LocalTime.fromMillisOfDay(offset);

        // convert to a time on the current day
        DateTime dateTime = localTime.toDateTimeToday();  // throws org.joda.time.IllegalFieldValueException exception
        System.out.println("dateTime = " + dateTime);
    }
}

The exception:

Exception in thread "main" org.joda.time.IllegalFieldValueException: Value 0 for hourOfDay is not supported: Illegal instant due to time zone offset transition (daylight savings time 'gap'): 2015-10-18T00:29:18.078 (America/Sao_Paulo)
    at org.joda.time.chrono.ZonedChronology$ZonedDateTimeField.set(ZonedChronology.java:486)
    at org.joda.time.chrono.BaseChronology.set(BaseChronology.java:240)
    at org.joda.time.LocalTime.toDateTimeToday(LocalTime.java:1287)
    at org.joda.time.LocalTime.toDateTimeToday(LocalTime.java:1270)

I'm using Java 1.8.0_60 on OS X 10.11 with Joda Time 2.8.2.

What work-around will allow me to correctly get a DateTime instance representing a time on the current day that is offset milliseconds after the start of the day?


Answer:

Don't go though a LocalTime. Create a DateTime, and add the offset in milliseconds:

DateTime startOfDay = LocalDate.now().toDateTimeAtStartOfDay();
DateTime dateTime = startOfDay.plus(offset);

System.out.println("dateTime = " + dateTime);

Question:

How is it possible in Java to convert an instance of java.util.TimeZone to org.joda.DateTimeZone and keeping the daylight saving time?


Answer:

Joda-Time in maintenance-mode

The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

java.time.ZoneId

The modern replacement for java.util.TimeZone is java.time.ZoneId & java.time.ZoneOffset.

You should avoid the old legacy date-time classes. But if necessary, you can convert to/from the java.time types. Look to new methods added to the old classes. You can move between TimeZone and ZoneId.

java.util.TimeZone tz = java.util.TimeZone.getTimeZone( myZoneId );

…and…

java.time.ZoneId z = myLegacyTimeZone.toZoneId();

If you are looking for the offset-from-UTC or Daylight Saving Time (DST) info for the zone, look at the ZoneRules class. Search Stack Overflow for more discussion and examples on that, or edit your Question to describe more about your goal.

Question:

I have a date range (start and end date) and require to know whether this falls within a Daylight Saving change over.

Is there any Java API available to check this or any Java code to achieve this?


Answer:

Daylight Saving changes occur at different dates in each country/region, so the first thing to know is the name of the timezone you're checking.

I'm writing this answer using both Joda-Time and the new Java Date/Time API and both use the IANA's list of timezone names (in the format Continent/City). Both API's also avoid to use the 3-letter names because they are ambiguous and not standard.

For the code below I'm gonna use America/Sao_Paulo (the timezone where I live, which has DST changes every year), but you can replace it with the timezone you want.

The code below shows you how to check if a date is in DST and find the next date when a DST change will occur. So, if you have a start and end dates and want to know if both are in within a DST change, you can check if both are in DST or not and also find the next and previous DST changes (and check if the dates are between those changes - it's not clear to me how your check should be done).


Also be aware that Joda-Time is in maintainance mode and is being replaced by the new APIs, so I don't recommend start a new project with it. Even in joda's website it says: "Note that Joda-Time is considered to be a largely "finished" project. No major enhancements are planned. If using Java SE 8, please migrate to java.time (JSR-310).".


Joda-Time

You can use the org.joda.time.DateTimeZone class. To know all the available timezones, call DateTimeZone.getAvailableIDs().

The code below checks if a date is in DST and also finds the next date when a DST change will occur:

// create timezone object
DateTimeZone zone = DateTimeZone.forID("America/Sao_Paulo");

// check if a date is in DST
DateTime inDst = new DateTime(2017, 1, 1, 10, 0, zone);
// isStandardOffset returns false (it's in DST)
System.out.println(zone.isStandardOffset(inDst.getMillis()));
// check when it'll be the next DST change
DateTime nextDstChange = new DateTime(zone.nextTransition(inDst.getMillis()), zone);
System.out.println(nextDstChange); // 2017-02-18T23:00:00.000-03:00

// check if a date is in DST
DateTime noDst = new DateTime(2017, 6, 18, 10, 0, zone);
// isStandardOffset returns true (it's not in DST)
System.out.println(zone.isStandardOffset(noDst.getMillis()));
// check when it'll be the next DST change
nextDstChange = new DateTime(zone.nextTransition(noDst.getMillis()), zone);
System.out.println(nextDstChange); // 2017-10-15T01:00:00.000-02:00

If you want to find the previous DST change (instead of the next), call previousTransition() instead of nextTransition().


Java new Date/Time API

If you're using Java 8, the new java.time API already comes natively.

If you're using Java <= 7, you can use the ThreeTen Backport, a great backport for Java 8's new date/time classes. And for Android, there's the ThreeTenABP (more on how to use it here).

The code below works for both. The only difference is the package names (in Java 8 is java.time and in ThreeTen Backport (or Android's ThreeTenABP) is org.threeten.bp), but the classes and methods names are the same.

The code is very similar to Joda-Time's version. The main differences:

  • While Joda-Time has isStandardOffset() to check if the date is not in DST, the new API has isDaylightSavings() to check if the date is in DST.
  • Joda-Time provides the methods directly in the DateTimeZone class, but the new API has a dedicated class to its DST rules (java.time.zone.ZoneRules)
  • The methods for next and previous transitions return a java.time.zone.ZoneOffsetTransition instead of directly returning a date (this object provides more information about the DST change, as showed below).

Despite all those differences, the idea is very similar:

// create timezone object
ZoneId zone = ZoneId.of("America/Sao_Paulo");
// get the timezone's rules
ZoneRules rules = zone.getRules();

// check if a date is in DST
ZonedDateTime inDST = ZonedDateTime.of(2017, 1, 1, 10, 0, 0, 0, zone);
// isDaylightSavings returns true (it's in DST)
System.out.println(rules.isDaylightSavings(inDST.toInstant()));
// check when it'll be the next DST change
ZoneOffsetTransition nextTransition = rules.nextTransition(inDST.toInstant());
// getInstant() returns the UTC instant; atZone converts to the specified timezone
System.out.println(nextTransition.getInstant().atZone(zone)); // 2017-02-18T23:00-03:00[America/Sao_Paulo]

// you can also check the date/time and offset before and after the DST change
// in this case, at 19/02/2017, the clock is moved 1 hour back (from midnight to 11 PM)
ZonedDateTime beforeDST = ZonedDateTime.of(nextTransition.getDateTimeBefore(), nextTransition.getOffsetBefore());
System.out.println(beforeDST); // 2017-02-19T00:00-02:00
ZonedDateTime afterDST = ZonedDateTime.of(nextTransition.getDateTimeAfter(), nextTransition.getOffsetAfter());
System.out.println(afterDST); // 2017-02-18T23:00-03:00

// check if a date is in DST
ZonedDateTime noDST = ZonedDateTime.of(2017, 6, 1, 10, 0, 0, 0, zone);
// isDaylightSavings returns false (it's not in DST)
System.out.println(rules.isDaylightSavings(noDST.toInstant()));
// check when it'll be the next DST change
nextTransition = rules.nextTransition(noDST.toInstant());
// getInstant() returns the UTC instant; atZone converts to the specified timezone
System.out.println(nextTransition.getInstant().atZone(zone)); // 2017-10-15T01:00-02:00[America/Sao_Paulo]

// you can also check the date/time and offset before and after the DST change
// in this case, at 15/10/2017, the clock is moved 1 hour forward (from midnight to 1 AM)
beforeDST = ZonedDateTime.of(nextTransition.getDateTimeBefore(), nextTransition.getOffsetBefore());
System.out.println(beforeDST); // 2017-10-15T00:00-03:00
afterDST = ZonedDateTime.of(nextTransition.getDateTimeAfter(), nextTransition.getOffsetAfter());
System.out.println(afterDST); // 2017-10-15T01:00-02:00

If you want to find the previous DST change instead of the next, you can call rules.previousTransition() instead of rules.nextTransition().

Question:

According to the JodaTime Javadoc, plusHours and plusDays both handle DST adjustments when adding time to a date. However, from my tests adding a days worth of hours vs adding 1 day gives a different result. Can someone explain to me why the following code gives the output below?

Code:

SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
Date date = format.parse("2014-11-01T02:00:00.000-0700");

System.out.println("origDate:  " + date);
System.out.println("plusDays:  " + new DateTime(date).plusDays(1).toDate());
System.out.println("plusHours: " + new DateTime(date).plusHours(24).toDate());

Output:

origDate:  Sat Nov 01 02:00:00 PDT 2014
plusDays:  Sun Nov 02 02:00:00 PST 2014
plusHours: Sun Nov 02 01:00:00 PST 2014

Answer:

Not all local days have 24 hours. Assuming you are working in the US Pacific time zone, there are 25 hours on that particular day, due to the fall-back daylight saving time transition.

Calling plusHours(24) adds an exact duration of 24 hours elapsed time.

Calling plusDays(1) increments the calendar day, even if the day is not exactly 24 hours.

Question:

I'm getting strange behavior from Joda Time when trying to get the seconds since midnight on a DST switch day.

My code:

public static void main(String[] args) {

    DateTimeZone timeZone = DateTimeZone.getDefault();
    System.out.println(timeZone);

    DateTimeFormatter dateFormatter = DateTimeFormat.shortDate();
    DateTimeFormatter dateTimeFormatter = DateTimeFormat.fullDateTime();
    LocalDate date = new LocalDate(2016, 10, 29);
    for (int i=0; i<3; i++) {

        System.out.println();
        System.out.println(dateFormatter.print(date) + ":");

        {
            DateTime instant = new DateTime(date.getYear(), date.getMonthOfYear(), date.getDayOfMonth(), 1, 0, 0, timeZone);
            System.out.println(dateTimeFormatter.print(instant) + " // " + instant.getSecondOfDay() + " // " + instant.getZone().getOffset(instant));
        }

        {
            DateTime instant = new DateTime(date.getYear(), date.getMonthOfYear(), date.getDayOfMonth(), 10, 0, 0, timeZone);
            System.out.println(dateTimeFormatter.print(instant) + " // " + instant.getSecondOfDay() + " // " + instant.getZone().getOffset(instant));
        }

        date = date.plusDays(1);
    }
}

Output:

Europe/Berlin

29.10.16:
Samstag, 29. Oktober 2016 01:00 Uhr MESZ // 3600 // 7200000
Samstag, 29. Oktober 2016 10:00 Uhr MESZ // 36000 // 7200000

30.10.16:
Sonntag, 30. Oktober 2016 01:00 Uhr MESZ // 3600 // 7200000
Sonntag, 30. Oktober 2016 10:00 Uhr MEZ // 36000 // 3600000

31.10.16:
Montag, 31. Oktober 2016 01:00 Uhr MEZ // 3600 // 3600000
Montag, 31. Oktober 2016 10:00 Uhr MEZ // 36000 // 3600000

(translation from German: MEZ = CET, MESZ = CEST)

As you can see, Oct 30 is the DST switch day. The switch occurs between 2 and 3 AM. If I'm not mistaken, summer time is UTC+2 and winter time is UTC+1, so at 3 AM, clocks are turned back to 2 AM.

Now this obviously implies an overlap for 2-3, but my test uses 1 AM and 10 AM, which should be unambiguous. I'd expect the instant at Oct 30, 1 AM CEST to be 3600 seconds since midnight, and the instant at Oct 30, 10 AM CET to be 39600 seconds since midnight (11 hours). But getSecondOfDay() returns 10 hours since midnight.

The only implication I could imagine (besides a stupid error) is that getSecondOfDay() does not actually return the seconds since midnight, but something else. However, every example I found on the net seems to imply that it does return the number of seconds since midnight. There's no DST-related redefinition of "midnight" that I know of, either.

Why do I get these results? What is getSecondOfDay() meant to do in this situation? What else should I do to get the number of seconds since midnight?


Answer:

The field SECOND_OF_DAY is inherently based on the local timeline. This means, it does not count the physically elapsed SI- or POSIX-seconds since midnight but only represents the nominal count of clock strikes as displayed on a wall clock which always has 24 hours/strikes per day-night-cycle ignoring any DST-issue. Or another description: Just imagine that this field displays the position of the second hand on the clock face.

But you can determine the physically elapsed seconds in another way. The class DateTime is an instant, so you can first determine the instant for midnight and then the second instant for let's say at 10 AM. Then you apply this expression:

int elapsedSecondsSinceMidnight =
  Seconds.secondsBetween(instantMidnight, instant10AM).getSeconds();

Note that Joda-Time sometimes causes an exception to create an instant for local midnight if this local time does not exist due to DST-switch (like in Brazil when moving to summer time). So please handle the creation of instants in Joda-Time with care.

Question:

Currently, I used method to calculate the time with respect to the timezone, as below

DateTimeFormatter fmt = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss-HH:mm").withZone(DateTimeZone.forID("America/Denver"));

DateTime f = fmt.parseDateTime("2014-09-20T20:00:33+07:00");

but it also considers the daylight saving time in account to calculate the time. So, Anyone has any method/workaround to calculate the time without considering the DST.(i.e. I don‘t want to add the DST in my calculated time).


Answer:

I am still wondering why you want to calculate local standard time which is of no practical relevance IMHO but okay, here you go:

DateTimeZone dtz = DateTimeZone.forID("America/Denver");
DateTimeFormatter fmt = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ssZ").withZone(dtz);

DateTime f = fmt.parseDateTime("2014-09-20T20:00:33+07:00");
System.out.println("JODA-DT=" + f); // 2014-09-20T07:00:33.000-06:00
System.out.println("STD-OFFSET=" + dtz.getStandardOffset(f.getMillis()) / 3600000); // -7
f = f.withZone(DateTimeZone.forOffsetMillis(dtz.getStandardOffset(f.getMillis())));
System.out.println("Standard-time=" + f); // 2014-09-20T06:00:33.000-07:00

Note: I have first corrected your wrong format pattern.

Question:

Is there a way to configure Daylight Saving transition time in Joda-Time?

For example, the spring transition in California will begin at 2:00 am on 3/11.

I'd like to configure Joda-Time (my app is using) so that the transition begins at a specific time (e.g 4pm on 2/21), so that I can test some logics in my app depending on the Daylight Saving per current time.


Answer:

You can extend org.joda.time.DateTimeZone:

public class FakeTimeZone extends DateTimeZone {

    private DateTime dstStart;

    private DateTimeZone zone;

    protected FakeTimeZone(String id) {
        super(id);
        this.zone = DateTimeZone.forID(id);
        // DST starts at 21/Feb/2018, at 4 PM 
        this.dstStart = new DateTime(2018, 2, 21, 16, 0, 0, 0, zone);
    }

    @Override
    public String getNameKey(long instant) {
        return this.getID();
    }

    @Override
    public int getOffset(long instant) {
        // check if it's in DST
        if (dstStart.getMillis() <= instant) {
            // DST, offset is 1 hour ahead the standard - value must be in milliseconds
            return this.zone.getStandardOffset(instant) + 3600000;
        }
        return this.zone.getStandardOffset(instant);
    }

    @Override
    public int getStandardOffset(long instant) {
        return this.zone.getStandardOffset(instant);
    }

    @Override
    public boolean isFixed() {
        return false;
    }

    @Override
    public long nextTransition(long instant) {
        if (instant < dstStart.getMillis()) {
            return dstStart.getMillis();
        }
        return instant;
    }

    @Override
    public long previousTransition(long instant) {
        if (instant > dstStart.getMillis()) {
            return dstStart.getMillis();
        }
        return instant;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj instanceof FakeTimeZone) {
            return getID().equals(((FakeTimeZone) obj).getID());
        }
        return false;
    }
}

It uses the same offsets of the timezone you pass in the constructor, the only difference is the DST transitions - in this case, I'm using just one and ignoring the rest - but you can change the code above and make a more complex logic to consider all the other transitions + your custom transition.

Then you just use it like this:

// 1 hour before DST starts
DateTime d = new DateTime(2018, 2, 21, 15, 0, 0, 0, new FakeTimeZone("America/Los_Angeles"));

// This prints 2018-02-21T15:00:00.000-08:00 (standard offset)
System.out.println(d);

// 1 hour later, DST is in effect, it prints 2018-02-21T17:00:00.000-07:00
System.out.println(d.plusHours(1));

Note that the first date is 3PM (one hour before DST starts), so the offset is the standard (-08:00).

Then, and 1 hour later, it was supposed to be 4PM, but due to DST start, it's shifted to 5PM and the offset changes to -07:00.

Question:

I need to create a new Java Date based on two strings provided by a user: a date (e.g. "1.1.2015"), and a time of day (e.g. "23:00"). First the user enters the date, which is sent to the server and parsed into a Date (time of day is set to midnight in the user's time zone). After this, the user enters the time of day, which is sent to the server, and a new Date needs to be created, combining the date from the first Date instance and time of day from the new user input.

Example: Say the server's time zone is UTC, and the user's time zone is UTC-2. The user enters "1.1.2015" into the date field, which is interpreted in the server as 2:00 1.1.2015 UTC (1st of January at 2:00 AM in UTC, which is midnight in the user's time zone). The user then enters "23:00" into the time field (24-hour clock). This needs to be interpreted in the server as 1:00 2.1.2015 UTC (2nd of January at 1:00 AM).

We use Apache Commons FastDateFormat for transforming strings to Dates and vice versa, and Joda Time for date manipulation. The result needs to be a plain old Java Date. I've tried to combine the existing Date instance and the time of day input from the user like this:

Date datePart= ...; // The date parsed from the first user input
FastDateFormat timeFormat = ...;
DateTimeZone userTimeZone = DateTimeZone.forTimeZone(timeFormat.getTimeZone());
String userTimeInput = ...; // The time of day from the user

MutableDateTime dateTime = new MutableDateTime(datePart, DateTimeZone.UTC);
Date newTime = timeFormat.parse(userTimeInput);
dateTime.setTime(new DateTime(newTime, DateTimeZone.UTC));

// Determine if the date part needs to be changed due to time zone adjustment
long timeZoneOffset = userTimeZone.getOffset(dateTime);
long newMillisOfDay = dateTime.getMillisOfDay();
if (newMillisOfDay + timeZoneOffset > 24 * 60 * 60 * 1000) {
    dateTime.addDays(-1);
} else if (newMillisOfDay + timeZoneOffset < 0) {
    dateTime.addDays(1);
}

Date newServerDate = dateTime.toDate();

Changing the time of day of an existing Date like this is a bit problematic. The above doesn't work; if the user changes the time of day multiple times, the +/-1 day adjustment is potentially made every time. Also, the above code doesn't take DST into account. If datePart is in DST, the times entered by our example user should be treated as being in UTC-1. When using FastDateFormat and only parsing the time of day, the date is set to the epoch, meaning that the time entered by the user will always be treated as being in UTC-2. This will cause a one hour offset in the result.

How to adjust the Date in the server based on the given time of day and properly take the time zone and DST into account?


Answer:

I solved this by using the suggestions by Jon in the comments. I still have to end up with a Date, so I couldn't start using Joda Time for everything. I did however move away from FastDateFormat and MutableDateTime for this particular use case. Thanks for the tips! The solution looks like this:

Date datePart= ...;           // The date parsed from the first user input
String userTimeInput = ...;   // The time of day from the user
Locale userLocale = ...;
DateTimeZone userTimeZone = ...;

DateTime dateInUserTimeZone = new DateTime(datePart, userTimeZone);
DateTimeFormatter formatter = DateTimeFormat.shortTime().withLocale(userLocale);
LocalTime time = formatter.parseLocalTime(userTimeInput);

Date newDate = dateInUserTimeZone.withTime(time.getHourOfDay(), time.getMinuteOfHour(),
        time.getSecondOfMinute(), time.getMillisOfSecond()).toDate();