Чому в календарі Java 0 січня місяць?


300

У java.util.Calendarсічні визначається як місяць 0, а не місяць 1. Чи є якась конкретна причина цього?

Я бачив, як багато людей плутаються з цього приводу ...


4
Хіба це не детальна інформація щодо впровадження, оскільки існують константи ЯНВУР, ЛЮТИНИ тощо? Заняття датами передують належній підтримці java enum.
gnud

6
Ще більше дратує - чому там нежить?
мат b

40
@gnud: Ні, це не детальна інформація про реалізацію. Це болить, коли вам дали ціле число в "природній" базі (тобто січ = 1), і вам потрібно використовувати це з API календаря.
Джон Скіт

1
@matt b: це для негригоріанських календарів (місячних календарів тощо), яким тринадцять місяців. Ось чому краще не думати в цифрах, а дозволити Календарі робити це локалізацією.
erickson

7
13-місячний аргумент не має сенсу. Якщо це так, то чому б зайвий місяць не був 0 або 13?
Квінн Тейлор

Відповіді:


323

Це просто частина жахливого безладу, який є API дати / часу Java. Перерахування того, що з цим не так, зайняло б дуже багато часу (і я впевнений, що не знаю половини проблем). Справді, робота з датами та часом - складна, але все-таки сприятлива.

Зробіть собі прихильність і скоріше використовуйте Joda Time , або, можливо, JSR-310 .

EDIT: Що стосується причин, що - як зазначається в інших відповідях, цілком може бути пов'язано зі старими API API або просто загальним відчуттям починати все з 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 місяцями ... але принаймні вказаний розмір - це кількість місяців, які ви очікуєте.

Це не хороша причина, але це причина ...

РЕДАКТУВАТИ: Як коментар, це запит на деякі ідеї щодо того, що я думаю, що це не так з датою / календарем:

  • Дивовижні бази (1900 як рік року в Date, правда, для застарілих конструкторів; 0 як місячна база в обох)
  • Змінюваність - використання незмінних типів значно спрощує роботу з тими, що є дійсно ефективними значеннями
  • Недостатній набір типів: приємно мати Dateі Calendarяк різні речі, але відмежування значень "локальне" від "районованого" відсутнє, як і дата / час проти дати проти часу
  • API, який призводить до некрасивого коду з магічними константами, а не чітко названими методами
  • API, на який дуже важко міркувати - весь бізнес про те, коли речі перераховуються тощо
  • Використання конструкторів без параметрів за замовчуванням до "зараз", що призводить до важкого для перевірки коду
  • Date.toString()Реалізація , яка завжди використовує локальну тимчасову зону системи (це помилка багатьох користувачів переповнення стека до сих пір)

14
... і що з депресуванням усіх корисних простих методів дати? Тепер я повинен використовувати цей жахливий об’єкт календаря складними способами, щоб робити речі, які раніше були простими.
Брайан Кноблауш

3
@Brian: Я відчуваю твій біль. Знову ж таки, Joda Time простіший :) (Коефіцієнт незмінюваності також робить речі набагато приємнішими для роботи.)
Джон Скіт

8
Ви не відповіли на запитання.
Zeemee

2
@ user443854: Я перерахував деякі моменти в редагуванні - подивіться, чи це допомагає.
Джон Скіт

2
Якщо ви використовуєте Java 8, то ви можете скинути клас Календар і перейти на новий і витончений API DateTime . Новий API також включає в себе незмінне / потокове безпечне DateTimeFormatter, що є великим покращенням порівняно з проблемним і дорогим SimpleDateFormat.
ccpizza

43

Тому що займатися математикою з місяцями набагато простіше.

Через 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

Всі місяці працюють однаково, і обійтися не потрібно.


5
Це задовольняє. Принаймні є деяка цінність цього безумства!
moljac024

"Багато магічних чисел" - так, це лише одне, що з'являється двічі.
користувач123444555621

Повернення на місяць все ще є дуже пристрасним, однак, завдяки невдалому використанню оператора "залишок", а не "модуля". Я також не впевнений, як часто потрібно насправді обробляти місяць без коригування року, а місяці 1-12 не створюють проблем із `while (місяць> 12) {month- = 12; рік ++;}
supercat

2
Оскільки здорові функції, такі як DateTime.AddMonths, занадто важкі, щоб їх належним чином реалізувати в lib, ми повинні зробити математику, яку ви самі описали ... Mmmmmkay
nsimeonov

8
Я не розумію цих upvotes - ((11 - 1 + 1) % 12) + 1 = 12це як раз (11 % 12) + 1те в протягом декількох місяців 1..12 вам просто потрібно додати 1 після того, як робити по модулю. Ніякої магії не потрібно.
mfitzp

35

Мови на основі C копіюють C певною мірою. tmСтруктура (безумовно в time.h) має ціле поле tm_monз (коментарями) діапазоном 0-11.

Мови на основі C починають масиви з індексу 0. Отже, це було зручно для виведення рядка в масив назв місяців, з tm_monіндексом.


22

На це було багато відповідей, але я все-таки викладу свою думку з цього приводу. Причина такої дивної поведінки, як було зазначено раніше, походить з POSIX C, time.hде місяці зберігаються в int з діапазоном 0-11. Щоб пояснити чому, подивіться на це так; роки і дні вважаються числами в розмовній мові, але місяці мають власні назви. Отже, оскільки січень є першим місяцем, він буде зберігатися як зміщення 0, перший елемент масиву. monthname[JANUARY]було б "January". Перший місяць у році є елементом масиву першого місяця.

З іншого боку, числа днів, оскільки вони не мають імен, зберігання їх у int як 0-30 було б заплутаним, додайте багато day+1інструкцій щодо виведення даних і, звичайно, схильні до багатьох помилок.

Незважаючи на це, непослідовність є заплутаною, особливо у javascript (який також успадкував цю "особливість"), мовою сценаріїв, де це слід абстрагувати далеко від мовної мови.

TL; DR : Тому що місяці мають назви, а дні місяця - ні.


1
"місяці мають імена, а дні - ні." Коли-небудь чули про "п'ятницю"? ;) Гаразд я здогадуюсь, що ти мав на увазі ".. дні місяця не роблять" - можливо, це заплатить за редагування вашої (інакше хорошої) відповіді. :-)
Ендрю Томпсон

Чи 0/0/0000 краще відображається як "00-Jan-0000" або як "00-XXX-0000"? ІМХО, чимало коду було б чистішим, якби було тринадцять «місяців», але місяць 0 отримав назву фіктивного.
supercat

1
це цікаво, але 0/0/0000 не є дійсною датою. як би ви дали 40/40/0000?
piksel bitworks

12

У Java 8 є новий API Дата / Час JSR 310, який є більш здоровим. Спеціалізація така ж, як і основний автор JodaTime, і вони поділяють багато подібних концепцій та зразків.


2
Новий API Date Time тепер є частиною Java 8
mschenk74

9

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


9
Ні, я б не став. Важливо оптимізувати ефективність своїх клієнтів, ніж програмісти. Оскільки цей клієнт проводить тут час на прохання, він у цьому не зміг.
TheSmurf

2
Це абсолютно не пов'язано з ефективністю - це не так, як якщо місяці зберігаються в масиві, і вам знадобиться 13, щоб представити 12 місяців. Справа не в тому, щоб зробити API таким зручним для користувачів, як вони повинні були бути в першу чергу. Джош Блох лахміть дату та календар у "Ефективній Java". Дуже мало API є ідеальними, і API дати / часу на Яві мають невдалу роль бути тими, що були пошкоджені. Це життя, але не будемо робити вигляд, що це має відношення до ефективності.
Квінн Тейлор

1
Чому тоді не рахувати днів від 0 до 30? Це просто непослідовно і неохайно.
Хуангуй Йордан


8

Тому що програмісти одержимі індексами на основі 0. Гаразд, це трохи складніше, ніж це: має більше сенсу, коли ви працюєте з логікою нижчого рівня, використовувати індексацію на основі 0. Але за великим рахунком, я все одно буду дотримуватися свого першого речення.


1
Це ще одна з тих ідіоми / звичок , які йдуть шлях назад до асемблері або на машинному мові , де все зроблено з точки зору зміщення, а НЕ індексів. Позначення масиву стали скороченням для доступу до суміжних блоків, починаючи з зміщення 0.
Кен Ніжний

4

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

Що з перерахованого нижче є більш вірогідним?

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

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

Це ілюструє одне, що мене трохи дратує про Joda Time - це може спонукати програмістів мислити з точки зору твердих констант. (Хоча лише небагато. Це не так, як Joda змушує програмістів погано програмувати.)


1
Але за якою схемою, швидше за все, буде боліти голова, коли у вас немає постійного коду - у вас є значення, яке є результатом дзвінка веб-служби чи будь-чого іншого.
Джон Скіт

Цей виклик веб-служби також повинен звичайно використовувати цю константу. :-) Те саме стосується будь-якого зовнішнього абонента. Після того, як ми встановили, що існує декілька стандартів, необхідність їх застосування стає очевидною. (Я сподіваюся, що я зрозумів ваш коментар ...)
Пол Брінклі

3
Так, нам слід застосовувати стандарт, який використовують майже все інше у світі, коли виражають місяці, - стандарт на основі 1.
Джон Скіт

Ключове слово тут - «майже». Очевидно, що Jan = 1 і т. Д. Відчуває себе природним у системі дат із надзвичайно широким використанням, але чому ми дозволяємо собі зробити виняток, щоб уникнути твердо кодованих констант навіть у цьому випадку?
Пол Брінклі

3
Тому що це полегшує життя. Це просто так. Я жодного разу не стикався з однією проблемою із системою, що базується на 1 місяць. Я бачив безліч таких помилок з Java API. Ігнорувати те, що роблять усі інші у світі, просто не має сенсу.
Джон Скіт

4

Для мене ніхто не пояснює це краще, ніж 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:00 2001/01/01 для MST.

GregorianCalendarуправляється гігантом купи нетипових магічних констант. Ця методика повністю знищує будь-яку надію на перевірку помилок під час компіляції. Наприклад, щоб отримати місяць, який ви використовуєте 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. Проте ДатаФормат. розбір поводиться традиційно з січня = 1.


4

java.util.Month

Java надає вам інший спосіб використання 1 індексів на основі місяців. Використовуйте java.time.Monthenum. Один об’єкт заздалегідь визначений для кожного з дванадцяти місяців. Вони мають номери, призначені кожному 1-12 за січень-грудень; зателефонуйте getValueза номером.

Скористайтеся Month.JULY(дає 7) замість Calendar.JULY(дає 6).

(import java.time.*;)

3

тл; д-р

Month.FEBRUARY.getValue()  // February → 2.

2

Деталі

Відповіді на цей питання Jon тарілочках є правильним.

Зараз у нас є сучасна заміна для тих клопітних старих застарілих класів дати: час java.time .

java.time.Month

Серед цих класів - перелік . Перерахунок переносить один або більше заздалегідь визначених об'єктів, об'єкти, які автоматично інстанціюються під час завантаження класу. На нас є десяток таких об'єктів, кожен дав ім'я: , , , і так далі. Кожен із них - константа класу. Ви можете використовувати та передавати ці об’єкти в будь-якому місці коду. Приклад:Month MonthJANUARYFEBRUARYMARCHstatic final publicsomeMethod( 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 
    );

фев’єр

Крім того, ви повинні передавати об'єкти цього переліку навколо вашої кодової бази, а не просто цілі числа . Це забезпечує безпеку типу, забезпечує дійсний діапазон значень та робить ваш код більш самодокументованим. Дивіться підручник Oracle, якщо ви не знайомі з дивовижним потужним засобом перерахунку на Яві.

Вам також можуть бути корисні заняття Yearта YearMonthзаняття.


Про java.time

Java.time каркас вбудований в Java 8 і пізніших версій. Ці класи витісняти неприємні старі застарілі класи дати і часу , такі як java.util.Date, .Calendar, і java.text.SimpleDateFormat.

Проект Joda-Time , який зараз знаходиться в режимі обслуговування , радить перейти на java.time.

Щоб дізнатися більше, дивіться навчальний посібник Oracle . І шукайте переповнення стека за багатьма прикладами та поясненнями. Специфікація - JSR 310 .

Де отримати класи java.time?

  • Java SE 8 та SE 9 та новіші версії
    • Вбудований.
    • Частина стандартного Java API з пакетною реалізацією.
    • Java 9 додає деякі незначні функції та виправлення.
  • Java SE 6 і SE 7
    • Значна частина функцій java.time підтримується на Java 6 і 7 у ThreeTen-Backport .
  • Android

Проект ThreeTen-Extra розширює java.time додатковими класами. Цей проект є передумовою для можливих майбутніх доповнень до java.time. Ви можете знайти деякі корисні класи тут , такі як Interval, YearWeek, YearQuarter, і більш .


0

Це не точно визначено як нульове саме по собі, воно визначається як Calendar.January. Проблема використання ints як констант замість enums. Календар. Січень == 0.


1
Значення одне й те саме. API можуть також повернути 0, це ідентично константі. Calendar.JANUARY можна було б визначити як 1 - у цьому вся суть. Перерахування було б приємним рішенням, але справжні перерахунки не були додані до мови до Java 5, а Дата існувала з самого початку. Це прикро, але ви не можете "виправити" такий фундаментальний API, як тільки сторонній код використовує його. Найкраще, що можна зробити, - це надати новий API та знехтувати старий, щоб заохотити людей рухатися далі. Дякую, Java 7 ...
Квінн Тейлор

0

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


-1

На додаток до відповіді DannySmurf про лінь я додам, що це спонукає вас до використання констант, таких як Calendar.JANUARY.


5
Це все дуже добре, коли ви чітко пишете код на конкретний місяць, але це біль, коли ви отримали місяць у "звичайній" формі з іншого джерела.
Джон Скіт

1
Це також біль, коли ви намагаєтеся якось певним чином роздрукувати ці місячні значення - ви завжди додаєте до нього 1.
Брайан Варшо

-2

Тому що все починається з 0. Це основний факт програмування на Java. Якщо одне від цього відхилиться, то це призвело б до цілковитої плутанини. Не будемо сперечатися утворення їх і кодувати з ними.


2
Ні, більшість речей у реальному світі починаються з 1. Зсуви починаються з 0, а місяць року не є компенсуванням, це один із дванадцяти, так само, як день місяця є одним із 31 або 30 або 29 або 28. Ставлення до місяця як до компенсації є просто примхливим, особливо якщо в той же час ми не поводимось із днем ​​місяця однаково. У чому причина цієї різниці?
SantiBailors

у реальному світі починаються з 1, у світі Яви починаються з 0. АЛЕ ... Я думаю, що це тому, що: - для обчислення дня тижня не можна компенсувати пару обчислень, не додаючи ще пару кроків до це ... - додатково він показує цілі дні місяця, якщо це потрібно (без плутанини чи необхідності перевіряти лютий). - За місяць він змушує вас виводити у форматі дати, який слід використовувати будь-яким способом. Крім того, оскільки кількість місяців у році є регулярними, а дні в місяці чи не має сенсу, якщо вам потрібно оголосити масиви та використовувати зміщення, щоб краще підходити до масиву.
Сиррус
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.