CodeGym /Các khóa học /JAVA 25 SELF /Tổng quan về java.time API, khác biệt so với các API cũ

Tổng quan về java.time API, khác biệt so với các API cũ

JAVA 25 SELF
Mức độ , Bài học
Có sẵn

1. Bối cảnh lịch sử: trước Java 8 người ta làm việc với ngày giờ như thế nào

Ngày xưa (trước Java 8), để làm việc với ngày và thời gian, người ta dùng các lớp java.util.Date, java.util.Calendar và để định dạng — java.text.SimpleDateFormat. Đó là trường hợp khiến lập trình viên khắp thế giới thở dài và, nghiến răng, viết những thứ kiểu như:

import java.util.Date;

Date now = new Date();
System.out.println(now); // Sẽ in ra thứ gì đó kỳ lạ kiểu như "Wed Jun 05 14:15:22 MSK 2025"

Nghe có vẻ đơn giản, nhưng thực tế thì không hề “màu hồng”. Dưới đây chỉ là một vài “niềm vui” của API cũ:

  • Date — là đối tượng có thể thay đổi (mutable). Có thể vô tình bị sửa đổi, và điều đó thường dẫn đến lỗi.
  • Các tháng trong DateCalendar bắt đầu từ 0 (tháng 1 là 0, tháng 12 là 11), còn ngày thì bắt đầu từ 1.
  • SimpleDateFormat không an toàn luồng: nếu hai luồng cùng định dạng ngày đồng thời, bạn có thể nhận kết quả khó lường.
  • Rất nhiều phương thức được đánh dấu @Deprecated (lỗi thời), và IDE liên tục “dọa” bạn bằng các cảnh báo màu vàng.
  • Làm việc với múi giờ là một nỗi đau thực sự: rất dễ nhầm lẫn giữa giờ địa phương và UTC, còn chuyển đổi giờ mùa hè/đông thì tốt nhất đừng nhắc tới.

Ví dụ nỗi đau

import java.util.Date;

Date date = new Date(2025, 5, 1); // năm 2025, tháng 5 (tháng sáu?), ngày 1
System.out.println(date); // Không như bạn mong đợi!

2. Sự ra đời java.time: cách tiếp cận mới

Đến năm 2014, đã rõ ràng: API cũ không chỉ bất tiện — mà còn nguy hiểm. Vì vậy Java đã có gói mới — java.time, hiện thực đặc tả JSR‑310. API này lấy cảm hứng từ thư viện Joda-Time nổi tiếng và nhanh chóng trở thành tiêu chuẩn de facto.

Các package và lớp chính

  • java.time — gói chính, nơi chứa các lớp mới về ngày và thời gian.
  • java.time.format — để định dạng và phân tích (parse) ngày giờ.
  • java.time.temporal — cho các thao tác thời gian nâng cao hơn.
  • java.time.zone — để làm việc với múi giờ.

Đây là các “nhân vật chính” của API mới:

Lớp Dùng để làm gì? Ví dụ sử dụng
LocalDate
Chỉ ngày (năm, tháng, ngày) Ngày sinh, không có giờ
LocalTime
Chỉ thời gian (giờ, phút, giây) Giờ hẹn, không có ngày
LocalDateTime
Ngày và thời gian, không có múi giờ Sự kiện cục bộ
ZonedDateTime
Ngày và thời gian có múi giờ Cuộc hẹn tại Minsk theo giờ địa phương
Instant
Mốc thời gian tuyệt đối (UTC) Dấu mốc sự kiện trong log
Duration
Khoảng thời gian (giờ, phút, giây) Thời lượng cuộc gọi
Period
Khoảng (năm, tháng, ngày) Thâm niên làm việc, tuổi

Ví dụ: tạo ngày theo cách mới

import java.time.LocalDate;

LocalDate today = LocalDate.now();
System.out.println(today); // Ví dụ: "2025-06-05"

3. Ưu điểm của API mới

Tính bất biến (immutable)

Tất cả các lớp trong java.time đều bất biến. Nghĩa là: nếu bạn tạo một đối tượng LocalDate, bạn không thể thay đổi nó. Bất kỳ thao tác nào (ví dụ, cộng 1 ngày) đều trả về một đối tượng mới.

LocalDate today = LocalDate.now();
LocalDate tomorrow = today.plusDays(1);

System.out.println(today);    // 2025-06-05
System.out.println(tomorrow); // 2025-06-06

Làm việc rõ ràng với múi giờ

Trong API cũ rất dễ quên đối tượng đang ở múi giờ nào. Trong java.time mọi thứ đều rõ ràng: nếu cần múi giờ — dùng ZonedDateTime, nếu không cần — dùng LocalDateTime.

import java.time.ZonedDateTime;
import java.time.ZoneId;

ZonedDateTime MinskTime = ZonedDateTime.now(ZoneId.of("Europe/Minsk"));
System.out.println(MinskTime); // 2025-06-05T14:23:45+03:00[Europe/Minsk]

Các phương thức tiện lợi cho tính toán và so sánh

LocalDate today = LocalDate.now();
LocalDate nextMonth = today.plusMonths(1);
boolean isAfter = LocalDate.now().plusDays(1).isAfter(today); // true

Định dạng và phân tích (parse)

Định dạng và parse — qua DateTimeFormatter (chi tiết — ở bài sau):

import java.time.format.DateTimeFormatter;

LocalDate today = LocalDate.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy");
String formatted = today.format(formatter); // "05.08.2025"

4. Tương thích: làm việc với mã cũ như thế nào

Trong thực tế, rất thường xuyên bạn phải làm việc với thư viện hoặc hệ thống cũ dùng DateCalendar. May mắn thay, API mới thân thiện với di sản: có thể chuyển đổi các kiểu cũ sang kiểu mới và ngược lại.

Chuyển đổi DateInstant

import java.util.Date;
import java.time.Instant;

// Date → Instant
Date legacyDate = new Date();
Instant instant = legacyDate.toInstant();

// Instant → Date
Date dateBack = Date.from(instant);

Chuyển đổi CalendarZonedDateTime

import java.util.Calendar;
import java.time.ZonedDateTime;
import java.time.ZoneId;
import java.util.Date;

// Calendar → ZonedDateTime
Calendar calendar = Calendar.getInstance();
ZonedDateTime zdt = ZonedDateTime.ofInstant(
        calendar.toInstant(),
        calendar.getTimeZone().toZoneId()
);

// ZonedDateTime → Calendar
Calendar calBack = Calendar.getInstance();
calBack.setTime(Date.from(zdt.toInstant()));

Bảng: đối chiếu các lớp cũ và mới

Lớp cũ Lớp mới Ghi chú
java.util.Date
Instant
Thời gian tuyệt đối
java.util.Calendar
ZonedDateTime
Ngày và giờ có múi giờ
java.text.SimpleDateFormat
DateTimeFormatter
Định dạng/parse ngày

5. Thực hành: những bước đầu với java.time

Giả sử bạn có một người dùng với ngày sinh. Hãy lưu và in ngày này:

import java.time.LocalDate;

public class UserProfile {
    private String name;
    private LocalDate birthDate;

    public UserProfile(String name, LocalDate birthDate) {
        this.name = name;
        this.birthDate = birthDate;
    }

    public void printProfile() {
        System.out.println("Tên: " + name);
        System.out.println("Ngày sinh: " + birthDate);
    }
}

public class Main {
    public static void main(String[] args) {
        UserProfile user = new UserProfile("Alisa", LocalDate.of(1998, 12, 25));
        user.printProfile();
    }
}

Kết quả:

Tên: Alisa
Ngày sinh: 1998-12-25

6. So sánh: API cũ vs API mới

Ví dụ: cộng một tuần vào ngày sinh

Cách cũ (Date/Calendar):

import java.util.Calendar;

Calendar cal = Calendar.getInstance();
cal.set(1998, Calendar.DECEMBER, 25);
cal.add(Calendar.WEEK_OF_YEAR, 1);
System.out.println(cal.getTime()); // Rườm rà và khó hiểu

Cách mới (java.time):

import java.time.LocalDate;

LocalDate birthDate = LocalDate.of(1998, 12, 25);
LocalDate nextWeek = birthDate.plusWeeks(1);
System.out.println(nextWeek); // 1999-01-01

Với API mới, mã ngắn hơn, đơn giản hơn và an toàn hơn.

7. Các lỗi thường gặp khi làm việc với java.time

Lỗi số 1: quên rằng các đối tượng là bất biến.
Nếu gọi date.plusDays(1); mà không lưu kết quả, ngày ban đầu sẽ giữ nguyên.

Lỗi số 2: nhầm lẫn giữa LocalDateLocalDateTime.
LocalDate chỉ lưu ngày (năm, tháng, ngày), còn LocalDateTime lưu cả thời gian. Đừng nhầm lẫn nếu bạn cần xử lý cả giờ và phút.

Lỗi số 3: sử dụng các lớp cũ trong các dự án mới.
Nếu có thể — luôn dùng java.time. Các lớp cũ chỉ cần cho mục đích tương thích.

Lỗi số 4: xử lý múi giờ không đúng.
Nếu cần lưu sự kiện quan trọng cho nhiều vùng, hãy dùng ZonedDateTime hoặc ít nhất là Instant. LocalDateTime không chứa thông tin về múi giờ!

Lỗi số 5: cố so sánh trực tiếp LocalDateLocalDateTime.
Đây là hai kiểu dữ liệu khác nhau, không thể so sánh trực tiếp. Hãy chuyển về cùng một kiểu trước.

Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION