W java.util.Calendar
styczniu jest zdefiniowany jako miesiąc 0, a nie miesiąc 1. Czy jest jakiś konkretny powód?
Widziałem wielu ludzi zdezorientowanych z tego powodu ...
W java.util.Calendar
styczniu jest zdefiniowany jako miesiąc 0, a nie miesiąc 1. Czy jest jakiś konkretny powód?
Widziałem wielu ludzi zdezorientowanych z tego powodu ...
Odpowiedzi:
To tylko część okropnego bałaganu, jakim jest interfejs API daty / godziny Java. Wypisanie, co jest nie tak, zajęłoby bardzo dużo czasu (i jestem pewien, że nie znam połowy problemów). Wprawdzie praca z datami i godzinami jest trudna, ale i tak jest.
Zrób sobie przysługę i zamiast tego użyj Czas Jody lub ewentualnie JSR-310 .
EDYCJA: Z powodów, dla których - jak zauważono w innych odpowiedziach, może to być spowodowane starymi interfejsami API C lub po prostu ogólnym poczuciem, że wszystko zaczyna się od 0 ... z wyjątkiem tego, że dni zaczynają się od 1, oczywiście. Wątpię, czy ktokolwiek spoza pierwotnego zespołu wdrożeniowego naprawdę mógłby podać powody - ale ponownie zachęcam czytelników, aby nie martwili się tak bardzo o to, dlaczego podjęto złe decyzje, aby spojrzeć na całą gamę paskudności java.util.Calendar
i znaleźć coś lepszego.
Jeden punkt, który jest na korzyść stosując 0 oparte indeksów jest fakt, że takie rzeczy jak „tablicami nazw” łatwiej:
// I "know" there are 12 months
String[] monthNames = new String[12]; // and populate...
String name = monthNames[calendar.get(Calendar.MONTH)];
Oczywiście kończy się to niepowodzeniem, gdy tylko otrzymasz kalendarz z 13 miesiącami ... ale przynajmniej podany rozmiar to oczekiwana liczba miesięcy.
To nie jest dobry powód, ale to powód ...
EDYCJA: W ramach komentarza żądam pewnych pomysłów na temat tego, co uważam za złe w przypadku daty / kalendarza:
Date
i Calendar
jak różne rzeczy, ale oddzielenia „lokalny” vs „strefowego” Wartości brakuje, podobnie jak data / czas vs daty vs czasDate.toString()
Realizacja który zawsze wykorzystuje system lokalnej strefy czasowej (to mylić wielu użytkowników przepełnienie stosu przedtem)Ponieważ robienie matematyki w miesiącach jest znacznie łatwiejsze.
1 miesiąc po grudniu przypada na styczeń, ale aby to normalnie zrozumieć, musisz wziąć numer miesiąca i zrobić matematykę
12 + 1 = 13 // What month is 13?
Wiem! Mogę to szybko naprawić za pomocą modułu 12.
(12 + 1) % 12 = 1
Działa to dobrze przez 11 miesięcy do listopada ...
(11 + 1) % 12 = 0 // What month is 0?
Możesz sprawić, by wszystko to znów działało, odejmując 1 przed dodaniem miesiąca, a następnie zrób swój moduł i na koniec dodaj 1 z powrotem ... czyli obejdź podstawowy problem.
((11 - 1 + 1) % 12) + 1 = 12 // Lots of magical numbers!
Pomyślmy teraz o problemie z miesiącami 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
Wszystkie miesiące pracują tak samo i obejście nie jest konieczne.
((11 - 1 + 1) % 12) + 1 = 12
to (11 % 12) + 1
znaczy, że dla miesięcy 1..12 wystarczy dodać 1 po zrobieniu modulo. Nie wymaga magii.
Języki oparte na języku C w pewnym stopniu kopiują język C. tm
Struktura (zdefiniowane w time.h
) ma pole całkowitą tm_mon
z (komentarzem) zakresie 0-11.
Języki oparte na języku C rozpoczynają tablice od indeksu 0. Było to wygodne w przypadku wyprowadzania ciągu w tablicy nazw miesięcy, z tm_mon
indeksem.
Istnieje wiele odpowiedzi na to pytanie, ale i tak przedstawię swój pogląd na ten temat. Powód tego dziwnego zachowania, jak wspomniano wcześniej, pochodzi z POSIX C, time.h
gdzie miesiące były przechowywane w int z zakresu 0-11. Aby wyjaśnić dlaczego, spójrz na to w ten sposób; lata i dni są uważane za liczby w języku mówionym, ale miesiące mają swoje własne nazwy. Ponieważ styczeń jest pierwszym miesiącem, będzie przechowywany jako offset 0, pierwszy element tablicy. monthname[JANUARY]
byłoby "January"
. Pierwszy miesiąc w roku to element tablicy pierwszego miesiąca.
Z drugiej strony liczby dzienne, ponieważ nie mają nazw, przechowywanie ich w liczbach całkowitych jako 0-30 byłoby mylące, dodawało wiele day+1
instrukcji dotyczących wyświetlania i, oczywiście, było podatne na wiele błędów.
To powiedziawszy, niespójność jest myląca, szczególnie w javascript (który również odziedziczył tę „cechę”), języku skryptowym, w którym należy go wyodrębnić z dala od języka.
TL; DR : Ponieważ miesiące mają nazwy, a dni miesiąca nie.
W Javie 8 jest nowy API JSR 310 Data / Czas, który jest bardziej rozsądny. Lead spec jest taki sam jak główny autor JodaTime i mają wiele podobnych koncepcji i wzorców.
Powiedziałbym lenistwo. Tablice zaczynają się od 0 (wszyscy o tym wiedzą); miesiące w roku są tablicą, co prowadzi mnie do przekonania, że jakiś inżynier w firmie Sun po prostu nie zadał sobie trudu, aby wprowadzić ten jeden drobiazg do kodu Java.
Prawdopodobnie dlatego, że „struct tm” C robi to samo.
Ponieważ programiści mają obsesję na punkcie indeksów opartych na 0. OK, jest to nieco bardziej skomplikowane: bardziej sensowne jest, gdy pracujesz z logiką niższego poziomu, aby używać indeksowania opartego na 0. Ale w zasadzie nadal będę trzymać pierwsze zdanie.
Osobiście wziąłem obcość interfejsu API kalendarza Java jako wskazówkę, że muszę oderwać się od gregoriańskiego sposobu myślenia i starać się programować bardziej agnostycznie pod tym względem. W szczególności po raz kolejny nauczyłem się unikać stałych zakodowanych na przykład przez miesiące.
Które z poniższych stwierdzeń jest bardziej prawdopodobne?
if (date.getMonth() == 3) out.print("March");
if (date.getMonth() == Calendar.MARCH) out.print("March");
Ilustruje to jedną rzecz, która trochę mnie denerwuje w Joda Time - może zachęcić programistów do myślenia w kategoriach stałych zakodowanych na stałe. (Ale tylko trochę. To nie tak, że Joda zmusza programistów do złego programowania.)
Dla mnie nikt nie wyjaśnia tego lepiej niż mindpro.com :
Gotchas
java.util.GregorianCalendar
ma o wiele mniej błędów i błędów niż wold java.util.Date
klasie, ale nadal nie jest to piknik.Gdyby istnieli programiści, kiedy po raz pierwszy zaproponowano czas letni, zawetowaliby go jako szalony i trudny do rozwiązania. W świetle dziennym istnieje zasadnicza dwuznaczność. Jesienią, kiedy cofniesz swoje zegary o godzinę o 2 w nocy, istnieją dwa różne momenty, oba zwane 1:30 czasu lokalnego. Możesz je odróżnić tylko wtedy, gdy podczas czytania zarejestrujesz, czy zamierzałeś zaoszczędzić czas letni, czy standardowy czas.
Niestety nie ma sposobu, aby powiedzieć,
GregorianCalendar
co zamierzałeś. Aby uniknąć niejednoznaczności, należy podać czas lokalny w atrapie strefy czasowej UTC. Programiści zwykle zamykają oczy na ten problem i mają tylko nadzieję, że nikt nic nie zrobi w tej godzinie.Błąd Millennium. Błędy nadal nie są poza klasami kalendarza. Nawet w JDK (Java Development Kit) 1.3 występuje błąd z 2001 roku. Rozważ następujący kod:
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 */
Błąd znika o 7 rano 2001/01/01 dla MST.
GregorianCalendar
kontrolowany jest przez gigantyczny stos nietypowych stałych magicznych. Ta technika całkowicie niweczy wszelkie nadzieje na sprawdzenie błędów podczas kompilacji. Na przykład, aby uzyskać miesiąc, którego używaszGregorianCalendar. get(Calendar.MONTH));
GregorianCalendar
maGregorianCalendar.get(Calendar.ZONE_OFFSET)
oszczędność czasu surowego i dziennegoGregorianCalendar. get( Calendar. DST_OFFSET)
, ale nie ma możliwości uzyskania rzeczywistego przesunięcia strefy czasowej. Musisz zdobyć te dwa osobno i dodać je razem.
GregorianCalendar.set( year, month, day, hour, minute)
nie ustawia sekund na 0.
DateFormat
iGregorianCalendar
nie siatkuj poprawnie. Musisz podać kalendarz dwa razy, raz pośrednio jako datę.Jeśli użytkownik nie skonfigurował poprawnie swojej strefy czasowej, domyślnie po cichu wybierze PST lub GMT.
W GregorianCalendar miesiące są numerowane od stycznia = 0, a nie 1, jak wszyscy inni na planecie. Jednak dni zaczynają się od 1, podobnie jak dni tygodnia z niedzielą = 1, poniedziałek = 2,… sobota = 7. Mimo to DateFormat. parsowanie zachowuje się w tradycyjny sposób, a styczeń = 1.
java.util.Month
Java zapewnia inny sposób korzystania z indeksów opartych na 1 przez miesiące. Użyj java.time.Month
wyliczenia. Jeden obiekt jest predefiniowany dla każdego z dwunastu miesięcy. Mają numery przypisane do każdego 1-12 na styczeń-grudzień; zadzwoń getValue
pod numer.
Użyj Month.JULY
(Daje ci 7) zamiast Calendar.JULY
(Daje ci 6).
(import java.time.*;)
Month.FEBRUARY.getValue() // February → 2.
2)
Odpowiedz przez Jon Skeet jest poprawna.
Teraz mamy nowoczesny zamiennik dla tych kłopotliwych starych klas daty i godziny: klasy java.time .
java.time.Month
Wśród tych klas jest wyliczenie . Wyliczenie przenosi jeden lub więcej predefiniowanych obiektów, obiektów, które są automatycznie tworzone w postaci instancji podczas ładowania klasy. Na mamy kilkanaście takich obiektów, z których każdy otrzymał nazwę: , , , i tak dalej. Każda z nich jest stałą klasową. Możesz używać i przekazywać te obiekty w dowolnym miejscu w kodzie. Przykład:Month
Month
JANUARY
FEBRUARY
MARCH
static final public
someMethod( Month.AUGUST )
Na szczęście mają rozsądną numerację, 1-12, gdzie 1 to styczeń, a 12 to grudzień.
Zdobądź Month
obiekt dla określonego numeru miesiąca (1-12).
Month month = Month.of( 2 ); // 2 → February.
Idąc w innym kierunku, poproś Month
obiekt o numer miesiąca.
int monthNumber = Month.FEBRUARY.getValue(); // February → 2.
Wiele innych przydatnych metod w tej klasie, takich jak znajomość liczby dni w każdym miesiącu . Klasa może nawet wygenerować zlokalizowaną nazwę miesiąca.
Możesz uzyskać zlokalizowaną nazwę miesiąca, w różnych długościach lub skrótach.
String output =
Month.FEBRUARY.getDisplayName(
TextStyle.FULL ,
Locale.CANADA_FRENCH
);
gorączka
Powinieneś także przekazywać obiekty tego wyliczenia wokół swojej bazy kodu, a nie tylko liczb całkowitych . Takie postępowanie zapewnia bezpieczeństwo typu, prawidłowy zakres wartości i sprawia, że kod jest bardziej samodokumentujący. Zobacz Oracle Tutorial, jeśli nie znasz zaskakująco potężnego narzędzia wyliczania w Javie.
Przydatne mogą być również klasy Year
i YearMonth
.
Środowisko java.time jest wbudowane w Javę 8 i nowsze wersje . Klasy te kłopotliwe zastąpić stary starszych klas Date-Time, takich jak java.util.Date
, .Calendar
, i java.text.SimpleDateFormat
.
Projekt Joda-Time , teraz w trybie konserwacji , zaleca migrację do java.time.
Aby dowiedzieć się więcej, zobacz samouczek Oracle . I przeszukaj stos przepełnienia dla wielu przykładów i wyjaśnień. Specyfikacja to JSR 310 .
Gdzie można uzyskać klasy java.time?
Projekt ThreeTen-Extra rozszerza java.time o dodatkowe klasy. Ten projekt jest poligonem doświadczalnym dla ewentualnych przyszłych dodatków do java.time. Można znaleźć kilka przydatnych klas tutaj takie jak Interval
, YearWeek
, YearQuarter
, i więcej .
Nie jest dokładnie zdefiniowane jako zero samo w sobie, jest zdefiniowane jako Kalendarz. Styczeń. Problem polega na użyciu ints jako stałych zamiast wyliczeń. Kalendarz. Styczeń == 0.
Ponieważ pisanie języków jest trudniejsze niż się wydaje, a zwłaszcza radzenie sobie z czasem jest o wiele trudniejsze niż większość ludzi myśli. Niewielką część problemu (w rzeczywistości nie Java) można znaleźć na wideo na YouTube „Problem z czasem i strefami czasowymi - Computerphile” na stronie https://www.youtube.com/watch?v=-5wpm-gesOY . Nie zdziw się, jeśli twoja głowa spadnie ze śmiechu w dezorientacji.
Oprócz odpowiedzi lenistwa DannySmurfa dodam, że ma to zachęcić cię do używania stałych, takich jak Calendar.JANUARY
.
Ponieważ wszystko zaczyna się od 0. Jest to podstawowy fakt programowania w Javie. Jeśli jedna rzecz miałaby się od tego odstąpić, doprowadziłoby to do całego zamieszania. Nie kłóćmy się o ich tworzenie i kodowanie z nimi.