CodeGym /Blog Java /Poland /Daty w Javie - Calendar
Autor
Milan Vucic
Programming Tutor at Codementor.io

Daty w Javie - Calendar

Opublikowano w grupie Poland
Cześć! Dzisiaj zaczniemy pracować z nowym typem danych, którego dotąd nie znaliśmy, a mianowicie datami. Chyba nie muszę wyjaśniać, czym jest data. :) W zasadzie możemy przechowywać aktualne dane i czas w zwykłym stringu Java.

public class Main {
   public static void main(String[] args) {

       String date = "June 11, 2018";
       System.out.println(date);
   }
}
Lecz to podejście ma wiele wad. Klasa String zaprojektowana została do pracy z tekstem i jej metody są odpowiednie do tego zadania. Jeśli musimy w jakiś sposób manipulować datą (np. dodać 2 godziny), String nie działa tak dobrze. Lub jeśli chcemy wyświetlić aktualną datę i godzinę kompilacji programu. String tutaj też nie pomaga: zanim napiszesz kod i uruchomisz go, czas ulegnie zmianie, a konsola wyświetli błędne informacje. Dlatego twórcy Javy udostępnili kilka klas do pracy z datami i godzinami. Pierwszą z nich jest java.util.Date

Klasa Date w Java (Java Date)

Podaliśmy jego pełną nazwę, ponieważ inny pakiet Java ma klasę java.sql.Date. Nie pomieszaj ich! Pierwszą rzeczą, o której musisz wiedzieć, jest to, że zapisuje datę jako liczbę milisekund, które upłynęły od 1 stycznia 1970 r. Ten system czasu ma nawet swoją własną nazwę: "Unix-time" Całkiem interesujące podejście, prawda? :) Drugą rzeczą wartą zapamiętania jest: Jeśli utworzysz obiekt Date przy użyciu domyślnego konstruktora, wynik reprezentuje bieżącą datę i godzinę w momencie utworzenia obiektu. Pamiętasz, gdy powiedzieliśmy, że data reprezentowana jako String będzie miała problemy z takim zadaniem? Klasa Date z łatwością sobie z tym radzi.

public class Main {
   public static void main(String[] args) {

       Date date = new Date();
       System.out.println(date);
   }
}
Uruchom ten kod kilka razy, a zobaczysz wielokrotną zmianę czasu. :) Jest to możliwe, ponieważ czas przechowywany jest w milisekundach: są to bardzo małe jednostki czasu, więc wyniki są bardzo dokładne. Inny konstruktor klasy Date: możesz przekazać dokładną liczbę milisekund od godziny 00:00 1 stycznia 1970 do wymaganej daty, a utworzony zostanie odpowiedni obiekt daty:

public class Main {
   public static void main(String[] args) {

       Date date = new Date(1212121212121L);
       System.out.println(date);
   }
}
Wydruk konsoli:
Fri May 30 04:20:12 GMT 2008
Otrzymujemy 30 maja 2008. "Fri" oznacza dzień tygodnia ("Pt" - piątek), a GMT to strefa czasowa (Greenwich Mean Time - czas Greenwich). Milisekundy mają typ long, ponieważ liczba milisekund zazwyczaj nie mieści się w int. Więc jakie operacje z datami możemy mieć potrzebę wykonać? Cóż, najbardziej oczywistą jest z pewnością porównanie. Do określenia, czy jedna data przypada przed, czy po drugiej. Można to zrobić na kilka sposobów. Na przykład można wywołać metodę Date.getTime(), zwracającą liczbę milisekund, które upłynęły od północy 1 stycznia 1970 r. Wywołaj to po prostu dla dwóch obiektów Date i porównaj wyniki:

public class Main {
   public static void main(String[] args) {

       Date date1 = new Date();

       Date date2 = new Date();

       System.out.println((date1.getTime() > date2.getTime())?
               "date1 is later than date2" : "date1 is earlier than date2");
   }
}
Wydruk:
date1 jest wcześniejsza niż date2
Ale jest też wygodniejszy sposób, np. przy użyciu specjalnych metod dostarczonych przez klasę Date: before(), after() i equals(). Wszystkie zwracają wartość logiczną. Metoda before() sprawdza, czy nasza data jest wcześniejsza niż data podana jako argument:

public class Main {
   public static void main(String[] args) throws InterruptedException {

       Date date1 = new Date();

       Thread.sleep(2000);// Suspend the program for 2 seconds
       Date date2 = new Date();

       System.out.println(date1.before(date2));
   }
}
Wydruk konsoli:
true
Podobnie, metoda after() sprawdza, czy nasza data jest późniejsza niż data przekazana jako argument:

