Java Joda time进行时间转换';太早了';
我们有一个时间非常关键的应用程序。我们使用joda进行时间转换,并以UTC时间存储所有数据。我们已经制作了一段时间,一切都很完美,但是 现在我们注意到,在时间更改前几个小时发生的事件已经转换得太早了!事实上,保存到数据库的UTC时间缩短了一小时 这里有一个例子。我的事件发生在2010年11月6日太平洋时间晚上9点,通常会保存为2010年11月7日凌晨4点。然而,由于夏令时在7号结束(大概是凌晨2点),所以这段时间会被转换并存储为2010年7月11日凌晨5点 我们需要在太平洋标准时间凌晨2点,在太平洋标准时间区域内实际发生DST变化之前,不记录DST变化。我认为joda会处理这个问题,特别是因为它被吹捧为比java的默认功能有很大的改进 您的任何反馈都会很有帮助,特别是如果您能在明天的时间改变之前将其发送给我们!在那之后,它将是学术性的,但仍然是一个有用的讨论 下面是我们用来执行时区更改并将结果作为常规java日期对象的一些代码Java Joda time进行时间转换';太早了';,java,timezone,utc,jodatime,Java,Timezone,Utc,Jodatime,我们有一个时间非常关键的应用程序。我们使用joda进行时间转换,并以UTC时间存储所有数据。我们已经制作了一段时间,一切都很完美,但是 现在我们注意到,在时间更改前几个小时发生的事件已经转换得太早了!事实上,保存到数据库的UTC时间缩短了一小时 这里有一个例子。我的事件发生在2010年11月6日太平洋时间晚上9点,通常会保存为2010年11月7日凌晨4点。然而,由于夏令时在7号结束(大概是凌晨2点),所以这段时间会被转换并存储为2010年7月11日凌晨5点 我们需要在太平洋标准时间凌晨2点,在太
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);
}
}您的代码从根本上被破坏了,因为
日期
对象不能在时区之间“转换”——它代表时间上的一个瞬间getTime()
返回自UTC Unix纪元以来的时间(毫秒)。Date
不依赖于时区,因此将Date
实例从一个时区转换为另一个时区的想法毫无意义。这有点像将一个int
从“基数10”转换为“基数16”——当你考虑用数字而不是基本数字表示时,基数才有意义
您应该使用LocalDateTime
表示没有固定时区的日期/时间,或者使用DateTime
表示有特定时区的日期/时间,或者使用Instant
表示与java.util.date
相同的概念
一旦你使用了合适的类型,我相信你不会有任何问题
编辑:如果您的实际代码使用的是具有正确时区的日历
,则无需执行任何操作即可将其转换为UTC。只需调用calendar.getTime()
,它将为您提供相应的日期
例如:
// 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,因为我们使用hibernate来持久化数据。我的理解是,joda正在对日期应用偏移量,即使date对象实际上并不知道时区。如果代码从根本上被破坏了,它不是一直被破坏,而不是一年中只有14个小时吗?@samspot:不是日期对象没有真正意识到时区,而是日期总是代表一个瞬间。在时区之间“转换”日期毫无意义。至于为什么它没有一直坏掉——一个简短但完整的例子在这里会很有帮助。仅仅因为你必须在某个时候使用Date
,并不意味着你必须在任何地方都使用它,是吗?嘿,Jon,我附加了一个非joda的示例,效果非常好。这就是我认为乔达一开始为我所做的。你是说我需要使用joda的其他方面来实现这一点吗?@samspot:是的,你应该使用正确的类型<代码>日期
不是表示“本地”日期/时间的正确类型。非Joda示例可能只能正常工作,因为Java时区数据库太旧,无法了解最近的时区更改。(也许)再一次,通过一个简短但完整的例子,我们可以找出Joda Time为什么会这样做——这也可能说明你是如何因为使用了错误的类型而陷入麻烦的。@Jon我已经发布了一个完整的程序。请参阅convertJodaNew(),了解我根据对话和此链接进行转换的尝试。请注意,这也不起作用。我们在亚利桑那州运行的服务器没有观察到DST,所以我们应该看到DST处于活动状态时时间保持不变,然后DST处于非活动状态时时间比pacific早一个小时。