Mengapa bulan Januari 0 di Kalender Jawa?


300

Di java.util.Calendar, Januari didefinisikan sebagai bulan 0, bukan bulan 1. Apakah ada alasan khusus untuk itu?

Saya telah melihat banyak orang bingung tentang itu ...


4
Bukankah itu semacam detail implementasi, karena konstanta JANUARI, FEBRUARI dll ada? Kelas tanggal mendahului dukungan java enum yang tepat.
gnud

6
Bahkan lebih menyebalkan - mengapa ada Undecember?
matt b

40
@gnud: Tidak, ini bukan detail implementasi. Itu membuatnya menyakitkan ketika Anda telah diberi bilangan bulat di basis "alami" (yaitu Jan = 1) dan Anda harus menggunakannya dengan API kalender.
Jon Skeet

1
@matt b: ini untuk kalender non-Gregorian (kalender lunar, dll) yang memiliki tiga belas bulan. Itu sebabnya yang terbaik adalah tidak berpikir dalam angka, tetapi biarkan Kalender yang melakukannya.
erickson

7
Argumen 13 bulan tidak masuk akal. Jika demikian, mengapa tidak memiliki bulan tambahan menjadi 0 atau 13?
Quinn Taylor

Jawaban:


323

Itu hanya bagian dari kekacauan mengerikan yang merupakan API tanggal / waktu Java. Mendaftar apa yang salah dengan itu akan memakan waktu sangat lama (dan saya yakin saya tidak tahu setengah dari masalahnya). Harus diakui, bekerja dengan tanggal dan waktu memang sulit, tetapi toh tetap saja.

Bantulah diri Anda sendiri dan gunakan Joda Time , atau mungkin JSR-310 .

EDIT: Adapun alasan mengapa - seperti yang disebutkan dalam jawaban lain, itu bisa jadi karena API C lama, atau hanya perasaan umum memulai semuanya dari 0 ... kecuali hari-hari itu dimulai dengan 1, tentu saja. Saya ragu apakah ada orang di luar tim implementasi asli yang benar-benar dapat menyatakan alasan - tetapi sekali lagi, saya mendorong pembaca untuk tidak terlalu khawatir tentang mengapa keputusan yang buruk diambil, untuk melihat keseluruhan keseluruhan kekesalan java.util.Calendardan menemukan sesuatu yang lebih baik.

Satu titik yang merupakan mendukung menggunakan 0 berbasis indeks adalah bahwa hal itu membuat hal-hal seperti "array dari nama" lebih mudah:

// I "know" there are 12 months
String[] monthNames = new String[12]; // and populate...
String name = monthNames[calendar.get(Calendar.MONTH)];

Tentu saja, ini gagal segera setelah Anda mendapatkan kalender dengan 13 bulan ... tetapi setidaknya ukuran yang ditentukan adalah jumlah bulan yang Anda harapkan.

Ini bukan yang baik alasan, tapi itu sebuah alasan ...

EDIT: Sebagai komentar, minta beberapa ide tentang apa yang saya pikir salah dengan Tanggal / Kalender:

  • Basis mengejutkan (1900 sebagai basis tahun di Date, diakui untuk konstruktor yang sudah usang; 0 sebagai basis bulan di keduanya)
  • Mutability - menggunakan tipe yang tidak berubah membuatnya lebih mudah untuk bekerja dengan nilai yang benar-benar efektif
  • Satu set tipe yang tidak mencukupi: senang memiliki Datedan Calendarsebagai hal yang berbeda, tetapi pemisahan nilai "lokal" vs "dikategorikan" tidak ada, seperti tanggal / waktu vs tanggal vs waktu
  • API yang mengarah ke kode jelek dengan konstanta ajaib, alih-alih metode yang disebutkan dengan jelas
  • API yang sangat sulit untuk dipikirkan - semua bisnis tentang kapan segala sesuatunya dihitung ulang, dll
  • Penggunaan konstruktor tanpa parameter ke default ke "sekarang", yang mengarah ke kode yang sulit untuk diuji
  • The Date.toString()pelaksanaan yang selalu menggunakan sistem zona waktu lokal (yang ini bingung banyak pengguna Stack Overflow sebelum sekarang)

14
... dan ada apa dengan mencela semua metode Tanggal sederhana yang berguna? Sekarang saya harus menggunakan objek Kalender yang mengerikan itu dengan cara yang rumit untuk melakukan hal-hal yang dulunya sederhana.
Brian Knoblauch

3
@ Brian: Aku merasakan sakitmu. Sekali lagi, Joda Time lebih sederhana :) (Faktor immutabilitas membuat hal-hal jauh lebih menyenangkan untuk dikerjakan juga.)
Jon Skeet

8
Anda tidak menjawab pertanyaan itu.
Zeemee

2
@ user443854: Saya telah mendaftarkan beberapa poin dalam edit - lihat apakah itu membantu.
Jon Skeet

2
Jika Anda menggunakan Java 8 maka Anda dapat membuang kelas Kalender dan beralih ke DateTime API yang baru dan ramping . API baru ini juga mencakup DateTimeFormatter yang tidak berubah / threadsafe yang merupakan peningkatan besar atas SimpleDateFormat yang bermasalah dan mahal.
ccpizza

43

Karena melakukan matematika dengan berbulan-bulan jauh lebih mudah.

1 bulan setelah Desember adalah Januari, tetapi untuk mengetahui hal ini secara normal Anda harus mengambil angka bulan dan melakukan matematika

12 + 1 = 13 // What month is 13?

Aku tahu! Saya dapat memperbaiki ini dengan cepat menggunakan modulus 12.

(12 + 1) % 12 = 1

Ini berfungsi dengan baik selama 11 bulan hingga November ...

(11 + 1) % 12 = 0 // What month is 0?

Anda dapat membuat semua ini berfungsi lagi dengan mengurangi 1 sebelum Anda menambahkan bulan, lalu lakukan modulus Anda dan akhirnya tambahkan 1 kembali ... alias selesaikan masalah yang mendasarinya.

((11 - 1 + 1) % 12) + 1 = 12 // Lots of magical numbers!

Sekarang mari kita pikirkan masalah dengan bulan 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

Semua bulan bekerja sama dan bekerja di sekitar tidak perlu.


5
Ini memuaskan. Setidaknya ada nilai untuk kegilaan ini!
moljac024

"Banyak angka ajaib" - nah, itu hanya satu yang muncul dua kali.
user123444555621