public class Main {
   public static void main(String[] args) throws InterruptedException {

       Date date1 = new Date();

       Thread.sleep(2000);// Suspend the program for 2 seconds
       Date date2 = new Date();

       System.out.println(date1.after(date2));
   }
}
Wydruk konsoli:
false
W naszych przykładach "usypiamy program" na 2 sekundy, aby zagwarantować, że te dwie daty będą różne. Na szybkich komputerach czas między utworzeniem date1 i date2 może być krótszy niż jedna milisekunda, powodując, że zarówno before() i after() zwracają wartość false. Ale w tym przypadku metoda equals() zwróci wartość true! W końcu porównuje ona liczbę milisekund od godziny 00:00 1 stycznia 1970 dla każdej daty. Obiekty te uważane są za równe tylko wtedy, gdy pasują do milisekundy:

public static void main(String[] args) {

   Date date1 = new Date();
   Date date2 = new Date();

   System.out.println(date1.getTime());
   System.out.println(date2.getTime());

   System.out.println(date1.equals(date2));
}
Oto kolejna rzecz, na którą musisz zwrócić uwagę. Jeśli otworzysz dokumentację klasy Date na stronie Oracle, zobaczysz, że wiele z jej metod i konstruktorów zostało oznaczonych jako Deprecated (tzn. nie zalecane do stosowania). Oto, co twórcy Javy mają do powiedzenia na temat części klas, które zostały wycofane:
Element programu z adnotacją @Deprecated to coś, czego nie zaleca się używać programistom, dlatego, że zazwyczaj są niebezpieczne lub istnieje lepsza alternatywa”.
Nie oznacza to, że tych metod nie można używać w ogóle. Jeśli spróbujesz uruchomić kod przy użyciu przestarzałych metod w IDE, najprawdopodobniej zadziała. Weźmy pod uwagę, na przykład przestarzałą metodę Date.getHours(), która zwraca liczbę godzin związanych z obiektem Date.

public static void main(String[] args) {

   Date date1 = new Date();

   System.out.println(date1.getHours());
}
Jeśli uruchomisz kod o 14:21 (2:21 PM), wyświetli się liczba 14. Daty w Javie - Calendar - 1Jak widać, przestarzała metoda jest przekreślona, ale wciąż działa. Te metody nie są usuwane, aby nie zniszczyć ogromnej ilości istniejącego kodu, który z nich korzysta. Innymi słowy, metody te nie są ani "zepsute" ani "usunięte". Po prostu nie są zalecane do użytku, ponieważ wygodniejsza alternatywa jest dostępna. Nawiasem mówiąc, w dokumentacji wyraźnie wspomina się o tej alternatywie:
Daty w Javie - Calendar - 2
Większość metod klasy Date została przeniesiona do ulepszonej i rozszerzonej klasy Calendar. Następnie zapoznamy się z tą klasą. :)

Klasa Calendar w Java (Java Calendar)

JDK 1.1 wprowadził nową klasę: Calendar. Dzięki temu praca z datami w Javie stała się nieco łatwiejsza niż dotychczas. Jedyną implementacją klasy Calendar, z którą będziemy pracować, jest klasa GregorianCalendar. Implementuje kalendarz gregoriański, który jest uznawany przez większość krajów świata. Jego główną zaletą jest to, że może pracować z datami w wygodniejszym formacie. Na przykład, może:
  • Dodać miesiąc lub dzień do aktualnej daty
  • Sprawdzić, czy dany rok jest rokiem przestępnym;
  • Zwrócić poszczególne składniki daty (na przykład wyodrębnić numer miesiąca z całej daty)
  • Zawiera on również bardzo wygodny system stałych (wiele z nich zobaczymy poniżej).
Innym ważnym ulepszeniem klasy Calendar jest jej stała Calendar.ERA: można wskazać datę przed naszą erą (BC - Before Christ) lub z naszej ery (AD - Anno Domini). Zobaczmy to wszystko na przykładach. Stwórzmy obiekt calendar z datą 25 stycznia 2017:

