Йода время делает преобразование времени "слишком рано" - PullRequest
5 голосов
/ 06 ноября 2010

У нас есть приложение, в котором время имеет решающее значение. Мы используем joda для преобразования времени и хранения всех данных в формате UTC. Мы были в производстве некоторое время, и все было идеально, НО ...

Теперь мы замечаем, что события, происходящие за несколько часов до изменения времени, уже слишком рано преобразуются! Фактически время UTC, сохраненное в базе данных, отключено на час.

Вот пример. Мое событие происходит в 6.11.2010 в 9 вечера по тихоокеанскому времени и обычно сохраняется как 11.07.2010 в 4 часа утра. Однако, поскольку летнее время закончилось 7-го числа (предположительно, в 2 часа ночи), это время сдвигается и сохраняется как 11/7/2010 @ 5 утра.

Нам нужно, чтобы изменение DST не регистрировалось до тех пор, пока оно фактически не произойдет в области PST, в 2:00 по тихоокеанскому времени. Я предположил, что joda справится с этим, тем более, что это намного лучше, чем стандартная функциональность java.

Любая ваша обратная связь будет полезна, особенно если вы сможете получить ее до завтрашнего дня! После этого это будет академическим, но все же полезным обсуждением.

Вот часть кода, который мы используем для изменения часового пояса и получения результата в виде обычного объекта даты Java.

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

Редактировать: Пример кода для комментариев ниже

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

Полный пример модульного теста

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

}

1 Ответ

10 голосов
/ 06 ноября 2010

Ваш код в основном сломан, потому что объект Date нельзя «преобразовать» между часовыми поясами - он представляет собой момент времени.getTime() возвращает время в миллисекундах с эпохи UTC Unix .Date не зависит от часового пояса, поэтому идея преобразования экземпляра Date из одного часового пояса в другой не имеет смысла.Это немного похоже на преобразование int из «базы 10» в «базу 16» - базы имеют смысл только тогда, когда вы думаете о представлении в цифрах, а не о фундаментальном числе.

Вы должны использовать LocalDateTime для представления даты / времени без фиксированного часового пояса, или DateTime для представления даты / времени с конкретным часовым поясом, или Instant для представления того же рода концепции, что и java.util.Date.

Как только вы используете соответствующие типы, я уверен, что у вас не возникнет никаких проблем.

РЕДАКТИРОВАТЬ: Если ваш фактический код использует 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 ...

...