Kembali sebulan masih agak menjijikkan, meskipun, berkat penggunaan C yang tidak menguntungkan dari operator "sisa" daripada "modulus". Saya juga tidak yakin seberapa sering seseorang benar-benar perlu menabrak satu bulan tanpa menyesuaikan tahun, dan memiliki bulan pergi 1-12 tidak menciptakan masalah dengan `while (bulan> 12) {bulan- = 12; tahun ++;}
supercat

2
Karena fungsi waras seperti DateTime.AddMonths terlalu sulit untuk diimplementasikan dengan benar dalam lib, kita harus melakukan matematika yang Anda gambarkan sendiri ... Mmmmmkay
nsimeonov

8
Saya tidak mengerti upvotes ini - yaitu ((11 - 1 + 1) % 12) + 1 = 12hanya (11 % 12) + 1untuk bulan 1..12 Anda hanya perlu menambahkan 1 setelah melakukan modulo. Tidak perlu sihir.
mfitzp

35

Bahasa berbasis C menyalin C sampai tingkat tertentu. The tmStruktur (didefinisikan dalam time.h) memiliki bidang bilangan bulat tm_mondengan (berkomentar) berbagai 0-11.

Bahasa berbasis C mulai array di indeks 0. Jadi ini nyaman untuk menghasilkan string dalam array nama bulan, dengan tm_monsebagai indeks.


22

Ada banyak jawaban untuk ini, tetapi saya tetap akan memberikan pandangan saya tentang masalah ini. Alasan di balik perilaku aneh ini, seperti yang dinyatakan sebelumnya, berasal dari POSIX C di time.hmana bulan disimpan dalam sebuah int dengan kisaran 0-11. Untuk menjelaskan alasannya, lihatlah seperti ini; tahun dan hari dianggap angka dalam bahasa lisan, tetapi bulan memiliki nama sendiri. Jadi karena Januari adalah bulan pertama akan disimpan sebagai offset 0, elemen array pertama. monthname[JANUARY]akan "January". Bulan pertama dalam tahun tersebut adalah elemen array bulan pertama.

Angka hari di sisi lain, karena mereka tidak memiliki nama, menyimpannya dalam sebuah int sebagai 0-30 akan membingungkan, menambahkan banyak day+1instruksi untuk menghasilkan dan, tentu saja, rentan terhadap banyak bug.

Yang sedang berkata, inkonsistensi membingungkan, terutama dalam javascript (yang juga telah mewarisi "fitur" ini), bahasa scripting di mana ini harus diabstraksi jauh dari langague.

TL; DR : Karena bulan memiliki nama dan hari dalam sebulan tidak.


1
"Bulan punya nama dan hari tidak." Pernah dengar 'Friday'? ;) OK Saya rasa Anda bermaksud '.. hari dalam sebulan tidak' - mungkin itu akan membayar untuk mengedit jawaban Anda (jika tidak baik). :-)
Andrew Thompson

Apakah 0/0/0000 lebih baik diterjemahkan sebagai "00-Jan-0000" atau sebagai "00-XXX-0000"? IMHO, banyak kode akan lebih bersih jika ada tiga belas "bulan" tetapi bulan 0 diberi nama dummy.
supercat

1
itu pengambilan yang menarik, tetapi 0/0/0000 bukan tanggal yang valid. bagaimana Anda membuat 40/40/0000?
piksel bitworks

12

Di Java 8, ada Date / Time API JSR 310 baru yang lebih waras. Lead spec sama dengan penulis utama JodaTime dan mereka memiliki banyak konsep dan pola yang sama.


2
API Tanggal Waktu baru sekarang menjadi bagian dari Java 8
mschenk74

9

Saya akan mengatakan kemalasan. Array mulai dari 0 (semua orang tahu itu); bulan-bulan dalam setahun adalah sebuah array, yang membuat saya percaya bahwa beberapa insinyur di Sun tidak mau repot-repot memasukkan sedikit kebaikan ini ke dalam kode Java.


9
Tidak, tidak akan. Lebih penting untuk mengoptimalkan efisiensi pelanggan daripada pemrogram seseorang. Karena pelanggan ini menghabiskan waktu di sini untuk bertanya, mereka gagal dalam hal itu.
TheSmurf

2
Ini sama sekali tidak terkait dengan efisiensi - bukan seolah-olah bulan disimpan dalam array dan Anda perlu 13 untuk mewakili 12 bulan. Ini masalah tidak menjadikan API sebagai user-friendly seperti seharusnya di tempat pertama. Josh Bloch gombal tentang Tanggal dan Kalender di "Java Efektif". Sangat sedikit API yang sempurna, dan tanggal / waktu API di Jawa memiliki peran yang disayangkan menjadi yang salah. Itu hidup, tapi jangan pura-pura ada hubungannya dengan efisiensi.
Quinn Taylor

1
Mengapa tidak menghitung hari dari 0 hingga 30? Itu hanya tidak konsisten dan ceroboh.
Juangui Jordán


8

Karena programmer terobsesi dengan indeks berbasis 0. OK, ini sedikit lebih rumit dari itu: lebih masuk akal ketika Anda bekerja dengan logika tingkat rendah untuk menggunakan pengindeksan berbasis 0. Tetapi pada umumnya, saya masih akan tetap dengan kalimat pertama saya.


1
Ini adalah satu lagi dari mereka idiom / kebiasaan yang pergi jalan kembali ke assembler atau bahasa mesin di mana semuanya dilakukan dalam hal offset, tidak indeks. Notasi array menjadi jalan pintas untuk mengakses blok yang berdekatan, mulai dari offset 0.
Ken Gentle

4

Secara pribadi, saya menganggap keanehan API kalender Jawa sebagai indikasi bahwa saya harus menceraikan diri dari pola pikir Gregorian-centric dan mencoba memprogram lebih agnostik dalam hal itu. Secara khusus, saya belajar sekali lagi untuk menghindari konstanta hardcode untuk hal-hal seperti berbulan-bulan.

Manakah dari berikut ini yang lebih benar?

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

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

Ini menggambarkan satu hal yang membuat saya sedikit jengkel tentang Joda Time - itu mungkin mendorong programmer untuk berpikir dalam hal konstanta hardcode. (Hanya sedikit, meskipun. Ini bukan seolah-olah Joda memaksa programmer untuk memprogram dengan buruk.)


1
Tetapi skema mana yang lebih cenderung memberi Anda sakit kepala ketika Anda tidak memiliki konstanta dalam kode Anda - Anda memiliki nilai yang merupakan hasil dari panggilan layanan web atau apa pun.
Jon Skeet

Panggilan layanan web itu juga harus menggunakan konstanta itu, tentu saja. :-) Hal yang sama berlaku untuk penelepon eksternal. Setelah kami menetapkan bahwa ada beberapa standar, kebutuhan untuk menegakkannya menjadi jelas. (Saya harap saya mengerti komentar Anda ...)
Paul Brinkley

3
Ya, kita harus menegakkan standar yang digunakan hampir semua hal lain di dunia saat mengekspresikan bulan - standar berbasis 1.
Jon Skeet

Kata kuncinya di sini adalah "hampir". Jelas, Jan = 1, dll. Terasa alami dalam sistem tanggal dengan penggunaan yang sangat luas, tetapi mengapa membiarkan diri kita membuat pengecualian untuk menghindari konstanta hardcode, bahkan dalam kasus yang satu ini?
Paul Brinkley

3
Karena itu membuat hidup lebih mudah. Itu benar. Saya tidak pernah mengalami masalah satu per satu dengan sistem berbasis 1 bulan. Saya telah melihat banyak bug seperti itu dengan Java API. Mengabaikan apa yang dilakukan orang lain di dunia tidak masuk akal.
Jon Skeet

4

Bagi saya, tidak ada yang menjelaskannya lebih baik daripada mindpro.com :

Gotcha

java.util.GregorianCalendarmemiliki bug dan Gotcha jauh lebih sedikit daripada old java.util.Datekelas tetapi masih ada piknik.

Seandainya ada programmer ketika Daylight Saving Time pertama kali diusulkan, mereka akan memveto itu sebagai gila dan tidak bisa dilaksanakan. Dengan penghematan siang hari, ada ambiguitas mendasar. Pada musim gugur ketika Anda menyetel jam kembali satu jam pada jam 2 pagi ada dua contoh waktu yang keduanya disebut jam 1:30 pagi waktu setempat. Anda dapat membedakannya hanya jika Anda merekam apakah Anda bermaksud menghemat siang hari atau waktu standar dengan pembacaan.

Sayangnya, tidak ada cara untuk mengatakan GregorianCalendaryang Anda maksudkan. Anda harus menggunakan waktu setempat untuk memberitahukannya dengan TimeZone UTC dummy untuk menghindari ambiguitas. Pemrogram biasanya menutup mata mereka terhadap masalah ini dan hanya berharap tidak ada yang melakukan apa pun selama jam ini.

Bug milenium. Bug masih belum keluar dari kelas Kalender. Bahkan di JDK (Java Development Kit) 1.3 ada bug tahun 2001. Pertimbangkan kode berikut:

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 */

Bug menghilang pada pukul 07:00 pada 2001/01/01 untuk MST.

GregorianCalendardikendalikan oleh raksasa tumpukan konstanta sihir int yang tidak diketik. Teknik ini benar-benar menghancurkan harapan pengecekan kesalahan waktu kompilasi. Misalnya untuk mendapatkan bulan yang Anda gunakan GregorianCalendar. get(Calendar.MONTH));

GregorianCalendarmemiliki GregorianCalendar.get(Calendar.ZONE_OFFSET)penghematan mentah dan siang hari GregorianCalendar. get( Calendar. DST_OFFSET), tetapi tidak ada cara untuk mendapatkan offset zona waktu yang sebenarnya digunakan. Anda harus mendapatkan keduanya secara terpisah dan menambahkannya bersama.

GregorianCalendar.set( year, month, day, hour, minute) tidak mengatur detik ke 0.

DateFormatdan GregorianCalendartidak bertautan dengan benar. Anda harus menentukan Kalender dua kali, sekali secara tidak langsung sebagai Tanggal.

Jika pengguna belum mengonfigurasi zona waktunya dengan benar, defaultnya akan diam-diam ke PST atau GMT.

Di GregorianCalendar, Bulan diberi nomor mulai Januari = 0, bukan 1 seperti yang dilakukan orang lain di planet ini. Namun hari-hari dimulai pada 1 seperti halnya hari dalam seminggu dengan Minggu = 1, Senin = 2, ... Sabtu = 7. Namun DateFormat. parse berperilaku dengan cara tradisional dengan Januari = 1.


4

java.util.Month

Java memberi Anda cara lain untuk menggunakan 1 indeks berbasis selama berbulan-bulan. Gunakan java.time.Monthenum. Satu objek sudah ditentukan untuk masing-masing dua belas bulan. Mereka memiliki nomor yang ditetapkan untuk masing-masing 1-12 untuk Januari-Desember; panggilan getValueuntuk nomor tersebut.

Manfaatkan Month.JULY(Memberi Anda 7) alih-alih Calendar.JULY(Memberi Anda 6).

(import java.time.*;)

3

tl; dr

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

2

Detail

The Answer oleh Jon Skeet benar.

Sekarang kami memiliki pengganti modern untuk kelas lama tanggal waktu warisan sebelumnya: kelas java.time .

java.time.Month

Di antara kelas-kelas itu adalah enum . Enum membawa satu atau lebih objek yang telah ditetapkan, objek yang secara otomatis dipakai saat kelas memuat. Pada kami memiliki selusin benda seperti itu, masing-masing diberi nama: , , , dan sebagainya. Masing-masing adalah konstanta kelas. Anda dapat menggunakan dan meneruskan objek-objek ini di mana saja dalam kode Anda. Contoh:Month MonthJANUARYFEBRUARYMARCHstatic final publicsomeMethod( Month.AUGUST )

Untungnya, mereka memiliki penomoran yang waras, 1-12 di mana 1 adalah Januari dan 12 adalah Desember.

Dapatkan Monthobjek untuk nomor bulan tertentu (1-12).

Month month = Month.of( 2 );  // 2 → February.

Ke arah lain, tanyakan Monthobjek untuk nomor bulannya.

int monthNumber = Month.FEBRUARY.getValue();  // February → 2.

Banyak metode praktis lainnya di kelas ini, seperti mengetahui jumlah hari dalam setiap bulan . Kelas bahkan dapat menghasilkan nama lokal bulan itu.

Anda bisa mendapatkan nama lokal bulan itu, dalam berbagai panjang atau singkatan.

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

février

Juga, Anda harus melewatkan objek enum ini di sekitar basis kode Anda daripada hanya angka integer . Melakukannya memberikan keamanan jenis, memastikan rentang nilai yang valid, dan membuat kode Anda lebih banyak mendokumentasikan diri. Lihat Tutorial Oracle jika tidak terbiasa dengan fasilitas enum yang sangat kuat di Jawa.

Anda juga dapat menemukan kelas Yeardan berguna YearMonth.


Tentang java.time

The java.time kerangka dibangun ke Jawa 8 dan kemudian. Kelas-kelas ini menggantikan tua merepotkan warisan kelas tanggal-waktu seperti java.util.Date, .Calendar, & java.text.SimpleDateFormat.

Proyek Joda-Time , sekarang dalam mode pemeliharaan , menyarankan migrasi ke java.time.

Untuk mempelajari lebih lanjut, lihat Tutorial Oracle . Dan cari Stack Overflow untuk banyak contoh dan penjelasan. Spesifikasi adalah JSR 310 .

Di mana mendapatkan kelas java.time?

Proyek ThreeTen-Extra memperpanjang java.time dengan kelas tambahan. Proyek ini adalah ajang pembuktian untuk kemungkinan penambahan masa depan ke java.time. Anda mungkin menemukan beberapa kelas berguna di sini seperti Interval, YearWeek, YearQuarter, dan lebih .


0

Itu tidak persis didefinisikan sebagai nol per se, itu didefinisikan sebagai Kalender. Januari. Ini adalah masalah menggunakan int sebagai konstanta alih-alih enum. Calendar.January == 0.


1
Nilai-nilainya satu dan sama. API juga dapat mengembalikan 0, identik dengan konstanta. Kalender.JANUARI bisa didefinisikan sebagai 1 - itulah intinya. Enum akan menjadi solusi yang bagus, tetapi enum sejati tidak ditambahkan ke bahasa sampai Java 5, dan Date sudah ada sejak awal. Sangat disayangkan, tetapi Anda benar-benar tidak dapat "memperbaiki" API mendasar seperti itu setelah kode pihak ketiga menggunakannya. Yang terbaik yang bisa dilakukan adalah menyediakan API baru dan mencabut yang lama untuk mendorong orang untuk melanjutkan. Terima kasih, Java 7 ...
Quinn Taylor

0

Karena menulis bahasa lebih sulit daripada yang terlihat, dan menangani waktu pada khususnya jauh lebih sulit daripada yang dipikirkan kebanyakan orang. Untuk sebagian kecil masalah (pada kenyataannya, bukan Jawa), lihat video YouTube "Masalah dengan Waktu & Zona Waktu - Computerphile" di https://www.youtube.com/watch?v=-5wpm-gesOY . Jangan terkejut jika kepala Anda jatuh karena tertawa kebingungan.


-1

Selain jawaban kemalasan DannySmurf, saya akan menambahkan bahwa itu mendorong Anda untuk menggunakan konstanta, seperti Calendar.JANUARY.


5
Itu semua sangat baik ketika Anda secara eksplisit menulis kode untuk bulan tertentu, tetapi itu menyakitkan ketika Anda mendapatkan bulan dalam bentuk "normal" dari sumber yang berbeda.
Jon Skeet

1
Ini juga menyakitkan ketika Anda mencoba mencetak nilai bulan itu dengan cara tertentu - Anda selalu menambahkan 1 untuk nilai itu.
Brian Warshaw

-2

Karena semuanya dimulai dengan 0. Ini adalah fakta dasar pemrograman di Java. Jika ada satu hal yang menyimpang dari itu, maka itu akan menyebabkan seluruh kebingungan. Jangan berdebat pembentukan mereka dan kode dengan mereka.


2
Tidak, sebagian besar hal di dunia nyata dimulai dengan 1. Offset dimulai dengan 0, dan bulan tahun bukan merupakan offset, itu salah satu dari dua belas, sama seperti hari dalam sebulan adalah satu dari 31 atau 30 atau 29 atau 28. Memperlakukan bulan sebagai pengganti hanya berubah-ubah, terutama jika pada saat yang sama kami tidak memperlakukan hari dalam bulan dengan cara yang sama. Apa alasan perbedaan ini?
SantiBailors

di Dunia Nyata dimulai dengan 1, Di dunia Jawa mulai dengan 0. TETAPI ... Saya pikir itu karena: - untuk menghitung hari dalam seminggu itu tidak dapat diimbangi untuk penghitungan pasangan tanpa menambahkan beberapa langkah lagi untuk itu ... - tambahan itu menunjukkan hari-hari lengkap dalam bulan jika diperlukan (tanpa kebingungan atau perlu memeriksa Februari) - Untuk bulan itu memaksa Anda untuk menghasilkan dalam format tanggal yang harus digunakan dengan cara baik. Selain itu karena jumlah bulan dalam setahun adalah teratur dan hari dalam sebulan tidak masuk akal jika Anda perlu mendeklarasikan array dan menggunakan offset untuk menyesuaikan array dengan lebih baik.
Syrrus
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.