public static void main(String[] args) {

  Calendar calendar = new GregorianCalendar(2017, 0 , 25);
}
W klasie Calendar (a także w klasie Date) miesiące zaczynają się od zera, więc podajemy liczbę 0 jako drugi argument. Podczas pracy z klasą Calendar należy pamiętać, że jest to właśnie kalendarz, a nie indywidualna data. Data to tylko kilka liczb wskazujących konkretny przedział czasu. Kalendarz to cały system, który pozwala robić wiele rzeczy z datami. :) Jest to bardzo widoczne, jeśli spróbujesz wyświetlić obiekt Calendar: Wydruk:
java.util.GregorianCalendar[time=?,areFieldsSet=false,areAllFieldsSet=false,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Europe/London",offset=0,dstSavings=0,useDaylight=false,transitions=79,lastRule=null],firstDayOfWeek=2,minimalDaysInFirstWeek=1,ERA=?,YEAR=2017,MONTH=0,WEEK_OF_YEAR=?,WEEK_OF_MONTH=?,DAY_OF_MONTH=25,DAY_OF_YEAR=?,DAY_OF_WEEK=?,DAY_OF_WEEK_IN_MONTH=?,AM_PM=0,HOUR=0,HOUR_OF_DAY=0,MINUTE=0,SECOND=0,MILLISECOND=?,ZONE_OFFSET=?,DST_OFFSET=?]
Zobacz, ile informacji uzyskasz! Kalendarz ma kilka właściwości, których zwykła data nie ma i wszystkie z nich są wyświetlane (tak działa metoda toString() w klasie Calendar). Jeśli potrzebujesz tylko prostej daty z kalendarza, czyli obiektu Date, użyj metody Calendar.getTime() (nazwa nie brzmi zbyt logicznie, no ale co zrobić?):

public static void main(String[] args) {

   Calendar calendar = new GregorianCalendar(2017, 0 , 25);
   Date date = calendar.getTime();
   System.out.println(date);
}
Wydruk:
Śr. Stycz. 25 00:00:00 GMT 2017
Teraz zajęliśmy się kalendarzem i "zredukowaliśmy go" do zwykłej daty. Przejdźmy dalej. Oprócz oznaczania miesięcy według ich liczby można użyć wartości pól stałych klasy Calendar. Te stałe to pola statyczne klasy Calendar z wstępnie ustawioną wartością, która nie może być zmieniona. W rzeczywistości jest to jeszcze lepsza opcja, ponieważ korzystanie z nich poprawia czytelność kodu.

public static void main(String[] args) {
   GregorianCalendar calendar = new GregorianCalendar(2017, Calendar.JANUARY , 25);
}
Calendar.JANUARY to jedna ze stałych reprezentujących miesiące w roku. Używając tych nazwanych stałych, nikt nie zapomni na przykład, że liczba 3 oznacza kwiecień, a nie trzeci miesiąc, który lubimy nazywać marcem. Po prostu napisz Calendar.APRIL i gotowe. :) Wszystkie pola kalendarza (liczba, miesiąc, minuty, sekundy itp.) można określić osobno używając metody set(). Metoda ta jest bardzo wygodna, ponieważ klasa Calendar ma stałą dla każdego pola, a wynikowy kod jest bardzo łatwy do odczytania. W ostatnim przykładzie utworzyliśmy datę, ale nie ustawiliśmy dla niej czasu. Ustawmy czas 19:42:12

public static void main(String[] args) {
   Calendar calendar = new GregorianCalendar();
   calendar.set(Calendar.YEAR, 2017);
   calendar.set(Calendar.MONTH, 0);
   calendar.set(Calendar.DAY_OF_MONTH, 25);
   calendar.set(Calendar.HOUR_OF_DAY, 19);
   calendar.set(Calendar.MINUTE, 42);
   calendar.set(Calendar.SECOND, 12);

   System.out.println(calendar.getTime());
}
Wydruk:
Śr. Stycz. 25 19:42:12 GMT 2017
Wywołujemy metodę set(), przekazując stałą (w zależności od pola, które chcemy zmienić) i nową wartość dla pola. Okazuje się, że ta metoda set() jest rodzajem "super-settera", który wie, jak ustawić wartość nie tylko dla jednego pola, ale dla wielu pól. :) Klasa Calendar używa metody add() do dodawania i odejmowania wartości. Podajesz pole, które chcesz zmienić oraz liczbę (dokładnie, ile chcesz dodać/odjąć od aktualnej wartości). Na przykład weźmy datę, która jest 2 miesiące przed datą, którą utworzyliśmy:

public static void main(String[] args) {
   Calendar calendar = new GregorianCalendar(2017, Calendar.JANUARY , 25);
   calendar.set(Calendar.HOUR, 19);
   calendar.set(Calendar.MINUTE, 42);
   calendar.set(Calendar.SECOND, 12);

   calendar.add(Calendar.MONTH, -2); // To subtract, pass a negative number
   System.out.println(calendar.getTime());
}
Wydruk:
Pt. List. 25 19:42:12 GMT 2016
Bardzo dobrze! Mamy datę sprzed 2 miesięcy. Spowodowało to nie tylko zmianę miesiąca: zmienił się również rok z 2017 na 2016. Oczywiście podczas konwersji dat aktualny rok obliczany jest automatycznie, bez konieczności jego ręcznego śledzenia. Ale jeśli z jakiegoś powodu musisz wyłączyć te działania, możesz to zrobić. Metoda roll() może dodawać i odejmować wartości bez wpływu na wartości pozostałe. Jak na przykład tutaj:

