在Java中更改时区而不更改时间
Posted
技术标签:
【中文标题】在Java中更改时区而不更改时间【英文标题】:Changing timezone without changing time in Java 【发布时间】:2011-02-21 19:41:55 【问题描述】:我从 SOAP 网络服务接收到日期时间,但没有时区信息。因此,Axis 解串器采用 UTC。但是,日期时间确实是悉尼时间。我已经通过减去时区偏移解决了这个问题:
Calendar trade_date = trade.getTradeDateTime();
TimeZone est_tz = TimeZone.getTimeZone("Australia/Sydney");
long millis = trade_date.getTimeInMillis() - est_tz.getRawOffset();
trade_date.setTimeZone( est_tz );
trade_date.setTimeInMillis( millis );
但是,我不确定此解决方案是否还考虑了夏令时。我认为应该,因为所有操作都在 UTC 时间。有在 Java 中操纵时间的经验吗?关于如何解决这个问题的更好的想法?
【问题讨论】:
我总是,永远,并且只使用唯一固定的单位来操纵日期(任何语言)。该单位是秒(或毫秒)。有 59 或 61 秒的分钟,有 23 或 25 小时的天。有 60*60 +/- 1 秒等小时。我所有的日期都存储为自纪元以来的(毫秒)秒。这就是它们在数据库中的方式,这就是它们的排序方式。我进行 timezone/yyyy-mm-dd 任何转换的唯一时间是当它需要显示给用户时(或者当使用不理解日期可以以秒为单位表示的损坏的 API 时)。 @WizardOfOdds。这是合理的建议,但 OP 清楚地发布了他从 Web 服务(可能是第 3 方)接收时间戳并使用第 3 方库来处理该请求。显然有些事情超出了他的直接控制范围,使用 java.util.Calendar 从时区转换到时区绝对不是直接的。 感谢您的 cmets。我正在使用 Axis 从 WSDL 文件生成 Web 服务存根。我正在使用生成的类连接到第三方 Web 服务。实际上,我认为他们应该使用时区信息或 UTC 传输日期时间字段,这是 Axis 所期望的。但这不是我的选择。 【参考方案1】:我可怜那个不得不用 Java 约会的傻瓜。
您所做的几乎肯定会在夏令时转换期间出错。最好的方法可能是创建一个新的 Calendar 对象,在其上设置 Timezone,然后单独设置所有字段,例如年、月、日、小时、分钟、秒,从 Date 对象中获取值.
编辑: 为了让每个人都开心,你应该这样做:
Calendar utcTime = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
Calendar sydneyTime = Calendar.getInstance(TimeZone.getTimeZone("Australia/Sydney");
utcTime.setTime(trade_date);
for (int i = 0; i < Calendar.FIELD_COUNT; i++)
sydneyTime.set(i, utcTime.get(i));
那么您将不会使用任何已弃用的方法。
【讨论】:
-1:建议使用 java.util.Date 类的已弃用方法 可能:newCalendar.set(oldCalendar.get(Calender.YEAR), oldCalendar.get(Calendar.MONTH),..., oldCalendar.get(Calendar.SECOND));
@Alexander Pogrebnyak :我已经更新了答案,这样用户就不会被带到黑暗的一面;-)【参考方案2】:
我要感谢回复 6 的人。这对我来说是一个很好的开始,也是我没有考虑过的一种方法。将其带到生产代码级别需要一些额外的步骤。特别注意 DST_OFFSET 和 ZONE_OFFSET 所需的步骤。我想分享我想出的解决方案。
这会从输入 Calendar 对象中获取时间,将其复制到输出时间,然后将新时区设置为输出。这用于从数据库中获取时间并在不更改时间的情况下设置时区。
public static Calendar setNewTimeZoneCopyOldTime( Calendar inputTime,
TimeZone timeZone )
if( (inputTime == null) || (timeZone == null) ) return( null );
Calendar outputTime = Calendar.getInstance( timeZone );
for( int i = 0; i < Calendar.FIELD_COUNT; i++ )
if( (i != Calendar.ZONE_OFFSET) && (i != Calendar.DST_OFFSET) )
outputTime.set(i, inputTime.get(i));
return( (Calendar) outputTime.clone() );
【讨论】:
【参考方案3】:但是,我不确定这个解决方案是否 也将夏令时纳入 帐户。我认为应该,因为 所有操作均在 UTC 时间。
是的,您应该考虑夏令时,因为它会影响到 UTC 的偏移量。
有在 Java 中操纵时间的经验吗?关于如何解决这个问题的更好的想法?
Joda-Time 是更好的时间 API。也许以下 sn-p 可能会有所帮助:
DateTimeZone zone; // TODO : get zone
DateTime fixedTimestamp = new DateTime(year, monthOfYear, dayOfMonth, hourOfDay, minuteOfHour, secondOfMinute, millisOfSecond, zone);
JodaTime 类型是不可变的,这也是一个好处。
【讨论】:
感谢 JodaTime 的链接。但是,我不会为该项目引入新的第三方库。但下一次我会考虑 JodaTime。 Joda-Time 值得为添加第三方库而烦恼。久经考验,久经考验。 java.util.Date 和 Calendar 类是出了名的麻烦。【参考方案4】:我通常这样做
Calendar trade_date_utc = trade.getTradeDateTime();
TimeZone est_tz = TimeZone.getTimeZone("Australia/Sydney");
Calendar trade_date = Calendar.GetInstance(est_tz);
trade_date.setTimeInMillis( millis );
【讨论】:
【参考方案5】:您是否从那个混乱的 Web 服务中获得了 ISO 8601 样式的字符串?如果是这样,Joda-Time 2.3 库使这变得非常容易。
如果你得到一个没有任何时区偏移的 ISO 8601 字符串,你将一个时区对象传递给 DateTime 构造函数。
DateTimeZone timeZone = DateTimeZone.forID( "Australia/Sydney" );
String input = "2014-01-02T03:00:00"; // Note the lack of time zone offset at end.
DateTime dateTime = new DateTime( input, timeZone );
转储到控制台...
System.out.println( "dateTime: " + dateTime );
运行时……
dateTime: 2014-01-02T03:00:00.000+11:00
【讨论】:
【参考方案6】: @Test
public void tzTest()
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS Z");
TimeZone tz1 = TimeZone.getTimeZone("Europe/Moscow");
Calendar cal1 = Calendar.getInstance(tz1);
long l1 = cal1.getTimeInMillis();
df.setTimeZone(tz1);
System.out.println(df.format(cal1.getTime()));
System.out.println(l1);
TimeZone tz2 = TimeZone.getTimeZone("Africa/Douala");
Calendar cal2 = Calendar.getInstance(tz2);
long l2 = l1 + tz1.getRawOffset() - tz2.getRawOffset();
cal2.setTimeInMillis(l2);
df.setTimeZone(tz2);
System.out.println(df.format(cal2.getTime()));
System.out.println(l2);
assertNotEquals(l2, l1);
运行 CalendarTest 2016-06-30 19:09:16.522 +0300 1467302956522 2016-06-30 19:09:16.522 +0100 1467310156522 测试运行:1,失败:0,错误:0,跳过:0,经过时间:0.137 秒
【讨论】:
【参考方案7】:我决定重新解析收到的日期时间字符串并设置正确的时区。这也应该考虑夏令时:
public class DateTest
private static SimpleDateFormat soapdatetime = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
/**
* @param args
*/
public static void main(String[] args)
TimeZone oztz = TimeZone.getTimeZone("Australia/Sydney");
TimeZone gmtz = TimeZone.getTimeZone("GMT");
Calendar datetime = Calendar.getInstance( gmtz );
soapdatetime.setTimeZone( gmtz );
String soap_datetime = soapdatetime.format( datetime.getTime() );
System.out.println( soap_datetime );
soapdatetime.setTimeZone( oztz );
datetime.setTimeZone( oztz );
try
datetime.setTime(
soapdatetime.parse( soap_datetime )
);
catch (ParseException e)
e.printStackTrace();
soapdatetime.setTimeZone( gmtz );
soap_datetime = soapdatetime.format( datetime.getTime() );
System.out.println( soap_datetime );
【讨论】:
以上是关于在Java中更改时区而不更改时间的主要内容,如果未能解决你的问题,请参考以下文章
我们如何动态更改 java rest api 响应中的时区?