views:

671

answers:

3

I have a milliseconds-since-local-epoch timestamp that I'd like to convert into a milliseconds-since-UTC-epoch timestamp. From a quick glance through the docs it looks like something like this would work:

int offset = TimeZone.getDefault().getRawOffset();
long newTime = oldTime - offset;

Is there a better way to do this?

+1  A: 

No, that definitely won't work - it doesn't take DST into account. You can't just use getOffset(oldTime) either, as the DST may have changed between the two...

You could use getOffset(oldTime) to get an initial guess at the timestamp, then check getOffset(utcTime) to see whether they're the same or not. It gets fun, basically.

Joda Time should support this using DateTimeZone.getOffsetFromLocal but that's slightly broken (IMO) around DST transitions.

All of this really depends on what you mean by "milliseconds since local epoch". If you really mean elapsed milliseconds since local 1970, you could just find out the offset at that date, and apply that regardless. Typically (IME) a "local" millis value doesn't mean quite that though - it means "the number of millis to get to a particular date and time (e.g. April 9th 2010, 18:06pm) in UTC, but in respect of a different time zone". In other words, it can represent ambiguous or impossible date/time combinations based on DST transitions.

Jon Skeet
But I think you could use `getOffset(0L)` All that matters is the time difference back then, at the start of the epoch - and I hope, that DST transition didn't occur on Jan 1st.
Chris Lercher
A: 

Use a Calendar to get what the offset was at the local Epoch, then add that to the local-epoch timestamp.

public static long getLocalToUtcDelta() {
    Calendar local = Calendar.getInstance();
    local.clear();
    local.set(1970, Calendar.JANUARY, 1, 0, 0, 0);
    return local.getTimeInMillis();
}

public static long converLocalTimeToUtcTime(long timeSinceLocalEpoch) {
    return timeSinceLocalEpoch + getLocalToUtcDelta();
}
dave
This doesn't account for DST at all.
Quartz
@Quartz - it doesn't have to, since we're dealing with deltas from the Epoch. Do you have an example (zone and timeSinceLocalEpoch) where this fails?
dave
+1  A: 

Sadly, this seems to be the best way to do this:

public static Date convertLocalTimestamp(long millis)
{
    TimeZone tz = TimeZone.getDefault();
    Calendar c = Calendar.getInstance(tz);
    long localMillis = millis;
    int offset, time;

    c.set(1970, Calendar.JANUARY, 1, 0, 0, 0);

    // Add milliseconds
    while (localMillis > Integer.MAX_VALUE)
    {
        c.add(Calendar.MILLISECOND, Integer.MAX_VALUE);
        localMillis -= Integer.MAX_VALUE;
    }
    c.add(Calendar.MILLISECOND, (int)localMillis);

    // Stupidly, the Calendar will give us the wrong result if we use getTime() directly.
    // Instead, we calculate the offset and do the math ourselves.
    time = c.get(Calendar.MILLISECOND);
    time += c.get(Calendar.SECOND) * 1000;
    time += c.get(Calendar.MINUTE) * 60 * 1000;
    time += c.get(Calendar.HOUR_OF_DAY) * 60 * 60 * 1000;
    offset = tz.getOffset(c.get(Calendar.ERA), c.get(Calendar.YEAR), c.get(Calendar.MONTH), c.get(Calendar.DAY_OF_MONTH), c.get(Calendar.DAY_OF_WEEK), time);

    return new Date(millis - offset);
}

(I know that this is several months past post date, but it's a problem that is very useful to solve when working with text messages on Android. dave's answer is wrong.)

Quartz