java-календарь, добавляющий 1 месяц, когда насмехается, кто-нибудь видел это раньше? [Дубликат]

261
задан Stéphane Bonniez 5 December 2008 в 17:23
поделиться

16 ответов

Это просто часть ужасающего беспорядка, который является 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: В качестве комментариев типа запросов есть некоторые идеи о том, что я считаю неправильным Дата / Календарь:

  • Удивительные базы (1900 как база года в дате, по общему признанию, для устаревших конструкторов, 0 в качестве базы месяца в обоих)
  • Возможность использования - с использованием неизменяемых типов делает намного проще работать с тем, что действительно эффективно значения
  • Недостаточный набор типов: приятно иметь Date и Calendar как разные вещи, но отсутствует разделение «локальных» и «зональных» значений, а также дата / время и дата против времени
  • API, который приводит к уродливому коду с магическими константами вместо явно названных методов
  • API, о котором очень сложно рассуждать - все дело о том, когда вещи пересчитываются и т. д.
  • Использование конструкторов без параметров по умолчанию для «сейчас», что приводит к жесткому тестированию кода
  • Реализация Date.toString(), которая всегда использует локальный часовой пояс системы (это путают многие Stack Переполнение пользователей до сих пор)
297
ответ дан assylias 17 August 2018 в 09:45
поделиться
  • 1
    ... и что с устаревшими всеми полезными простыми методами Date? Теперь я должен использовать этот ужасный объект Calendar сложными способами, чтобы делать вещи, которые были простыми. – Brian Knoblauch 5 December 2008 в 18:22
  • 2
    @Brian: Я чувствую твою боль. Опять же, Joda Time проще :) (Фактор неизменности делает работу намного приятнее для работы). – Jon Skeet 5 December 2008 в 18:26
  • 3
    Downvoters: причины? – Jon Skeet 13 May 2009 в 22:52
  • 4
    Вы не ответили на вопрос. – Zeemee 2 February 2012 в 15:38
  • 5
    @ user443854: Я перечислил некоторые моменты в редактировании - посмотрите, помогает ли это. – Jon Skeet 9 February 2012 в 16:23

В Java 8 появился новый API Дата / Время JSR 310 , который более разумен. Спектр свинца такой же, как и основной автор JodaTime, и они имеют много похожих концепций и шаблонов.

9
ответ дан Alex Miller 17 August 2018 в 09:45
поделиться
  • 1
    Новый API Date Time теперь является частью Java 8 – mschenk74 25 May 2013 в 19:16

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

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

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

