Joda-Time là một thư viện để làm việc với ngày và giờ trong Java, được tạo ra bởi Stephen Colebourne như một sự thay thế cho java.util.Date và java.util.Calendar cồng kềnh. Trước khi phát hành Java 8, thư viện này là tiêu chuẩn thực tế cho việc xử lý ngày trong phát triển công nghiệp, bao gồm cả các ứng dụng Android. Theo Oracle Java Magazine (2023), hơn 60% dự án đã di chuyển từ Joda-Time sang java.time trong vòng hai năm sau khi phát hành Android API 26.
Điểm chính
Joda-Time là một thư viện mã nguồn mở (Apache 2.0) cung cấp sự thay thế chất lượng cho các lớp java.util.Date và java.util.Calendar trong ngôn ngữ Java. Dự án được thành lập bởi Stephen Colebourne vào năm 2004 để đối phó với nhiều vấn đề của API ngày và giờ tiêu chuẩn, bao gồm tính khả biến, độ phức tạp của định dạng và hỗ trợ múi giờ hạn chế. Thư viện đã được cộng đồng Java chấp nhận rộng rãi và được hàng ngàn dự án sử dụng, bao gồm các hệ thống doanh nghiệp lớn và các ứng dụng Android, nơi các công cụ xử lý ngày tích hợp sẵn đặc biệt bất tiện. Kể từ khi phát hành Java 8 vào năm 2014 và sự ra đời của gói java.time (JSR-310), thư viện đã được chuyển sang chế độ bảo trì — các tính năng mới không được thêm vào và chức năng hiện có được API mới bao phủ hoàn toàn.
Joda-Time được xây dựng dựa trên nguyên tắc bất biến: mỗi đối tượng ngày hoặc giờ không thể bị thay đổi sau khi tạo. Bất kỳ thao tác nào — thêm một ngày, thay đổi tháng, đặt múi giờ — đều trả về một đối tượng mới, giữ nguyên đối tượng gốc. Cách tiếp cận này loại bỏ hoàn toàn loại lỗi liên quan đến việc sửa đổi đối tượng không chủ ý vốn là đặc điểm của java.util.Date và java.util.Calendar có thể thay đổi. Thiết kế bất biến cũng làm cho thư viện an toàn luồng mà không cần đồng bộ hóa thêm.
Thư viện cung cấp một số lớp chính cho các tình huống khác nhau: DateTime — ngày và giờ đầy đủ với múi giờ, LocalDate — chỉ ngày không có giờ, LocalTime — chỉ giờ không có ngày, LocalDateTime — ngày và giờ không có ràng buộc múi giờ. Ngoài ra, các lớp làm việc với khoảng (Interval), chu kỳ (Period) và thời lượng (Duration) được bao gồm, cho phép tính toán sự khác biệt giữa hai thời điểm trong các đơn vị đo lường khác nhau. Mỗi lớp triển khai giao diện ReadableInstant hoặc ReadablePartial để xử lý thống nhất.
Vấn đề với API ngày tiêu chuẩn trong Java đã được biết đến ngay từ khi nền tảng ra đời. Các lớp java.util.Date và java.util.Calendar có những thiếu sót cơ bản: tháng được đánh số từ 0, Date đồng thời đại diện cho cả ngày và giờ, Calendar có thể thay đổi và yêu cầu cấu hình phức tạp. Định dạng qua SimpleDateFormat không an toàn luồng — trong các ứng dụng đa luồng, điều này dẫn đến phân tích ngày không chính xác. Stephen Colebourne, làm việc trên các dự án sử dụng ngày nhiều, đã quyết định tạo ra một giải pháp thay thế khắc phục tất cả những vấn đề này.
Theo một cuộc phỏng vấn trên InfoQ (2014), Colebourne đã thử nghiệm Joda-Time trong các dự án sản xuất hơn một năm trước khi phát hành công khai đầu tiên. Phiên bản đầu tiên được phát hành vào năm 2004 và ngay lập tức thu hút sự chú ý của cộng đồng. Đến năm 2010, thư viện đã trở thành một phụ thuộc tiêu chuẩn trong hầu hết các dự án Java, bao gồm các framework như Spring và Hibernate. Sự thành công của Joda-Time đã ảnh hưởng trực tiếp đến đặc tả JSR-310 (Date and Time API), được bao gồm trong Java 8 — với cùng Stephen Colebourne là trưởng nhóm đặc tả.
Joda-Time không chỉ giải quyết vấn đề ngày tháng — nó đã thay đổi cách tiếp cận thiết kế API trong Java. Khái niệm về đối tượng giá trị bất biến, được thư viện minh họa, đã được chấp nhận như tiêu chuẩn trong Java 8 cho tất cả các API mới. Hơn nữa, sự phổ biến của Joda-Time đã cho Oracle thấy rằng cộng đồng không sẵn sàng chấp nhận các thư viện tiêu chuẩn chất lượng thấp — sau Joda-Time, các API Java cho xử lý tệp (NIO.2), thời gian (java.time) và giá trị tùy chọn (Optional) đã được thiết kế lại.
Joda-Time cung cấp một tập hợp các tính năng không có sẵn trong thư viện tiêu chuẩn trước Java 8. Những lợi thế chính bao gồm hỗ trợ hơn 200 múi giờ với xử lý tự động giờ mùa hè, API định dạng phong phú với các mẫu và locale, cũng như tính toán khoảng thời gian giữa các ngày trong nhiều đơn vị — năm, tháng, ngày, giờ. Đáng chú ý là hệ thống khoảng: Interval (khoảng giữa hai thời điểm), Period (khác biệt trong đơn vị lịch) và Duration (độ dài chính xác bằng mili giây).
Lớp DateTimeFormatter trong Joda-Time cung cấp định dạng ngày an toàn luồng — không giống như java.text.SimpleDateFormat. Các bộ định dạng có thể được tạo thông qua các mẫu (ví dụ, “yyyy-MM-dd HH:mm:ss”) hoặc thông qua các kiểu (SHORT, MEDIUM, LONG, FULL) cho đầu ra địa phương hóa. Thư viện cũng hỗ trợ lưu cache bộ định dạng, giúp tăng tốc hiệu suất trong các ứng dụng có tải cao. Việc phân tích ngày từ chuỗi được thực hiện với kiểm soát nghiêm ngặt — bạn có thể cho phép hoặc từ chối các ngày không đầy đủ hoặc không chính xác.
Một trong những điểm mạnh của Joda-Time là làm việc với khoảng thời gian. Lớp Interval đại diện cho một khoảng thời gian chính xác giữa hai thời điểm và hỗ trợ các thao tác giao, hợp và kiểm tra chứa. Period, không giống Interval, hoạt động trong các đơn vị lịch — ví dụ, sự khác biệt giữa ngày 1 tháng 3 và ngày 1 tháng 4 là chính xác 1 tháng, mặc dù về thời lượng có thể là 28–31 ngày. Mặt khác, Duration đại diện cho độ dài chính xác bằng mili giây mà không ràng buộc lịch và phù hợp để đo thời gian thực thi thao tác.
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(); // ngày = 7
Interval interval = new Interval(now, nextWeek);
boolean contains = interval.contains(now); // đúng
Gói java.time (JSR-310) được phát triển bởi cùng một tác giả — Stephen Colebourne — và kết hợp các ý tưởng tốt nhất của Joda-Time với mười năm kinh nghiệm vận hành. Cả hai API đều sử dụng các lớp bất biến, hỗ trợ múi giờ và cung cấp một tập hợp phong phú các thao tác. Tuy nhiên, java.time được tích hợp hoàn toàn vào thư viện chuẩn Java, không yêu cầu phụ thuộc bên ngoài và sử dụng hệ thống đặt tên nhất quán hơn: Instant thay vì ReadableInstant, LocalDate thay vì LocalDate (tên được giữ nguyên), ZonedDateTime thay vì DateTime.
| Đặc điểm | Joda-Time | java.time |
|---|---|---|
| Năm phát hành | 2004 | 2014 (Java 8) |
| Trạng thái | Bảo trì | Phát triển tích cực |
| Ngày/giờ cơ bản | DateTime | ZonedDateTime |
| Chỉ ngày | LocalDate | LocalDate |
| Tính bất biến | Có | Có |
| Định dạng | DateTimeFormatter | DateTimeFormatter |
| Chênh lệch ngày | Period / Duration | Period / Duration |
java.time sửa một số quyết định kiến trúc của Joda-Time. Thứ nhất, java.time loại bỏ sự nhầm lẫn giữa các phương thức trả về null và các giá trị bị thiếu — sử dụng Optional để thay thế. Thứ hai, hỗ trợ cho các hệ thống lịch thay thế (Nhật Bản, Thái Lan, Hồi giáo) đã được thêm vào thông qua giao diện Chronology. Thứ ba, java.time bao gồm lớp Instant để làm việc với các thời điểm UTC mà không ràng buộc lịch — điều này đơn giản hóa việc tích hợp với các giao thức mạng và cơ sở dữ liệu. Đối với Android, nên sử dụng java.time từ API 26 (Android 8.0) hoặc thông qua desugaring cho các phiên bản cũ hơn.
Việc di chuyển từ Joda-Time sang java.time là một quá trình mà các tác giả thư viện đã cố gắng làm cho an toàn và có thể dự đoán được. Hầu hết các lớp đều có đối tác trực tiếp: DateTime → ZonedDateTime, LocalDate → LocalDate (tên trùng khớp), LocalTime → LocalTime, DateTimeZone → ZoneId, Period → Period, Duration → Duration. Các thay đổi chính liên quan đến cách tạo đối tượng và tên phương thức — ví dụ, thay vì `now.toDate()`, hãy sử dụng `Date.from(instant)` và `DateTime.now()` được thay thế bằng `ZonedDateTime.now()`.
Nên bắt đầu di chuyển bằng cách cô lập Joda-Time trong một lớp trừu tượng mỏng — điều này cho phép thay thế thư viện một cách tăng dần mà không ảnh hưởng đến toàn bộ cơ sở mã. Bước đầu tiên thường là thay thế công việc với LocalDate và LocalTime — các lớp này có ít khác biệt nhất giữa các thư viện. Giai đoạn thứ hai di chuyển DateTime (thành ZonedDateTime) và múi giờ. Giai đoạn thứ ba thay thế Interval, Period và Duration — ở đây bạn cần kiểm tra cẩn thận hành vi giờ mùa hè. Sau khi hoàn tất di chuyển, Joda-Time bị loại bỏ hoàn toàn khỏi các phụ thuộc dự án.
// Joda-Time (trước khi di chuyển)
val dateTime = DateTime.now(DateTimeZone.forID("Europe/Moscow"))
val formatted = dateTime.toString("dd.MM.yyyy")
val plusDay = dateTime.plusDays(1)
// java.time (sau khi di chuyển)
val zonedDateTime = ZonedDateTime.now(ZoneId.of("Europe/Moscow"))
val formatted2 = zonedDateTime.format(
DateTimeFormatter.ofPattern("dd.MM.yyyy")
)
val plusDay2 = zonedDateTime.plusDays(1)
Có một số tiện ích giúp đơn giản hóa việc chuyển đổi từ Joda-Time sang java.time. OpenRewrite (của Moderne) hỗ trợ di chuyển tự động thông qua công thức org.openrewrite.java.migrate.JodaTimeToJavaTime — nó thay thế các lớp, phương thức và import trong khi vẫn giữ nguyên logic chương trình. Đối với các dự án Gradle, plugin de.fayard.refreshVersions cũng bao gồm các quy tắc di chuyển. Thủ công, việc di chuyển dễ thực hiện nhất với IDEA: plugin Java Time Migration Assistant làm nổi bật các lời gọi Joda-Time và đề xuất các thay thế tương ứng trong java.time, tăng tốc quá trình lên 2–3 lần so với thay thế thủ công.
Hãy xem các ví dụ thực tế về làm việc với Joda-Time trong các ứng dụng Android. Thư viện đặc biệt hữu ích cho việc định dạng ngày theo tiêu chuẩn địa phương, tính tuổi người dùng, xác định thời gian sự kiện và tính toán khoảng cách giữa hai ngày. Tất cả các ví dụ đều sử dụng đối tượng bất biến — mỗi lần gọi trả về một thể hiện mới, trong khi đối tượng gốc vẫn không thay đổi.
// Tính tuổi người dùng
val birthDate = LocalDate.parse("1990-05-15")
val today = LocalDate.now()
val age = Years.yearsBetween(birthDate, today).getYears()
// tuổi == 36
// Định dạng với locale người dùng
val dateTime = DateTime.now()
val formatter = DateTimeFormat.forStyle("MM").withLocale(Locale.getDefault())
val localized = formatter.print(dateTime)
Các khoảng trong Joda-Time cho phép giải quyết hiệu quả các tác vụ như kiểm tra chồng chéo khoảng thời gian, tính toán giao nhau và tìm sự kiện gần nhất. Ví dụ, khi phát triển ứng dụng lịch, bạn có thể kiểm tra xem sự kiện mới có trùng với sự kiện hiện có hay không. Lớp Interval hỗ trợ các thao tác contains, overlaps, gap và abuts — thao tác cuối cho biết liệu các khoảng có chạm nhau ở ranh giới hay không. Duration được sử dụng để đo độ dài giữa hai thời điểm với độ chính xác đến mili giây.
// Kiểm tra chồng chéo khoảng
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); // đúng
Duration gap = meeting1.gap(meeting2); // null (không có khoảng trống)
Câu hỏi thường gặp
Joda-Time là thư viện làm việc với ngày và giờ trong Java, được tạo ra trước khi phát hành Java 8. Nó đã khắc phục các thiếu sót của java.util.Date và java.util.Calendar: tính khả biến, đánh số tháng từ 0, thiếu hỗ trợ đầy đủ múi giờ và định dạng không an toàn luồng.
Java.time (JSR-310) là sự kế thừa của Joda-Time, được phát triển bởi cùng tác giả. Sự khác biệt chính: java.time được tích hợp vào thư viện chuẩn Java 8+, sử dụng hệ thống đặt tên nhất quán hơn, hỗ trợ các hệ thống lịch thay thế và bao gồm lớp Instant để làm việc với thời gian UTC.
Không, cho các dự án mới nên sử dụng java.time. Joda-Time đã ở chế độ bảo trì từ năm 2014 và không còn nhận được tính năng mới. Đối với Android với API 26+, java.time có sẵn nguyên bản, và cho các phiên bản cũ hơn sử dụng desugaring — một cơ chế cho phép sử dụng java.time trên các thiết bị có API 19+.
Việc di chuyển được thực hiện từng bước: cô lập Joda-Time trong các lớp trừu tượng, thay thế LocalDate và LocalTime trước, sau đó DateTime → ZonedDateTime, và cuối cùng là Period, Duration và Interval. Công cụ OpenRewrite hỗ trợ di chuyển tự động thông qua công thức JodaTimeToJavaTime, tăng tốc quá trình lên 2–3 lần.
DateTime → ZonedDateTime, LocalDate → LocalDate (trùng khớp), LocalTime → LocalTime, LocalDateTime → LocalDateTime, DateTimeZone → ZoneId, Interval → không có tương đương trực tiếp (sử dụng kết hợp Instant + Duration), Period → Period, Duration → Duration. Tên phương thức cũng tương tự: plusDays, minusMonths, withZone có sẵn trong cả hai thư viện.
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm