Будущие даты в Java-календаре дают странное поведение - PullRequest
0 голосов
/ 06 ноября 2018

У меня есть приложение, в котором я создаю даты, которые пользователь может выбрать для встречи. Если пользователь начинает работать в 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;
    }

Я работаю с часовым поясом "Америка / Сан-Паулу", чтобы создать эти даты. Я установил переменные «начальный» и «конец» как «Америка / Sao_Paulo». Мой системный часовой пояс "GMT", и это нормально, потому что я хочу сохранить эти даты в GMT в базе данных. Когда я печатаю даты в последнем «для», волшебным образом они уже преобразуются из «Америка / Сан-Паулу» в «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

Пока в Гринвиче, это правильно, но я не могу понять это BST. Может ли это быть потому, что это слишком много в будущем? Это всегда начинается в апреле.

1 Ответ

0 голосов
/ 06 ноября 2018

Ваше системное время не GMT, это Европа / Лондон (или что-то подобное). В марте лондонское время совпадает с GMT. Не в апреле. Вот почему.

getWorkingHour() возвращает экземпляр Date (еще один плохо разработанный и давно устаревший класс, но пусть пока это будет другая история). Когда вы добавляете его к пустой строке, неявно вызывается Date.toString и строит строку, используя системный часовой пояс. В стандартное время печатается GMT как сокращение часового пояса. Летнее время (DST) начинается в Лондоне в последнее воскресенье марта, в данном случае 31 марта. Поэтому в апреле Date.toString на вашей JVM использует British Summer Time и его сокращение, BST для печати времени.

Хорошее решение включает в себя два изменения:

  1. Не полагайтесь на часовой пояс JVM по умолчанию. Его можно изменить в любой момент из другой части вашей программы или из другой программы, запущенной в той же JVM, поэтому он слишком хрупок. Вместо этого дайте явный часовой пояс для ваших операций с датой и временем.
  2. Пропустите старые классы даты и времени Calendar и Date и вместо этого используйте java.time, современный Java-интерфейс даты и времени. С ним гораздо приятнее работать, и он дает гораздо более четкий код, не в последнюю очередь, когда речь идет о преобразованиях между часовыми поясами.

Вместо Calendar используйте ZonedDateTime. В зависимости от возможностей вашего драйвера JDBC, преобразуйте его в Instant или OffsetDateTime в UTC для сохранения в базе данных.

Чтобы создать ZonedDateTime, можно использовать один из его методов of (их несколько):

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

Это создает дату и время 10 марта 2019 года в 09:00 в Сан-Паулу. Чтобы добавить к нему 2 часа:

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

Выход:

2019-03-10T11: 00-03: 00 [Америка / Sao_Paulo]

Чтобы преобразовать в 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.

...