比较不同时区的 java.util.Calendar 日期

时间:2021-06-29 08:03:03

标签: java date datetime java-8

我有两个日期 - 发货日期和交货日期。业务案例是 - 发货日期不能晚于交货日期。

我的一个输入日期由三个独立的 String 组件组成 - 日期(例如 05.07.2021)、时间(例如 00:00:00)、时区(例如 CST)。

我尝试将此输入日期转换为 Calendar 实例,然后使用 Calender.after()。但看起来它在比较时没有考虑时区。

输入:

ShipDate - 05.07.2021, 01:00:00, EST

DelDate - 05.07.2021, 00:00:00, CST

预期:

虽然时间不同,但考虑到时区转换,发货和交货日期应该相同,通过业务案例。

请帮助实现预期。

代码:

public static void main(String[] args) {

    String delDate = "05.07.2021";
    String delTime = "00:00:00";
    String delTz = "CST";
    String shipDate = "05.07.2021";
    String shipTime = "01:00:00";
    String shipTz = "EST";
        
    SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss");
    Calendar delCal = Calendar.getInstance(TimeZone.getTimeZone(delTz));
    Calendar shipCal = Calendar.getInstance(TimeZone.getTimeZone(shipTz));
    try {
        delCal.setTime(sdf.parse(delDate + " " + delTime));
        shipCal.setTime(sdf.parse(shipDate + " " + shipTime));
    } catch (ParseException e) {
        e.printStackTrace();
    }
    
    if (shipCal.after(delCal)) {
        System.out.println("ERROR: Ship datetime is after Del datetime");
    } else {
        System.out.println("SUCCESS: Ship datetime and Del datetime are correct");
    }
}

2 个答案:

答案 0 :(得分:3)

您可以使用 Java 8 中引入的 datetime API,即 java.time

您可以使用 ZonedDateTime 来实现预期效果,这是一个小例子:

public static void main(String[] args) {
    // your example input
    String delDate = "05.07.2021";
    String delTime = "00:00:00";
    String delTz = "CST";
    String shipDate = "05.07.2021";
    String shipTime = "01:00:00";
    String shipTz = "EST";
    // concatenate the input, just comma separated
    String delDateTimeZone = String.join(",", delDate, delTime, delTz);
    String shipDateTimeZone = String.join(",", shipDate, shipTime, shipTz);
    // define a formatter that parses Strings like the concatenated ones
    DateTimeFormatter dateTimeZoneDtf = DateTimeFormatter.ofPattern(
                                                "dd.MM.uuuu,HH:mm:ss,z");
    // parse them to objects that consider time zones
    ZonedDateTime delZdt = ZonedDateTime.parse(delDateTimeZone, dateTimeZoneDtf);
    ZonedDateTime shipZdt = ZonedDateTime.parse(shipDateTimeZone, dateTimeZoneDtf);
    // find out if their instants are equal
    String e = delZdt.toInstant().equals(shipZdt.toInstant()) ?
                                    "the same moment" : "different moments";
    // and print them along with a statement about their equality
    System.out.println(delZdt + " and " + shipZdt + " are " + e + " in time");
}

这个例子的输出是

2021-07-05T00:00-05:00[America/Chicago] and 2021-07-05T01:00-04:00[America/New_York] are the same moment in time

请注意,您可以通过分别处理输入的每个部分来解析或创建 ZonedDateTime 组成的部分。

但如果你这样做...

...它不会在每种情况下产生相同的结果,因为 ZoneId.of(delTz, ZoneId.SHORT_IDS) 似乎将 "EST" 解释为距 UTC 全年 -05:00 小时,而 DateTimeFormatter似乎使用了考虑夏令时的真实区域。

感谢 Ole V. V. 在此答案下方的评论中指出与上述示例的不同之处。

以下是将三个字母的时区缩写解析为固定偏移量的示例:

