Banyak tanggal dalam kehidupan sehari-hari tidak terikat zona waktu: tanggal lahir seseorang, tanggal faktur, jam buka toko, atau jadwal rapat yang belum diikat ke kota tertentu. Untuk kasus seperti ini, java.time menyediakan tiga tipe terpisah: LocalDate (tanggal saja), LocalTime (jam saja), dan LocalDateTime (gabungan keduanya). Bab ini memperkenalkan cara membuat ketiga tipe tersebut, mengakses propertinya, serta mengurai string ISO-8601 menjadi instance valid.
Tipe LocalDate dan LocalTime #
LocalDate merepresentasikan tanggal tanpa jam dan tanpa zona. LocalTime merepresentasikan jam tanpa tanggal dan tanpa zona. Keduanya tidak membawa informasi offset sehingga tidak dapat diubah menjadi Instant tanpa menyertakan zona secara eksplisit. Tabel berikut memetakan perbedaannya:
| Tipe | Mengandung | Tidak Mengandung | Cocok Untuk |
|---|---|---|---|
LocalDate | tahun, bulan, hari | jam, zona | tanggal lahir, hari libur |
LocalTime | jam, menit, detik, nano | tanggal, zona | jam buka, jam alarm |
LocalDateTime | gabungan keduanya | zona | jadwal abstrak |
Pemilihan tipe yang tepat menentukan apakah tanggal bisa dibandingkan, dihitung selisihnya, atau dikonversi ke zona tertentu. LocalDate cocok untuk input pengguna yang tidak peduli jam, sedangkan LocalDateTime dipakai bila kita butuh jam namun belum tahu zona pengguna.
Konstruksi LocalDate, LocalTime, LocalDateTime #
Kode berikut membuat ketiga tipe dengan beberapa cara, lalu mengakses properti harian seperti dayOfWeek, dayOfYear, month, dan sebagainya:
import java.time.DayOfWeek
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
import java.time.Month
fun main() {
// 1. LocalDate.of(year, month, day)
val tanggalLahir: LocalDate = LocalDate.of(2026, 1, 15)
println("=== LocalDate ===")
println("tanggalLahir : $tanggalLahir")
println("tahun : ${tanggalLahir.year}")
println("bulan (Month) : ${tanggalLahir.month}")
println("bulan (Int) : ${tanggalLahir.monthValue}")
println("hari : ${tanggalLahir.dayOfMonth}")
println("hariKeDalamTahun: ${tanggalLahir.dayOfYear}")
println("hariDalamPekan : ${tanggalLahir.dayOfWeek}")
// 2. LocalDate.of dengan enum Month (lebih deskriptif)
val hariLibur: LocalDate = LocalDate.of(2026, Month.AUGUST, 17)
println()
println("=== LocalDate dengan Month enum ===")
println("hariLibur : $hariLibur (Hari Kemerdekaan)")
// 3. LocalTime.of(hour, minute) atau of(hour, minute, second, nano)
val jamBuka: LocalTime = LocalTime.of(8, 30)
val jamTutup: LocalTime = LocalTime.of(21, 0, 0)
println()
println("=== LocalTime ===")
println("jamBuka : $jamBuka")
println("jamTutup : $jamTutup")
println("jam : ${jamBuka.hour}")
println("menit : ${jamBuka.minute}")
println("detik : ${jamTutup.second}")
// 4. LocalDateTime.of(date, time) atau of(year,month,day,hour,minute,second)
val jadwalPesta: LocalDateTime = LocalDateTime.of(tanggalLahir, jamBuka)
val jadwalRapat: LocalDateTime = LocalDateTime.of(2026, 3, 1, 14, 30, 0)
println()
println("=== LocalDateTime ===")
println("jadwalPesta : $jadwalPesta")
println("jadwalRapat : $jadwalRapat")
println("tanggal diambil: ${jadwalRapat.toLocalDate()}")
println("jam diambil : ${jadwalRapat.toLocalTime()}")
// 5. Parsing ISO-8601 string
val dariString1: LocalDate = LocalDate.parse("2026-01-15")
val dariString2: LocalTime = LocalTime.parse("08:30:00")
val dariString3: LocalDateTime = LocalDateTime.parse("2026-01-15T08:30:00")
println()
println("=== Parsing ISO-8601 ===")
println("dariString1 : $dariString1")
println("dariString2 : $dariString2")
println("dariString3 : $dariString3")
// 6. Perbandingan tanggal
println()
println("=== Perbandingan ===")
println("sebelum? ${tanggalLahir.isBefore(hariLibur)}")
println("setelah? ${tanggalLahir.isAfter(hariLibur)}")
println("sama? ${tanggalLahir == LocalDate.of(2026, 1, 15)}")
// 7. Cek hari kerja / akhir pekan
val hariPekan: DayOfWeek = tanggalLahir.dayOfWeek
val akhirPekan: Boolean = hariPekan.value >= 6
println()
println("=== Hari Pekan ===")
println("hariPekan : $hariPekan (value=${hariPekan.value})")
println("akhirPekan? : $akhirPekan")
}Output yang Dihasilkan:
=== LocalDate ===
tanggalLahir : 2026-01-15
tahun : 2026
bulan (Month) : JANUARY
bulan (Int) : 1
hari : 15
hariKeDalamTahun: 15
hariDalamPekan : THURSDAY
=== LocalDate dengan Month enum ===
hariLibur : 2026-08-17 (Hari Kemerdekaan)
=== LocalTime ===
jamBuka : 08:30
jamTutup : 21:00
jam : 8
menit : 30
detik : 0
=== LocalDateTime ===
jadwalPesta : 2026-01-15T08:30
jadwalRapat : 2026-03-01T14:30
tanggal diambil: 2026-03-01
jam diambil : 14:30
=== Parsing ISO-8601 ===
dariString1 : 2026-01-15
dariString2 : 08:30
dariString3 : 2026-01-15T08:30
=== Perbandingan ===
sebelum? true
setelah? false
sama? true
=== Hari Pekan ===
hariPekan : THURSDAY (value=4)
akhirPekan? : falseMari kita telaah baris-baris kode di atas:
- Baris 8 (
LocalDate.of(2026, 1, 15)): membuatLocalDatedengan tahun, bulan (numerik 1-12), dan hari. Konstruktor melakukan validasi sehingga tanggal seperti 2026-02-30 akan melemparDateTimeExceptionsaat runtime. - Baris 16 (
LocalDate.of(2026, Month.AUGUST, 17)): varian yang memakai enumMonth. Lebih deskriptif karena tidak ada ambiguitas apakah angka8berarti Agustus atau September (kesalahan klasik off-by-one dari programmer C lama yang memakaiCalendar.MONTHberbasis nol). - Baris 22-23 (
LocalTime.of(8, 30)danLocalTime.of(21, 0, 0)): ada empat overload —(h,m),(h,m,s),(h,m,s,n). Tidak menyertakan detik berarti detik diisi 0. Pemanggilan dengan jam 25 akan melemparDateTimeException. - Baris 31 (
LocalDateTime.of(tanggalLahir, jamBuka)): menggabungkan instanceLocalDatedanLocalTimemenjadiLocalDateTime. Komposisi ini lebih jelas daripada konstruktor 6 argumen. - Baris 41-43 (
LocalDate.parse("2026-01-15")): parsing ISO-8601. Format defaultLocalDateadalahyyyy-MM-dd,LocalTimeadalahHH:mm:ss(atauHH:mmbila detik 0), danLocalDateTimeadalah gabungan dengan pemisahT. Pemanggilanparsetanpa formatter hanya menerima format ISO standar. - Baris 51 (
tanggalLahir.isBefore(hariLibur)): methodisBefore,isAfter, danisEqualmembandingkan tanggal murni tanpa zona. Hasilnya deterministik karenaLocalDatetidak terpengaruh zona. - Baris 58 (
hariPekan.value >= 6): enumDayOfWeekmemberi nilai 1-7 (Senin-Minggu) sesuai ISO-8601, bukan 0-6 sepertijava.util.Calendar. Sabtu (6) dan Minggu (7) adalah akhir pekan.
Parsing dan Komposisi #
LocalDate.parse(...) memakai DateTimeFormatter.ISO_LOCAL_DATE sebagai default. Format ini ketat: tahun 4 digit, bulan 2 digit dengan leading zero, hari 2 digit dengan leading zero. Untuk format lain (mis. 15/01/2026) kita harus menyediakan DateTimeFormatter eksplisit, yang akan dibahas tuntas di chapter Parsing Date-Time.
Komposisi LocalDate dan LocalTime dapat dipisah kembali lewat toLocalDate() dan toLocalTime(). Operasi ini tidak membuat objek baru — hanya memancarkan view ke field yang sudah ada. Hal ini aman dari segi performa.
Referensi Lanjutan #
- java.time package
— dokumentasi paket
java.timeuntukLocalDate,LocalTime, danLocalDateTime
Selanjutnya #
LocalDate dan LocalTime tidak membawa zona. Saat kita perlu merepresentasikan momen spesifik di UTC atau di zona tertentu, gunakan Instant. Bab Instant dan Time Zones akan membahas konversi antar zona dengan aman.