Java Calendar'da neden 0 Ocak ay?


300

İçinde java.util.Calendar, Ocak 1 ayı değil 0 ay olarak tanımlanır. Bunun özel bir nedeni var mı?

Birçok insanın bu konuda kafası karıştığını gördüm ...


4
Sabitler OCAK, ŞUBAT vb. Var olduğundan bu tür bir uygulama detayı değil mi? Date sınıfları uygun java enum desteğinden önce gelir.
gnud

6
Daha da sinir bozucu - neden bir Aralık ayı var?
matt b

40
@gnud: Hayır, bu bir uygulama detayı değil. "Doğal" bazda bir tamsayı (yani Ocak = 1) verildiğinde ve bunu takvim API'sı ile kullanmanız gerektiğinde acı verir.
Jon Skeet

1
@matt b: on üç ay süren Gregoryen olmayan takvimler (ay takvimleri vb.) içindir. Bu yüzden rakamlarla düşünmemek en iyisidir, ancak Takvim'in yerelleştirmesini yapmasına izin verin.
erickson

7
13 aylık argüman hiç mantıklı değil. Öyleyse, neden ekstra ayın 0 veya 13 olmasın?
Quinn Taylor

Yanıtlar:


323

Java tarih / saat API'sı olan korkunç karışıklığın sadece bir parçası. Neyin yanlış olduğunu listelemek çok uzun zaman alacaktır (ve eminim problemlerin yarısını bilmiyorum). Kuşkusuz tarihler ve saatler ile çalışmak zor, ama yine de aaargh.

Kendinize bir iyilik yapın ve bunun yerine Joda Time veya muhtemelen JSR-310 kullanın .

DÜZENLEME: Nedenlerine gelince - diğer cevaplarda belirtildiği gibi, eski C API'leri veya elbette 0'dan başlayarak her şeye 0'dan başlayarak genel bir his olabilir. Orijinal uygulama ekibinin dışındaki herhangi birinin gerçekten nedenlerini açıklayıp ifade edemeyeceğinden kuşkuluyum - ama tekrar, okuyucuları neden bu kadar çok endişelenmemeleri için çağırıyorum , kötü niyetli oyunun tüm gamına bakmak java.util.Calendarve daha iyi bir şey bulmak için kötü kararların alındığı .

0 tabanlı indekslerin kullanılmasını destekleyen bir nokta , "isimler dizileri" gibi şeyleri kolaylaştırmasıdır:

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

Tabii ki, 13 aylık bir takvim alır almaz başarısız olur ... ancak en azından belirtilen boyut beklediğiniz ay sayısıdır.

Bu iyi bir neden değil , ama bir nedeni ...

EDIT: Yorum olarak Tarih / Takvim ile yanlış olduğunu düşündüğüm hakkında bazı fikirler istekleri:

  • Şaşırtıcı üsler (1900'de yıl bazında, itiraz edilen inşaatçılar için itirafla; her ikisinde de ay tabanı olarak 0)
  • Değiştirilebilirlik - değişmez tipler kullanmak , gerçekten etkili değerlerle çalışmayı çok daha basit hale getirir
  • Yetersiz tür kümesi: sahip olmak güzel Dateve Calendarfarklı şeyler gibi, ancak "yerel" ve "bölgelenmiş" değerlerin ayrılması eksik, tarih / saat ve tarih vs saat
  • Açıkça adlandırılmış yöntemler yerine sihirli sabitlerle çirkin kodlara yol açan bir API
  • Akıl yürütmesi çok zor olan bir API - işlerin ne zaman yeniden hesaplandığı hakkında tüm işler vb.
  • Parametresiz kurucuların varsayılan olarak "now" olarak kullanılması, test edilmesi zor koda yol açar
  • Her Date.toString()zaman sistem yerel saat dilimini kullanan uygulama (şimdiye kadar birçok Stack Overflow kullanıcısının kafasını karıştırdı)

14
... ve tüm kullanışlı basit Date yöntemlerini kullanımdan kaldırmanın nesi var? Şimdi o korkunç Takvim nesnesini eskiden basit olan şeyleri yapmak için karmaşık şekillerde kullanmalıyım.
Brian Knoblauch

3
@Brian: Acını hissediyorum. Yine, Joda Time daha basit :) (Değişmezlik faktörü, işleri de çok daha keyifli hale getiriyor.)
Jon Skeet