public static void main(String[] args) {
    // your example input
    String delDate = "05.07.2021";
    String delTime = "00:00:00";
    String delTz = "CST";
    String shipDate = "05.07.2021";
    String shipTime = "01:00:00";
    String shipTz = "EST";
    // define a formatter that parses the date String
    DateTimeFormatter dateDtf = DateTimeFormatter.ofPattern("dd.MM.uuuu");
    // parse the dates using the date formatter 
    LocalDate delLocalDate = LocalDate.parse(delDate, dateDtf);
    LocalDate shipLocalDate = LocalDate.parse(shipDate, dateDtf);
    // and parse the times of day without (they are in standard ISO format already)
    LocalTime delLocalTime = LocalTime.parse(delTime);
    LocalTime shipLocalTime = LocalTime.parse(shipTime);
    // then create zone objects from the zone short ids
    ZoneId delZone = ZoneId.of(delTz, ZoneId.SHORT_IDS);
    ZoneId shipZone = ZoneId.of(shipTz, ZoneId.SHORT_IDS);
    // create objects that consider time zones using the 
    ZonedDateTime delZdt = ZonedDateTime.of(delLocalDate, delLocalTime, delZone);
    ZonedDateTime shipZdt = ZonedDateTime.of(shipLocalDate, shipLocalTime, shipZone);
    // find out if their instants are equal
    String e = delZdt.toInstant().equals(shipZdt.toInstant()) ?
                                    "the same moment" : "different moments";
    // and print them along with a statement about their equality
    System.out.println(delZdt + " and " + shipZdt + " are " + e + " in time");
}

答案 1 :(得分:2)

您的代码出了什么问题?

这是好奇者的答案。 deHaar 早就发布了很好的答案,展示了如何解决您的问题。当您尝试使用设计不佳且已过时的 SimpleDateFormatCalendarTimeZone 类时,我们实际上并不需要知道出了什么问题,因为我们根本不应该使用这些类.但是,在这种情况下,好奇心并不会完全杀死猫,所以让我们看看。

您的两个 Calendar 对象之间的比较按照我认为您预期的方式进行:比较时间点以查看一个对象是否一个接一个。换句话说,它不是比较来自不同时区的挂钟时间。

但在开始进行比较之前,还有两件事出了问题。

第一件事是,您的两个三字母时区缩写 CST 和 EST 为您提供相同的 UTC 偏移量,即 -05:00。什么?!如何?!这是因为不一致的 CST 被用来表示美国/芝加哥时区,即考虑夏令时 (DST),而 EST 从字面上看是指(北美)东部标准时间所有年,也就是说,没有夏令时。因此,虽然芝加哥在标准时间的偏移量为 -06:00,但您的日期 7 月 5 日在一年中的夏季时间部分,因此应用偏移量 -05:00。东部标准时间全年偏移 -05:00,因此此处应用相同的偏移。

学习经验:不要使用三个字母的时区缩写。使用实时时区 ID,例如 America/Winnipeg、America/Chicago、America/Toronto 或 America/New_York,始终地区/城市

这里的第二件事严重错误:

        delCal.setTime(sdf.parse(delDate + " " + delTime));

您原以为这会将已经位于芝加哥时区的 Calendar 设置为芝加哥时区的 7 月 5 日 00:00。这(可能)没有发生。 sdf.parse() 返回一个 Date 对象。尽管名称为 Date,但它既不包含日期也不包含一天中的一个小时。它持有一个时间点。并且您的 SimpleDateFormat 不知道您想到的是中央时间,将您的串联字符串解析为一个时间点,该时间点对应于 JVM 的默认时区 中的 7 月 5 日 00:00。在我的时区(欧洲/哥本哈根),我于 7 月 4 日 17:00 在芝加哥。您现在可能已经猜到了,下面这行代码会解析为同一 JVM 默认时区的晚上 01:00 对应的时间点,所以 1 小时后(除非您所在的时间发生了非常特殊的事情)在今晚的区域)。

学习经验:不要使用 SimpleDateFormatCalendarTimeZone。使用那些旧类很容易犯错误,而这些错误很难看透和追查。

相关问题