【问题标题】:Joda time conversion incorrect with non-DST timezone非 DST 时区的 Joda 时间转换不正确
【发布时间】:2011-07-06 02:31:03
【问题描述】:

我遇到了 Joda 的问题,我认为这可能是一个错误。但是,我很有可能在使用该库时犯了错误,所以请给我您的反馈。

我们一直在生产中使用 joda 在我们的数据库存储格式 (UTC) 和用户的时区偏好之间转换时间。使用该系统的员工都在不遵守夏令时的亚利桑那州工作。除其他外,该系统还跟踪全国体育赛事的开始时间。

Joda 为我们工作得很好,直到我们注意到在时间变化的前一天我们得到了一些不正确的结果。我们发现 joda 似乎在 UTC 午夜改为夏令时,而不是在特定时区的适当时间。此外,仅在将 DST 观察状态与非 DST 状态(如亚利桑那州)之间的时间转换时,才会出现此问题。

我制作了一个完整的测试用例来说明这个问题。如您所见,joda 为所有美国/东部 -> 美国/太平洋测试用例提供了预期结果。对于美国/亚利桑那州 -> 美国/太平洋,它在 11 月变更之前和之后的全年都有效。但是,在时间更改当天(11 月 6 日)时间不正确。行军时间变化也可能存在问题,尽管我还没有彻底测试过。

这是所提供测试的输出(11 月 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);
    }
}

谢谢!

【问题讨论】:

    标签: java calendar jodatime


    【解决方案1】:

    不要使用“US/Arizona”,它已经过时了。

    使用“美国/凤凰”

    “US/Pacific”也是如此,请改用“America/Los_Angeles”。

    【讨论】:

    • 很高兴知道,但是使用您建议的时区不会影响测试结果。您是否有关于美国/* 时区已过时的信息的链接?
    • @samspot。这是“官方”列表 -> en.wikipedia.org/wiki/List_of_tz_database_time_zones。这是一些非官方的信息 -> timezoneconverter.com/cgi-bin/zoneinfo.tzc?s=default&tz=US/…
    • @samspot。在我看来,您实际上并没有使用 Joda 时间。构造 DateTime 对象,看看你仍然有问题。我一直在我的代码中使用 DateTime 并围绕标准/日光更改进行了大量测试,因为它们对我们的系统至关重要。 Joda 时间类总是返回正确的结果。
    • 感谢您的提示。在使用 DateTime 进一步挖掘之后,我发现 joda 在内部正确地表示了我的时间,但是当我尝试转换回 Java 日期(使用 DateTime.toDate())时,问题就出现了。整个 ui 与 java.util.Date 对象紧密耦合,因此我们确实需要一种方法以适当显示的方式转换为它们。
    猜你喜欢
    • 2015-10-19
    • 2015-10-26
    • 2017-06-20
    • 1970-01-01
    • 2013-08-03
    • 2016-04-19
    • 2013-04-15
    • 1970-01-01
    • 2015-12-31
    相关资源
    最近更新 更多