8
Soruya cevap vermedin.
Zeemee

2
@ user443854: Bir düzenlemede bazı noktalar listeledim - bunun yardımcı olup olmadığını görün.
Jon Skeet

2
Java 8 kullanıyorsanız Calendar sınıfını bırakıp yeni ve şık DateTime API'sine geçebilirsiniz . Yeni API ayrıca sorunlu ve pahalı SimpleDateFormat üzerinde büyük bir gelişme olan değişmez / threadsafe DateTimeFormatter içerir.
ccpizza

43

Çünkü aylarla matematik yapmak çok daha kolay.

Aralık ayından 1 ay sonra, ancak normal olarak bunu anlamak için ay numarasını almanız ve matematik yapmanız gerekir

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

Biliyorum! 12 modül kullanarak bunu çabucak çözebilirim.

(12 + 1) % 12 = 1

Bu, kasım ayına kadar 11 ay boyunca iyi çalışıyor ...

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

Ayı eklemeden önce 1'i çıkararak, sonra modülünüzü yapabilir ve son olarak tekrar 1'i ekleyerek tüm bu işleri tekrar yapabilirsiniz ... aka altta yatan bir problemin etrafında çalışma.

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

Şimdi 0-11 ay arasındaki sorunu düşünelim.

(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

Tüm aylar aynı şekilde çalışır ve etrafta çalışmak gerekmez.


5
Bu tatmin edici. En azından bu deliliğin bir değeri var!
moljac024

"Çok sayıda sihirli sayı" - hayır, sadece iki kez görünen bir tane.
user123444555621

Yine de, C'nin "modül" yerine "kalan" bir operatörün talihsiz kullanımı sayesinde, bir ay geriye gitmek hâlâ sorunlu. Ayrıca, bir kişinin yılı ayarlamadan gerçekten ne sıklıkta bir ay çarpması gerektiğinden de emin değilim ve ayların 1-12 arasında gitmesi `while (month> 12) {month- = 12; yıl ++;}
supercat

2
DateTime.AddMonths gibi aklı başında işlevler bir lib'de düzgün bir şekilde uygulanamayacak kadar zor olduğundan, sizin tanımladığınız matematiği kendimiz yapmak zorundayız ... Mmmmmkay
nsimeonov

8
Bu upvotes anlamıyorum - ((11 - 1 + 1) % 12) + 1 = 12sadece (11 % 12) + 1ay 1..12 için sadece modulo yaptıktan sonra 1 eklemeniz gerekir . Büyü gerekmez.
mfitzp

35

C tabanlı diller C'yi bir dereceye kadar kopyalar. tmYapı (tanımlanan time.h) bir tam sayı alanı olan tm_mon0-11 arasında (yorum) aralığı.

C tabanlı diller dizinleri 0 dizininde başlatır. Bu nedenle bu, ay adlarından oluşan bir dizede tm_mondizin olarak bir dize çıktılamak için kullanışlıdır .


22

Buna bir sürü cevap geldi, ama yine de konuyla ilgili görüşümü vereceğim. Bu garip davranışın arkasındaki neden, daha önce belirtildiği gibi, time.h0-11 aralığında bir int'de saklandığı ayların bulunduğu POSIX C'den geliyor . Nedenini açıklamak için şöyle bak; yıllar ve günler konuşma dilinde sayılar olarak kabul edilir, ancak ayların kendi adları vardır. Böylece Ocak ilk ay olduğu için, ilk dizi elemanı olan ofset 0 olarak saklanacaktır. monthname[JANUARY]olabilir"January" . Yılın ilk ayı, ilk ay dizi öğesidir.

Öte yandan gün sayıları, isimleri olmadığı için 0-30 olarak int olarak saklamak kafa karıştırıcı olurdu, day+1 talimat ve elbette bir sürü hataya eğilimli olacaktır.

Bununla birlikte, özellikle javascript'te (bu "özelliği" miras alan) tutarsızlık kafa karıştırıcıdır, bu dilin dilden çok uzakta olması gereken bir betik dilidir.

TL; DR : Çünkü ayların isimleri ve ayları yoktur.


1
"ayların isimleri var ve günler yok." Hiç 'Cuma'yı duydun mu? ;) Tamam '.. ayın günleri' demek istediğinizi tahmin ediyorum - belki (aksi halde iyi) cevabınızı düzenlemek için ödeme yapar. :-)
Andrew Thompson

0/0/0000 daha iyi "00-Ocak-0000" veya "00-XXX-0000" olarak mı oluşturulur? IMHO, on üç "ay" olsaydı, ancak 0 ayına sahte bir ad verilirse, çok sayıda kod daha temiz olurdu.
supercat

1
bu ilginç bir durum, ancak 0/0/0000 geçerli bir tarih değil. 40/40/0000'ü nasıl render edersiniz?
piksel bitworks

12

Java 8'de daha aklı başında olan yeni bir Date / Time API JSR 310 var. Teknik özellik, JodaTime'ın birincil yazarı ile aynıdır ve birçok benzer kavram ve modeli paylaşırlar.


2
Yeni Date Time API'sı şimdi Java 8'in bir parçası
mschenk74

9

Tembellik derim. Diziler 0'dan başlar (herkes bunu bilir); yılın ayları bir dizi, bu da Sun'daki bir mühendisin bunu Java koduna küçük bir hoşluk koymaya zahmet etmediğine inanmamı sağlıyor.


9
Hayır. Müşterilerinin verimliliğini optimize etmek, programcılarından daha önemlidir. Bu müşteri burada sormak için zaman harcadığından, bu konuda başarısız oldular.
TheSmurf

2
Verimlilikle tamamen alakasız - aylar bir dizide saklanıyor ve 12 ayı temsil etmek için 13'e ihtiyacınız var gibi değil. Bu, API'yı ilk etapta olması gerektiği gibi kullanıcı dostu yapmama meselesidir. Josh Bloch "Etkili Java" da Tarih ve Takvim üzerinde paçavra. Çok az API mükemmeldir ve Java'daki tarih / saat API'leri aptal olanlar olma konusunda talihsiz bir role sahiptir. Hayat bu, ama verimlilikle bir ilgisi varmış gibi davranmayalım.
Quinn Taylor

1
O zaman neden 0'dan 30'a kadar gün saymıyorsunuz? Sadece tutarsız ve özensiz.
Juangui Jordán


8

Çünkü programcılar 0 tabanlı indekslere takıntılı. Tamam, bundan biraz daha karmaşık: 0 tabanlı endekslemeyi kullanmak için alt düzey mantıkla çalışırken daha mantıklı. Ama genel olarak, hala ilk cümle bağlı kalacağım.


1
Böylece, söz konusu deyimler / go alışkanlıkları başka bir şeydir yolu her şey uzaklıklar değil endeksleri açısından yapılır assembler veya makine diline geri. Dizi notasyonu, 0 ofsetinden başlayarak bitişik bloklara erişmek için kısa bir yol haline geldi
Ken Gentle

4

Şahsen, Java takvim API'sinin garipliğini kendimi Gregoryen merkezli zihniyetten boşamam ve bu konuda daha agresif bir şekilde programlamaya çalışmamın bir göstergesi olarak aldım. Özellikle, aylar gibi şeyler için kodlanmış sabitlerden kaçınmayı bir kez daha öğrendim.

