【问题标题】:Joda time does time conversion 'too soon'Joda time 的时间转换“为时过早”
【发布时间】:2010-11-05 21:02:36
【问题描述】:

我们有一个时间很关键的应用程序。我们使用 joda 进行时间转换并以 UTC 时间存储所有数据。我们已经生产了一段时间,一切都很完美,但是......

现在我们注意到在时间更改前几个小时发生的事件已经转换得太早了!事实上,保存到数据库的 UTC 时间相差一个小时。

这是一个例子。我的活动发生在太平洋夏令时间 2010 年 11 月 6 日晚上 9 点,通常会保存为 2010 年 11 月 7 日凌晨 4 点。但是,由于夏令时在 7 日结束(大概是凌晨 2 点),所以这个时间会被转移并存储为 2010 年 11 月 7 日@凌晨 5 点。

我们需要在太平洋标准时间凌晨 2 点实际发生在 PST 区域中之前不记录 DST 更改。我认为 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;
}

编辑:下面的 cmets 代码示例

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

}

【问题讨论】:

  • 您能否提供一个简短但完整的程序来演示问题,而不是提供单一方法?

标签: java timezone utc jodatime


【解决方案1】:

您的代码从根本上被破坏了,因为 Date 对象不能在时区之间“转换” - 它代表了一个瞬间。 getTime() 返回自 UTC Unix 纪元以来的时间毫秒。 Date 不依赖于时区,因此将Date 的实例从一个时区转换为另一个时区的想法毫无意义。这有点像将int 从“base 10”转换为“base 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 时,我基本上不清楚您为什么要尝试从美国/亚利桑那州转换到美国/太平洋地区...

【讨论】:

  • 我们现在被 java.util.Date 困住了,因为我们使用休眠来持久化数据。我的理解是,joda 正在对日期应用偏移量,即使日期对象并不真正了解时区。如果代码从根本上被破坏,它不会一直被破坏,而不是一年中只有 14 个小时?
  • @samspot:并不是日期对象不知道时区——而是日期总是代表一个瞬间。在时区之间“转换”日期是没有意义的。至于为什么它没有一直被破坏——一个简短但完整的例子在这里真的很有帮助。仅仅因为你必须在一些点使用Date并不意味着你必须在任何地方使用它,是吗?
  • 嘿乔恩,我附加了一个非 joda 样本,效果很好。这就是我认为乔达首先为我做的事情。你是说我需要使用 joda 的其他方面来实现这一点?
  • @samspot:是的,您应该使用正确的类型。 Date 不是代表“本地”日期/时间的正确类型。非 Joda 示例很可能只能正常工作,因为您的 Java 时区数据库太旧而无法了解最近的时区更改。 (也许。)再一次,通过一个简短但完整的例子,我们可以弄清楚为什么 Joda Time 会以这种方式表现 - 这也可能表明你是如何因为使用错误的类型而陷入困境的。
  • @Jon 我已经发布了一个完整的程序。请参阅 convertJodaNew() 以了解我根据我们的对话和此链接 joda-time.sourceforge.net/userguide.html#Changing_TimeZone 进行转换的尝试。请注意,这也不起作用。我们在不遵守 DST 的亚利桑那州运行我们的服务器,因此我们应该看到在 DST 处于活动状态时时间保持不变,然后在 DST 不活动时比太平洋时间早一小时。
猜你喜欢
  • 1970-01-01
  • 2013-08-18
  • 2013-10-22
  • 2017-11-13
  • 2015-02-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多