Lớp java.time.ZonedDateTime

Lớp ZonedDateTime trong gói java.time của Java được sử dụng để biểu diễn cả ngày tháng, thời gian và múi giờ theo định dạng ISO-8601, bao gồm cả thông tin về quy tắc chuyển đổi giờ mùa hè (DST). Ví dụ: "2007-12-03T10:15:30+01:00 Europe/Paris" (ngày 03 tháng 12 năm 2007, 10:15:30 giờ cộng thêm 1 giờ so với UTC theo múi giờ Châu Âu/Paris). ZonedDateTime là một đối tượng ngày giờ bất biến (immutable) lưu trữ thông tin ngày, tháng, năm, giờ, phút, giây, thậm chí có thể chính xác đến nano giây, cùng với múi giờ và độ lệch chuẩn UTC.

Ⅰ. Đặc điểm của lớp ZonedDateTime

Lưu trữ cả ngày tháng (giống LocalDate) và thời gian (giống LocalTime) kèm theo múi giờ (ZoneId) và độ lệch chuẩn UTC.

Xử lý việc chuyển đổi giữa thời gian địa phương (LocalDateTime) và thời điểm trên trục thời gian (Instant).

Hỗ trợ các trường hợp phức tạp như thay đổi giờ mùa hè (DST) dẫn đến việc có nhiều độ lệch chuẩn hợp lệ cho cùng một ngày giờ địa phương (khoảng trống - Gap và chồng chéo - Overlap).

Là lớp bất biến (immutable), các phép toán dựa trên sự so sánh bằng (==) hoặc sử dụng hash code không nên được áp dụng cho ZonedDateTime. Bạn nên sử dụng phương thức equals để so sánh hai ngày giờ.

An toàn cho nhiều luồng (thread-safe).

So sánh lớp ZonedDateTime với lớp LocalDateTime và lớp OffsetDateTime:

Lớp ZonedDateTime chi tiết hơn lớp LocalDateTime vì nó lưu trữ cả múi giờ.

Lớp ZonedDateTime chi tiết hơn lớp OffsetDateTime vì nó lưu trữ đầy đủ thông tin quy tắc chuyển đổi giờ mùa hè (DST) của múi giờ.

Tóm lại, lớp ZonedDateTime là lớp hữu ích khi bạn cần lưu trữ ngày giờ theo múi giờ cụ thể và xử lý các trường hợp phức tạp liên quan đến thay đổi giờ mùa hè. Nó cung cấp thông tin chi tiết hơn về múi giờ so với lớp OffsetDateTime.

Ⅱ. Khai báo lớp ZonedDateTime trong Java

Để sử dụng lớp ZonedDateTime và các lớp khác trong gói java.time, bạn cần thêm câu lệnh import vào đầu file Java của mình.

Cú pháp câu lệnh import:

Cú pháp

import java.time.ZonedDateTime;

Cú pháp khai báo lớp ZonedDateTime:

Cú pháp

public final class ZonedDateTime
extends Object
implements Temporal, ChronoZonedDateTime<LocalDate>, Serializable

Dưới đây là giải thích chi tiết về cú pháp khai báo này:

↳ public: Lớp này có thể được truy cập từ bất kỳ đâu trong chương trình, không bị giới hạn phạm vi.

↳ final: Lớp này không thể bị kế thừa. Điều này có nghĩa là bạn không thể tạo một lớp con từ ZonedDateTime.

↳ class ZonedDateTime: Khai báo một lớp có tên là ZonedDateTime.

extends Object: Tất cả các lớp trong Java đều kế thừa từ lớp Object (lớp gốc của tất cả các lớp trong Java). Việc này cho phép lớp ZonedDateTime kế thừa các phương thức cơ bản từ Object, chẳng hạn như toString(), equals(), và hashCode().

implements Temporal, ChronoZonedDateTime<LocalDate>, Serializable

Lớp ZonedDateTime thực hiện (implements) các giao diện implements Temporal, ChronoZonedDateTime<LocalDate>, Serializable. Điều này có nghĩa là lớp ZonedDateTime phải cung cấp các phương thức được khai báo trong những giao diện này.

↳ implements Temporal: Lớp ZonedDateTime thực hiện giao diện Temporal. Giao diện này cung cấp các phương thức để thao tác với các đối tượng thời gian và ngày tháng, chẳng hạn như truy vấn và điều chỉnh các thành phần ngày và giờ.

↳ implements ChronoZonedDateTime<LocalDate>: Lớp ZonedDateTime thực hiện giao diện ChronoZonedDateTime<LocalDate>. Đây là một giao diện mở rộng của ZonedDateTime, định nghĩa các phương thức liên quan đến việc xử lý ngày và giờ trong một hệ thống lịch cụ thể (như Gregorian). ChronoZonedDateTime cho phép các lớp như ZonedDateTime tương tác với LocalDate và hỗ trợ các thao tác thời gian liên quan đến múi giờ.