public static void main(String[] args) {
   Calendar calendar = new GregorianCalendar(2017, Calendar.JANUARY , 25);
   calendar.set(Calendar.HOUR, 10);
   calendar.set(Calendar.MINUTE, 42);
   calendar.set(Calendar.SECOND, 12);

   calendar.roll(Calendar.MONTH, -2);
   System.out.println(calendar.getTime());
}
Zrobiliśmy dokładnie to samo, co w poprzednim przykładzie: odjęliśmy 2 miesiące od obecnej daty. Ale teraz kod robi coś innego: miesiąc zmienił się od stycznia do listopada, ale rok pozostał niezmieniony - 2017! Wydruk:
Sob. List. 25 10:42:12 GMT 2017
Idziemy dalej. Jak powiedzieliśmy powyżej, możemy wszystkie pola Calendar uzyskać oddzielnie. Robimy to za pomocą metody get():

public static void main(String[] args) {
   GregorianCalendar calendar = new GregorianCalendar(2017, Calendar.JANUARY , 25);
   calendar.set(Calendar.HOUR, 10);
   calendar.set(Calendar.MINUTE, 42);
   calendar.set(Calendar.SECOND, 12);

   System.out.println("Year: " + calendar.get(Calendar.YEAR));
   System.out.println("Month: " + calendar.get(Calendar.MONTH));
   System.out.println("Week in the month: " + calendar.get(Calendar.WEEK_OF_MONTH));// Week in this month?

   System.out.println("Day: " + calendar.get(Calendar.DAY_OF_MONTH));

   System.out.println("Hours: " + calendar.get(Calendar.HOUR));
   System.out.println("Minutes: " + calendar.get(Calendar.MINUTE));
   System.out.println("Seconds: " + calendar.get(Calendar.SECOND));
   System.out.println("Milliseconds: " + calendar.get(Calendar.MILLISECOND));

}
Wydruk:
Rok: 2017 Miesiąc: 0 Tydzień w miesiącu: 5 Dzień: 25  Godzin: 10  Minut: 42 Sekund: 12 Milisekund: 0
Tak więc, oprócz "super-settera" klasy Calendar, istnieje również "super-getter". :) Oczywiście innym interesującym aspektem tej klasy jest praca z epokami. Aby stworzyć datę "BC" (p.n.e), należy użyć pola Calendar.ERA. Stwórzmy na przykład datę Bitwy pod Kanną, gdzie Hannibal pokonał armię rzymską. Stało się to 2 sierpnia 216 r. p.n.e.:

public static void main(String[] args) {
   GregorianCalendar cannae = new GregorianCalendar(216, Calendar.AUGUST, 2);
   cannae.set(Calendar.ERA, GregorianCalendar.BC);

   DateFormat df = new SimpleDateFormat("MMM dd, yyy GG");
   System.out.println(df.format(cannae.getTime()));
}
Tutaj użyliśmy klasy SimpleDateFormat do wydrukowania daty w formacie, który jest dla nas łatwiejszy do zrozumienia (litery ”GG” wskazują, że chcemy, aby data była wyświetlana). Wydruk:
Sierp. 02, 216 p.n.e.
Klasa Calendar ma znacznie więcej metod i stałych. Możesz o nich przeczytać w dokumentacji. Jeśli nie podoba Ci się ten format daty Sob. List. 25 10:42:12 GMT 2017, możesz użyć SimpleDateFormat, aby łatwo ustawić go tak, jak chcesz.

public static void main(String[] args) {

   SimpleDateFormat dateFormat = new SimpleDateFormat("EEEE, MMMM d, yyyy");
   Calendar calendar = new GregorianCalendar(2017, Calendar.JANUARY , 25);
   calendar.set(Calendar.HOUR, 10);
   calendar.set(Calendar.MINUTE, 42);
   calendar.set(Calendar.SECOND, 12);

   calendar.roll(Calendar.MONTH, -2);
   System.out.println(dateFormat.format(calendar.getTime()));
}
Wydruk:
Sobota, Listopad 25, 2017
O wiele lepiej, prawda? :)
Ten artykuł przeczytasz także po angielsku.
Read the English version of this article about the DateTime and Calendar classes. Programmers need to be good at time management!
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION