В java.util.Calendar
, январь определяется как месяц 0, а не месяц 1. Есть ли какая-то конкретная причина для этого?
Я видел, как многие люди путались в этом...
В java.util.Calendar
, январь определяется как месяц 0, а не месяц 1. Есть ли какая-то конкретная причина для этого?
Я видел, как многие люди путались в этом...
Это просто часть ужасающего беспорядка, который является 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 месяцами... но, по крайней мере, указанный размер - это количество ожидаемых месяцев.
Это не очень хорошая причина, но это причина...
РЕДАКТИРОВАТЬ: В качестве комментария к запросам некоторые идеи о том, что я думаю, ошибочны в Date/Calendar:
Date
и Calendar
как разные вещи,
но разделение "локальных" и "зональных" значений отсутствует, так как дата/время против даты и времениDate.toString()
, которая всегда использует системный локальный часовой пояс (который ранее путал многих пользователей)Потому что математика с месяцами намного проще.
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
Все месяцы работают одинаково, а работа вокруг не нужна.
Языки C на языке копируют C в некоторой степени. Структура tm
(определенная в time.h
) имеет целое поле tm_mon
с диапазоном (commented) 0-11.
Языки, основанные на языке C, запускают массивы с индексом 0. Таким образом, это удобно для вывода строки в массиве имен месяцев, а tm_mon
- как индекс.
На это было много ответов, но я дам свое мнение по этому вопросу в любом случае.
Причина этого нечетного поведения, как говорилось ранее, исходит от POSIX C time.h
, где месяцы, когда они хранятся в int с диапазоном 0-11.
Чтобы объяснить, почему, посмотрите на это так: годы и дни считаются числами на разговорном языке, но у месяцев есть свои имена. Поэтому, поскольку январь является первым месяцем, он будет сохранен как смещение 0, первый элемент массива. monthname[JANUARY]
будет "January"
. Первый месяц в году - это элемент массива первого месяца.
Число дней, с другой стороны, так как у них нет имен, сохранение их в int как 0-30 будет путать, добавить много команд day+1
для вывода и, конечно же, быть склонным к большому количеству ошибок.
При этом непоследовательность запутывает, особенно в javascript (который также унаследовал эту "функцию" ), язык сценариев, где это должно быть абстрагировано далеко от langague.
TL; DR. Поскольку в месяцах имена и дни месяца этого не делают.
В Java 8 есть новый API даты/времени JSR 310, который является более разумным. Спецификация ведет себя так же, как основной автор JodaTime, и у них много общих концепций и шаблонов.
Я бы сказал, лень. Массивы начинаются с 0 (все это знают); месяцы года - это массив, который заставляет меня поверить, что какой-то инженер из Sun просто не потрудился поставить эту маленькую мелочь в код Java.
Возможно, потому, что C "struct tm" делает то же самое.
Потому что программисты одержимы индексами 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
enum. Один объект предопределен для каждого из двенадцати месяцев. У них есть номера, присвоенные каждому 1-12 за январь-декабрь; вызовите getValue
для номера.
Использовать Month.JULY
(дает вам 7)
вместо Calendar.JULY
(дает вам 6).
(import java.time.*;)
Month.FEBRUARY.getValue() // February → 2.
2
Ответ Джона Скита правильный.
Теперь у нас есть современная замена этим проблемным старым классам даты и времени: классы java.time.
java.time.Month
Среди этих классов перечисление Month
. Перечисление содержит один или несколько предопределенных объектов - объектов, которые автоматически создаются при загрузке класса. В Month
у нас есть дюжина таких объектов, каждому из которых дано имя: JANUARY
, FEBRUARY
, MARCH
и так далее. Каждый из них является static final public
константой 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.
Многие другие удобные методы в этом классе, такие как определение количества дней в каждом месяце. Класс может даже генерировать локализованное имя месяца.
Вы можете получить локализованное название месяца, различной длины или сокращения.
String output =
Month.FEBRUARY.getDisplayName(
TextStyle.FULL ,
Locale.CANADA_FRENCH
);
Février
Кроме того, вы должны передавать объекты этого перечисления вокруг вашей кодовой базы, а не просто целые числа. Это обеспечивает безопасность типов, обеспечивает допустимый диапазон значений и делает ваш код более самодокументируемым. См. Oracle Tutorial, если вы не знакомы с удивительно мощным средством перечисления в Java.
Вы также можете найти полезными классы Year
и YearMonth
.
Инфраструктура java.time встроена в Java 8 и более поздние версии. Эти классы вытеснять неприятные старые устаревшие классы даты и времени, такие как java.util.Date
, .Calendar
, и java.text.SimpleDateFormat
.
Проект Joda-Time, находящийся сейчас в режиме обслуживания, рекомендует перейти на java.time.
Чтобы узнать больше, смотрите Oracle Tutorial. И поиск для многих примеров и объяснений. Спецификация JSR 310.
Где взять классы java.time?
Проект ThreeTen-Extra расширяет java.time дополнительными классами. Этот проект является полигоном для возможных будущих дополнений к java.time. Здесь вы можете найти некоторые полезные классы, такие как Interval
, YearWeek
, YearQuarter
и другие.
Для меня никто не объясняет это лучше, чем mindpro.com:
Gotchas
java.util.GregorianCalendar
имеет гораздо меньше ошибок и ошибок, чемold java.util.Date
, но до сих пор нет пикника.Если бы были программисты, когда первый день перехода на летнее время предложили, они наложили бы вето на него как на безумного и неразрешимого. С летнее время, существует фундаментальная двусмысленность. Осенью, когда вы устанавливаете часы на один час в 2 часа ночи, есть два разных моментов времени, называемых 1:30 утра по местному времени. Вы можете сказать им кроме того, если вы записываете, планируете ли вы использовать летнее время или стандартное время с чтением.
К сожалению, нет способа сказать
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 магические константы. Эта техника полностью разрушает любую надежду проверка ошибок компиляции. Например, чтобы получить месяц, который вы используете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
не корректно соединяются. Вы должны дважды укажите календарь, косвенно, как дату.Если пользователь не настроил свой часовой пояс правильно, он будет по умолчанию тихо или PST или GMT.
В GregorianCalendar месяцы нумеруются начиная с января = 0, а не 1, как это делают все остальные на планете. Но дни начинаются с 1 как и дни недели с воскресеньем = 1, понедельник = 2,... суббота = 7. Все же Формат даты. parse ведет себя традиционным способом с January = 1.
В дополнение к ответу ланды DannySmurf, я добавлю, что это побуждает вас использовать константы, такие как Calendar.JANUARY
.
Он точно не определен как нуль как таковой, он определен как Calendar.January. Это проблема использования ints как констант, а не перечислений. Calendar.January == 0.
Потому что написание языка сложнее, чем кажется, а обработка времени, в частности, намного сложнее, чем думает большинство людей. Небольшую часть проблемы (на самом деле это не Java) смотрите в видео YouTube "Проблема с часами и часовыми поясами - компьютерный файл" по адресу https://www.youtube.com/watch?v=-5wpm-gesOY. Не удивляйтесь, если ваша голова отвалится от смеха в замешательстве.
Потому что все начинается с 0. Это основной факт программирования на Java. Если бы одно было отклоняться от этого, то это привело бы к целому путанице. Не будем спорить о их формировании и кодировать с ними.