在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 和 DB 时区会自动更改?

os时区更改tomcat需要重启

我们如何动态更改 java rest api 响应中的时区?

怎么实时同步java虚拟机与操作系统时区 及JVM启动后再更改操作系统时区或时间也能保持其同步?

如何在 iPhone 模拟器中更改时间和时区?

如何在 Linux Time 中更改时区? [复制]