32
ответ дан arucker 17 August 2018 в 09:45
поделиться
  • 1
    Это удовлетворительно. По крайней мере, есть какое-то значение для этого безумия! – moljac024 9 July 2014 в 10:34
  • 2
    & quot; Множество магических чисел & quot; - Нет, это просто один, который появляется дважды. – user123444555621 20 August 2014 в 23:53
  • 3
    Возвращение в месяц по-прежнему довольно нехорошо, однако, благодаря неудачному использованию C «остатка», скорее, чем «модуль». Я также не уверен, как часто нужно набирать месяц без корректировки года, а месяцы идут 1-12, не создает проблем с `while (month & gt; 12) {month- = 12; год ++;} – supercat 14 September 2015 в 22:32
  • 4
    Поскольку разумные функции, такие как DateTime.AddMonths, слишком сложны для правильной реализации в lib, мы должны выполнить математику, которую вы описали сами ... Mmmmmkay – nsimeonov 30 March 2016 в 06:19
  • 5
    Кто-то делает математику где-то. Просто потому, что система имеет функцию, определенную для математики в месяцах, не означает, что одна и та же математика не была выполнена. Мой пример не состоял в том, чтобы побуждать людей делать математику в течение нескольких месяцев, это было только для того, чтобы показать, что я объявляю январь как 0. – arucker 11 April 2016 в 18:39

tl; dr

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.

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

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

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

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 .

3
ответ дан Basil Bourque 17 August 2018 в 09:45
поделиться

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

5
ответ дан Dinah 17 August 2018 в 09:45
поделиться
  • 1
    Это еще одна из тех идиом / привычек, которые идут way обратно на ассемблер или машинный язык, где все делается с точки зрения смещений, а не индексов. Обозначение массива стало сокращением для доступа к смежным блокам, начиная со смещения 0. – Ken Gentle 5 December 2008 в 18:56

Для меня никто не объясняет это лучше, чем 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.

1
ответ дан Edwin Dalorzo 17 August 2018 в 09:45
поделиться

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

0
ответ дан Pål GD 17 August 2018 в 09:45
поделиться
  • 1
    Значения одно и то же. API также могут возвращать 0, он идентичен константе. Calendar.JANUARY можно было бы определить как 1 - вот и все. Перечисление было бы хорошим решением, но настоящие перечисления не были добавлены к языку до Java 5, а Date - с самого начала. Это несчастливо, но вы действительно не можете «исправить». такой фундаментальный API, когда используется сторонний код. Лучшее, что можно сделать, это предоставить новый API и отказаться от старого, чтобы побудить людей двигаться дальше. Спасибо, Java 7 ... – Quinn Taylor 11 August 2009 в 04:20

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

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

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

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

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

4
ответ дан Paul Brinkley 17 August 2018 в 09:45
поделиться
  • 1
    Но какая схема с большей вероятностью даст вам головную боль, когда у вас нет постоянной в вашем коде - у вас есть значение, которое является результатом вызова веб-службы или чего-то еще. – Jon Skeet 5 December 2008 в 18:28
  • 2
    Разумеется, этот вызов веб-службы также должен использовать эту константу. :-) То же самое относится к любому внешнему абоненту. Как только мы установили, что существует несколько стандартов, необходимо обеспечить соблюдение одного из них. (Надеюсь, я понял ваш комментарий ...) – Paul Brinkley 5 December 2008 в 19:59
  • 3
    Да, мы должны обеспечить соблюдение стандарта, который почти все остальное в мире использует, выражая месяцы - стандарт на основе 1. – Jon Skeet 5 December 2008 в 20:20
  • 4
    Ключевое слово здесь «почти». Очевидно, что Jan = 1 и т. Д. Чувствуют себя естественными в системе дат с чрезвычайно широким использованием, но почему позволяют себе делать исключение, чтобы избежать жестко закодированных констант, даже в этом случае? – Paul Brinkley 5 December 2008 в 22:34
  • 5
    Потому что это облегчает жизнь. Это просто так. У меня never возникла проблема «один за другим» с 1-месячной системой. Я видел много таких ошибок с Java API. Игнорирование того, что делают все остальные в мире, просто не имеет смысла. – Jon Skeet 6 December 2008 в 08:33

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

9
ответ дан Paul Tomblin 17 August 2018 в 09:45
поделиться

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

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

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

TL; DR: Поскольку в месяцах имена и дни месяца этого не делают.

22
ответ дан piksel bitworks 17 August 2018 в 09:45
поделиться
  • 1
    "месяца имеют имена и дни нет. & quot; Когда-либо слышали о« пятнице »? ;) Хорошо. Я предполагаю, что вы имели в виду «.. дней месяца нет» - возможно, он заплатит за редактирование вашего (иначе хорошего) ответа. :-) – Andrew Thompson 24 August 2011 в 18:11
  • 2
    Лучше отображается 0/0/0000 как «00-янв-0000». или как «00-XXX-0000»? ИМХО, много кода было бы более чистым, если бы было тринадцать месяцев " но месяц 0 получил фиктивное имя. – supercat 14 September 2015 в 22:29
  • 3
    Это интересное занятие, но 0/0/0000 не является допустимой датой. как бы вы сделали 40/40/0000? – piksel bitworks 15 September 2015 в 19:09

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

0
ответ дан Powerlord 17 August 2018 в 09:45
поделиться
  • 1
    Это очень хорошо, когда вы явно пишете код за определенный месяц, но это боль, когда у вас есть месяц в «нормальном» состоянии. формы из другого источника. – Jon Skeet 5 December 2008 в 18:11
  • 2
    Это также боль, когда вы пытаетесь напечатать значение месяца в определенном порядке - вы всегда добавляете 1 к нему. – Brian Warshaw 11 February 2011 в 13:39

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

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

34
ответ дан stesch 17 August 2018 в 09:45
поделиться

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

-2
ответ дан Syrrus 17 August 2018 в 09:45
поделиться
  • 1
    Нет, большинство вещей в реальном мире начинаются с 1. Offsets начинаются с 0, а месяц в году не является смещением, это один из двенадцати, точно так же, как день месяца является одним из 31 или 30 или 29 или 28. Рассмотрение месяца в качестве смещения является просто капризным, особенно если в то же время мы не рассматриваем день месяца таким же образом. В чем причина этой разницы? – SantiBailors 8 February 2016 в 14:27
  • 2
    в реальном мире начните с 1, В мире Java начните с 0. НО ... Я думаю, это потому, что: - для расчета дня недели он не может быть компенсирован для нескольких вычислений без добавления еще нескольких шагов к это ... - дополнительно показывает полные дни месяца, если это необходимо (без путаницы или необходимости проверять февраль). В течение месяца он заставляет вас выводить в формате даты, который следует использовать в любом случае. Кроме того, поскольку количество месяцев в году является регулярным, а дни в месяц - это не имеет смысла, если вам нужно объявить массивы и использовать смещение, чтобы лучше соответствовать массиву. – Syrrus 7 April 2016 в 21:04

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

9
ответ дан TheSmurf 17 August 2018 в 09:45
поделиться
  • 1
    вы можете назвать его эффективностью. – Milhous 5 December 2008 в 17:31
  • 2
    Нет, я бы не стал. Более важно оптимизировать эффективность своих клиентов, чем программистов. Поскольку этот клиент проводит здесь время, они не справились с этим. – TheSmurf 5 December 2008 в 17:45
  • 3
    Это абсолютно не связано с эффективностью - это не так, как если бы месяцы хранились в массиве, и вам понадобится 13, чтобы представлять 12 месяцев. Речь идет не о том, чтобы API не был удобен для пользователя, как должен был быть в первую очередь. Джош Блох тряпится по дате и календарю в «Эффективной Java». Очень немногие API-интерфейсы идеальны, а API-интерфейсы даты и времени на Java имеют неудачную роль в том, что они были запутаны. Это жизнь, но давайте не будем притворяться, что это имеет какое-то отношение к эффективности. – Quinn Taylor 11 August 2009 в 04:06
  • 4
    Почему бы не считать дни от 0 до 30? Это просто непоследовательно и неряшливо. – Juangui Jordán 24 January 2017 в 14:00

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

0
ответ дан Tihamer 17 August 2018 в 09:45
поделиться
3
ответ дан Basil Bourque 6 September 2018 в 07:22
поделиться
Другие вопросы по тегам:

Похожие вопросы: