LocalDate, LocalTime และ LocalDateTime เป็นคลาสหลักของแพ็คเกจ java.time ที่ให้การทำงานกับวันที่และเวลาโดยไม่ขึ้นกับเขตเวลา ตามเอกสารของ Oracle (Java 17, 2024) ชนิดเหล่านี้ถูกออกแบบให้เป็น immutable และ thread-safe ทำให้ปลอดภัยสำหรับแอปพลิเคชันแบบหลายเธรด ชนิดเหล่านี้พร้อมใช้งานบน Android ผ่าน desugaring ตั้งแต่ API 26 และสำหรับเวอร์ชันเก่ากว่า — ผ่านไลบรารี ThreeTenABP
ประเด็นสำคัญ
LocalDate — คลาสที่แสดงวันที่ในรูปแบบปี-เดือน-วันโดยไม่มีข้อมูลเวลาและเขตเวลา ใช้สำหรับเก็บข้อมูลเช่น วันเกิด วันที่เกิดเหตุการณ์ หรือวันหมดอายุ
LocalDate เก็บปีในช่วง -999999999 ถึง +999999999 เดือนตั้งแต่ 1 ถึง 12 และวันของเดือนโดยคำนึงถึง ปีอธิกสุรทิน คลาสนี้เป็น immutable อย่างสมบูรณ์ — การดำเนินการใด ๆ จะส่งคืนออบเจกต์ใหม่
LocalTime แสดงเวลาของวัน: ชั่วโมง, นาที, วินาที และนาโนวินาที ความแม่นยำสูงสุดถึงนาโนวินาที LocalTime ไม่มีข้อมูลวันที่หรือเขตเวลา ทำให้สะดวกสำหรับการเก็บเวลาเปิดทำการหรือระยะเวลาของกระบวนการ
LocalDateTime รวม LocalDate และ LocalTime เข้าเป็นออบเจกต์เดียว เป็นชนิดที่ใช้บ่อยที่สุดเมื่อต้องการเก็บทั้งวันที่และเวลา แต่ไม่จำเป็นต้องผูกกับเขตเวลา ตัวอย่างเช่น วันที่และเวลาของคอนเสิร์ตในรูปแบบท้องถิ่น
ตาม เอกสาร Oracle Java (2024) ทั้งสามคลาสถูกออกแบบตามแนวคิดจากไลบรารี Joda-Time แต่มีสถาปัตยกรรมที่ดีขึ้นและรวมเข้ากับไลบรารีมาตรฐานอย่างสมบูรณ์
แพ็คเกจ java.time ปรากฏใน Java 8 เพื่อแทนที่คลาส Date, Calendar และ SimpleDateFormat ที่ล้าสมัย สถาปัตยกรรมของมันสร้างบนหลักการของออบเจกต์ immutable และอินเทอร์เฟซแบบ fluent
คุณลักษณะสำคัญ — คลาสหลักทั้งหมดเป็น value-based หมายความว่าอินสแตนซ์ของคลาสเหล่านี้ถูกเปรียบเทียบด้วยค่า ไม่ใช่การอ้างอิง และไม่สามารถสืบทอดได้ ในการเปรียบเทียบสองออบเจกต์ ให้ใช้เมธอด equals ไม่ใช้ตัวดำเนินการ ==
แพ็คเกจแบ่งออกเป็นหลายหมวดหมู่ ชนิดไม่มีเขตเวลา — LocalDate, LocalTime, LocalDateTime — ใช้สำหรับวันที่และเวลาท้องถิ่น ชนิดมีเขตเวลา — ZonedDateTime, OffsetDateTime, OffsetTime — เพิ่มข้อมูลออฟเซตหรือโซน ชนิดทันที — Instant — แสดงจุดบนเส้นเวลาใน UTC
การแบ่งนี้แก้ปัญหาที่มีอยู่ใน API เดิม: นักพัฒนาไม่เคยรู้ว่าออบเจกต์ Date มีข้อมูลเขตเวลาหรือไม่ ใน java.time แต่ละชนิดประกาศความหมายของมันอย่างชัดเจน
คลาส LocalDate มีเมธอดมากมายสำหรับสร้าง อ่าน และแก้ไขวันที่ วันที่ปัจจุบันสามารถรับได้ผ่านเมธอด static now() วันที่เฉพาะ — ผ่านเมธอด of(int year, int month, int dayOfMonth)
ในการอ่านส่วนประกอบของวันที่ ใช้ getter: getYear(), getMonthValue(), getDayOfMonth(), getDayOfWeek(), getDayOfYear() เมธอด getMonth() ส่งคืน enum Month และ getDayOfWeek() ส่งคืน enum DayOfWeek
LocalDate รองรับการตรวจสอบวันที่ เมธอด isBefore(), isAfter() และ isEqual() อนุญาตให้เปรียบเทียบวันที่ เมธอด isLeapYear() ตรวจสอบว่าปีเป็นปีอธิกสุรทินหรือไม่ เมธอด lengthOfMonth() ส่งคืนจำนวนวันในเดือน และ lengthOfYear() ส่งคืนจำนวนวันในปี
สำหรับการแก้ไข ใช้เมธอด withYear(), withMonth(), withDayOfMonth() ซึ่งส่งคืนออบเจกต์ใหม่ที่มีส่วนประกอบที่เปลี่ยนไป เมธอด plusDays(), minusMonths() และที่คล้ายกันดำเนินการทางคณิตศาสตร์วันที่
LocalTime แสดงเวลาของวันด้วยความแม่นยำระดับนาโนวินาที รูปแบบมาตรฐานคือ ISO-8601 (HH:mm:ss.nnnnnnnnn) ค่าต่ำสุดคือ 00:00 สูงสุดคือ 23:59:59.999999999
คุณสามารถสร้างออบเจกต์ LocalTime โดยใช้ now() สำหรับเวลาปัจจุบัน, of(int hour, int minute), of(int hour, int minute, int second) หรือ of(int hour, int minute, int second, int nanoOfSecond) เมธอด parse(CharSequence text) แยกวิเคราะห์สตริงในรูปแบบ ISO-8601
getter ประกอบด้วย getHour(), getMinute(), getSecond(), getNano() เมธอด toSecondOfDay() ส่งคืนจำนวนวินาทีตั้งแต่เริ่มวัน และ toNanoOfDay() ส่งคืนนาโนวินาที ซึ่งสะดวกสำหรับการคำนวณระยะเวลาภายในวันเดียว
LocalTime รองรับการดำเนินการเปรียบเทียบและแก้ไขเช่นเดียวกับ LocalDate: plusHours(), minusMinutes(), withHour(), withMinute() เมธอด isBefore() และ isAfter() ทำงานโดยพิจารณาว่าเวลาเป็นวัฏจักรภายในหนึ่งวัน
LocalDateTime รวมความสามารถของ LocalDate และ LocalTime ไว้ในคลาสเดียว มันเก็บทั้งวันที่และเวลา แต่ไม่มีเขตเวลา เป็นชนิดท้องถิ่นที่ยืดหยุ่นที่สุด แต่ต้องใช้ความระมัดระวังเมื่อใช้ในระบบกระจาย
คุณสามารถสร้าง LocalDateTime ผ่านเมธอด static now(), of(LocalDate date, LocalTime time), of(int year, Month month, int dayOfMonth, int hour, int minute) และโอเวอร์โหลดของมัน คุณยังสามารถรวม LocalDate และ LocalTime ผ่านเมธอด atTime()
LocalDateTime ให้การเข้าถึงฟิลด์วันที่และเวลาทั้งหมดผ่าน getter ที่เกี่ยวข้อง: toLocalDate() และ toLocalTime() ส่งคืนส่วนประกอบแต่ละส่วน เมธอด truncatedTo(TemporalUnit unit) อนุญาตให้ปัดเศษเวลาตามความแม่นยำที่กำหนด — เช่น ปัดเป็นนาที
ในการแปลงเป็นเขตเวลา ให้ใช้เมธอด atZone(ZoneId zone) ซึ่งส่งคืน ZonedDateTime นี่เป็นวิธีเดียวในการเพิ่มเขตเวลาให้กับ LocalDateTime
ทั้งสามคลาสใช้รูปแบบการสร้างแบบรวมผ่านเมธอด factory แบบ static คอนสตรัคเตอร์ของคลาสถูกประกาศเป็น private — คุณไม่สามารถสร้างออบเจกต์โดยตรงด้วย new
เมธอดหลักในการสร้าง:
เมธอด of มีโอเวอร์โหลดมากมาย สำหรับ LocalDate ต้องการปี, เดือนและวัน สำหรับ LocalTime — ชั่วโมงและนาที (เลือกได้ว่าต้องการวินาทีและนาโนวินาที) สำหรับ LocalDateTime — ปี, เดือน, วัน, ชั่วโมง, นาที เดือนสามารถส่งเป็น int (1-12) หรือเป็น enum Month
val today = LocalDate.now()
val specificDate = LocalDate.of(2026, Month.JULY, 21)
val parsedDate = LocalDate.parse("2026-07-21")
val currentTime = LocalTime.now()
val lunchTime = LocalTime.of(13, 30, 0)
val parsedTime = LocalTime.parse("13:30:00")
val now = LocalDateTime.now()
val meeting = LocalDateTime.of(2026, 7, 21, 15, 0)
คลาส java.time ถูกออกแบบมาเพื่อการแปลงที่สะดวกระหว่างกัน LocalDate สามารถแปลงเป็น LocalDateTime ผ่านเมธอด atTime(LocalTime) หรือ atStartOfDay() LocalTime — ผ่าน atDate(LocalDate)
LocalDateTime สามารถแปลงกลับเป็น LocalDate ผ่าน toLocalDate() และเป็น LocalTime ผ่าน toLocalTime() ในการแปลงเป็น ZonedDateTime ให้ใช้เมธอด atZone(ZoneId)
การแปลงเป็น java.util.Date (เพื่อความเข้ากันได้กับโค้ดเก่า) ต้องมีขั้นตอนกลางผ่าน Instant และเขตเวลา ตาม Baeldung (2024) การดำเนินการนี้ทำผ่าน Date.from(instant)
val date = LocalDate.of(2026, 7, 21)
val dateTime = date.atTime(LocalTime.of(10, 30))
val time = LocalTime.of(14, 0)
val dateTimeFromTime = time.atDate(date)
val extractedDate = dateTime.toLocalDate()
val extractedTime = dateTime.toLocalTime()
val zoned = dateTime.atZone(ZoneId.of("Europe/Moscow"))
สำหรับการจัดรูปแบบและการแยกวิเคราะห์ ใช้คลาส DateTimeFormatter มันมีรูปแบบที่กำหนดไว้ล่วงหน้าผ่านค่าคงที่ (ISO_LOCAL_DATE, ISO_LOCAL_TIME, ISO_LOCAL_DATE_TIME) และความสามารถในการสร้างรูปแบบที่กำหนดเองผ่านสตริงรูปแบบ
รูปแบบการจัดรูปแบบใช้สัญลักษณ์: yyyy — ปี, MM — เดือน (สองหลัก), dd — วัน, HH — ชั่วโมง (0-23), mm — นาที, ss — วินาที เมธอด format() ถูกเรียกบนออบเจกต์วันที่-เวลาหรือผ่าน DateTimeFormatter
DateTimeFormatter ยังรองรับการแปลภาษาผ่านเมธอด static ofLocalizedDate(FormatStyle), ofLocalizedTime(FormatStyle) และ ofLocalizedDateTime(FormatStyle) รูปแบบที่มีคือ SHORT, MEDIUM, LONG และ FULL
val formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm")
val formatted = LocalDateTime.now().format(formatter)
val parsed = LocalDate.parse(
"21.07.2026",
DateTimeFormatter.ofPattern("dd.MM.yyyy")
)
ทั้งสามคลาสใช้อินเทอร์เฟส Comparable ซึ่งอนุญาตให้เปรียบเทียบได้ตามธรรมชาติ เมธอด compareTo() ส่งคืนจำนวนลบ ศูนย์ หรือบวกขึ้นอยู่กับลำดับ เมธอด isBefore(), isAfter() และ isEqual() ส่งคืน boolean
สำหรับ LocalDate การเปรียบเทียบเป็นตามลำดับเวลา — วันที่ก่อนหน้าถือว่าน้อยกว่า สำหรับ LocalTime — ตามเวลาของวัน สำหรับ LocalDateTime — ก่อนตามวันที่ ตามด้วยเวลา การเปรียบเทียบทั้งหมดคำนึงถึงปีอธิกสุรทินและจำนวนวันในเดือนอย่างถูกต้อง
ความแตกต่างสำคัญจาก API เดิม: equals() สำหรับ LocalDate, LocalTime และ LocalDateTime เปรียบเทียบค่า ไม่ใช่การอ้างอิง หมายความว่าสองออบเจกต์ที่มีฟิลด์เดียวกันจะเท่ากัน แม้ว่าจะเป็นคนละอินสแตนซ์
val d1 = LocalDate.of(2026, 7, 21)
val d2 = LocalDate.of(2026, 12, 25)
if (d1.isBefore(d2)) {
Log.d("วันที่", "d1 มาก่อน d2")
}
val sortedDates = listOf(d2, d1).sorted()
ทั้งสามคลาสรองรับการดำเนินการทางคณิตศาสตร์ผ่านเมธอด plus และ minus สำหรับ LocalDate มี plusDays(), plusWeeks(), plusMonths(), plusYears() และเมธอด minus ที่เกี่ยวข้อง LocalTime รองรับ plusHours(), plusMinutes(), plusSeconds(), plusNanos()
LocalDateTime สืบทอดการดำเนินการทางคณิตศาสตร์ทั้งหมดจากทั้งสองชนิด คุณสมบัติที่โดดเด่นของ LocalDate: เมื่อเพิ่มเดือน ผลลัพธ์จะจัดการกับความยาวเดือนที่แตกต่างกันอย่างถูกต้อง ตัวอย่างเช่น 31 มกราคม + 1 เดือน = 28 (29 ในปีอธิกสุรทิน) กุมภาพันธ์
สำหรับการดำเนินการที่ซับซ้อนมากขึ้น มีคลาส Period (สำหรับวันที่) และ Duration (สำหรับเวลา) เมธอด plus(TemporalAmount) และ minus(TemporalAmount) รับออบเจกต์เหล่านี้
val today = LocalDate.now()
val nextWeek = today.plusDays(7)
val nextMonth = today.plusMonths(1)
val lastYear = today.minusYears(1)
val now = LocalTime.now()
val inTwoHours = now.plusHours(2)
val halfHourAgo = now.minusMinutes(30)
มาดูตัวอย่างที่ใช้งานได้จริง: แอปพลิเคชันติดตามกะการทำงาน เราต้องคำนวณระยะเวลาของกะและพิจารณาว่าตรงกับเวลากลางคืนหรือไม่ เราใช้ LocalTime สำหรับเวลาเริ่มและสิ้นสุด LocalDate สำหรับวันที่ และ LocalDateTime สำหรับคำนวณกะที่ข้ามเที่ยงคืน
data class Shift(
val startTime: LocalTime,
val endTime: LocalTime,
val date: LocalDate
) {
fun isOvernight(): Boolean = endTime.isBefore(startTime)
fun durationInMinutes(): Long {
val start = LocalDateTime.of(date, startTime)
val end = LocalDateTime.of(
if (isOvernight()) date.plusDays(1) else date,
endTime
)
return Duration.between(start, end).toMinutes()
}
}
ตัวอย่างที่สอง — การคำนวณอายุผู้ใช้ เราใช้ LocalDate สำหรับวันเกิดและเปรียบเทียบกับวันที่ปัจจุบัน โดยคำนึงถึงวันและเดือนเกิด
fun calculateAge(birthDate: LocalDate): Int {
val today = LocalDate.now()
val period = Period.between(birthDate, today)
return period.years
}
ตัวอย่างที่สาม — การทำงานกับการแจ้งเตือน LocalDateTime ใช้สำหรับกำหนดการแจ้งเตือน เราตรวจสอบว่าถึงเวลาที่กำหนดแล้วหรือไม่
data class Reminder(
val id: Long,
val scheduledAt: LocalDateTime
) {
fun isDue(): Boolean =
LocalDateTime.now().isAfter(scheduledAt)
}
การรองรับ java.time ในตัวปรากฏบน Android ตั้งแต่ API 26 (Android 8.0 Oreo) สำหรับอุปกรณ์ที่มี Android เวอร์ชันเก่ากว่า จำเป็นต้องใช้ desugaring — กลไกที่เพิ่มการรองรับ API Java ใหม่ในเวอร์ชันก่อนหน้า
Desugaring ใน Android Gradle Plugin กำหนดค่าผ่าน compileOptions ใน build.gradle เพียงตั้งค่า isCoreLibraryDesugaringEnabled = true และเพิ่มไลบรารี desugar_jdk_libs หลังจากนั้น java.time จะพร้อมใช้งานสำหรับทุกระดับ API ตั้งแต่ 14 ขึ้นไป
สำหรับโปรเจกต์ที่ไม่สามารถใช้ desugaring (เช่น โปรเจกต์เก่าบน AGP ต่ำกว่า 4.0) มีไลบรารี ThreeTenABP — backport ของ java.time มันให้คลาสเดียวกัน (LocalDate, LocalTime, LocalDateTime) แต่ในแพ็คเกจ org.threeten.bp
@Suppress("UnstableApiUsage")
android {
compileOptions {
isCoreLibraryDesugaringEnabled = true
}
}
dependencies {
"coreLibraryDesugaring"("com.android.tools:desugar_jdk_libs:2.1.4")
}
ข้อผิดพลาดแรก — การใช้ LocalDateTime ในระบบกระจายโดยไม่คำนึงถึงเขตเวลา หากเซิร์ฟเวอร์อยู่ใน Europe/Moscow และไคลเอนต์อยู่ใน Asia/Tokyo LocalDateTime จะถูกตีความต่างกัน วิธีแก้ไข: ใช้ Instant หรือ ZonedDateTime สำหรับข้อมูลทั่วโลก
ข้อผิดพลาดที่สอง — การแยกวิเคราะห์สตริงไม่ถูกต้อง โดยค่าเริ่มต้น LocalDate.parse() คาดหวังรูปแบบ ISO-8601 (yyyy-MM-dd) หากสตริงอยู่ในรูปแบบอื่น คุณต้องส่ง DateTimeFormatter อย่างชัดเจน นอกจากนี้คุณควรจัดการ DateTimeParseException เพื่อให้แอปพลิเคชันไม่หยุดทำงานเมื่ออินพุตไม่ถูกต้อง
ข้อผิดพลาดที่สาม — การละเลยความปลอดภัยของ null LocalDate, LocalTime และ LocalDateTime เป็นออบเจกต์ที่สามารถเป็น null ได้ ใน Kotlin แนะนำให้ใช้ ชนิด nullable ด้วยการตรวจสอบอย่างชัดเจนหรือตัวดำเนินการ Elvis ใน Java — ตรวจสอบ null ก่อนเรียกเมธอด
ข้อผิดพลาดที่สี่ — การสับสนระหว่าง LocalDateTime และ ZonedDateTime LocalDateTime ไม่มีข้อมูลเขตเวลา หากคุณต้องการส่งช่วงเวลาที่แน่นอน — ใช้ ชนิดโซน หากเวลาท้องถิ่นเพียงพอ — ใช้ชนิดท้องถิ่น
คำถามที่พบบ่อย
Date เก็บจำนวนมิลลิวินาทีตั้งแต่ 1970-01-01 UTC ในขณะที่ LocalDate เก็บปี, เดือนและวันโดยไม่ผูกกับเขตเวลา Date เปลี่ยนแปลงได้และไม่ปลอดภัยต่อเธรด LocalDate เป็น immutable และปลอดภัยต่อเธรด Date ถูกละทิ้งตั้งแต่ Java 8
ได้, LocalDateTime แมปเข้ากับชนิด SQL TIMESTAMP WITHOUT TIME ZONE ได้ดี JPA และ Room รองรับผ่าน TypeConverter สำหรับ TIMESTAMP WITH TIME ZONE ให้ใช้ ZonedDateTime หรือ OffsetDateTime
ใช้ ChronoUnit.DAYS.between(startDate, endDate) เมธอดนี้ส่งคืน long — ความแตกต่างเป็นวัน สำหรับการคำนวณที่ละเอียดขึ้น ให้ใช้ Period.between() ซึ่งส่งคืน Period ที่มีปี, เดือนและวัน
LocalTime รองรับความแม่นยำระดับนาโนวินาที (ทศนิยม 9 ตำแหน่ง) หากความแม่นยำระดับมิลลิวินาทีเพียงพอ ให้ใช้ truncateTo(ChronoUnit.MILLIS) ก่อนบันทึก ซึ่งป้องกันปัญหาการปัดเศษระหว่างการทำให้เป็นอนุกรม
เมธอด now() ใช้นาฬิการะบบและเขตเวลาเริ่มต้นของอุปกรณ์ หากอุปกรณ์อยู่ในเขตเวลาต่างกัน วันที่อาจแตกต่างกัน สำหรับการประทับเวลาแบบรวม ให้ใช้ Instant.now() ซึ่งส่งคืนเวลา UTC เสมอ
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