Java日历中的未来日期会产生奇怪的行为

时间:2018-11-06 19:57:43

标签: java date calendar timezone

我有一个应用程序,可以创建用户可以选择约会的日期。如果用户从9点开始工作,并且约会需要2个小时,那么我将在9点,11点,13点创建日期,直到达到限制为止。然后我改变了一天,然后重新开始。 这是执行此操作的代码:

    public List<Agenda> createListOfDates(Calendar initial, Calendar end, 
        int appointmentDuration, int lunchTimeDuration, int lunchTimeStart) {

        List<Agenda> agendaList = new ArrayList<Agenda>();

        Agenda agenda = new Agenda();
        agenda.setWorkingHour(initial.getTime());
        agendaList.add(agenda);
        while (true) {

            initial.add(Calendar.HOUR_OF_DAY, appointmentDuration);
//          Logger.error("" + initial.getTime());

            if (initial.getTime().after(end.getTime())) {
                break;

            } else if (initial.get(Calendar.HOUR_OF_DAY) == lunchTimeStart
                    && initial.get(Calendar.DAY_OF_WEEK) != Calendar.SATURDAY
                    ) {
                initial.add(Calendar.HOUR_OF_DAY, lunchTimeDuration);
                agenda = new Agenda();
                agenda.setWorkingHour(initial.getTime());
                agendaList.add(agenda);

            } else {
                agenda = new Agenda();
                agenda.setWorkingHour(initial.getTime());
                agendaList.add(agenda);
            }
        }

        for(Agenda agendaX : agendaList){
        Logger.info("" + agendaX.getWorkingHour());

}

        return agendaList;
    }

我正在使用“ America / Sao_Paulo”时区来创建这些日期。我将变量“ initial”和“ end”设置为“ America / Sao_Paulo”。我的系统时区是“ GMT”,没关系,因为我想将这些日期保存在数据库的GMT中。当我在最后一个“ for”中打印日期时,已经神奇地将其从“ America / Sao_Paulo”转换为“ GMT”,并且打印正确。奇怪的是,从某个日期开始,它会更改时区。打印示例:

Sat Mar 30 12:00:00 GMT 2019
Sat Mar 30 14:00:00 GMT 2019
Sat Mar 30 16:00:00 GMT 2019
Sat Mar 30 18:00:00 GMT 2019
Mon Apr 01 13:00:00 BST 2019
Mon Apr 01 15:00:00 BST 2019
Mon Apr 01 18:00:00 BST 2019
Mon Apr 01 20:00:00 BST 2019
Mon Apr 01 22:00:00 BST 2019

虽然在格林尼治标准时间(GMT),这是正确的,但是我不明白此BST。可能是因为将来太多了吗?它总是从四月开始。

1 个答案:

答案 0 :(得分:3)

您的系统时间不是格林尼治标准时间,而是欧洲/伦敦(或类似时间)。 3月,伦敦时间恰逢格林尼治标准时间。不在四月。这就是为什么。

getWorkingHour()返回Date的实例(另一个设计欠佳且过时的类,但现在让它变成一个不同的故事)。当您将其附加到空字符串时,Date.toString被隐式调用,并使用您的系统时区来构建字符串。在标准时间,它会打印GMT作为时区缩写。夏令时(DST)在3月的最后一个星期日(在本例中为3月31日)在伦敦开始。因此,在4月,您的JVM上的Date.toString使用英国夏令时及其缩写BST来打印时间。

好的解决方案涉及两个更改:

  1. 不要依赖JVM的默认时区。可以随时从程序的另一部分或在同一JVM中运行的另一个程序更改它,这太脆弱了。而是为日期时间操作指定明确的时区。
  2. 跳过旧的日期时间类CalendarDate,而使用现代Java日期和时间API java.time。这样操作起来更好,代码也更清晰,尤其是在时区之间进行转换时。

使用Calendar代替ZonedDateTime。根据JDBC驱动程序的功能,将其转换为UTC中的InstantOffsetDateTime以保存到数据库。

要创建ZonedDateTime,一种选择是使用其of方法之一(有几种):

    ZonedDateTime initial = ZonedDateTime.of(2019, 3, 10, 9, 0, 0, 0, ZoneId.of("America/Sao_Paulo"));

这将在圣保罗创建一个2019年3月10日09:00的日期时间。要添加2个小时:

    int appointmentDuration = 2;
    ZonedDateTime current = initial.plusHours(appointmentDuration);
    System.out.println(current);

输出:

  

2019-03-10T11:00-03:00 [美国/圣保罗]

要为您的数据库转换为Instant,请执行以下操作:

    Instant inst = current.toInstant();
    System.out.println(inst);

输出:

  

2019-03-10T14:00:00Z

即时消息是时区中立的,只是一个时间点,但以UTC打印。一些JDBC驱动程序在UTC时间接受它们。如果您的情况并非如此,则需要给它一个OffsetDateTime。像这样转换:

    OffsetDateTime odt = current.toOffsetDateTime().withOffsetSameInstant(ZoneOffset.UTC);
    System.out.println(odt);

输出:

  

2019-03-10T14:00Z

请注意,我明确给出了UTC,而不是依赖JVM默​​认值。因此,这在UTC中是明确地。您会注意到日期和时间与Instant上打印的内容一致。