Aşağıdakilerden hangisinin doğru olma olasılığı daha yüksektir?

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

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

Bu bana Joda Time hakkında biraz bilgi veren bir şeyi gösteriyor - programcıları sabit kodlu sabitler açısından düşünmeye teşvik edebilir. (Yine de biraz. Joda programcıları kötü programlamaya zorlamak gibi değil .)


1
Ancak, kodunuzda sabit olmadığınızda hangi şemanın size bir baş ağrısı vermesi daha olasıdır - bir web hizmeti çağrısının sonucu olan bir değeriniz vardır.
Jon Skeet

Bu web hizmeti çağrısı da elbette bu sabiti kullanıyor olmalıdır. :-) Aynı durum herhangi bir harici arayan için de geçerlidir. Birden fazla standardın mevcut olduğunu belirledikten sonra, bir standardı uygulama ihtiyacı belirginleşir. (Umarım yorumunu anladım ...)
Paul Brinkley

3
Evet, ayları ifade ederken dünyadaki hemen hemen her şeyin kullandığı standardı uygulamalıyız - 1 tabanlı standart.
Jon Skeet

Burada anahtar kelime "neredeyse" olmak. Tabii ki, Ocak = 1, vb., Son derece geniş kullanımlı bir tarih sisteminde doğal hissediyor, ancak neden bu durumda bile, sabit kodlanmış sabitlerden kaçınmak için bir istisna yapmamıza izin verelim?
Paul Brinkley

3
Çünkü hayatı kolaylaştırıyor. Sadece öyle. Ben var asla bir 1 tabanlı ay sistemiyle kirli-tek sorunla karşılaştı. Java API ile bu kadar çok hata gördüm . Dünyadaki diğer herkesin ne yaptığını görmezden gelmek hiç mantıklı değil.
Jon Skeet

4

Benim için kimse mindpro.com'dan daha iyi açıklamıyor :

Sorunlar

java.util.GregorianCalendar daha az hata ve gotchas var old java.util.Datesınıftan ama hala piknik yok.

Yaz Saati Uygulaması önerildiğinde programcılar olsaydı, bunu çılgın ve zorlanamaz olarak veto ederlerdi. Gün ışığından yararlanma ile temel bir belirsizlik söz konusudur. Saatlerinizi saat 2'de bir saat geri ayarladığınızda sonbaharda, her ikisi de yerel saatle 01: 30'da adlandırılan iki farklı örnek vardır. Bunları sadece gün ışığından tasarruf etmeyi veya okuma ile standart saati kaydetmeyi planlıyorsanız ayırabilirsiniz.

Ne yazık ki anlatmanın bir yolu yok GregorianCalendar hangisini istediğinizi . Belirsizliği önlemek için kukla UTC TimeZone ile yerel saati söylemeye başvurmalısınız. Programcılar genellikle bu soruna gözlerini kapatırlar ve umarım bu saat boyunca kimse bir şey yapmaz.

Milenyum böcek. Hatalar hala Takvim sınıflarının dışında değil. JDK (Java Geliştirme Kiti) 1.3'te bile 2001 hatası var. Aşağıdaki kodu göz önünde bulundurun:

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

Hata, MST için 2001/01 / 01'de 07: 00'da kaybolur.

GregorianCalendardev bir tür sihirli sihirli sabit yığın tarafından kontrol edilir. Bu teknik, derleme zamanı hata denetimi umudunu tamamen yok eder. Örneğin, kullandığınız ayı elde etmek için GregorianCalendar. get(Calendar.MONTH));

GregorianCalendarham GregorianCalendar.get(Calendar.ZONE_OFFSET)ve gün ışığından yararlanma tasarrufuna sahiptir GregorianCalendar. get( Calendar. DST_OFFSET), ancak gerçek saat dilimi dengesini kullanmanın bir yolu yoktur. Bu ikisini ayrı ayrı almalı ve birlikte eklemelisiniz.

