Joda's time makes the temporary conversion "too early"

We have an application in which time is critical. We use joda to convert time and store all data in UTC. We were in production for a while, and everything was perfect, BUT ...

Now we notice that the events that occurred several hours before the time change were already converted too soon! In fact, the UTC time stored in the database is disabled for an hour.

Here is an example. My event takes place on 11/6/2010 @ 9pm PDT and will usually be saved as 11/7/2010 @ 4am. However, since Daylight Savings Time ended on the 7th (presumably at 2 a.m.), this time it was changed and saved as 11/7/2010 @ 5 a.m.

We need to ensure that the DST change is not recorded until it actually occurs in the PST region, at 2 a.m. PST. I suggested that joda handle this, especially since it advertises significantly improved over standard Java features.

Any feedback you get will be helpful, especially if you can get it before tomorrow's change! After that, it will be an academic, but still useful discussion.

Here are some of the code we use to perform the time zone change, and get the result as a regular java date object.

public Date convertToTimeZone(Date dt, TimeZone from, TimeZone to){
    DateTimeZone tzFrom = DateTimeZone.forTimeZone(from);
    DateTimeZone tzTo = DateTimeZone.forTimeZone(to);

    Date utc = new Date(tzFrom.convertLocalToUTC(dt.getTime(), false));
    Date convertedTime = new Date(tzTo.convertUTCToLocal(utc.getTime()));
    return convertedTime;
}

Edit: sample code for comments below

public Date convert(Date dt, TimeZone from, TimeZone to) {
    long fromOffset = from.getOffset(dt.getTime());
    long toOffset = to.getOffset(dt.getTime());

    long convertedTime = dt.getTime() - (fromOffset - toOffset);
    return new Date(convertedTime);
}

Complete Unit Test Example

package com.test.time;

import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;

import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.Instant;
import org.junit.Before;
import org.junit.Test;

