4

我最近一直在处理时区转换,并且对我得到的结果感到非常惊讶。基本上,我想将日期从一个时区转换为另一个时区。下面是代码,转换工作正常,但我在调试时观察到的是,除非我打电话,否则日期不会转换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 我得到预期的有效输出。

任何帮助表示赞赏

4

5 回答 5

3

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


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

日历以这种方式工作以提高效率:每次调用 setter 时都不会重新计算,而是等到调用 getter 时才重新计算。

Calendar 应该主要用于日期计算(请参阅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 轻松国际化。

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

于 2013-04-23T15:43:49.650 回答
2

答案在以下评论部分中得到了setTimeZone()部分解释:

考虑调用顺序: cal.setTimeZone(EST); cal.set(HOUR, 1); cal.setTimeZone(PST)。cal 设置为 1 点 EST 还是 1 点 PST?答案:太平洋标准时间。更一般地,对 setTimeZone() 的调用会影响对 set() 之前和之后的调用,直到下一次调用 complete()。

换句话说,序列

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

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

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

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

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

于 2013-04-23T15:58:17.173 回答
0

您是否仅限于使用 TimeZone 和 Calendar?如果不是,我建议使用出色的 Library 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 之间进行方便的对话。

于 2013-04-23T15:47:23.967 回答
0

要添加到@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];
}
于 2013-04-23T16:00:54.767 回答
0

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

  1. 创建一个带有 UTC 时区的日历对象

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

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

于 2016-09-21T23:10:27.487 回答