Почему январь месяц 0 в Java-календаре?

В java.util.Calendar январь определяется как месяц 0, а не месяц 1. Есть ли какая-то конкретная причина для этого?

Я видел, как многие люди путаются в этом …

Это всего лишь часть ужасающего беспорядка, который представляет собой API дат / времени Java. Перечислить, что с ним не так, займет очень много времени (и я уверен, что не знаю половины проблем). По общему признанию, работа с датами и временем сложна, но aaargh в любом случае.

Сделайте себе одолжение и используйте Joda Time вместо этого, или, возможно, JSR-310 .

EDIT: Что касается причин, почему, как отмечено в других ответах, это может быть связано с старыми API-интерфейсами C, или просто с общим чувством начала всего от 0 … кроме того, что дни начинаются с 1, конечно. Я сомневаюсь, что кто-то, кто находится за пределами первоначальной команды по внедрению, действительно может объяснить причины, но я настоятельно призываю читателей не беспокоиться о том, почему были приняты плохие решения, чтобы посмотреть на всю гамму гадости в java.util.Calendar и найти что-то лучшее.

Один момент, который заключается в использовании индексов на основе 0, заключается в том, что он упрощает такие вещи, как «массивы имен»:

 // I "know" there are 12 months String[] monthNames = new String[12]; // and populate... String name = monthNames[calendar.get(Calendar.MONTH)]; 

Конечно, это не удается, как только вы получите календарь с 13 месяцами … но, по крайней мере, указанный размер – это количество ожидаемых месяцев.

Это не очень хорошая причина, но это причина …

EDIT: В качестве комментария, типа запросов некоторые идеи о том, что я думаю, неправильно с Date / Calendar:

  • Удивительные базы (1900 год как база года в Дате, по общему признанию, для устаревших конструкторов, 0 в качестве базы месяца в обоих)
  • Mutability – использование неизменяемых типов упрощает работу с действительно эффективными значениями
  • Недостаточный набор типов: приятно иметь Date и Calendar как разные вещи, но разделение «локальных» и «зональных» значений отсутствует, так как дата / время против даты и времени
  • API, который приводит к уродливому коду с магическими константами, вместо явно названных методов
  • API, о котором очень сложно рассуждать – все дело о том, когда вещи пересчитываются и т. Д.
  • Использование конструкторов без параметров по умолчанию «сейчас», что приводит к жесткому тестированию кода
  • Реализация Date.toString() которая всегда использует локальный часовой пояс системы (это путало многих пользователей переполнения стека до сих пор)

Языки C на языке копируют C в некоторой степени. Структура tm (определенная во time.h ) имеет целое поле tm_mon с (прокомментированным) диапазоном 0-11.

Языки, основанные на языке C, запускают массивы с индексом 0. Таким образом, это было удобно для вывода строки в массиве имен месяцев, а tm_mon как индекс.

Потому что математика с месяцами намного проще.

1 месяц после декабря – январь, но, чтобы понять это, вы обычно должны взять номер месяца и сделать математику

 12 + 1 = 13 // What month is 13? 

Я знаю! Я могу исправить это быстро, используя модуль 12.

 (12 + 1) % 12 = 1 

Это работает отлично в течение 11 месяцев до ноября …

 (11 + 1) % 12 = 0 // What month is 0? 

Вы можете сделать всю эту работу снова, вычитая 1 до того, как вы добавите месяц, затем выполните свой модуль и, наконец, добавьте 1 назад … иначе обходите основную проблему.

 ((11 - 1 + 1) % 12) + 1 = 12 // Lots of magical numbers! 

Теперь давайте подумаем о проблеме с месяцами 0 – 11.

 (0 + 1) % 12 = 1 // February (1 + 1) % 12 = 2 // March (2 + 1) % 12 = 3 // April (3 + 1) % 12 = 4 // May (4 + 1) % 12 = 5 // June (5 + 1) % 12 = 6 // July (6 + 1) % 12 = 7 // August (7 + 1) % 12 = 8 // September (8 + 1) % 12 = 9 // October (9 + 1) % 12 = 10 // November (10 + 1) % 12 = 11 // December (11 + 1) % 12 = 0 // January 

Все месяцы работают одинаково, а работа вокруг не нужна.

На это было много ответов, но я дам свое мнение по этому вопросу в любом случае. Причина этого нечетного поведения, как говорилось ранее, исходит из POSIX C time.h где месяцы, когда они хранятся в int с диапазоном 0-11. Чтобы объяснить, почему, посмотрите на это так: годы и дни считаются числами на разговорном языке, но у месяцев есть свои имена. Поэтому, поскольку январь является первым месяцем, он будет сохранен как смещение 0, первый элемент массива. monthname[JANUARY] будет "January" . Первый месяц в году – это элемент первого месяца.

