Joda-Time — ไลบรารีสำหรับการทำงานกับวันที่และเวลาใน Java สร้างโดย Stephen Colebourne เพื่อเป็นทางเลือกแทน java.util.Date และ java.util.Calendar ที่ไม่สะดวก ก่อนที่ Java 8 จะออก ไลบรารีนี้เป็นมาตรฐานโดยพฤตินัยสำหรับการทำงานกับวันที่ในการพัฒนาระดับอุตสาหกรรม รวมถึงแอปพลิเคชัน Android ตามข้อมูลจาก Oracle Java Magazine (2023) โปรเจกต์มากกว่า 60% ได้ย้ายจาก Joda-Time ไปยัง java.time ภายในสองปีหลังจาก Android API 26 เปิดตัว
ประเด็นสำคัญ
Joda-Time — เป็นไลบรารีโอเพนซอร์ส (Apache 2.0) ที่ให้การทดแทนคุณภาพสูงสำหรับคลาส java.util.Date และ java.util.Calendar ในภาษา Java โปรเจกต์นี้ก่อตั้งโดย Stephen Colebourne ในปี 2004 เพื่อตอบสนองต่อปัญหามากมายของ API วันที่มาตรฐาน รวมถึงความไม่แน่นอน (mutability) ความซับซ้อนของการจัดรูปแบบ และการรองรับเขตเวลาที่จำกัด ไลบรารีนี้แพร่หลายในชุมชน Java และถูกใช้โดยโปรเจกต์นับพัน รวมถึงระบบองค์กรขนาดใหญ่และแอปพลิเคชัน Android ซึ่งเครื่องมือจัดการวันที่ในตัวนั้นไม่สะดวกเป็นพิเศษ หลังจาก Java 8 เปิดตัวในปี 2014 และแพ็คเกจ java.time (JSR-310) ปรากฏขึ้น ไลบรารีถูกเปลี่ยนเป็นโหมดบำรุงรักษา — ไม่มีการเพิ่มฟีเจอร์ใหม่ และฟังก์ชันที่มีอยู่ทั้งหมดครอบคลุมโดย API ใหม่
Joda-Time สร้างขึ้นบนหลักการของ immutability: ออบเจกต์วันที่หรือเวลาทุกตัวหลังจากสร้างแล้วไม่สามารถเปลี่ยนแปลงได้ การดำเนินการใด ๆ — การเพิ่มวัน การเปลี่ยนเดือน การกำหนดเขตเวลา — จะคืนค่าออบเจกต์ใหม่ โดยปล่อยให้ออบเจกต์เดิมไม่เปลี่ยนแปลง วิธีการนี้ช่วยขจัดข้อผิดพลาดประเภทที่เกี่ยวข้องกับการแก้ไขออบเจกต์โดยไม่ตั้งใจ ซึ่งเป็นลักษณะเฉพาะของ java.util.Date และ java.util.Calendar ที่ไม่แน่นอน การออกแบบแบบ immutable ยังทำให้ไลบรารีปลอดภัยต่อเธรดโดยไม่ต้องมีการซิงโครไนซ์เพิ่มเติม
ไลบรารีมีคลาสหลักหลายคลาสสำหรับสถานการณ์ต่าง ๆ: DateTime — วันที่และเวลาเต็มพร้อมเขตเวลา, LocalDate — เฉพาะวันที่โดยไม่มีเวลา, LocalTime — เฉพาะเวลาโดยไม่มีวันที่, LocalDateTime — วันที่และเวลาโดยไม่ผูกกับเขตเวลา นอกจากนี้ยังมีคลาสสำหรับทำงานกับช่วงเวลา (Interval) ระยะเวลา (Period) และความยาวเวลา (Duration) ซึ่งช่วยให้คำนวณความแตกต่างระหว่างสองจุดเวลาในหน่วยต่าง ๆ ได้ แต่ละคลาสใช้อินเทอร์เฟซ ReadableInstant หรือ ReadablePartial เพื่อการประมวลผลที่สอดคล้องกัน
ปัญหาเกี่ยวกับ API วันที่มาตรฐานใน Java เป็นที่รู้จักกันตั้งแต่เริ่มต้นของแพลตฟอร์ม คลาส java.util.Date และ java.util.Calendar มีข้อบกพร่องพื้นฐาน: เดือนเริ่มนับจาก 0, Date แทนทั้งวันที่และเวลาพร้อมกัน, Calendar ไม่แน่นอนและต้องมีการตั้งค่าที่ซับซ้อน การจัดรูปแบบผ่าน SimpleDateFormat ไม่ปลอดภัยต่อเธรด — ในแอปพลิเคชันแบบหลายเธรด สิ่งนี้นำไปสู่การแยกวิเคราะห์วันที่ที่ไม่ถูกต้อง Stephen Colebourne ขณะทำงานในโปรเจกต์ที่ใช้วันที่จำนวนมาก ตัดสินใจสร้างทางเลือกที่จะแก้ไขปัญหาทั้งหมดนี้
ตามบทสัมภาษณ์บน InfoQ (2014) Colebourne ทดสอบ Joda-Time ในโปรเจกต์อุตสาหกรรมนานกว่าหนึ่งปีก่อนการเผยแพร่สาธารณะครั้งแรก เวอร์ชันแรกออกในปี 2004 และดึงดูดความสนใจของชุมชนได้ทันที ภายในปี 2010 ไลบรารีกลายเป็นการพึ่งพามาตรฐานในโปรเจกต์ Java ส่วนใหญ่ รวมถึงเฟรมเวิร์กอย่าง Spring และ Hibernate ความสำเร็จของ Joda-Time ส่งผลโดยตรงต่อข้อกำหนด JSR-310 (Date and Time API) ซึ่งรวมอยู่ใน Java 8 — โดยที่ผู้เขียนข้อกำหนดคือ Stephen Colebourne คนเดียวกัน
Joda-Time ไม่เพียงแต่แก้ปัญหาวันที่ — มันเปลี่ยนวิธีการออกแบบ API ใน Java แนวคิดของ immutable value-objects ที่ไลบรารีแสดงให้เห็น ถูกนำมาใช้เป็นมาตรฐานใน Java 8 สำหรับ API ใหม่ทั้งหมด ยิ่งไปกว่านั้น ความนิยมของ Joda-Time แสดงให้ Oracle เห็นว่าชุมชนไม่พร้อมที่จะยอมรับไลบรารีมาตรฐานที่มีคุณภาพต่ำ — หลังจาก Joda-Time API ใน Java สำหรับการทำงานกับไฟล์ (NIO.2) เวลา (java.time) และค่าที่เป็นทางเลือก (Optional) ได้ถูกปรับปรุงใหม่
Joda-Time มีชุดความสามารถที่ไม่สามารถใช้ได้ในไลบรารีมาตรฐานก่อน Java 8 ข้อดีหลักรวมถึง การรองรับเขตเวลามากกว่า 200 เขต พร้อมการจัดการเวลาออมแสงอัตโนมัติ API การจัดรูปแบบที่หลากหลายพร้อมรูปแบบและตำแหน่งที่ตั้ง รวมถึงการคำนวณระยะเวลาระหว่างวันที่ในหน่วยต่าง ๆ — ปี เดือน วัน ชั่วโมง ระบบช่วงเวลาสมควรได้รับความสนใจเป็นพิเศษ: Interval (ช่วงระหว่างสองจุด), Period (ความแตกต่างในหน่วยปฏิทิน) และ Duration (ระยะเวลาที่แน่นอนในหน่วยมิลลิวินาที)
คลาส DateTimeFormatter ใน Joda-Time ให้การจัดรูปแบบวันที่ที่ปลอดภัยต่อเธรด — แตกต่างจาก java.text.SimpleDateFormat สามารถสร้างตัวจัดรูปแบบผ่านรูปแบบ (เช่น “yyyy-MM-dd HH:mm:ss”) หรือผ่านสไตล์ (SHORT, MEDIUM, LONG, FULL) สำหรับเอาต์พุตที่ปรับตามท้องถิ่น ไลบรารียังรองรับการแคชตัวจัดรูปแบบ ซึ่งช่วยเร่งการทำงานในแอปพลิเคชันที่มีโหลดสูง การแยกวิเคราะห์วันที่จากสตริงทำได้ด้วยการควบคุมความเข้มงวด — สามารถอนุญาตหรือปฏิเสธวันที่ที่ไม่สมบูรณ์หรือไม่ถูกต้อง
จุดแข็งประการหนึ่งของ Joda-Time คือการทำงานกับช่วงเวลา คลาส Interval แทนช่วงเวลาที่แน่นอนระหว่างสองจุด (Instant) และรองรับการดำเนินการตัดกัน การรวม และการตรวจสอบการเป็นสมาชิก Period แตกต่างจาก Interval ตรงที่ทำงานกับหน่วยปฏิทิน — ตัวอย่างเช่น ความแตกต่างระหว่างวันที่ 1 มีนาคมถึง 1 เมษายนคือ 1 เดือนพอดี แม้ว่าตามระยะเวลาอาจเป็น 28–31 วัน ส่วน Duration แทนระยะเวลาที่แน่นอนในหน่วยมิลลิวินาทีโดยไม่ผูกกับปฏิทิน และเหมาะสำหรับการวัดเวลาดำเนินการ
import org.joda.time.*;
import org.joda.time.format.*;
DateTime now = new DateTime();
DateTimeFormatter fmt = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss");
String formatted = fmt.print(now);
DateTime nextWeek = now.plusWeeks(1);
Period period = new Period(now, nextWeek);
int days = period.getDays(); // วัน = 7
Interval interval = new Interval(now, nextWeek);
boolean contains = interval.contains(now); // จริง
แพ็คเกจ java.time (JSR-310) ได้รับการพัฒนาโดยผู้เขียนคนเดียวกัน — Stephen Colebourne — และรวบรวมแนวคิดที่ดีที่สุดของ Joda-Time โดยคำนึงถึงประสบการณ์การใช้งานสิบปี API ทั้งสองใช้คลาส immutable รองรับเขตเวลา และมีชุดการดำเนินการที่หลากหลาย อย่างไรก็ตาม java.time ถูกรวมเข้ากับไลบรารีมาตรฐานของ Java อย่างสมบูรณ์ ไม่ต้องการการพึ่งพาภายนอก และใช้ระบบการตั้งชื่อที่สอดคล้องมากขึ้น: Instant แทน ReadableInstant, LocalDate แทน LocalDate (ชื่อคงเดิม), ZonedDateTime แทน DateTime
| คุณลักษณะ | Joda-Time | java.time |
|---|---|---|
| ปีที่เปิดตัว | 2004 | 2014 (Java 8) |
| สถานะ | บำรุงรักษา | กำลังพัฒนาอย่างแข็งขัน |
| วันที่/เวลาหลัก | DateTime | ZonedDateTime |
| เฉพาะวันที่ | LocalDate | LocalDate |
| ความไม่เปลี่ยนแปลง | ใช่ | ใช่ |
| การจัดรูปแบบ | DateTimeFormatter | DateTimeFormatter |
| ความแตกต่างของวันที่ | Period / Duration | Period / Duration |
java.time แก้ไขการตัดสินใจทางสถาปัตยกรรมหลายอย่างของ Joda-Time ประการแรก ใน java.time ความสับสนระหว่างวิธีการที่คืนค่า null และค่าที่ไม่มีอยู่ถูกกำจัด — แทนที่จะใช้ Optional ประการที่สอง เพิ่มการรองรับระบบปฏิทินทางเลือก (ญี่ปุ่น ไทย อิสลาม) ผ่านอินเทอร์เฟซ Chronology ประการที่สาม java.time มีคลาส Instant สำหรับการทำงานกับจุดเวลาใน UTC โดยไม่ผูกกับปฏิทิน — ซึ่งช่วยลดความซับซ้อนในการรวมกับโปรโตคอลเครือข่ายและฐานข้อมูล สำหรับ Android แนะนำให้ใช้ java.time ตั้งแต่ API 26 (Android 8.0) หรือผ่าน desugaring สำหรับเวอร์ชันที่เก่ากว่า
การย้ายจาก Joda-Time ไปยัง java.time — เป็นกระบวนการที่ผู้เขียนไลบรารีพยายามทำให้ปลอดภัยและคาดเดาได้มากที่สุด คลาสส่วนใหญ่มีสิ่งที่เทียบเท่าโดยตรง: DateTime → ZonedDateTime, LocalDate → LocalDate (ชื่อตรงกัน), LocalTime → LocalTime, DateTimeZone → ZoneId, Period → Period, Duration → Duration การเปลี่ยนแปลงหลักเกี่ยวข้องกับวิธีการสร้างออบเจกต์และชื่อของเมธอด — ตัวอย่างเช่น แทนที่ `now.toDate()` ใช้ `Date.from(instant)` และ `DateTime.now()` ถูกแทนที่ด้วย `ZonedDateTime.now()`
แนะนำให้เริ่มการย้ายโดยแยก Joda-Time ออกเป็นชั้นนามธรรมแบบบาง — ซึ่งจะช่วยให้สามารถเปลี่ยนไลบรารีเป็นระยะ ๆ โดยไม่กระทบต่อฐานโค้ดทั้งหมด ขั้นตอนแรกมักจะเปลี่ยนการทำงานกับ LocalDate และ LocalTime — คลาสเหล่านี้มีความแตกต่างน้อยที่สุดระหว่างไลบรารี ขั้นตอนที่สองย้าย DateTime (เป็น ZonedDateTime) และเขตเวลา ขั้นตอนที่สามเปลี่ยน Interval, Period และ Duration — ที่นี่ต้องตรวจสอบพฤติกรรมอย่างระมัดระวังโดยคำนึงถึงเวลาออมแสง หลังจากเสร็จสิ้นการย้าย Joda-Time จะถูกลบออกจากการพึ่งพาของโปรเจกต์อย่างสมบูรณ์
// Joda-Time (ก่อนการย้าย)
val dateTime = DateTime.now(DateTimeZone.forID("Europe/Moscow"))
val formatted = dateTime.toString("dd.MM.yyyy")
val plusDay = dateTime.plusDays(1)
// java.time (หลังการย้าย)
val zonedDateTime = ZonedDateTime.now(ZoneId.of("Europe/Moscow"))
val formatted2 = zonedDateTime.format(
DateTimeFormatter.ofPattern("dd.MM.yyyy")
)
val plusDay2 = zonedDateTime.plusDays(1)
มียูทิลิตี้หลายอย่างที่ช่วยให้การเปลี่ยนจาก Joda-Time ไปยัง java.time ง่ายขึ้น OpenRewrite (โดย Moderne) รองรับการย้ายอัตโนมัติผ่าน recipe org.openrewrite.java.migrate.JodaTimeToJavaTime — ซึ่งเปลี่ยนคลาส เมธอด และ imports โดยคงตรรกะของโปรแกรมไว้ สำหรับโปรเจกต์ Gradle ปลั๊กอิน de.fayard.refreshVersions ก็รวมกฎการย้ายไว้ด้วย การย้ายด้วยตนเองทำได้ง่ายที่สุดด้วย IDEA: ปลั๊กอิน Java Time Migration Assistant จะเน้นการเรียก Joda-Time และเสนอการแทนที่ที่สอดคล้องกันใน java.time ซึ่งทำให้กระบวนการเร็วขึ้น 2–3 เท่าเมื่อเทียบกับการแทนที่ด้วยตนเอง
มาดูตัวอย่างการทำงานจริงกับ Joda-Time ในแอปพลิเคชัน Android กัน ไลบรารีมีประโยชน์อย่างยิ่งสำหรับงานจัดรูปแบบวันที่ตามมาตรฐานท้องถิ่น การคำนวณอายุผู้ใช้ การกำหนดระยะเวลาของกิจกรรม และการคำนวณช่วงระหว่างวันที่สองวัน ตัวอย่างทั้งหมดใช้ออบเจกต์ immutable — การเรียกแต่ละครั้งจะคืนค่าอินสแตนซ์ใหม่ ในขณะที่ออบเจกต์ดั้งเดิมยังคงไม่เปลี่ยนแปลง
// คำนวณอายุผู้ใช้
val birthDate = LocalDate.parse("1990-05-15")
val today = LocalDate.now()
val age = Years.yearsBetween(birthDate, today).getYears()
// อายุ == 36
// จัดรูปแบบตามตำแหน่งที่ตั้งของผู้ใช้
val dateTime = DateTime.now()
val formatter = DateTimeFormat.forStyle("MM").withLocale(Locale.getDefault())
val localized = formatter.print(dateTime)
ช่วงเวลาใน Joda-Time ช่วยแก้ปัญหาการตรวจสอบการทับซ้อนของช่วงเวลา การคำนวณการซ้อนทับ และการค้นหาเหตุการณ์ที่ใกล้ที่สุดได้อย่างมีประสิทธิภาพ ตัวอย่างเช่น เมื่อพัฒนาแอปพลิเคชันปฏิทิน สามารถตรวจสอบได้ว่าเหตุการณ์ใหม่ทับซ้อนกับเหตุการณ์ที่มีอยู่แล้วหรือไม่ คลาส Interval รองรับการดำเนินการ contains, overlaps, gap และ abuts — อย่างสุดท้ายแสดงว่าช่วงเวลาสัมผัสกันที่ขอบเขตหรือไม่ สำหรับการวัดระยะเวลาระหว่างสองจุด จะใช้ Duration ด้วยความแม่นยำถึงมิลลิวินาที
// ตรวจสอบการทับซ้อนของช่วงเวลา
Interval meeting1 = new Interval(
new DateTime(2026, 7, 21, 10, 0),
new DateTime(2026, 7, 21, 11, 0)
);
Interval meeting2 = new Interval(
new DateTime(2026, 7, 21, 10, 30),
new DateTime(2026, 7, 21, 11, 30)
);
boolean overlaps = meeting1.overlaps(meeting2); // จริง
Duration gap = meeting1.gap(meeting2); // null (ไม่มีช่องว่าง)
คำถามที่พบบ่อย
Joda-Time — ไลบรารีสำหรับทำงานกับวันที่และเวลาใน Java สร้างขึ้นก่อน Java 8 จะออก มันแก้ไขข้อบกพร่องของ java.util.Date และ java.util.Calendar: ความไม่แน่นอน (mutability) การนับเดือนจาก 0 การขาดการรองรับเขตเวลาอย่างสมบูรณ์ และการจัดรูปแบบที่ไม่ปลอดภัยต่อเธรด
java.time (JSR-310) — ผู้สืบทอดของ Joda-Time พัฒนาโดยผู้เขียนคนเดียวกัน ความแตกต่างหลัก: java.time ถูกรวมอยู่ใน ไลบรารีมาตรฐานของ Java 8+ ใช้ระบบการตั้งชื่อที่สอดคล้องมากขึ้น รองรับระบบปฏิทินทางเลือก และรวมคลาส Instant สำหรับการทำงานกับเวลา UTC
ไม่ สำหรับโปรเจกต์ใหม่แนะนำให้ใช้ java.time Joda-Time อยู่ในโหมดบำรุงรักษาตั้งแต่ปี 2014 และไม่ได้รับฟีเจอร์ใหม่อีกต่อไป สำหรับ Android ที่ API 26+ java.time พร้อมใช้งานโดยตรง และสำหรับเวอร์ชันที่เก่ากว่าจะใช้ desugaring — กลไกที่ช่วยให้ใช้ java.time บนอุปกรณ์ที่ใช้ API 19+
การย้ายทำเป็นขั้นตอน: แยก Joda-Time ออกเป็น abstraction layer, เปลี่ยน LocalDate และ LocalTime ก่อน, จากนั้น DateTime → ZonedDateTime, และขั้นตอนสุดท้ายคือ Period, Duration และ Interval เครื่องมือ OpenRewrite รองรับการย้ายอัตโนมัติผ่าน recipe JodaTimeToJavaTime ซึ่งทำให้กระบวนการเร็วขึ้น 2–3 เท่า
DateTime → ZonedDateTime, LocalDate → LocalDate (ตรงกัน), LocalTime → LocalTime, LocalDateTime → LocalDateTime, DateTimeZone → ZoneId, Interval — ไม่มีสิ่งที่เทียบเท่าโดยตรง (ใช้การรวมกันของ Instant + Duration), Period → Period, Duration → Duration ชื่อเมธอดก็ใกล้เคียงกัน: plusDays, minusMonths, withZone พร้อมใช้งานในทั้งสองไลบรารี
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