В java.util.Calendar
январе январь определяется как месяц 0, а не месяц 1. Есть ли для этого какая-либо конкретная причина?
Я видел, как многие люди запутались в этом ...
В java.util.Calendar
январе январь определяется как месяц 0, а не месяц 1. Есть ли для этого какая-либо конкретная причина?
Я видел, как многие люди запутались в этом ...
Ответы:
Это всего лишь часть ужасного беспорядка, который является API даты / времени Java. Список того, что с ним не так, занял бы очень много времени (и я уверен, что я не знаю и половины проблем). По общему признанию, работать с датами и временем сложно, но в любом случае.
Сделайте себе одолжение и используйте вместо этого Joda Time или, возможно, JSR-310 .
РЕДАКТИРОВАТЬ: Что касается причин, почему - как отмечалось в других ответах, это вполне может быть связано со старыми 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.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 ... так что обойти основную проблему.
((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
Все месяцы работают одинаково и обходить не нужно.
((11 - 1 + 1) % 12) + 1 = 12
просто в (11 % 12) + 1
течение месяцев 1..12 вам просто нужно добавить 1 после выполнения по модулю. Никакой магии не требуется.
Языки на основе Си до некоторой степени копируют Си. tm
Структура (определенно в time.h
) имеет целое поле tm_mon
с (комментариями) диапазоном 0-11.
Для языков на основе C массивы начинаются с индекса 0. Таким образом, это было удобно для вывода строки в массиве названий месяцев с tm_mon
индексом.
На это было много ответов, но я все равно выскажу свое мнение по этому вопросу. Причина этого странного поведения, как указано ранее, исходит от POSIX C, time.h
где месяцы хранятся в целых числах с диапазоном 0-11. Чтобы объяснить почему, посмотрите на это так; годы и дни считаются числами в разговорной речи, но месяцы имеют свои собственные названия. Так как январь является первым месяцем, он будет сохранен как смещение 0, первый элемент массива. monthname[JANUARY]
будет "January"
. Первый месяц в году является первым элементом массива месяца.
Числа дня, с другой стороны, поскольку у них нет имен, хранение их в целых числах от 0 до 30 может привести к путанице, добавить много day+1
инструкций для вывода и, конечно, быть склонным к множеству ошибок.
При этом несоответствие сбивает с толку, особенно в javascript (который также унаследовал эту «особенность»), языке сценариев, где его следует абстрагировать далеко от langague.
TL; DR : потому что месяцы имеют названия, а дни месяца - нет.
Я бы сказал, лень. Массивы начинаются с 0 (это знают все); месяцы года - это массив, и это заставляет меня поверить, что какой-то инженер в Sun просто не удосужился внести эту маленькую тонкость в код Java.
Вероятно, потому что C "struct tm" делает то же самое.
Потому что программисты помешаны на индексах, основанных на 0. Хорошо, это немного сложнее, чем это: имеет смысл, когда вы работаете с низкоуровневой логикой, чтобы использовать индексирование на основе 0. Но по большому счету я все же буду придерживаться своего первого предложения.
Лично я принял странность API календаря Java как указание на то, что мне нужно отделиться от григорианско-центрированного мышления и попытаться программировать в этом отношении более агностично. В частности, я снова научился избегать закодированных констант для таких вещей, как месяцы.
Что из следующего наиболее вероятно будет правильным?
if (date.getMonth() == 3) out.print("March");
if (date.getMonth() == Calendar.MARCH) out.print("March");
Это иллюстрирует одну вещь, которая немного раздражает меня в Joda Time - это может побудить программистов думать в терминах жестко закодированных констант. (Хотя и немного. Это не так, как будто Joda заставляет программистов плохо программировать.)
Для меня никто не объясняет это лучше, чем mindpro.com :
Gotchas
java.util.GregorianCalendar
имеет гораздо меньше ошибок и ошибок, чемold java.util.Date
класс, но это все еще не пикник.Если бы программисты впервые предложили переход на летнее время, они бы наложили вето на это безумие и неразрешимость. С переходом на летнее время возникает фундаментальная неопределенность. Осенью, когда вы переводите часы на один час в 2 часа ночи, появляются два разных момента времени, которые называются 1:30 утра по местному времени. Вы можете различить их только в том случае, если записали, предполагали ли вы переход на летнее или стандартное время с показаниями.
К сожалению, нет способа сказать,
GregorianCalendar
что вы намеревались. Вы должны прибегнуть к указанию местного времени с помощью фиктивной UTC TimeZone, чтобы избежать двусмысленности. Программисты обычно закрывают глаза на эту проблему и просто надеются, что никто не сделает ничего в течение этого часа.Ошибка тысячелетия Ошибки все еще не из классов Календаря. Даже в 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. Все же DateFormat. parse ведет себя традиционным образом с января = 1.
java.util.Month
Java предоставляет вам другой способ использования индексов на основе 1 в течение нескольких месяцев. Используйте java.time.Month
перечисление. Один объект предопределен для каждого из двенадцати месяцев. У них есть номера, присвоенные каждому 1-12 на январь-декабрь; позвоните getValue
по номеру.
Используйте Month.JULY
(Дает вам 7) вместо Calendar.JULY
(Дает вам 6).
(import java.time.*;)
Month.FEBRUARY.getValue() // February → 2.
2
Ответа на этот вопрос Jon тарелочкам является правильным.
Теперь у нас есть современная замена этим проблемным старым классам даты и времени: классы java.time .
java.time.Month
Среди этих классов есть enum . Перечисление содержит один или несколько предопределенных объектов, объектов, которые автоматически создаются при загрузке класса. На нас есть десяток таких объектов, каждый дал имя: , , , и так далее. Каждый из них является константой класса. Вы можете использовать и передавать эти объекты в любом месте вашего кода. Пример: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.
Многие другие удобные методы в этом классе, такие как определение количества дней в каждом месяце . Класс может даже генерировать локализованное имя месяца.
Вы можете получить локализованное название месяца, различной длины или сокращения.
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
, и более .
Оно не определено как ноль само по себе, оно определено как Calendar.January. Это проблема использования int как констант вместо перечислений. Календарь. Январь == 0.
Потому что написание языка сложнее, чем кажется, а обработка времени, в частности, намного сложнее, чем думает большинство людей. Небольшую часть проблемы (на самом деле это не Java) смотрите в видео на YouTube «Проблема с часами и часовыми поясами - компьютерный файл» по адресу https://www.youtube.com/watch?v=-5wpm-gesOY . Не удивляйтесь, если ваша голова отвалится от смеха в замешательстве.
В дополнение к ответу DannySmurf о лени, я добавлю, что это побуждает вас использовать константы, такие как Calendar.JANUARY
.
Потому что все начинается с 0. Это основной факт программирования на Java. Если что-то будет отклоняться от этого, то это приведет к путанице. Давайте не будем спорить о формировании их и кодировать их.