TimeZone与日历令人困惑的结果

我最近一直在处理时区转换,并且对我得到的结果感到非常惊讶。基本上,我希望将日期从一个时区转换为另一个时区。 下面是代码,转换工作正常,但我在调试时观察到的是,除非我调用Calendar#get(Calendar.FIELD)否则日期不会被转换。

  private static void convertTimeZone(String date, String time, TimeZone fromTimezone, TimeZone toTimeZone){ Calendar cal = Calendar.getInstance(fromTimezone); String[] dateSplit = null; String[] timeSplit = null; if(time !=null){ timeSplit = time.split(":"); } if(date!=null){ dateSplit = date.split("/"); } if(dateSplit !=null){ cal.set(Calendar.DATE, Integer.parseInt(dateSplit[0])); cal.set(Calendar.MONTH, Integer.parseInt(dateSplit[1])-1); cal.set(Calendar.YEAR, Integer.parseInt(dateSplit[2])); } if(timeSplit !=null){ cal.set(Calendar.HOUR_OF_DAY, Integer.parseInt(timeSplit[0])); cal.set(Calendar.MINUTE, Integer.parseInt(timeSplit[1])); } // System.out.println("Time in " + fromTimezone.getDisplayName() + " : " + cal.get(Calendar.DATE) +"/"+ (cal.get(Calendar.MONTH)+1)+"/"+ cal.get(Calendar.YEAR) +" " + ((cal.get(Calendar.HOUR_OF_DAY)<10) ? ("0"+cal.get(Calendar.HOUR_OF_DAY) ): (cal.get(Calendar.HOUR_OF_DAY))) // +":" + (cal.get(Calendar.MINUTE)<10 ? "0"+cal.get(Calendar.MINUTE) : cal.get(Calendar.MINUTE)) ); cal.setTimeZone(toTimeZone); System.out.println("Time in " + toTimeZone.getDisplayName() + " : " + cal.get(Calendar.DATE) +"/"+ (cal.get(Calendar.MONTH)+1)+"/"+ cal.get(Calendar.YEAR) +" " + ((cal.get(Calendar.HOUR_OF_DAY)<10) ? ("0"+cal.get(Calendar.HOUR_OF_DAY) ): (cal.get(Calendar.HOUR_OF_DAY))) +":" + (cal.get(Calendar.MINUTE)<10 ? "0"+cal.get(Calendar.MINUTE) : cal.get(Calendar.MINUTE)) ); } public static void main(String[] args) throws ParseException { convertTimeZone("23/04/2013", "23:00", TimeZone.getTimeZone("EST5EDT"), TimeZone.getTimeZone("GB")); } 

预期产出: Time in Greenwich Mean Time : 24/4/2013 04:00

我在评论sysout 1时获得的输出: Time in Greenwich Mean Time : 23/4/2013 23:00

如果我取消注释sysout1我得到预期的有效输出。

任何帮助表示赞赏

在确实需要之前,不会评估给定日期的内部表示,直到您尝试通过这些getter访问它。 但是,解析日期的最佳方法是通过SimpleDateFormat。


编辑 (添加用于总结以下评论并更好地澄清我的答案)。

日历以这种方式工作以提高效率:每次调用setter时都会重新计算everithing,它会一直等到你调用getter。

日历应主要用于日期计算(请参阅add()roll() ),但您正在使用它进行解析和格式化:使用SimpleDateFormat可以更好地完成这些任务,这就是为什么我说您对Calendar的使用不够优雅。

看这个例子:

 private static void convertTimeZone(String date, String time, TimeZone fromTimezone, TimeZone toTimeZone) throws ParseException { SimpleDateFormat df = new SimpleDateFormat("dd/MM/yyyy HH:mm"); df.setTimeZone(fromTimezone); Date d = df.parse(date + " " + time); df.setTimeZone(toTimeZone); System.out.println("Time in " + toTimeZone.getDisplayName() + " : " + df.format(d)); } 

我只使用SimpleDateFormat重新实现了您的方法。 我的方法较小,没有拆分逻辑(它隐藏在parse() ),并且输出也以更简单的方式处理。 此外,日期格式以紧凑和标准的方式表示,可以使用ResourceBundle轻松实现国际化。

另请注意,时区转换只是一个格式化任务:解析日期的内部表示不会更改。

答案部分在setTimeZone()的注释部分解释:

考虑调用顺序:cal.setTimeZone(EST); cal.set(小时,1); cal.setTimeZone(PST)。 校准设置为美国东部标准时间1点或太平洋标准时间1点? 答:PST。 更一般地说,对setTimeZone()的调用会影响对set()之前和之后的调用,直到下一次调用complete()为止。

换句话说,序列

  1. 设置日历时间
  2. 更改TimeZone

被解释为“在这个新时区使用此时间”,而序列

  1. 设置日历时间
  2. 得到时间(或其中的一部分)
  3. 更改时区

将被解释为“在旧时区使用此时间,然后更改它”。

因此,在您的情况下,为了获得您希望的行为,您需要在更改时区之前调用get()或内部调用Calendar complete()任何其他方法。

您是否仅限于使用TimeZone和日历? 如果不是,我建议使用优秀的图书馆JodaTime ,这使处理时区更容易。

您的示例将如下所示:

 public static void convertTimeZoneJoda(String date, String time, DateTimeZone fromTimezone, DateTimeZone toTimeZone) { DateTimeFormatter dtf = DateTimeFormat.forPattern("dd/MM/yyyyHH:mm").withZone(fromTimezone); DateTime dt = dtf.parseDateTime(date+time).withZone(toTimeZone); System.out.println("Time in " + toTimeZone.getID() + " : " + dt.toString()); } public static void main(String[] args) { convertTimeZoneJoda("23/04/2013", "23:00", DateTimeZone.forID("EST5EDT"), DateTimeZone.forID("GMT")); } 

JodaTime还允许TimeZone和DateTimeZone之间的便捷对话。

要添加到@Pino的答案, get()不返回更新时间的原因是因为setTimeZone()根本不更新字段,只是将areAllFieldsSet设置为false,这是没用的,因为get()不会检查一下。 如果你问我,SUN的编码非常糟糕。 以下是Calendar的主管代码:

setTimeZone()

  public void setTimeZone(TimeZone value){ zone = value; sharedZone = false; areAllFieldsSet = areFieldsSet = false; } 

get()

 protected final int internalGet(int field){ return fields[field]; } 

此代码适用于我转换为UTC:

  1. 使用UTC时区创建日历对象

     Calendar utcTime = Calendar.getInstance(TimeZone.getTimeZone("GMT")); 
  2. 从“任何时区”日历对象设置UTC日历对象中的时间点

     utcTime.setTimeInMillis(myCalendarObjectInSomeOtherTimeZone.getTimeInMillis()); 
  3. 现在, utcTime将包含与myCalendarObjectInSomeOtherTimeZone转换为UTC相同的时间点。