Это просто часть ужасающего беспорядка, который является API дат / времени Java. Перечислить, что с ним не так, займет очень много времени (и я уверен, что не знаю половины проблем). Разумеется, работа с датами и временем сложна, но все равно.
Сделайте себе одолжение и используйте 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 месяцами ... но, по крайней мере, указанный размер - это количество ожидаемых месяцев.
Это isn ' t a хорошая причина, но это причина ...
EDIT: В качестве комментариев типа запросов есть некоторые идеи о том, что я считаю неправильным Дата / Календарь:
Date
и Calendar
как разные вещи, но отсутствует разделение «локальных» и «зональных» значений, а также дата / время и дата против времени Date.toString()
, которая всегда использует локальный часовой пояс системы (это путают многие Stack Переполнение пользователей до сих пор) В Java 8 появился новый API Дата / Время JSR 310 , который более разумен. Спектр свинца такой же, как и основной автор JodaTime, и они имеют много похожих концепций и шаблонов.
Поскольку математика с месяцами намного проще.
1 месяц после декабря - январь, но, чтобы понять это, вы должны были бы взять номер месяца и сделать математику
12 + 1 = 13 // What month is 13?
Я знаю! Я могу исправить это быстро, используя модуль 12.
(12 + 1) % 12 = 1
Это работает отлично в течение 11 месяцев до ноября ...
(11 + 1) % 12 = 0 // What month is 0?
Вы можете сделать все это работаем снова, вычитая 1 до того, как вы добавите месяц, затем выполните свой модуль и, наконец, добавьте 1 обратно ... aka обходите основную проблему.
((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
Все месяцы работают одинаково, а работа вокруг не нужна.
Month.FEBRUARY.getValue() // February → 2.
2
Подробности
Ответа на этот вопрос Jon Skeet .
Теперь у нас есть современная замена для этих неприятных старых устаревших классов времени: классы java.time .
java.time.Month
Среди этих классов есть
Month
перечисление . Перечисление содержит один или несколько предопределенных объектов, объекты, которые автоматически создаются при загрузке класса. НаMonth
у нас есть дюжина таких объектов, каждая из которых имеет имя:JANUARY
,FEBRUARY
,MARCH
и т. Д. Каждая из них является константой классаstatic final public
. Вы можете использовать и передавать эти объекты в любом месте вашего кода. Пример: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.
Многие другие удобные методы для этого класса, такие как зная количество дней в каждом месяце . Класс может даже генерировать локализованное имя месяца.
Вы можете получить локализованное имя месяца в разных длинах или аббревиатурах.
< blockquote>String output = Month.FEBRUARY.getDisplayName( TextStyle.FULL , Locale.CANADA_FRENCH );
février
Кроме того, вы должны передавать объекты этого перечисления вокруг своей базы кода, а не просто целые числа. Это обеспечивает безопасность типов, обеспечивает допустимый диапазон значений и делает ваш код более самодокументированным. См. Учебник Oracle , если он не знаком с удивительно мощным средством перечисления в Java.
Вы также можете найти полезные
Year
иYearMonth
.
О java.time
Структура java.time встроена в Java 8 и более поздние версии. Эти классы вытесняют неприятные старые классы времени legacy , такие как
java.util.Date
,.Calendar
и & amp;java.text.SimpleDateFormat
.Проект Joda-Time , теперь в режиме обслуживания , советует перейти на java.time.
Чтобы узнать больше, см. учебное пособие Oracle . И поиск Stack Overflow для многих примеров и объяснений. Спецификация JSR 310 .
Где получить классы java.time?
- Java SE 8 и SE 9 и более поздние версии. Часть стандартного Java API с интегрированной реализацией. Java 9 добавляет некоторые незначительные функции и исправления.
- Java SE 6 и SE 7 Большая часть функций java.time возвращается в Java 6 & amp; 7 в ThreeTen-Backport .
- Android Проект ThreeTenABP адаптирует ThreeTen-Backport (упомянутый выше) специально для Android. См. Как использовать ... .
Проект ThreeTen-Extra расширяет java.time с дополнительными классами. Этот проект является доказательством возможных будущих дополнений к java.time. Здесь вы можете найти полезные классы, такие как
Interval
,YearWeek
,YearQuarter
и more .
Потому что программисты одержимы индексами 0. Хорошо, это немного сложнее: имеет смысл, когда вы работаете с логикой более низкого уровня, чтобы использовать индексирование на основе 0. Но по большому счету, я по-прежнему придерживаюсь своего первого предложения.
Для меня никто не объясняет это лучше, чем mindpro.com :
Gotchas
java.util.GregorianCalendar
имеет гораздо меньше ошибок и ошибок, чем классold java.util.Date
, но он все еще не является пикником.Если бы были программисты, когда впервые предлагалось переход на летнее время, они наложили бы вето на него как на безумного и неразрешимого. При дневном свете существует фундаментальная двусмысленность. Осенью, когда вы устанавливаете часы на один час в 2 часа ночи, есть два разных момента времени, которые называются 1:30 AM по местному времени. Вы можете рассказать им обособленно, только если вы записываете, планируете ли вы дневное или стандартное время с чтением.
К сожалению, нет способа рассказать
GregorianCalendar
, который вы намеревались. Вы должны прибегать к тому, чтобы сообщать местное время с манекеном UTC TimeZone, чтобы избежать двусмысленности. Программисты обычно закрывают глаза на эту проблему и просто надеются, что в этот час никто ничего не делает.Ошибка тысячелетия. Ошибки все еще не из классов 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
имеет исходныйGregorianCalendar.get(Calendar.ZONE_OFFSET)
и дневной сбереженияGregorianCalendar. get( Calendar. DST_OFFSET)
, но не позволяет получить фактическое смещение часового пояса. Вы должны получить эти два отдельно и добавить их вместе.
GregorianCalendar.set( year, month, day, hour, minute)
не устанавливает секунды в 0.
DateFormat
иGregorianCalendar
не сливаются должным образом. Вы должны указать календарь дважды, косвенно, как Date.Если пользователь не настроил свой часовой пояс правильно, он по умолчанию будет тихо или PST или GMT.
В GregorianCalendar, Месяцы нумеруются начиная с января = 0, а не 1, как это делают все остальные на планете. Но дни начинаются с 1, как и дни недели с воскресеньем = 1, понедельник = 2, ... Суббота = 7. Тем не менее DateFormat. parse ведет себя традиционным способом с января = 1.
Он точно не определен как нуль как таковой, он определен как Calendar.January. Это проблема использования ints как констант, а не перечислений. Calendar.January == 0.
Лично я принял странность API календаря Java как признак того, что мне нужно было отвлечься от григорианского мышления и попытаться более агрессивно программировать в этом отношении. В частности, я снова научился избегать жестко закодированных констант для таких вещей, как месяцы.
Какое из следующего, скорее всего, будет правильным?
if (date.getMonth() == 3) out.print("March");
if (date.getMonth() == Calendar.MARCH) out.print("March");
Это иллюстрирует одну вещь, которая раздражает меня немного о Joda Time - это может побудить программистов думать в терминах жестко закодированных констант. (Только немного, но это не так, как если бы Joda заставлял программистов плохо программировать.)
Возможно, потому, что C "struct tm" делает то же самое.
На это было много ответов, но я дам свое мнение по этому вопросу в любом случае. Причина этого нечетного поведения, как говорилось ранее, исходит от POSIX C time.h
, где указаны месяцы, когда они хранятся в int с диапазоном 0-11. Чтобы объяснить, почему, посмотрите на это так: годы и дни считаются числами на разговорном языке, но у месяцев есть свои имена. Поэтому, поскольку январь является первым месяцем, он будет сохранен как смещение 0, первый элемент массива. monthname[JANUARY]
будет "January"
. Первый месяц в году - это элемент массива первого месяца.
Число дней, с другой стороны, поскольку у них нет имен, сохранение их в int как 0-30 будет путать, добавьте много day+1
инструкций для вывода и, конечно же, склонны к большому количеству ошибок.
Как говорится, непоследовательность запутывает, особенно в javascript (который также унаследовал эту «функцию») язык сценариев, где это должно быть абстрагировано далеко от langague.
TL; DR: Поскольку в месяцах имена и дни месяца этого не делают.
В дополнение к ответу ланды DannySmurf, я добавлю, что это побуждает вас использовать константы, такие как Calendar.JANUARY
.
Языки C на языке копируют C в некоторой степени. Структура tm
(определенная в time.h
) имеет целочисленное поле tm_mon
с диапазоном (commented) от 0-11.
Языки на основе C начинаются с массива с индексом 0. Так что это было удобно для вывода строки в массиве имен месяцев, а tm_mon
в качестве индекса.
Поскольку все начинается с 0. Это основной факт программирования на Java. Если бы одно было отклоняться от этого, то это привело бы к целому путанице. Давайте не будем спорить о их формировании и кодексах с ними.
Я бы сказал, лень. Массивы начинаются с 0 (все это знают); месяцы года - это массив, который заставляет меня поверить, что какой-то инженер из Sun просто не потрудился поставить эту маленькую мелочь в код Java.
Поскольку языковая запись сложнее, чем выглядит, а время обработки в частности намного сложнее, чем думают многие. Для небольшой части проблемы (на самом деле, а не Java) см. Видео на YouTube «Проблема со временем и часовыми поясами - компьютерная печать» на странице https://www.youtube.com/watch?v=-5wpm -gesOY . Не удивляйтесь, если ваша голова упадет от смеха в замешательстве.