Странное поведение с GregorianCalendar - PullRequest
14 голосов
/ 31 мая 2010

Я только что столкнулся со странным поведением с классом GregorianCalendar, и мне было интересно, действительно ли я делал что-то плохое.

Добавляется только в том случае, если месяц даты инициализации имеет фактический максимум, превышающий месяц, на который я собираюсь установить календарь.

Вот пример кода:

    // today is 2010/05/31  
    GregorianCalendar cal = new GregorianCalendar();

    cal.set(Calendar.YEAR, 2010);
    cal.set(Calendar.MONTH, 1); // FEBRUARY

    cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH));
    cal.set(Calendar.HOUR_OF_DAY, cal.getActualMaximum(Calendar.HOUR_OF_DAY));
    cal.set(Calendar.MINUTE, cal.getActualMaximum(Calendar.MINUTE));
    cal.set(Calendar.SECOND, cal.getActualMaximum(Calendar.SECOND));
    cal.set(Calendar.MILLISECOND, cal.getActualMaximum(Calendar.MILLISECOND));

    return cal.getTime(); // => 2010/03/03, wtf

Я знаю, что проблема вызвана тем, что датой инициализации календаря является 31-дневный месяц (май), который путается с месяцем, установленным на февраль (28 дней). Исправить несложно (просто установите day_of_month в 1, прежде чем устанавливать год и месяц), но мне было интересно, действительно ли это было требуемое поведение. Есть мысли?

Ответы [ 8 ]

14 голосов
/ 31 мая 2010

Получает фактические максимумы текущей даты / времени. Май имеет 31 день, что на 3 больше, чем 28 февраля, и поэтому он будет перенесен на 3 марта.

Вам нужно позвонить Calendar#clear() после получения / создания:

GregorianCalendar cal = new GregorianCalendar();
cal.clear();
// ...

В результате:

Sun Feb 28 23:59:59 GMT-04:00 2010

(что соответствует моему часовому поясу)

Как сказано в одном из ответов, java.util.Calendar и Date являются эпическими неудачами. При выполнении интенсивных операций с датой / временем учитывайте JodaTime .

2 голосов
/ 31 мая 2010

Я уверен, что это нежелательное поведение. Я в равной степени уверен, что никто не думал, что этот сценарий будет успешным, когда они сделали урок. Дело в том, что у Calendar очень большая проблема с внутренним состоянием и тем, как он управляет всеми потенциальными переходами во всех заданных методах.

Если вы не можете использовать JodaTime или JSR-310 в своем проекте, интенсивно тестируйте модуль при использовании класса Calendar. Как вы видите, в этом случае код Календаря ведет себя по-разному в зависимости от того, в какой день месяца (или в какое время суток) вы выполняете код.

2 голосов
/ 31 мая 2010

Да, именно так оно и должно работать. Если вы начинаете с GregorianCalendar с точной датой и изменяете ее, делая ее непоследовательной, вам не следует доверять полученным результатам.

Согласно документации о getActualMaximum(..) говорится:

Например, если дата этого экземпляра - 1 февраля 2004 г., фактическое максимальное значение поля DAY_OF_MONTH - 29, потому что 2004 - високосный год, а если дата этого экземпляра - 1 февраля 2005 г., это 28 .

Так что это должно работать, но вы должны кормить его постоянными значениями 31 февраля 2010 неверно и применение вещей, основанных на значении даты (например, getActualMaximum), не может работать. Как это должно исправить это само по себе? Решив, что месяц неправильный? или что день не тот?

Кстати, как всегда говорят все, используйте JodaTime ..:)

1 голос
/ 29 апреля 2018

Я хотел бы внести современный ответ.

    ZonedDateTime endOfFebruary2010 = LocalDate.of(2010, Month.MARCH, 1)
            .atStartOfDay(ZoneId.systemDefault())
            .minusNanos(1);
    System.out.println(endOfFebruary2010);

В моем часовом поясе это печатает:

2010-02-28T23: 59: +59,999999999 + 01: 00 [Европа / Копенгаген]

Распечатка остается одинаковой независимо от времени года и месяца, когда вы ее запускаете. Зависимость от часового пояса может быть неудачной, но ее можно исправить, указав нужный часовой пояс, например ZoneId.of("Asia/Oral"). Я использую и рекомендую java.time, современный Java API даты и времени.

