Obsługa dat i czasu w Android Studio 4.0

Android Studio w wersji 4.0 wprowadza bardzo wyczekiwaną przeze mnie funkcjonalność, ponieważ pomimo wprowadzenia możliwości korzystania z Javy 8, obsługa tej wersji języka była bardzo mocno ograniczona. Jedną z nowości było wprowadzenie w Javie 8 przeprojektowanego API obsługi daty i czasu, które nie było dostępne dla każdego w Androidzie.

Kto pisał aplikacje na system operacyjny Android z obsługą operacji na dacie i czasie, wie, że nie jest to najprostsze i najwygodniejsze zadanie. Wprowadzenie obsługi Java 8 do Androida niestety nie pomogło w zupełności, pomimo tego, że ta wersja języka wprowadza zaprojektowane na nowo API do obsługi daty i czasu, było ono dostępne od Android SDK 26, więc aplikacja musiałaby wspierać minimum Androida w wersji 8.0. Korzystanie z Kotlina zamiast Javy również nie rozwiązywało problemu, ponieważ Kotlin nie posiada swojego API do realizacji tych zadań. Najlepszym wyjściem z tej sytuacji było skorzystanie z biblioteki Joda-Time, która oferuje API, które jest zamiennikiem do API Javy.

Teraz jest jeszcze jeden sposób na rozwiązanie tego problemu. Wraz z premierą Android Studio 4.0, została wydana nowa wersja pluginu Gradle Androida w wersji 4.0.0, która umożliwia skorzystanie z nowych API Javy 8 również w starszych wersjach systemu Android przy pomocy biblioteki służącej do desugaryzacji (zamianie ładnego, czytelnego kodu dla programisty na kod zrozumiały dla kompilatora).

Konfiguracja projektu

Aby skorzystać z nowego rozwiązania, należy wprowadzić kilka modyfikacji do projektu. Zmiany należy zastosować w pliku build.gradle swojego modułu:

android {
  defaultConfig {
    // wymagane dla minSdkVersion 20 lub niższej
    multiDexEnabled true
  }

  compileOptions {
    // flaga ustawiająca obsługę nowych API języka
    coreLibraryDesugaringEnabled true

    // włączenie kompatybilności z Javą 8
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
  }
}

dependencies {
  coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.5'
}

Jak widać powyżej, konfiguracja projektu nie jest wcale skomplikowana, a może przynieść nam sporo ułatwień podczas prac nad aplikacjami.

W praktyce całość ogranicza się do ustawienia flagi coreLibraryDesugaringEnabled, instalacji biblioteki do desugaryzacji oraz włączenia kompatybilności z Javą 8 (jak było to robione do tej pory).

LocalDate

Obiekt tej klasy jest przeznaczony do przechowywania informacji o dacie bez informacji o strefie czasowej. Obiekt tej klasy możemy stworzyć w następujący sposób:

  • LocalDate.now() - aktualna data
  • LocalDate.parse(String date) - obiekt na podstawie daty w formacie ISO
  • LocalDate.of(int year, int month, int day) - obiekt na podstawie liczbowej prezentacji rok-miesiąc-dzień

Przykład w języku Kotlin:

val localDateNow = LocalDate.now()
Log.i("TAG_DATE", localDateNow.toString())

val localDateParse = LocalDate.parse("2020-06-01")
Log.i("TAG_DATE", localDateParse.toString())

val localDateOf = LocalDate.of(2020, 6, 1)
Log.i("TAG_DATE", localDateOf.toString())

LocalTime

Klasa ta służy do przechowywania informacji o samym czasie bez informacji o strefie czasowej. Obiekt tej klasy możemy stworzyć w następujący sposób:

  • LocalTime.now() - aktualny czas
  • LocalTime.parse(String time) - obiekt na podstawie czasu w formacie ISO
  • LocalTime.of(int hour, int minute, int second, int nanoOfSecond) (obowiązkowe jest tylko podanie godziny oraz minuty) - obiekt na podstawie liczbowej prezentacji godzina-minuta-sekunda-nanosekunda

Przykład w języku Kotlin:

val localTimeNow = LocalTime.now()
Log.i("TAG_TIME", localTimeNow.toString())

val localTimeParse = LocalTime.parse("15:30:20")
Log.i("TAG_TIME", localTimeParse.toString())

val localTimeOf = LocalTime.of(15, 30, 20)
Log.i("TAG_TIME", localTimeOf.toString())

LocalDateTime

Ta klasa służy do przechowywania daty oraz czasu w jednym obiekcie. Również ona, tak jak dwie poprzednie, nie przechowuje informacji o strefie czasowej. Obiekty tej klasy można stworzyć w następujący sposób:

  • LocalDateTime.now() - aktualny czas
  • LocalDateTime.parse(String time) - obiekt na podstawie czasu w formacie ISO
  • LocalDateTime.of(int year, int month, int day, int hour, int minute, int second, int nanoOfSecond) (obowiązkowe jest podanie roku, miesiąca, dnia oraz godziny i minuty) - obiekt na podstawie liczbowej prezentacji godzina-minuta-sekunda-nanosekunda
  • LocalDateTime.of(LocalDate date, LocalTime time) - na podstawie obiektów klas LocalDate oraz LocalTime

Przykład w języku Kotlin:

val localDateTimeNow = LocalDateTime.now()
Log.i("TAG_DATE_TIME", localDateTimeNow.toString())

val localDateTimeParse = LocalDateTime.parse("2020-06-01T15:30:20")
Log.i("TAG_DATE_TIME", localDateTimeParse.toString())

val localDateTimeOf = LocalDateTime.of(2020, 6, 1, 15, 30, 20)
Log.i("TAG_DATE_TIME", localDateTimeOf.toString())

val localDateTimeOfLocal = LocalDateTime.of(LocalDate.now(), LocalTime.now())
Log.i("TAG_DATE_TIME", localDateTimeOfLocal.toString())

Modyfikacja daty i czasu

Nowe API dostarcza bardzo proste metody służące do modyfikacji zapisanych informacji. Z niebywałą łatwością jesteśmy w stanie dodawać czy odejmować konkretny okres czasu.

Podczas tych operacji warto pamiętać, że daty w nowym API są niezmienne w związku z czym metody modyfikujące datę i czas zwracają nową instancję, nie modyfikując istniejącej.

Do modyfikacji dat możemy wykorzystać metody:

  • LocaleDate.plusDays(long daysToAdd) lub LocaleDateTime.plusDays(long daysToAdd) - dodanie określonej ilości dni
  • LocaleDate.plusWeeks(long weeksToAdd) lub LocaleDateTime.plusWeeks(long weeksToAdd) - dodanie określonej ilości tygodni
  • LocaleDate.plusMonths(long monthsToAdd) lub LocaleDateTime.plusMonths(long monthsToAdd) - dodanie określonej ilości miesięcy
  • LocaleDate.plusYears(long yearsToAdd) lub LocaleDateTime.plusYears(long yearsToAdd) - dodanie określonej ilości lat
  • LocaleDate.minusDays(long daysToSubtract) lub LocaleDateTime.minusDays(long daysToSubtract) - odjęcie określonej ilości dni
  • LocaleDate.minusWeeks(long weeksToSubtract) lub LocaleDateTime.minusWeeks(long weeksToSubtract) - odjęcie określonej ilości tygodni
  • LocaleDate.minusMonths(long monthsToSubtract) lub LocaleDateTime.minusMonths(long monthsToSubtract) - odjęcie określonej ilości miesięcy
  • LocaleDate.minusYears(long yearsToSubtract) lub LocaleDateTime.minusYears(long yearsToSubtract) - odjęcie określonej ilości lat

Przykład w języku Kotlin:

val localDate = LocalDate.now()
Log.i("TAG_MODIFY", "Obecna data: $localDate")

val tomorrowDate = localDate.plusDays(1)
Log.i("TAG_MODIFY", "Jutrzejsza data: $tomorrowDate")

val date3MonthsBefore = localDate.minusMonths(3)
Log.i("TAG_MODIFY", "Data sprzed 3 miesięcy: $date3MonthsBefore")

Do modyfikacji czasu możemy wykorzystać metody:

  • LocalTime.plusHours(long hoursToAdd) lub LocalDateTime.plusHours(long hoursToAdd) - dodanie określonej ilości godzin
  • LocalTime.plusMinutes(long minutesToAdd) lub LocalDateTime.plusMinutes(long minutesToAdd) - dodanie określonej ilości minut
  • LocalTime.plusSeconds(long secondstoAdd) lub LocalDateTime.plusSeconds(long secondstoAdd) - dodanie określonej ilości sekund
  • LocalTime.plusNanos(long nanosToAdd) lub LocalDateTime.plusNanos(long nanosToAdd) - dodanie określonej ilości nanosekund

  • LocalTime.minusHours(long hoursToSubtract) lub LocalDateTime.minusHours(long hoursToSubtract) - odjęcie określonej ilości godzin
  • LocalTime.minusMinutes(long minutesToSubtract) lub LocalDateTime.minusMinutes(long minutesToSubtract) - odjęcie określonej ilości minut
  • LocalTime.minusSeconds(long secondsToSubtract) lub LocalDateTime.minusSeconds(long secondsToSubtract) - odjęcie określonej ilości sekund
  • LocalTime.minusNanos(long nanosToSubtract) lub LocalDateTime.minusNanos(long nanosToSubtract) - odjęcie określonej ilości nanosekund

Przykład w języku Kotlin:

val localTime = LocalTime.now()
Log.i("TAG_MODIFY", "Obecny czas: $localTime")

val time4HoursAfter = localTime.plusHours(4)
Log.i("TAG_MODIFY", "Czas za 4 godziny: $time4HoursAfter")

val time500SecondsBefore = localTime.minusSeconds(500)
Log.i("TAG_MODIFY", "Czas sprzed 500 sekund: $time500SecondsBefore")

Formatowanie daty i czasu

Nowe API daty i czasu wprowadza również nową klasę służącą za formatowanie. Dzięki niej możemy sformatować datę i czas do określonego formatu jako tekst, jak również z niestandardowego formatu tekstu na odpowiedni obiekt:

val localDateTime = LocalDateTime.now()
val dateTimeFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm:ss")

val stringFromDateTime = dateTimeFormatter.format(localDateTime)
Log.i("TAG_FORMAT", "Data i czas sformatowane do stringa: $stringFromDateTime")

val dateTimeFromString = LocalDateTime.parse("01.06.2020 15:30:20", dateTimeFormatter)
Log.i("TAG_FORMAT", "Data i czas ze stringa: $dateTimeFromString")

Porównywanie dat

Dzięki nowemu API również w łatwy sposób możemy porównywać ze sobą dwie daty, aby sprawdzić, czy nie są równie lub któraś z nich jest późniejsza. Możemy skorzystać z następujących metod:

  • date1.isAfter(date2) - data, na której wywołujemy metodę, jest po dacie z parametru
  • date1.isBefore(date2) - data, na której wywołujemy metodę, jest przed datą z parametru
  • date1.isEqual(date2) - data, na której wywołujemy metodę, jest równa dacie z parametru
  • date1.compareTo(date2) - większe od 0 - odpowiednik isAfter(), mniejsze od 0 - odpowiednik isBefore(), równe 0 - odpowiednik isEqual()

A to jest przykład w języku Kotlin:

val localDate1 = LocalDate.of(2020, 6, 1)
val localDate2 = LocalDate.of(2020, 5, 1)

val isAfter = localDate1.isAfter(localDate2)
Log.i("TAG_COMPARE", "Czy pierwsza data jest po drugiej: $isAfter")

val isBefore = localDate1.isBefore(localDate2)
Log.i("TAG_COMPARE", "Czy pierwsza data jest przed drugą: $isBefore")

val isEqual = localDate1.isEqual(localDate2)
Log.i("TAG_COMPARE", "Czy obie daty są równe: $isEqual")

val compareTo = localDate1.compareTo(localDate2)

when {
    compareTo > 0 -> {
        Log.i("TAG_COMPARE", "Pierwsza data jest po drugiej dacie")
    }
    compareTo < 0 -> {
        Log.i("TAG_COMPARE", "Pierwsza data jest przed drugą datą")
    }
    compareTo == 0 -> {
        Log.i("TAG_COMPARE", "Obie daty są równe")
    }
}

Podsumowanie

Ten wpis nie wyczerpuje tematu związanego z nowym API Javy 8 do daty i czasu, które właśnie stało się szeroko dostępne w systemie Android. Dostępne są również inne nowe klasy służące do pracy z datą i czasem jak na przykład Period, która odpowiada za reprezentację okresu pomiędzy datami, jak również ZonedDateTime, która jest odpowiednikiem LocalDateTime jednak przechowującą również informację na temat strefy czasowej. Po bardziej szczegółowe informacje odsyłam do dokumentacji języka Java.

Jak widać, nowe API jest bardzo pomocne i zdecydowanie ułatwia pracę z datą i czasem w trakcie pisania aplikacji na system Android. Szkoda tylko, że trzeba było czekać na nie aż ponad 6 lat, od daty wydania Javy 8.