public class TimeTest {
Calendar nov6;
Calendar nov1;
Calendar nov12;

@Before
public void doBefore() {
    // November 1st 2010, 9:00pm (DST is active)
    nov1 = Calendar.getInstance();
    nov1.setTimeZone(TimeZone.getTimeZone("US/Arizona"));
    nov1.set(Calendar.HOUR_OF_DAY, 21);
    nov1.set(Calendar.MINUTE, 0);
    nov1.set(Calendar.SECOND, 0);
    nov1.set(Calendar.YEAR, 2010);
    nov1.set(Calendar.MONTH, 10); // November
    nov1.set(Calendar.DATE, 1);

    // November 6st 2010, 9:00pm (DST is still active until early AM november 7th)
    nov6 = Calendar.getInstance();
    nov6.setTimeZone(TimeZone.getTimeZone("US/Arizona"));
    nov6.set(Calendar.HOUR_OF_DAY, 21);
    nov6.set(Calendar.MINUTE, 0);
    nov6.set(Calendar.SECOND, 0);
    nov6.set(Calendar.YEAR, 2010);
    nov6.set(Calendar.MONTH, 10); // November
    nov6.set(Calendar.DATE, 6);

    // November 12th 2010, 9:00pm (DST has ended)
    nov12 = Calendar.getInstance();
    nov12.setTimeZone(TimeZone.getTimeZone("US/Arizona"));
    nov12.set(Calendar.HOUR_OF_DAY, 21);
    nov12.set(Calendar.MINUTE, 0);
    nov12.set(Calendar.SECOND, 0);
    nov12.set(Calendar.YEAR, 2010);
    nov12.set(Calendar.MONTH, 10); // November
    nov12.set(Calendar.DATE, 12);
}

@Test
public void test1() {
    //      System.out.println("test1");
    timeTestJava(nov1.getTime(), "equivalent", "US/Arizona", "US/Pacific");
    timeTestJodaOld(nov1.getTime(), "equivalent", "US/Arizona", "US/Pacific");
    timeTestJodaNew(nov1.getTime(), "equivalent", "US/Arizona", "US/Pacific");

    timeTestJava(nov6.getTime(), "equivalent", "US/Arizona", "US/Pacific");
    timeTestJodaOld(nov6.getTime(), "equivalent", "US/Arizona", "US/Pacific");
    timeTestJodaNew(nov6.getTime(), "equivalent", "US/Arizona", "US/Pacific");

    timeTestJava(nov12.getTime(), "minus1", "US/Arizona", "US/Pacific");
    timeTestJodaOld(nov12.getTime(), "minus1", "US/Arizona", "US/Pacific");
    timeTestJodaNew(nov12.getTime(), "minus1", "US/Arizona", "US/Pacific");
}

private void timeTestJodaOld(Date startTime, String text, String from, String to) {
    System.out.println("joda_old: " + startTime);
    Date output = convertJodaOld(startTime, TimeZone.getTimeZone(from), TimeZone.getTimeZone(to));
    System.out.println(text + ": " + output + "\n");
}

private void timeTestJodaNew(Date startTime, String text, String from, String to) {
    System.out.println("joda_new: " + startTime);
    Date output = convertJodaNew(startTime, TimeZone.getTimeZone(from), TimeZone.getTimeZone(to));
    System.out.println(text + ": " + output + "\n");
}

private void timeTestJava(Date startTime, String text, String from, String to) {
    System.out.println("java: " + startTime);
    Date output = convertJava(startTime, TimeZone.getTimeZone(from), TimeZone.getTimeZone(to));
    System.out.println(text + ": " + output + "\n");
}

// Initial Joda implementation, works before and after DST change, but not during the period from 2am-7am UTC on the day of the change
public Date convertJodaOld(Date dt, TimeZone from, TimeZone to) {
    DateTimeZone tzFrom = DateTimeZone.forTimeZone(from);
    DateTimeZone tzTo = DateTimeZone.forTimeZone(to);

    Date utc = new Date(tzFrom.convertLocalToUTC(dt.getTime(), false));
    Date convertedTime = new Date(tzTo.convertUTCToLocal(utc.getTime()));
    return convertedTime;
}

// New attempt at joda implementation, doesn't work after DST (winter)
public Date convertJodaNew(Date dt, TimeZone from, TimeZone to) {
    Instant utcInstant = new Instant(dt.getTime());
    DateTime datetime = new DateTime(utcInstant);

    datetime.withZone(DateTimeZone.forID(to.getID()));
    return datetime.toDate();
}

// Java implementation.  Works.
public Date convertJava(Date dt, TimeZone from, TimeZone to) {
    long fromOffset = from.getOffset(dt.getTime());
    long toOffset = to.getOffset(dt.getTime());

    long convertedTime = dt.getTime() - (fromOffset - toOffset);
    return new Date(convertedTime);
}

}

+5
source share
1 answer

, Date "" - . getTime() UTC Unix epoch. A Date , Date . int "base 10" "base 16". , , .

LocalDateTime / DateTime / Instant , java.util.Date.

, , .

EDIT: Calendar , , UTC. calendar.getTime(), Date.

:

    // Display all Date values as UTC for convenience
    TimeZone.setDefault(TimeZone.getTimeZone("UTC"));

    // November 6st 2010, 9:00pm (DST is still active until
    // early AM november 7th)
    Calendar nov6 = Calendar.getInstance();
    nov6.setTimeZone(TimeZone.getTimeZone("US/Arizona"));
    nov6.set(Calendar.HOUR_OF_DAY, 21);
    nov6.set(Calendar.MINUTE, 0);
    nov6.set(Calendar.SECOND, 0);
    nov6.set(Calendar.YEAR, 2010);
    nov6.set(Calendar.MONTH, 10); // November
    nov6.set(Calendar.DATE, 6);

    // Prints Sun Nov 07 04:00:00 UTC 2010 which is correct
    System.out.println("Local Nov6 = " + nov6.getTime());

, / / , UTC...

+10

All Articles