Число дней, с другой стороны, поскольку у них нет имен, сохранение их в int как 0-30 будет путать, добавить много day+1 инструкцию для вывода и, конечно же, быть склонным к большому количеству ошибок.

При этом непоследовательность запутывает, особенно в javascript (который также унаследовал эту «функцию»), язык сценариев, где это должно быть абстрагировано далеко от langague.

TL; DR : Поскольку месяцы имеют имена и дни месяца, нет.

Я бы сказал, лень. Массивы начинаются с 0 (все это знают); месяцы года – это массив, который заставляет меня поверить, что какой-то инженер в Sun просто не потрудился поставить эту маленькую мелочь в код Java.

Вероятно, потому, что C struct tm делает то же самое.

В Java 8 существует новый API JSP 310 Date / Time, который более эффективен. Спектр свинца такой же, как и у первого автора JodaTime, и они имеют много похожих концепций и шаблонов.

Потому что программисты одержимы индексами на основе 0. Хорошо, это немного сложнее: имеет смысл, когда вы работаете с логикой более низкого уровня, чтобы использовать индексирование на основе 0. Но в целом, я по-прежнему буду придерживаться своего первого предложения.

Лично я принял странность Java-API календаря как признак того, что мне нужно было отвлечься от григорианского мышления и попытаться более агрессивно программировать в этом отношении. В частности, я снова научился избегать жестко закодированных констант для таких вещей, как месяцы.

Что из следующего более вероятно, будет правильным?

 if (date.getMonth() == 3) out.print("March"); if (date.getMonth() == Calendar.MARCH) out.print("March"); 

Это иллюстрирует одну вещь, которая немного раздражает меня по поводу Joda Time – это может побудить программистов мыслить в терминах жестко закодированных констант. (Только немного, но это не так, как если бы Джода заставляла программистов плохо программировать.)

java.util.Month

Java предоставляет вам другой способ использования индексов на основе 1 в течение нескольких месяцев. Используйте java.time.Month перечисление. Один объект предопределен для каждого из двенадцати месяцев. У них есть номера, присвоенные каждому 1-12 за январь-декабрь; вызовите getValue для числа.

Используйте Month.JULY (дает вам 7) вместо Calendar.JULY (дает вам 6).

 (import java.time.*;) 

Для меня никто не объясняет это лучше, чем mindpro.com :

Gotchas

java.util.GregorianCalendar имеет гораздо меньше ошибок и gotchas, чем old java.util.Date class old java.util.Date но до сих пор нет пикника.

Если бы были программисты, когда было предложено «Летнее время», они наложили бы вето на него как на безумного и неразрешимого. При дневном свете существует фундаментальная двусмысленность. Осенью, когда вы устанавливаете часы на один час в 2 часа ночи, есть два разных момента времени, которые называются 1:30 AM по местному времени. Вы можете рассказать им обособленно, только если вы записываете, планируете ли вы дневное или стандартное время с чтением.

К сожалению, вы не можете сказать GregorianCalendar который вы намеревались. Вы должны прибегать к тому, чтобы сообщать местное время с манекеном UTC TimeZone, чтобы избежать двусмысленности. Программисты обычно закрывают глаза на эту проблему и просто надеются, что никто ничего не сделает в течение этого часа.

Ошибка тысячелетия. Ошибки все еще не из classов Calendar. Даже в JDK (Java Development Kit) 1.3 есть ошибка 2001 года. Рассмотрим следующий код:

 GregorianCalendar gc = new GregorianCalendar(); gc.setLenient( false ); /* Bug only manifests if lenient set false */ gc.set( 2001, 1, 1, 1, 0, 0 ); int year = gc.get ( Calendar.YEAR ); /* throws exception */ 

Ошибка исчезла в 7 утра 2001/01/01 для MST.

GregorianCalendar контролируется гигантским кучей нетипизированных int magic констант. Этот метод полностью уничтожает любую надежду на проверку ошибок во время компиляции. Например, чтобы получить месяц, вы используете GregorianCalendar. get(Calendar.MONTH)); GregorianCalendar. get(Calendar.MONTH));

GregorianCalendar имеет исходный GregorianCalendar.get(Calendar.ZONE_OFFSET) и дневной свет GregorianCalendar. get( Calendar. DST_OFFSET) GregorianCalendar. get( Calendar. DST_OFFSET) , но не имеет способа получить фактическое смещение часового пояса. Вы должны получить эти два отдельно и добавить их вместе.