↳ implements Serializable: ZonedDateTime thực hiện giao diện Serializable, cho phép các đối tượng của lớp này được chuyển đổi thành một dạng byte để lưu trữ hoặc truyền qua mạng (serialization) và phục hồi từ dạng byte này (deserialization).

Ⅲ. Các phương thức của lớp ZonedDateTime

Lớp ZonedDateTime cung cấp nhiều phương thức để thao tác với ngày giờ theo múi giờ. Việc phân nhóm các phương thức theo chức năng sẽ giúp bạn hiểu rõ hơn về cách sử dụng lớp ZonedDateTime. Dưới đây là cách phân nhóm các phương thức phổ biến của lớp ZonedDateTime:

Tạo đối tượng ZonedDateTime

↳ now(): Lấy ngày giờ hiện tại từ hệ thống theo múi giờ mặc định.

↳ of(LocalDate date, LocalTime time, ZoneId zone) (static): Tạo một đối tượng ZonedDateTime từ ngày (LocalDate), thời gian (LocalTime) và múi giờ (ZoneId).

Dưới đây là một ví dụ về cách sử dụng hai phương thức now() và of(LocalDate date, LocalTime time, ZoneId zone) của lớp ZonedDateTime trong cùng một lớp Java:

Ví dụ: Example.java

import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;

public class Example {
    public static void main(String[] args) {
        // Sử dụng phương thức now() để lấy ngày giờ hiện tại theo múi giờ mặc định
        ZonedDateTime currentZonedDateTime = ZonedDateTime.now();
        System.out.println("Ngày giờ hiện tại: " + currentZonedDateTime);

        // Tạo một LocalDate và LocalTime cụ thể
        LocalDate date = LocalDate.of(2023, 7, 22);
        LocalTime time = LocalTime.of(15, 30, 45);
        ZoneId zone = ZoneId.of("America/New_York");

        // Sử dụng phương thức of(LocalDate date, LocalTime time, ZoneId zone) để tạo một đối tượng ZonedDateTime
        ZonedDateTime specificZonedDateTime = ZonedDateTime.of(date, time, zone);
        System.out.println("Ngày giờ cụ thể với múi giờ New York: " + specificZonedDateTime);
    }
}

Kết quả của chương trình là:

Ngày giờ hiện tại: 2024-07-22T21:18:53.117250300+07:00[Asia/Saigon]
Ngày giờ cụ thể với múi giờ New York: 2023-07-22T15:30:45-04:00[America/New_York]

Kết quả của chương trình sẽ là ngày giờ hiện tại theo múi giờ hệ thống mặc định và một ngày giờ cụ thể với múi giờ New York.

Lấy các thành phần

↳ get(TemporalField field): Lấy giá trị của một trường ngày tháng hoặc thời gian cụ thể (ví dụ như ngày, tháng, năm, giờ, phút, giây) dưới dạng số nguyên.

↳ getZone(): Lấy thông tin múi giờ (ZoneId), ví dụ như "Asia/Kolkata".

Dưới đây là một ví dụ về cách sử dụng các phương thức get(TemporalField field) và getZone() của lớp ZonedDateTime trong cùng một lớp Java:

Ví dụ: Example.java

import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoField;

public class Example {
    public static void main(String[] args) {
        // Tạo một đối tượng ZonedDateTime cụ thể
        LocalDate date = LocalDate.of(2023, 7, 22);
        LocalTime time = LocalTime.of(15, 30, 45);
        ZoneId zone = ZoneId.of("Asia/Kolkata");
        ZonedDateTime zonedDateTime = ZonedDateTime.of(date, time, zone);

        // Lấy giá trị của các trường ngày tháng và thời gian cụ thể
        int year = zonedDateTime.get(ChronoField.YEAR);
        int month = zonedDateTime.get(ChronoField.MONTH_OF_YEAR);
        int dayOfMonth = zonedDateTime.get(ChronoField.DAY_OF_MONTH);
        int hour = zonedDateTime.get(ChronoField.HOUR_OF_DAY);
        int minute = zonedDateTime.get(ChronoField.MINUTE_OF_HOUR);
        int second = zonedDateTime.get(ChronoField.SECOND_OF_MINUTE);

        // Lấy thông tin múi giờ
        ZoneId zoneId = zonedDateTime.getZone();

        // In ra kết quả
        System.out.println("Năm: " + year);
        System.out.println("Tháng: " + month);
        System.out.println("Ngày trong tháng: " + dayOfMonth);
        System.out.println("Giờ: " + hour);
        System.out.println("Phút: " + minute);
        System.out.println("Giây: " + second);
        System.out.println("Múi giờ: " + zoneId);
    }
}

Kết quả của chương trình là:

Năm: 2023
Tháng: 7
Ngày trong tháng: 22
Giờ: 15
Phút: 30
Giây: 45
Múi giờ: Asia/Kolkata

Kết quả của chương trình sẽ là các giá trị của năm, tháng, ngày trong tháng, giờ, phút, giây và múi giờ từ đối tượng ZonedDateTime đã tạo.

Thay đổi múi giờ

↳ withZoneSameInstant(ZoneId zone): Trả về một bản sao của ZonedDateTime với múi giờ khác (zone) nhưng vẫn giữ nguyên thời điểm (instant).

Dưới đây là một ví dụ về cách sử dụng phương thức withZoneSameInstant(ZoneId zone) của lớp ZonedDateTime để thay đổi múi giờ nhưng giữ nguyên thời điểm (instant) trong cùng một lớp Java:

Ví dụ: Example.java

import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;

public class Example {
    public static void main(String[] args) {
        // Tạo một đối tượng ZonedDateTime cụ thể
        LocalDate date = LocalDate.of(2023, 7, 22);
        LocalTime time = LocalTime.of(15, 30, 45);
        ZoneId originalZone = ZoneId.of("Asia/Kolkata");
        ZonedDateTime zonedDateTime = ZonedDateTime.of(date, time, originalZone);

        // Thay đổi múi giờ nhưng giữ nguyên thời điểm
        ZoneId newZone = ZoneId.of("America/New_York");
        ZonedDateTime zonedDateTimeInNewZone = zonedDateTime.withZoneSameInstant(newZone);

        // In ra kết quả
        System.out.println("ZonedDateTime gốc: " + zonedDateTime);
        System.out.println("Múi giờ gốc: " + zonedDateTime.getZone());
        
        System.out.println("ZonedDateTime sau khi thay đổi múi giờ: " + zonedDateTimeInNewZone);
        System.out.println("Múi giờ mới: " + zonedDateTimeInNewZone.getZone());
    }
}

Kết quả của chương trình là:

Ngày: 2024-07-22
ZonedDateTime gốc: 2023-07-22T15:30:45+05:30[Asia/Kolkata]
Múi giờ gốc: Asia/Kolkata
ZonedDateTime sau khi thay đổi múi giờ: 2023-07-22T06:00:45-04:00[America/New_York]
Múi giờ mới: America/New_York

Hy vọng ví dụ này sẽ giúp bạn hiểu cách chuyển đổi giữa các múi giờ khác nhautrong Java.

Cộng/Trừ thời gian

↳ minus(long amountToSubtract, TemporalUnit unit): Trả về một bản sao của ZonedDateTime hiện tại nhưng đã trừ đi một giá trị cụ thể theo đơn vị thời gian nhất định (ví dụ như trừ 30 phút).

↳ plus(long amountToAdd, TemporalUnit unit): Trả về một bản sao của ZonedDateTime hiện tại nhưng đã cộng thêm một giá trị cụ thể theo đơn vị thời gian nhất định (ví dụ như cộng 3 giờ).

Dưới đây là ví dụ về cách sử dụng các phương thức cộng và trừ thời gian của lớp ZonedDateTime trong Java.

Ví dụ: Example.java

import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;

public class Example {
    public static void main(String[] args) {
        // Tạo một ZonedDateTime đại diện cho thời điểm hiện tại ở Việt Nam
        ZonedDateTime nowInVietnam = ZonedDateTime.now(ZoneId.of("Asia/Ho_Chi_Minh"));

        // Trừ đi 2 ngày
        ZonedDateTime twoDaysAgo = nowInVietnam.minus(2, ChronoUnit.DAYS);
        System.out.println("Hai ngày trước: " + twoDaysAgo);

        // Cộng thêm 3 giờ
        ZonedDateTime threeHoursLater = twoDaysAgo.plus(3, ChronoUnit.HOURS);
        System.out.println("Ba giờ sau khi trừ 2 ngày: " + threeHoursLater);

        // Cộng thêm 1 tuần và 2 ngày
        ZonedDateTime nextWeek = nowInVietnam.plus(1, ChronoUnit.WEEKS).plus(2, ChronoUnit.DAYS);
        System.out.println("Một tuần hai ngày tới: " + nextWeek);
    }
}

Kết quả của chương trình là:

Hai ngày trước: 2024-07-20T21:34:11.516371600+07:00[Asia/Ho_Chi_Minh]
Ba giờ sau khi trừ 2 ngày: 2024-07-21T00:34:11.516371600+07:00[Asia/Ho_Chi_Minh]
Một tuần hai ngày tới: 2024-07-31T21:34:11.516371600+07:00[Asia/Ho_Chi_Minh]

Ví dụ này minh họa cách bạn có thể thay đổi thời gian bằng cách cộng hoặc trừ các khoảng thời gian khác nhau.

Định dạng

↳ format(DateTimeFormatter formatter): Định dạng ZonedDateTime hiện tại theo định dạng được cung cấp bởi DateTimeFormatter.

Dưới đây là ví dụ về cách sử dụng phương thức format để định dạng một đối tượng ZonedDateTime trong Java:

Ví dụ: Example.java

import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;

public class Example {
    public static void main(String[] args) {
        // Tạo một ZonedDateTime đại diện cho thời điểm hiện tại ở Việt Nam
        ZonedDateTime nowInVietnam = ZonedDateTime.now(ZoneId.of("Asia/Ho_Chi_Minh"));

        // Định dạng theo định dạng BASIC_ISO_DATE
        DateTimeFormatter formatter1 = DateTimeFormatter.BASIC_ISO_DATE;
        String formatted1 = nowInVietnam.format(formatter1);
        System.out.println("Định dạng BASIC_ISO_DATE: " + formatted1);

        // Định dạng theo mẫu tùy chỉnh
        DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z");
        String formatted2 = nowInVietnam.format(formatter2);
        System.out.println("Định dạng tùy chỉnh: " + formatted2);
    }
}

Kết quả của chương trình là:

Định dạng BASIC_ISO_DATE: 20240722+0700
Định dạng tùy chỉnh: 2024-07-22 21:44:50 ICT

Với ví dụ trên, bạn đã có thể sử dụng phương thức format để định dạng các đối tượng ZonedDateTime theo nhiều cách khác nhau, đáp ứng nhu cầu hiển thị thời gian phù hợp cho từng trường hợp.

Các ngoại lệ thường gặp khi sử dụng lớp ZonedDateTime trong Java

Dưới đây là các ngoại lệ thường gặp và nguyên nhân của chúng:

Ngoại lệ DateTimeException

↳ Nguyên nhân: Xảy ra khi có lỗi liên quan đến định dạng ngày tháng hoặc thời gian không hợp lệ, chẳng hạn như ngày không tồn tại trong tháng hoặc thời gian không hợp lệ.

↳ Ví dụ: Khi cố gắng tạo một đối tượng ZonedDateTime với một ngày không hợp lệ, như 30 tháng 2.

Ngoại lệ DateTimeParseException

↳ Nguyên nhân: Xảy ra khi chuỗi đầu vào không khớp với định dạng ngày tháng hoặc thời gian mong đợi.

↳ Ví dụ: Khi sử dụng phương thức parse(CharSequence text) và chuỗi đầu vào không phải là định dạng hợp lệ cho ZonedDateTime.

Ngoại lệ DateTimeException (do múi giờ không hợp lệ)

↳ Nguyên nhân: Xảy ra khi sử dụng múi giờ không hợp lệ hoặc không tồn tại.

↳ Ví dụ: Khi tạo một đối tượng ZonedDateTime với ZoneId không hợp lệ hoặc không được hỗ trợ, chẳng hạn như "Invalid/Zone".

Ngoại lệ UnsupportedTemporalTypeException

↳ Nguyên nhân: Xảy ra khi thao tác với một trường thời gian không được hỗ trợ bởi ZonedDateTime.

↳ Ví dụ: Khi cố gắng cộng một khoảng thời gian lớn như ngày vào ZonedDateTime nhưng loại đơn vị không được hỗ trợ.

Ngoại lệ ArithmeticException

↳ Nguyên nhân: Xảy ra khi có lỗi số học trong các phép toán liên quan đến thời gian, chẳng hạn như vượt quá giới hạn số nguyên.

↳ Ví dụ: Khi cộng hoặc trừ một khoảng thời gian quá lớn mà dẫn đến tràn số.

Lưu ý:

Khi làm việc với ZonedDateTime, việc kiểm tra dữ liệu đầu vào và các thao tác ngày tháng là rất quan trọng để tránh các ngoại lệ. Sử dụng các khối try-catch để xử lý ngoại lệ và cung cấp thông báo lỗi hoặc xử lý các tình huống bất thường để làm cho ứng dụng của bạn ổn định và dễ duy trì hơn.

Câu Nói Truyền Cảm Hứng

“Bắt đầu ở đâu không quan trọng, quan trọng là bạn sẵn sàng bắt đầu.” – W. Clement Stone

Không Gian Tích Cực

“Chúc bạn luôn giữ vững niềm tin và sức mạnh để vượt qua mọi thử thách trong cuộc sống.”