GregorianCalendar.set( year, month, day, hour, minute) saniye değerini 0 olarak ayarlamaz.

DateFormatve GregorianCalendardüzgün bir şekilde örmeyin. Takvimi dolaylı olarak Tarih olarak iki kez belirtmeniz gerekir.

Kullanıcı saat dilimini doğru şekilde yapılandırmamışsa, varsayılan olarak sessizce PST veya GMT olarak ayarlanacaktır.

GregorianCalendar'da Aylar, gezegendeki herkesin yaptığı gibi 1 yerine Ocak = 0'dan başlayarak numaralandırılır. Yine de günler, Pazar = 1, Pazartesi = 2,… Cumartesi = 7 ile haftanın günleri gibi 1'den başlar. Yine de DateFormat. ayrıştırma geleneksel şekilde Ocak = 1 ile davranır.


4

java.util.Month

Java, aylarca 1 tabanlı dizin kullanmanın başka bir yolunu sunar. java.time.MonthNumaralandırmayı kullanın . On iki ayın her biri için bir nesne önceden tanımlanmıştır. Ocak-Aralık ayları için her 1-12 numaraya atanmış sayıları vardır; getValuenumarayı arayın .

Ait yararlanın Month.JULY(size 7 verir) yerine Calendar.JULY(size 6 verir).

(import java.time.*;)

3

tl; Dr.

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

2

ayrıntılar

Jon Skeet tarafından cevap doğrudur.

Şimdi bu eski eski tarih-saat sınıfları için modern bir yerimiz var : java.time sınıfları.

java.time.Month

Bu sınıflar arasında numaralandırma vardır . Bir numaralandırma, bir veya daha fazla önceden tanımlanmış nesne, sınıf yüklendiğinde otomatik olarak başlatılan nesneler taşır. On : Biz bir düzine böyle nesneleri, her bir ad verilmiş , , , vb. Bunların her biri bir sınıf sabiti. Bu nesneleri kodunuzun herhangi bir yerinde kullanabilir ve iletebilirsiniz. Misal:Month MonthJANUARYFEBRUARYMARCHstatic final publicsomeMethod( Month.AUGUST )

Neyse ki, aklı başında numaralandırma var, 1-12, 1 Ocak ve 12 Aralık.

MonthBelirli bir ay numarası için nesne alın (1-12).

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

Diğer yöne giderken, bir Monthnesneye ay numarasını sorun.

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

Bu aydaki her aydaki gün sayısını bilmek gibi birçok kullanışlı yöntem . Sınıf ayın yerelleştirilmiş adını bile oluşturabilir .

Ayın yerelleştirilmiş adını çeşitli uzunluklarda veya kısaltmalarla alabilirsiniz.

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

février

Ayrıca, bu tam sayının nesnelerini yalnızca tamsayı sayıları yerine kod tabanınızın etrafına geçirmelisiniz . Bunu yapmak tip güvenliği sağlar, geçerli bir değer aralığı sağlar ve kodunuzu daha fazla kendi kendine belgelendirir. Oracle Eğitimine bakınJava'daki şaşırtıcı derecede güçlü numaralandırma tesisini bilmiyorsanız .

Ayrıca Yearve YearMonthsınıflarını da yararlı bulabilirsiniz .


Java.time hakkında

Java.time çerçevesi daha sonra Java 8 ve yerleşiktir. Bu sınıflar zahmetli eski yerini mirası gibi tarih-saat sınıfları java.util.Date, .Calendar& java.text.SimpleDateFormat.

Artık bakım modunda olan Joda-Time projesi java.time'a geçişi tavsiye ediyor.

Daha fazla bilgi için Oracle Eğiticisine bakın . Ve birçok örnek ve açıklama için Stack Overflow'da arama yapın. Spesifikasyon JSR 310'dur .

