Неверное преобразование времени в Joda с часовым поясом не в летнее время - PullRequest
4 голосов
/ 01 марта 2011

Я сталкиваюсь с Джодой, и считаю, что это может быть ошибкой. Однако вполне возможно, что я ошибаюсь при использовании библиотеки, поэтому, пожалуйста, оставьте свой отзыв.

Мы использовали joda в производстве для преобразования времени между форматом хранения нашей базы данных (UTC) и настройкой часового пояса пользователя. Все сотрудники, использующие эту систему, работают из Аризоны, которая не соблюдает летнее время. Помимо прочего, эта система отслеживает время начала спортивных соревнований по всей стране.

Йода отлично сработал для нас, пока мы не заметили, что за день до смены времени мы получили некоторые неверные результаты. Мы обнаружили, что joda, похоже, переходит на летнее время в полночь UTC, а не в подходящее время для конкретного часового пояса. Кроме того, эта проблема возникает только при преобразовании времени между состоянием наблюдения DST в состояние без DST, такое как Аризона.

Я создал полный тестовый пример, иллюстрирующий эту проблему. Как вы увидите, joda предоставляет ожидаемый результат для всех тестовых случаев США / Восток -> США / Тихий океан. Для США / Аризона -> США / Тихий океан, это работает в течение года до изменения ноября и после него. Однако в день изменения времени (6 ноября) время неверно. Также может быть проблема с изменением времени марша, хотя я еще не проверил ее полностью.

Вот вывод из предоставленного теста ( запись за 6 ноября иллюстрирует ошибку ):

=== November 1st, Expected Result (0 hour) ===
java:
Converting 2010-11-01 09:00 from US/Arizona to US/Pacific.
Result: 2010-11-01 09:00. Change (0 hour).

joda:
Converting 2010-11-01 09:00 from US/Arizona to US/Pacific.
Result: 2010-11-01 09:00. Change (0 hour).

=======================================

=== November 6th, Expected Result (0 hour) ===
java:
Converting 2010-11-06 09:00 from US/Arizona to US/Pacific.
Result: 2010-11-06 09:00. Change (0 hour).

joda:
Converting 2010-11-06 09:00 from US/Arizona to US/Pacific.
Result: 2010-11-06 08:00. Change (-1 hour).

=======================================

=== November 12th, Expected Result (-1 hour) ===
java:
Converting 2010-11-12 09:00 from US/Arizona to US/Pacific.
Result: 2010-11-12 08:00. Change (-1 hour).

joda:
Converting 2010-11-12 09:00 from US/Arizona to US/Pacific.
Result: 2010-11-12 08:00. Change (-1 hour).

=======================================

=== March 12th, Expected Result (-1 hour) ===
java:
Converting 2010-03-12 09:00 from US/Arizona to US/Pacific.
Result: 2010-03-12 08:00. Change (-1 hour).

joda:
Converting 2010-03-12 09:00 from US/Arizona to US/Pacific.
Result: 2010-03-12 08:00. Change (-1 hour).

=======================================

=== March 14th, Expected Result (0 hour) ===
java:
Converting 2010-03-14 09:00 from US/Arizona to US/Pacific.
Result: 2010-03-14 09:00. Change (0 hour).

joda:
Converting 2010-03-14 09:00 from US/Arizona to US/Pacific.
Result: 2010-03-14 09:00. Change (0 hour).

=======================================

=== November 1st, Expected Result (-3 hour) ===
java:
Converting 2010-11-01 09:00 from US/Eastern to US/Pacific.
Result: 2010-11-01 06:00. Change (-3 hour).

joda:
Converting 2010-11-01 09:00 from US/Eastern to US/Pacific.
Result: 2010-11-01 06:00. Change (-3 hour).

=======================================

=== November 6th, Expected Result (-3 hour) ===
java:
Converting 2010-11-06 09:00 from US/Eastern to US/Pacific.
Result: 2010-11-06 06:00. Change (-3 hour).

joda:
Converting 2010-11-06 09:00 from US/Eastern to US/Pacific.
Result: 2010-11-06 06:00. Change (-3 hour).

=======================================

=== November 12th, Expected Result (-3 hour) ===
java:
Converting 2010-11-12 09:00 from US/Eastern to US/Pacific.
Result: 2010-11-12 06:00. Change (-3 hour).

joda:
Converting 2010-11-12 09:00 from US/Eastern to US/Pacific.
Result: 2010-11-12 06:00. Change (-3 hour).

=======================================

=== March 12th, Expected Result (-3 hour) ===
java:
Converting 2010-03-12 09:00 from US/Eastern to US/Pacific.
Result: 2010-03-12 06:00. Change (-3 hour).

joda:
Converting 2010-03-12 09:00 from US/Eastern to US/Pacific.
Result: 2010-03-12 06:00. Change (-3 hour).

=======================================

=== March 14th, Expected Result (-3 hour) ===
java:
Converting 2010-03-14 09:00 from US/Eastern to US/Pacific.
Result: 2010-03-14 06:00. Change (-3 hour).

joda:
Converting 2010-03-14 09:00 from US/Eastern to US/Pacific.
Result: 2010-03-14 06:00. Change (-3 hour).

=======================================

А вот полный тестовый пример:

package com.test.time;

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

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

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

    Calendar mar12;
    Calendar mar14;

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

        // March 12th 2011, 9:00pm (DST has ended, will begin early a.m. march
        // 13th)
        mar12 = Calendar.getInstance();
        mar12.setTimeZone(TimeZone.getTimeZone("US/Arizona"));
        mar12.set(Calendar.HOUR_OF_DAY, 21);
        mar12.set(Calendar.MINUTE, 0);
        mar12.set(Calendar.SECOND, 0);
        mar12.set(Calendar.YEAR, 2010);
        mar12.set(Calendar.MONTH, 2); // March
        mar12.set(Calendar.DATE, 12);

        // March 14th 2011, 9:00pm (DST has started)
        mar14 = Calendar.getInstance();
        mar14.setTimeZone(TimeZone.getTimeZone("US/Arizona"));
        mar14.set(Calendar.HOUR_OF_DAY, 21);
        mar14.set(Calendar.MINUTE, 0);
        mar14.set(Calendar.SECOND, 0);
        mar14.set(Calendar.YEAR, 2010);
        mar14.set(Calendar.MONTH, 2); // March
        mar14.set(Calendar.DATE, 14);
    }

    @Test
    public void testArizonaToPacific() {
        System.out.println("=== November 1st, Expected Result (0 hour) ===");
        timeTestJava(nov1.getTime(), "US/Arizona", "US/Pacific");
        timeTestJoda(nov1.getTime(), "US/Arizona", "US/Pacific");
        System.out.println("=======================================\n");

        System.out.println("=== November 6th, Expected Result (0 hour) ===");
        timeTestJava(nov6.getTime(), "US/Arizona", "US/Pacific");
        timeTestJoda(nov6.getTime(), "US/Arizona", "US/Pacific");
        System.out.println("=======================================\n");

        System.out.println("=== November 12th, Expected Result (-1 hour) ===");
        timeTestJava(nov12.getTime(), "US/Arizona", "US/Pacific");
        timeTestJoda(nov12.getTime(), "US/Arizona", "US/Pacific");
        System.out.println("=======================================\n");

        System.out.println("=== March 12th, Expected Result (-1 hour) ===");
        timeTestJava(mar12.getTime(), "US/Arizona", "US/Pacific");
        timeTestJoda(mar12.getTime(), "US/Arizona", "US/Pacific");
        System.out.println("=======================================\n");

        System.out.println("=== March 14th, Expected Result (0 hour) ===");
        timeTestJava(mar14.getTime(), "US/Arizona", "US/Pacific");
        timeTestJoda(mar14.getTime(), "US/Arizona", "US/Pacific");
        System.out.println("=======================================\n");
    }

    @Test
    public void testEasternToPacific() {
        System.out.println("=== November 1st, Expected Result (-3 hour) ===");
        timeTestJava(nov1.getTime(), "US/Eastern", "US/Pacific");
        timeTestJoda(nov1.getTime(), "US/Eastern", "US/Pacific");
        System.out.println("=======================================\n");

        System.out.println("=== November 6th, Expected Result (-3 hour) ===");
        timeTestJava(nov6.getTime(), "US/Eastern", "US/Pacific");
        timeTestJoda(nov6.getTime(), "US/Eastern", "US/Pacific");
        System.out.println("=======================================\n");

        System.out.println("=== November 12th, Expected Result (-3 hour) ===");
        timeTestJava(nov12.getTime(), "US/Eastern", "US/Pacific");
        timeTestJoda(nov12.getTime(), "US/Eastern", "US/Pacific");
        System.out.println("=======================================\n");

        System.out.println("=== March 12th, Expected Result (-3 hour) ===");
        timeTestJava(mar12.getTime(), "US/Eastern", "US/Pacific");
        timeTestJoda(mar12.getTime(), "US/Eastern", "US/Pacific");
        System.out.println("=======================================\n");

        System.out.println("=== March 14th, Expected Result (-3 hour) ===");
        timeTestJava(mar14.getTime(), "US/Eastern", "US/Pacific");
        timeTestJoda(mar14.getTime(), "US/Eastern", "US/Pacific");
        System.out.println("=======================================\n");
    }

    // print some output from the test
    private void print(Date startTime, String text, String from, String to,
            Date output) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm");

        System.out.println(text + ":");
        System.out.println("Converting " + sdf.format(startTime) + " from "
                + from + " to " + to + ".");
        long difference = output.getTime() - startTime.getTime();
        System.out.println("Result: " + sdf.format(output) + ". Change ("
                + difference / 1000 / 60 / 60 + " hour).\n");
    }

    // wrapper around joda test
    private void timeTestJoda(Date startTime, String from, String to) {
        Date output = convertJodaOld(startTime, TimeZone.getTimeZone(from),
                TimeZone.getTimeZone(to));
        print(startTime, "joda", from, to, output);
    }

    // wrapper around java test
    private void timeTestJava(Date startTime, String from, String to) {
        Date output = convertJava(startTime, TimeZone.getTimeZone(from),
                TimeZone.getTimeZone(to));
        print(startTime, "java", from, to, output);
    }

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

    // 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 Ответ

4 голосов
/ 01 марта 2011

НЕ используйте «США / Аризона», оно устарело.

Используйте «Америка / Феникс»

То же самое относится к «США / Тихий океан», вместо этого используйте «Америка / Лос-Анджелес».

...