16 ответов:
Это просто часть ужасного беспорядка, который является Java Date / time API. Перечисление того, что с ним не так, займет очень много времени (и я уверен, что не знаю половины проблем). По общему признанию, работать с датами и временем сложно, но aaargh в любом случае.
сделайте себе одолжение и использовать Джода Времени вместо этого, или, возможно,JSR-310.
EDIT: что касается причин, почему - как отмечалось в других ответах, это вполне может быть связано со старыми API C или просто a общее ощущение начала всего с 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 месяцами... но, по крайней мере, указанный размер-это количество месяцев, которые вы ожидаете.
Это не хороший причина, но это a причина...
EDIT: в качестве комментария запрашивает некоторые идеи о том, что я думаю, неправильно с датой/календарем:
- удивительные основания (1900 как основание года в дате, по общему признанию для устаревшего конструкторы; 0 в качестве месячной базы в обоих)
- изменчивость-использование неизменяемых типов делает его много проще работать с тем, что действительно эффективно значения
- недостаточный набор типов: приятно иметь
DateиCalendarкак разные вещи, но разделение на "местных" против "зонированный" ценностей отсутствует, как и дата/время против против времени- API, который приводит к уродливому коду с магическими константами, а не явно именованные методы
- API, который очень трудно рассуждать о - все дело о том, когда вещи пересчитываются и т. д.
- использовать конструкторы без параметров по умолчанию "сейчас", что приводит к трудно проверить код
- The
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: потому что месяцы имеют имена и дни месяца не.
в Java 8 есть новый API даты/времени JSR 310 это более разумно. Spec lead-это то же самое, что и основной автор JodaTime, и они разделяют много похожих концепций и шаблонов.
Я бы сказал, что лень. Массивы начинаются с 0 (все это знают); месяцы года-это массив, что заставляет меня полагать, что какой-то инженер в Sun просто не потрудился поместить эту маленькую тонкость в код Java.
потому что программисты одержимы индексами на основе 0. Хорошо, это немного сложнее: это имеет больше смысла, когда вы работаете с логикой более низкого уровня, чтобы использовать индексацию на основе 0. Но по большому счету, я все равно буду придерживаться своего первого предложения.
что из нижеследующего, скорее всего, будет правильным?
if (date.getMonth() == 3) out.print("March"); if (date.getMonth() == Calendar.MARCH) out.print("March");это иллюстрирует одну вещь, которая меня немного раздражает в Joda Time - это может побудить программистов думать в условиях жестко заданных констант. (Правда, совсем немного. Это не так, как если бы Джода принуждение программисты плохо программу.)
java.util.MonthJava предоставляет вам другой способ использовать индексы на основе 1 в течение нескольких месяцев. Используйте
java.time.Monthперечисление. Один объект предопределен для каждого из двенадцати месяцев. У них есть номера, присвоенные каждому 1-12 за январь-декабрь; звонитеgetValueдля количества.использовать
Month.JULY(дает 7) вместоCalendar.JULY(дает вам 6).(import java.time.*;)
tl; dr
Month.FEBRUARY.getValue() // February → 2.2
подробности
The ответ от Jon скит является правильным.
теперь у нас есть современная замена для тех проблемных старых классов даты-времени наследия: java.время классы.
java.time.Monthсреди этих классов является
Monthперечисление. Перечисление содержит один или несколько предопределенных объектов, объектов которые автоматически создаются при загрузке класса. НаMonthу нас есть дюжина таких объектов, каждому дано имя:JANUARY,FEBRUARY,MARCHи так далее. Каждый из них являетсяstatic final publicкласс постоянные. Вы можете использовать и передавать эти объекты в любом месте вашего кода. Пример:someMethod( Month.AUGUST )к счастью, у них есть нормальная нумерация, 1-12, где 1-январь и 12-декабрь.
получить , теперь режим обслуживания, советует миграцию на java.время.
узнать больше, смотрите Oracle Tutorial. Поиск переполнения стека для многих примеров и объяснений. Спецификация является JSR 310.
где получить java.время занятий?
- 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.
- посмотреть как использовать....
The ThreeTen-Extra проект расширяет java.время с дополнительными занятиями. Этот проект является испытательным полигоном для возможных будущих дополнений к java.время. Вы можете найти некоторые полезные классы, такие как
Interval,YearWeek,YearQuarterи больше.
для меня никто не объясняет это лучше, чем mindpro.com:
Gotchas
java.util.GregorianCalendarимеет гораздо меньше багов и глюков, чемold java.util.Dateкласс, но это еще не подарок.были ли программисты, когда летнее время было первым предложенный, они наложили бы вето на это как безумное и неподатливое. С летнее время, есть фундаментальная двусмысленность. Осенью, когда вы установить часы на один час назад в 2 часа ночи есть два разных мгновения во времени оба вызвали 1: 30 по местному времени. Вы можете сказать им, отдельно только если вы записываете ли вы намеревались переход на летнее время или стандартное время с чтением.
к сожалению, нет никакого способа, чтобы сказать
GregorianCalendarчто вы предназначенный. Вы должны прибегнуть к тому, чтобы сказать ему местное время с манекеном Часовой пояс UTC, чтобы избежать двусмысленности. Программисты, как правило, закрывают свои глаза на эту проблему и просто надеюсь, что никто не делает ничего за это час.ошибка тысячелетия. Ошибки все еще не вышли из классов календаря. Даже в 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имеет rawGregorianCalendar.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. разбор ведет себя традиционным способом с января=1.
в дополнение к ответу dannysmurf о лени, я добавлю, что это побуждает вас использовать константы, такие как
Calendar.JANUARY.
Он точно не определен как ноль как таковой, он определен как календарь.Январь. Это проблема использования ints в качестве констант вместо перечислений. Календарь.Январь == 0.
потому что язык написания сложнее, чем кажется, и время обработки, в частности, намного сложнее, чем думает большинство людей. Для небольшой части проблемы (на самом деле, не Java), смотрите видео YouTube "проблема с временем и часовыми поясами-Computerphile" в https://www.youtube.com/watch?v=-5wpm-gesOY. Не удивляйтесь, если ваша голова отвалится от смеха в замешательстве.
потому что все начинается с 0. Это основной факт программирования на Java. Если бы одна вещь отклонялась от этого, то это привело бы к целому шлейфу путаницы. Давайте не будем спорить о формировании и код с ними.
Comments