İç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 ...
İç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 ...
Yanıtlar:
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.Calendar
ve 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:
Date
ve Calendar
farklı şeyler gibi, ancak "yerel" ve "bölgelenmiş" değerlerin ayrılması eksik, tarih / saat ve tarih vs saatDate.toString()
zaman sistem yerel saat dilimini kullanan uygulama (şimdiye kadar birçok Stack Overflow kullanıcısının kafasını karıştırdı)Çü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.
((11 - 1 + 1) % 12) + 1 = 12
sadece (11 % 12) + 1
ay 1..12 için sadece modulo yaptıktan sonra 1 eklemeniz gerekir . Büyü gerekmez.
C tabanlı diller C'yi bir dereceye kadar kopyalar. tm
Yapı (tanımlanan time.h
) bir tam sayı alanı olan tm_mon
0-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_mon
dizin olarak bir dize çıktılamak için kullanışlıdır .
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.h
0-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.
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.
Muhtemelen C'nin "struct tm" si aynısını yapıyor.
Çü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.
Ş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 .)
Benim için kimse mindpro.com'dan daha iyi açıklamıyor :
Sorunlar
java.util.GregorianCalendar
daha az hata ve gotchas varold java.util.Date
sı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.
GregorianCalendar
dev 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çinGregorianCalendar. get(Calendar.MONTH));
GregorianCalendar
hamGregorianCalendar.get(Calendar.ZONE_OFFSET)
ve gün ışığından yararlanma tasarrufuna sahiptirGregorianCalendar. 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.
DateFormat
veGregorianCalendar
dü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.
java.util.Month
Java, aylarca 1 tabanlı dizin kullanmanın başka bir yolunu sunar. java.time.Month
Numaralandı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; getValue
numarayı arayın .
Ait yararlanın Month.JULY
(size 7 verir) yerine Calendar.JULY
(size 6 verir).
(import java.time.*;)
Month.FEBRUARY.getValue() // February → 2.
2
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
Month
JANUARY
FEBRUARY
MARCH
static final public
someMethod( Month.AUGUST )
Neyse ki, aklı başında numaralandırma var, 1-12, 1 Ocak ve 12 Aralık.
Month
Belirli bir ay numarası için nesne alın (1-12).
Month month = Month.of( 2 ); // 2 → February.
Diğer yöne giderken, bir Month
nesneye 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 Year
ve YearMonth
sınıflarını da yararlı bulabilirsiniz .
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 .
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.
Çü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.
DannySmurf'un tembellik cevabına ek olarak, bunun gibi sabitleri kullanmaya teşvik etmenizi ekleyeceğim Calendar.JANUARY
.
Çü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.