Если вам необходим старомодный объект java.util.Date (и только в этом случае), преобразуйте:

    Date oldFashionedDate = Date.from(endOfFebruary2010.toInstant());
    System.out.println(oldFashionedDate);

вс 28 февраля 23:59:59 CET 2010

Если вам нужно было только количество дней в каком-либо месяце (это было задано в дублирующем вопросе ):

    YearMonth ym = YearMonth.of(2011, Month.FEBRUARY);
    int numDays = ym.lengthOfMonth();
    System.out.println(numDays);

28

Как я понимаю, ваш настоящий вопрос был:

... Мне было интересно, действительно ли это было требуемое поведение. Какие-нибудь мысли

Я твердо верю, что желаемым поведением является то, что конструктор no-arg GregorianCalendar возвращает текущий день и текущее время дня. И это Calender.set() только устанавливает поля, которые вы явно задаете, и пытается сохранить другие поля без изменений. И это 31 февраля 2010 года переполняется в марте без каких-либо признаков ошибки, потому что в месяце было только 28 дней. Сочетание этих дизайнерских решений приводит меня к неизбежному выводу: поведение, которое вы наблюдали, является умышленным.

Если вы считаете, что это плохой дизайн, мы многие с вами согласны. По этой же причине замена Calendar и GregorianCalendar появилась на java.time четыре года назад. Вам никогда не понадобится снова использовать Calendar.

Резервирование: наш инструмент все еще зависит от Java 1.7

java.time прекрасно работает на Java 7. Для этого требуется как минимум Java 6 .

  • В Java 8 и более поздних версиях и на более новых устройствах Android (от 26 уровня API, как мне сказали) современный API поставляется встроенным.
  • В Java 6 и 7 получите ThreeTen Backport, бэкпорт новых классов (ThreeTen для JSR 310; см. Ссылки внизу).
  • На более старых (Android) версиях Android используется ThreeTen Backport. Это называется ThreeTenABP. И убедитесь, что вы импортируете классы даты и времени из org.threeten.bp с подпакетами.

Ссылки

1 голос
/ 31 мая 2010

Может быть setLenient(boolean lenient) разберутся за вас. Я получаю исключение при выполнении кода ниже.

Если нет, то Йода - лучший ответ.

import java.util.Calendar;

public class CalTest
{
    public static void main(String[] args)
    {
        // today is 2010/05/31
        Calendar cal = Calendar.getInstance();
        cal.setLenient(false);

        cal.set(Calendar.YEAR, 2010);
        cal.set(Calendar.MONTH, 1); // FEBRUARY

        cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH));
        cal.set(Calendar.HOUR_OF_DAY, cal.getActualMaximum(Calendar.HOUR_OF_DAY));
        cal.set(Calendar.MINUTE, cal.getActualMaximum(Calendar.MINUTE));
        cal.set(Calendar.SECOND, cal.getActualMaximum(Calendar.SECOND));
        cal.set(Calendar.MILLISECOND, cal.getActualMaximum(Calendar.MILLISECOND));

        System.out.println(cal.getTime());
    }
}
0 голосов
/ 29 апреля 2018

Проблема в том, что DAY_OF_MONTH основан на 1, день 0 на один день меньше!

0 голосов
/ 06 апреля 2016

Календарь начинается с текущего дня - 31 мая 2010 года в вашем примере. Если вы установите месяц на февраль, дата изменится на 31 февраля 2010 года, который нормализовался до 3 марта 2010 года, поэтому cal.getActualMaximum (Calendar.DAY_OF_MONTH) возвращает 31 для марта.

Calendar c = Calendar.getInstance();
c.set(Calendar.YEAR, 2010);
c.set(Calendar.MONTH, Calendar.MAY);
c.set(Calendar.DAY_OF_MONTH, 31);
System.out.println(c.getTime());
c.set(Calendar.MONTH, Calendar.FEBRUARY);
System.out.println(c.getTime());

выход:

Mon May 31 20:20:25 GMT+03:00 2010
Wed Mar 03 20:20:25 GMT+03:00 2010

Чтобы исправить свой код, вы можете добавить cal.clear (); или установите день 1..28 до установки месяца

0 голосов
/ 18 февраля 2014

Причина должна быть в том, что МЕСЯЦ имеет логическую структуру, похожую на перечисление. Вы можете легко заполнять и читать массивы / коллекции / списки. Из-за интернационализации он должен быть перечислимым (косвенный доступ). ДЕНЬ - это просто целое число с прямым доступом. В этом разница.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...