Lớp java.time.Instant
Lớp Instant trong gói java.time của Java được sử dụng để biểu diễn một thời điểm cụ thể trên dòng thời gian. Nó thường được dùng để ghi lại dấu thời gian của các sự kiện trong ứng dụng.
Ⅰ. Đặc điểm của lớp Instant
↳ Lớp Instant không yêu cầu cung cấp đồng hồ có độ chính xác dưới giây hoặc hoạt động đơn điệu, trơn tru.
↳ Ứng dụng Java cần ghi chú cách thức xác định đồng hồ đại diện cho thời điểm hiện tại (xem lớp Clock để biết chi tiết).
↳ Các lớp ngày-giờ khác trong Java 8 Date-Time API như LocalDate, LocalTime, OffsetDateTime, ZonedDateTime, Duration đều sử dụng Thang Thời Gian Java.
↳ Instant là lớp dựa trên giá trị; việc sử dụng các phép toán dựa trên bản sắc (identity-sensitive operations) trên Instant có thể dẫn đến kết quả không mong đợi và nên tránh. Sử dụng phương thức equals để so sánh các Instant.
↳ Lớp Instant bất biến (immutable) và an toàn cho nhiều luồng (thread-safe).
Lưu trữ thời điểm
↳ Instant lưu trữ thời điểm dưới dạng số giây kể từ một mốc cố định (epoch) là 00:00:00 UTC ngày 01/01/1970.
↳ Giá trị giây được lưu trữ trong một kiểu số nguyên 64 bit (long).
↳ Kết hợp với số nguyên 32 bit (int) lưu trữ nano giây, tạo thành độ phân giải nano giây cho thời điểm.
Thang thời gian
↳ Ngày Mặt Trời là cách tiêu chuẩn để con người đo thời gian. Nó được chia thành 24 giờ, mỗi giờ 60 phút, mỗi phút 60 giây (tổng 86400 giây).
↳ Giờ hiện đại dựa trên đồng hồ nguyên tử, xác định chính xác một giây SI theo sự chuyển đổi của nguyên tử Caesium.
↳ Độ dài của ngày Mặt Trời thay đổi do Trái Đất tự quay. Ngoài ra, theo thời gian, ngày Mặt Trời trung bình ngày càng dài ra do Trái Đất quay chậm lại.
↳ Giờ UTC giải quyết vấn đề này bằng cách điều chỉnh theo giây nhuận (leap second) được thêm hoặc xóa tùy thuộc vào sự thay đổi tự quay của Trái Đất.
Thang thời gian Java
↳ API Thời gian Java định nghĩa thang thời gian riêng biệt, gọi là Thang Thời Gian Java.
↳ Thang Thời Gian Java chia mỗi ngày dương lịch thành chính xác 86400 đơn vị, gọi là giây.
↳ Giây trong Thang Thời Gian Java có thể khác với giây SI nhưng xấp xỉ với thang thời gian dân dụng quốc tế.
↳ Thang Thời Gian Java được chia thành các phân đoạn khác nhau, mỗi phân đoạn dựa trên sự đồng thuận quốc tế về thang thời gian dân dụng.
Ví dụ về các phân đoạn
↳ Từ 1972-11-03 đến hiện tại, Thang Thời Gian Java giống với UTC-SLS (giống UTC vào những ngày không có nhuận giây, còn những ngày có nhuận giây thì được trải đều trong 1000 giây cuối cùng).
↳ Phân đoạn trước 1972-11-03, Thang Thời Gian Java giống với UT1 (thời gian Mặt Trời trung bình trên kinh tuyến gốc Greenwich).
Ⅱ. Khai báo lớp Instant trong Java
Để sử dụng lớp Instant 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.Instant;
Cú pháp khai báo lớp Instant:
Cú pháp
public final class Instant
extends Object
implements Temporal, TemporalAdjuster, Comparable<Instant>, 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ừ Instant.
↳ class Instant: Khai báo một lớp có tên là Instant.
↳ 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 Instant 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, TemporalAdjuster, Comparable<Instant>, Serializable
Lớp Instant thực hiện (implements) các giao diện Temporal, TemporalAdjuster, Comparable<Instant>, Serializable. Điều này có nghĩa là lớp Instant 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 Instant triển khai giao diện Temporal, cho phép nó hỗ trợ các thao tác với thời gian như so sánh, điều chỉnh và tính toán.
↳ implements TemporalAdjuster:Lớp Instant triển khai giao diện TemporalAdjuster, cho phép thực hiện các điều chỉnh trên đối tượng thời gian, ví dụ như thay đổi thời điểm.
↳ implements Comparable<Instant>: Lớp Instant triển khai giao diện Comparable<Instant>, cho phép so sánh hai đối tượng Instant với nhau để xác định thứ tự.
↳ implements Serializable: Lớp Instant triển khai giao diện Serializable, cho phép các đối tượng Instant được chuyển đổi thành dạng có thể lưu trữ hoặc truyền qua mạng (serialization).
Ⅲ. Các phương thức của lớp Instant
Lớp Instant cung cấp nhiều phương thức để thao tác với thời điểm. 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 Instant. Dưới đây là cách phân nhóm các phương thức phổ biến của lớp Instant:
Tạo đối tượng Instant
↳ now(): Lấy thời điểm hiện tại.
↳ parse(CharSequence text): Tạo Instant từ chuỗi biểu diễn thời gian.
↳ ofEpochMilli(long epochMilli): Tạo Instant từ số mili giây kể từ epoch.
↳ ofEpochSecond(long epochSecond): Tạo Instant từ số giây kể từ epoch.
↳ from(TemporalAccessor temporal): Chuyển đổi một đối tượng thời gian khác thành Instant.
Dưới đây là ví dụ về cách sử dụng các phương thức tạo đối tượng Instant trong Java:
Ví dụ: Example.java
import java.time.Instant;
import java.time.temporal.TemporalAccessor;
public class Example {
public static void main(String[] args) {
// 1. Tạo Instant từ thời điểm hiện tại
Instant now = Instant.now();
System.out.println("Thời điểm hiện tại: " + now);
// 2. Tạo Instant từ chuỗi biểu diễn thời gian
Instant parsed = Instant.parse("2024-07-25T10:15:30Z");
System.out.println("Parsed Instant: " + parsed);
// 3. Tạo Instant từ số mili giây kể từ epoch
Instant fromEpochMilli = Instant.ofEpochMilli(1_675_492_800_000L); // Ví dụ: Mili giây từ epoch
System.out.println("Instant từ epoch milli: " + fromEpochMilli);
// 4. Tạo Instant từ số giây kể từ epoch
Instant fromEpochSecond = Instant.ofEpochSecond(1_675_492_800L); // Ví dụ: Giây từ epoch
System.out.println("Instant từ epoch second: " + fromEpochSecond);
// 5. Chuyển đổi một đối tượng Temporal khác thành Instant
TemporalAccessor temporal = Instant.now(); // Ví dụ: sử dụng chính Instant hiện tại
Instant fromTemporal = Instant.from(temporal);
System.out.println("Instant từ TemporalAccessor: " + fromTemporal);
}
}
Kết quả của chương trình là:
Parsed Instant: 2024-07-25T10:15:30Z
Instant từ epoch milli: 2023-02-04T06:40:00Z-04T06:40:00Z 2-04T06:40:00Z
Instant từ epoch second: 2023-024-07-25T02:14:56.802217900
Instant từ epoch milli: 2023-02-04T06:40:00Z
Instant từ epoch second: 2023-02-04T06:40:00Z
Instant từ TemporalAccessor: 2024-07-25T02:14:56.802217900Z
Trong ví dụ trên, mỗi phương thức đều được sử dụng để tạo đối tượng Instant từ các nguồn khác nhau và in ra kết quả để bạn có thể thấy sự khác biệt trong cách chúng hoạt động.
Lấy thông tin
↳ get(TemporalField field): Lấy giá trị của một trường thời gian cụ thể.
↳ getLong(TemporalField field): Giống get nhưng trả về giá trị kiểu long.
↳ getEpochSecond(): Lấy số giây kể từ epoch.
↳ getNano(): Lấy số nano giây trong giây.
↳ isSupported(TemporalField field): Kiểm tra xem trường thời gian có được hỗ trợ hay không.
Dưới đây là ví dụ về cách sử dụng các phương thức lấy thông tin từ đối tượng Instant trong Java:
Ví dụ: Example.java
import java.time.Instant;
import java.time.temporal.ChronoField;
public class Example {
public static void main(String[] args) {
Instant instant = Instant.now();
// 1. Lấy số giây kể từ epoch
long epochSecond = instant.getEpochSecond();
System.out.println("Số giây kể từ epoch: " + epochSecond);
// 2. Lấy số nano giây trong giây
int nanoOfSecond = instant.getNano();
System.out.println("Số nano giây trong giây: " + nanoOfSecond);
// 3. Lấy giá trị của một trường thời gian cụ thể
// Ví dụ: Lấy giây trong giây (sẽ luôn là 0 cho Instant)
int secondOfSecond = instant.get(ChronoField.NANO_OF_SECOND) / 1_000_000_000;
System.out.println("Giây trong giây: " + secondOfSecond);
// 4. Lấy giá trị kiểu long của một trường thời gian cụ thể
long nanoOfSecondLong = instant.getLong(ChronoField.NANO_OF_SECOND);
System.out.println("Nano giây trong giây: " + nanoOfSecondLong);
// 5. Kiểm tra xem trường thời gian có được hỗ trợ hay không
boolean isSupported = instant.isSupported(ChronoField.NANO_OF_SECOND);
System.out.println("Hỗ trợ NANO_OF_SECOND: " + isSupported);
}
}
Kết quả của chương trình là:
Số nano giây trong giây: 880403900
Giây trong giây: 0
Nano giây trong giây: 880403900
Hỗ trợ NANO_OF_SECOND: true
Trong ví dụ trên, mỗi phương thức đều được sử dụng để lấy thông tin từ đối tượng Instant và in ra kết quả để bạn có thể thấy cách chúng hoạt động và cung cấp thông tin chi tiết về thời điểm hiện tại.
So sánh
↳ compareTo(Instant otherInstant): So sánh hai Instant.
↳ equals(Object otherInstant): Kiểm tra hai Instant có bằng nhau không.
Dưới đây là ví dụ về việc sử dụng hai phương thức compareTo() và equals() trong lớp Instant:
Ví dụ: Example.java
import java.time.Instant;
public class Example {
public static void main(String[] args) {
// Tạo hai đối tượng Instant khác nhau
Instant instant1 = Instant.now();
Instant instant2 = Instant.now().plusSeconds(60); // instant2 là 60 giây sau instant1
// So sánh hai Instant
int comparisonResult = instant1.compareTo(instant2);
if (comparisonResult < 0) {
System.out.println("instant1 xảy ra trước instant2.");
} else if (comparisonResult > 0) {
System.out.println("instant1 xảy ra sau instant2.");
} else {
System.out.println("instant1 và instant2 xảy ra cùng một thời điểm.");
}
// Kiểm tra xem hai Instant có bằng nhau không
boolean isEqual = instant1.equals(instant2);
if (isEqual) {
System.out.println("instant1 và instant2 bằng nhau.");
} else {
System.out.println("instant1 và instant2 không bằng nhau.");
}
}
}
Kết quả của chương trình là:
instant1 và instant2 không bằng nhau.
Trong ví dụ trên, phương thức compareTo() được sử dụng để so sánh hai đối tượng Instant và in ra kết quả so sánh, cho biết cái nào xảy ra trước hoặc sau. Phương thức equals() được sử dụng để kiểm tra xem hai đối tượng Instant có bằng nhau không
Kiểm tra
↳ isAfter(Instant otherInstant): Kiểm tra xem Instant này có muộn hơn Instant khác hay không.
↳ isBefore(Instant otherInstant): Kiểm tra xem Instant này có sớm hơn Instant khác hay không.
Dưới đây là ví dụ về việc sử dụng hai phương thức isAfter() và isBefore() trong lớp Instant:
Ví dụ: Example.java
import java.time.Instant;
public class Example {
public static void main(String[] args) {
// Tạo hai đối tượng Instant khác nhau
Instant instant1 = Instant.now();
Instant instant2 = Instant.now().plusSeconds(60); // instant2 là 60 giây sau instant1
// Kiểm tra xem instant1 có muộn hơn instant2 không
boolean isAfter = instant1.isAfter(instant2);
if (isAfter) {
System.out.println("instant1 xảy ra sau instant2.");
} else {
System.out.println("instant1 không xảy ra sau instant2.");
}
// Kiểm tra xem instant1 có sớm hơn instant2 không
boolean isBefore = instant1.isBefore(instant2);
if (isBefore) {
System.out.println("instant1 xảy ra trước instant2.");
} else {
System.out.println("instant1 không xảy ra trước instant2.");
}
}
}
Kết quả của chương trình là:
instant1 xảy ra trước instant2.
Trong ví dụ trên, phương thức isAfter() được sử dụng để kiểm tra xem instant1 có xảy ra sau instant2 không, và phương thức isBefore() được sử dụng để kiểm tra xem instant1 có xảy ra trước instant2 không.
Điều chỉnh
↳ plus(TemporalAmount amountToAdd): Cộng thêm một khoảng thời gian.
↳ minus(TemporalAmount amountToSubtract): Trừ đi một khoảng thời gian.
↳ truncatedTo (TemporalAdjuster adjuster): Áp dụng một điều chỉnh thời gian.
↳ plus(long amountToAdd, TemporalUnit unit): Cộng thêm một giá trị theo đơn vị thời gian.
Dưới đây là ví dụ về việc sử dụng các phương thức plus(), minus(), truncatedTo(), và plus() (với đơn vị thời gian) trong lớp Instant:
Ví dụ: Example.java
import java.time.Instant;
import java.time.temporal.ChronoUnit;
public class Example {
public static void main(String[] args) {
// Tạo đối tượng Instant hiện tại
Instant now = Instant.now();
// Cộng thêm 10 phút vào Instant hiện tại
Instant plusMinutes = now.plus(10, ChronoUnit.MINUTES);
System.out.println("Cộng thêm 10 phút: " + plusMinutes);
// Trừ đi 5 giờ từ Instant hiện tại
Instant minusHours = now.minus(5, ChronoUnit.HOURS);
System.out.println("Trừ đi 5 giờ: " + minusHours);
// Cộng thêm 1 ngày vào Instant hiện tại
Instant plusOneDay = now.plus(1, ChronoUnit.DAYS);
System.out.println("Cộng thêm 1 ngày: " + plusOneDay);
// Điều chỉnh Instant hiện tại để tới nửa đêm của ngày hiện tại
Instant startOfDay = now.truncatedTo(ChronoUnit.DAYS);
System.out.println("Nửa đêm của ngày hiện tại: " + startOfDay);
}
}
Kết quả của chương trình là:
Trừ đi 5 giờ: 2024-07-24T21:28:16.366567100Z
Cộng thêm 1 ngày: 2024-07-26T02:28:16.366567100Z
Nửa đêm của ngày hiện tại: 2024-07-25T00:00:00Z
Trong ví dụ trên, các phương thức plus(), minus(), và truncatedTo() được sử dụng để điều chỉnh đối tượng Instant bằng cách cộng thêm hoặc trừ đi một khoảng thời gian, và để điều chỉnh về nửa đêm của ngày hiện tại.
Chuyển đổi
↳ adjustInto(Temporal temporal): Điều chỉnh một đối tượng thời gian khác về thời điểm của Instant này.
↳ atOffset(ZoneOffset offset): Kết hợp với ZoneOffset để tạo OffsetDateTime.
↳ atZone(ZoneId zone): Kết hợp với ZoneId để tạo ZonedDateTime.
Dưới đây là một ví dụ về cách sử dụng các phương thức adjustInto(), atOffset(), và atZone() của lớp Instant trong một lớp Java:
Ví dụ: Example.java
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
public class Example {
public static void main(String[] args) {
// Tạo đối tượng Instant hiện tại
Instant now = Instant.now();
// Ví dụ: adjustInto
// Điều chỉnh một LocalDateTime để khớp với thời điểm của Instant này
java.time.LocalDateTime localDateTime = java.time.LocalDateTime.now();
java.time.LocalDateTime adjustedDateTime = localDateTime.withNano(now.getNano());
System.out.println("Điều chỉnh LocalDateTime: " + adjustedDateTime);
// Ví dụ: atOffset
// Kết hợp Instant với ZoneOffset để tạo OffsetDateTime
ZoneOffset offset = ZoneOffset.ofHours(2); // UTC+2
OffsetDateTime offsetDateTime = now.atOffset(offset);
System.out.println("OffsetDateTime: " + offsetDateTime);
// Ví dụ: atZone
// Kết hợp Instant với ZoneId để tạo ZonedDateTime
ZoneId zoneId = ZoneId.of("America/New_York");
ZonedDateTime zonedDateTime = now.atZone(zoneId);
System.out.println("ZonedDateTime: " + zonedDateTime);
}
}
Kết quả của chương trình là:
OffsetDateTime: 2024-07-25T04:31:37.318954100+02:00
ZonedDateTime: 2024-07-24T22:31:37.318954100-04:00[America/New_York]
Trong ví dụ trên, phương thức adjustInto() điều chỉnh một LocalDateTime để khớp với thời điểm của Instant. Phương thức atOffset() kết hợp Instant với ZoneOffset để tạo OffsetDateTime, và phương thức atZone() kết hợp Instant với ZoneId để tạo ZonedDateTime.
Các ngoại lệ thường gặp khi sử dụng lớp Instant 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ệ DateTimeParseException
↳ Nguyên nhân: Xảy ra khi chuỗi đầu vào không thể phân tích cú pháp thành một đối tượng Instant. Ví dụ, chuỗi không đúng định dạng ISO 8601.
↳ Ví dụ: Cố gắng phân tích chuỗi "2024-07-25" thành Instant.
Ngoại lệ UnsupportedTemporalTypeException
↳ Nguyên nhân: Khi một trường thời gian không được hỗ trợ bởi Instant. Ví dụ, Instant không hỗ trợ trường như DayOfMonth hay Month.
↳ Ví dụ: Cố gắng lấy giá trị của trường DayOfMonth từ Instant.
Ngoại lệ ArithmeticException
↳ Nguyên nhân: Xảy ra khi thực hiện các phép toán với Instant mà dẫn đến giá trị vượt quá phạm vi hợp lệ. Ví dụ, cộng thêm khoảng thời gian quá lớn.
↳ Ví dụ: Cộng thêm một giá trị rất lớn vào Instant, dẫn đến giá trị không hợp lệ.
Ngoại lệ DateTimeException
↳ Nguyên nhân: Khi có vấn đề với giá trị ngày tháng hoặc thời gian, ví dụ, kết hợp Instant với ZoneOffset không hợp lệ.
↳ Ví dụ: Kết hợp Instant với ZoneOffset không hợp lệ dẫn đến lỗi.
Lưu ý:
Khi làm việc với Instant, 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 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.