【问题标题】:Changing timezone without changing time in Java在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 所期望的。但这不是我的选择。

标签: java datetime calendar timezone


【解决方案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 );
              }
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2013-05-24
            • 1970-01-01
            • 2016-01-30
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-02-16
            • 2014-11-14
            相关资源
            最近更新 更多