Java.time sınıflarını nereden edinebilirsiniz?

ThreeTen-Ekstra proje ek sınıfları ile java.time uzanır. Bu proje, java.time'a gelecekteki olası eklemeler için bir kanıt zeminidir. Burada bazı yararlı sınıfları gibi bulabilir Interval, YearWeek, YearQuarter, ve daha .


0

Tam olarak kendi başına sıfır, Takvim olarak tanımlanır. Ocak. Ints yerine numaralandırma yerine sabitleri kullanma sorunudur. Takvim.Ocak == 0.


1
Değerler bir ve aynıdır. API'ler de 0 döndürür, sabit ile aynıdır. Calendar.JANUARY 1 olarak tanımlanabilirdi - bütün mesele bu. Bir numaralandırma güzel bir çözüm olurdu, ancak Java 5'e kadar gerçek numaralandırmalar dile eklenmedi ve Date en başından beri var. Bu talihsiz bir durumdur, ancak üçüncü taraf kodu kullandıktan sonra böyle temel bir API'yi gerçekten "düzeltemezsiniz". Yapılabilecek en iyi şey, yeni API sağlamak ve insanları devam ettirmek için eskisini kullanımdan kaldırmaktır. Teşekkür ederim, Java 7 ...
Quinn Taylor

0

Çünkü dil yazımı göründüğünden daha zordur ve özellikle işlem süresi çoğu insanın düşündüğünden çok daha zordur. Sorunun küçük bir kısmı için (gerçekte Java değil), https://www.youtube.com/watch?v=-5wpm-gesOY adresindeki "Zaman ve Zaman Dilimlerinde Sorun - Computerphile" adlı YouTube videosuna bakın . Eğer kafanız karışıklık içinde gülmekten düşerse şaşırmayın.


-1

DannySmurf'un tembellik cevabına ek olarak, bunun gibi sabitleri kullanmaya teşvik etmenizi ekleyeceğim Calendar.JANUARY.


5
Belirli bir ay için kodu açıkça yazdığınızda her şey çok iyi, ancak ayı farklı bir kaynaktan "normal" formda aldığınızda acı çekiyor.
Jon Skeet

1
Bu ay değerini belirli bir şekilde yazdırmaya çalıştığınızda da bir acıdır - buna her zaman 1 eklersiniz.
Brian Warshaw

-2

Çünkü her şey 0 ile başlar. Bu, Java'da programlamanın temel bir gerçeğidir. Bir şey bundan sapacak olsaydı, bu bir karışıklığa neden olurdu. Bunların oluşumunu tartışmayalım ve onlarla kodlamayalım.


2
Hayır, gerçek dünyadaki çoğu şey 1 ile başlar. Ofsetler 0 ile başlar ve yılın ayı bir ofset değildir, ayın günü 31 veya 30 veya 29'dan biri gibi, oniki biri veya 28. Ayı ofset olarak işlemek kaprislidir, özellikle de ayın gününe aynı şekilde davranmazsak. Bu farkın nedeni ne olurdu?
SantiBailors

Gerçek Dünya'da 1 ile başlıyor, Java dünyasında 0 ile başlıyor. AMA ... sanırım çünkü: - haftanın gününü hesaplamak için birkaç hesaplama eklemeden birkaç hesaplama için dengelenemez. ek olarak - gerekirse aydaki tüm günleri gösterir (karışıklık olmadan veya Şubat'ı kontrol etmeye gerek kalmadan). - Ay boyunca sizi her iki şekilde kullanılması gereken bir tarih biçiminde çıkmaya zorlar. Buna ek olarak, bir yıldaki ay sayısı düzenli olduğundan ve bir aydaki günler dizileri bildirmeniz ve diziye daha iyi uyum sağlamak için bir uzaklık kullanmanız mantıklı değildir.
Syrrus
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.