GregorianCalendar.set( year, month, day, hour, minute) не устанавливает секунды в 0.

DateFormat и GregorianCalendar не DateFormat правильно. Вы должны указать календарь дважды, косвенно, как Date.

Если пользователь не настроил свой часовой пояс правильно, он по умолчанию будет тихо или PST или GMT.

В GregorianCalendar месяцы нумеруются начиная с января = 0, а не 1, как это делают все остальные на планете. Но дни начинаются с 1, как и дни недели с воскресеньем = 1, понедельник = 2, … Суббота = 7. Тем не менее DateFormat. parse ведет себя традиционным способом с января = 1.

ТЛ; др

 Month.FEBRUARY.getValue() // February → 2. 

2

Детали

Ответ Джона Скита правильный.

Теперь у нас есть современная замена тех неприятных старых устаревших classов времени: classы java.time .

java.time.Month

Среди этих classов – перечисление Month . Перечисление содержит один или несколько предопределенных объектов, объекты, которые автоматически создаются при загрузке classа. В течение Month у нас есть дюжина таких объектов, каждый из которых имеет имя: JANUARY , FEBRUARY , MARCH и т. Д. Каждая из них представляет собой static final public константу static final public classа. Вы можете использовать и передавать эти объекты в любом месте вашего кода. Пример: someMethod( Month.AUGUST )

К счастью, они имеют нормальную нумерацию, 1-12, где 1 – январь, а 12 – декабрь.

Получите объект Month для определенного номера месяца (1-12).

 Month month = Month.of( 2 ); // 2 → February. 

Идя в другом направлении, спросите объект Month за месяц.

 int monthNumber = Month.FEBRUARY.getValue(); // February → 2. 

Многие другие удобные методы в этом classе, такие как знание количества дней в каждом месяце . Класс может даже генерировать локализованное имя месяца.

Вы можете получить локализованное название месяца в разных длинах или аббревиатурах.

 String output = Month.FEBRUARY.getDisplayName( TextStyle.FULL , Locale.CANADA_FRENCH ); 

Février

Кроме того, вы должны передавать объекты этого enums вокруг своей базы кода, а не просто целые числа . Это обеспечивает безопасность типов, обеспечивает допустимый диапазон значений и делает ваш код более самодокументированным. См. Учебник Oracle, если он не знаком с удивительно мощным средством enums в Java.

Вы также можете найти classы Year и YearMonth .


О java.time

Рамка java.time встроена в Java 8 и более поздние версии . Эти classы вытесняют неприятные старые устаревшие classы времени, такие как java.util.Date , .Calendar , & java.text.SimpleDateFormat .

Проект Joda-Time , теперь в режиме обслуживания , советует перейти на java.time.

Чтобы узнать больше, ознакомьтесь с учебным пособием Oracle . И поиск Stack Overflow для многих примеров и объяснений. Спецификация – JSR 310 .

Где можно получить classы java.time?

  • Java SE 8 и SE 9 и более поздние версии
    • Встроенный.
    • Часть стандартного Java API с интегрированной реализацией.
    • Java 9 добавляет некоторые незначительные функции и исправления.
  • Java SE 6 и SE 7
    • Большая часть функциональных возможностей java.time включена обратно в Java 6 и 7 в ThreeTen-Backport .
  • Android
    • Проект ThreeTenABP адаптирует ThreeTen-Backport (упомянутый выше) специально для Android.
    • См. Как использовать ….

Проект ThreeTen-Extra расширяет java.time с дополнительными classами. Этот проект является доказательством возможных будущих дополнений к java.time. Здесь вы можете найти полезные classы, такие как Interval , YearWeek , YearQuarter и другие .

В дополнение к ответу ДэнниСмурфа о лени, я добавлю, что это побуждает вас использовать константы, такие как Calendar.JANUARY .

Он точно не определен как нуль как таковой, он определен как Calendar.January. Это проблема использования ints как констант, а не перечислений. Calendar.January == 0.

Потому что написание языка сложнее, чем кажется, а время обработки в частности намного сложнее, чем думают многие. Для небольшой части проблемы (на самом деле, а не Java) см. Видео на YouTube «Проблема с временем и часовыми поясами – компьютерная печать» на странице https://www.youtube.com/watch?v=-5wpm-gesOY . Не удивляйтесь, если ваша голова упадет от смеха в замешательстве.

Потому что все начинается с 0. Это основной факт программирования на Java. Если бы одно было отклоняться от этого, то это привело бы к целому путанице. Давайте не будем спорить о их формировании и кодировании.

  • .NET. Получите все элементы календаря Outlook.
  • Как получить предыдущий месяц и годы в java?
  • Давайте будем гением компьютера.