1. Cộng và trừ ngày giờ
Trong lập trình, làm việc với ngày và giờ không chỉ là “in ngày ra màn hình cho đẹp”. Rất thường xuyên cần:
- biết còn bao nhiêu ngày tới một sự kiện quan trọng;
- tính tuổi của người dùng;
- xác định đã bao nhiêu thời gian trôi qua giữa hai sự kiện;
- kiểm tra hạn chót đã đến chưa;
- cộng vào một ngày số ngày, tuần, tháng hoặc giờ nhất định.
Ví dụ, trong ứng dụng của chúng ta, ta muốn nhắc người dùng rằng còn 3 ngày tới hạn chót của nhiệm vụ. Hoặc chúc mừng họ nhân dịp sinh nhật đã qua (nếu ngày đó đã ở quá khứ).
Để làm điều này, trong java.time có các lớp và phương thức chuyên dụng giúp các phép tính như vậy trở nên đơn giản và an toàn.
Các phương thức chính để cộng và trừ
Tất cả các lớp về ngày và giờ (LocalDate, LocalTime, LocalDateTime, ZonedDateTime, Instant) đều có các phương thức:
- plusXxx() — thêm (ngày, tháng, năm, giờ, phút, v.v.)
- minusXxx() — trừ (tương tự)
import java.time.LocalDate;
LocalDate today = LocalDate.now(); // Ngày hiện tại
LocalDate tomorrow = today.plusDays(1); // Ngày mai
LocalDate nextMonth = today.plusMonths(1); // Sau một tháng
LocalDate lastWeek = today.minusWeeks(1); // Một tuần trước
System.out.println("Hôm nay: " + today);
System.out.println("Ngày mai: " + tomorrow);
System.out.println("Sau một tháng: " + nextMonth);
System.out.println("Một tuần trước: " + lastWeek);
Tất cả các phương thức này trả về đối tượng mới, không thay đổi đối tượng ban đầu. Tính bất biến — là tất cả!
import java.time.LocalTime;
LocalTime now = LocalTime.now();
LocalTime inTwoHours = now.plusHours(2);
LocalTime tenMinutesAgo = now.minusMinutes(10);
System.out.println("Bây giờ: " + now);
System.out.println("Sau 2 giờ: " + inTwoHours);
System.out.println("10 phút trước: " + tenMinutesAgo);
Phương thức kết hợp
LocalDate vacation = today.plusMonths(2).plusDays(10);
System.out.println("Kỳ nghỉ: " + vacation);
2. Lấy chênh lệch giữa các ngày: Period và Duration
Khi cần biết đã bao nhiêu thời gian trôi qua giữa hai ngày hoặc hai thời điểm, có hai “nhân vật” xuất hiện — Period và Duration. Hãy làm quen với chúng kỹ hơn.
Khác nhau giữa Period và Duration là gì?
- Period — dành cho làm việc với ngày tháng (năm, tháng, ngày). Ví dụ: “3 năm 2 tháng 5 ngày”.
- Duration — dành cho làm việc với thời gian (giờ, phút, giây, nano giây). Ví dụ: “5 giờ 30 phút”.
| Lớp | Mục đích | Ví dụ sử dụng |
|---|---|---|
|
Chênh lệch giữa các ngày | Có bao nhiêu năm/tháng/ngày giữa hai ngày |
|
Chênh lệch giữa các thời điểm | Có bao nhiêu giờ/phút/giây giữa các sự kiện |
Ví dụ: tính tuổi người dùng
import java.time.LocalDate;
import java.time.Period;
LocalDate birthday = LocalDate.of(2000, 1, 15); // Ngày sinh
LocalDate today = LocalDate.now();
Period age = Period.between(birthday, today);
System.out.println("Tuổi: " + age.getYears() + " năm, " +
age.getMonths() + " tháng, " +
age.getDays() + " ngày");
Tương tự: Period — giống như chênh lệch “theo cách con người” giữa các ngày (“tôi 24 năm 5 tháng 12 ngày”).
Ví dụ: còn bao nhiêu thời gian đến hạn chót
import java.time.LocalDate;
LocalDate deadline = LocalDate.of(2025, 7, 1);
LocalDate today = LocalDate.now();
if (today.isBefore(deadline)) {
Period left = Period.between(today, deadline);
System.out.println("Còn lại đến hạn chót: " +
left.getMonths() + " tháng và " +
left.getDays() + " ngày");
} else {
System.out.println("Hạn chót đã qua!");
}
Duration: chênh lệch về thời gian
import java.time.LocalDateTime;
import java.time.Duration;
LocalDateTime start = LocalDateTime.of(2025, 6, 1, 10, 0, 0);
LocalDateTime end = LocalDateTime.of(2025, 6, 1, 15, 30, 0);
Duration duration = Duration.between(start, end);
System.out.println("Thời lượng: " + duration.toHours() + " giờ " +
(duration.toMinutes() % 60) + " phút");
Duration đo chênh lệch theo giây, phút, giờ, ngày (nhưng ngày — luôn là 24 giờ, không xét đến đặc thù lịch).
So sánh: khi nào dùng Period, khi nào dùng Duration?
- Nếu bạn chỉ có ngày (không có thời gian) — hãy dùng Period.
- Nếu có thời gian (ví dụ LocalDateTime, Instant) — hãy dùng Duration.
3. So sánh ngày giờ: isBefore, isAfter, equals
Đôi khi chỉ cần hiểu: ngày đã tới hay chưa? Sự kiện ở quá khứ hay tương lai? Với điều đó, tất cả các lớp ngày giờ đều có các phương thức:
- isBefore(otherDate)
- isAfter(otherDate)
- isEqual(otherDate) (hoặc đơn giản là equals)
import java.time.LocalDate;
LocalDate today = LocalDate.now();
LocalDate deadline = LocalDate.of(2025, 7, 1);
if (today.isBefore(deadline)) {
System.out.println("Vẫn còn thời gian!");
} else if (today.isEqual(deadline)) {
System.out.println("Hôm nay là hạn chót!");
} else {
System.out.println("Hạn chót đã qua :(");
}
import java.time.LocalTime;
LocalTime now = LocalTime.now();
LocalTime lunch = LocalTime.of(13, 0);
if (now.isAfter(lunch)) {
System.out.println("Đã ăn trưa rồi!");
} else {
System.out.println("Còn thời gian trước bữa trưa.");
}
Quan trọng: chỉ so sánh được các đối tượng cùng kiểu (LocalDate với LocalDate, LocalDateTime với LocalDateTime, v.v.).
4. Thực hành: bài toán về tính chênh lệch và kiểm tra hạn
Ví dụ: còn bao nhiêu ngày đến hạn chót của nhiệm vụ
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
LocalDate today = LocalDate.now();
LocalDate deadline = LocalDate.of(2025, 7, 1);
long daysLeft = ChronoUnit.DAYS.between(today, deadline);
if (daysLeft > 0) {
System.out.println("Còn " + daysLeft + " ngày đến hạn chót");
} else if (daysLeft == 0) {
System.out.println("Hôm nay là hạn chót!");
} else {
System.out.println("Hạn chót đã qua " + Math.abs(daysLeft) + " ngày trước");
}
ChronoUnit.DAYS.between() — cách tiện lợi để biết chính xác số ngày giữa hai ngày (không xét tháng và năm).
Ví dụ: có bao nhiêu giây giữa hai sự kiện
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
LocalDateTime start = LocalDateTime.of(2025, 6, 1, 10, 0, 0);
LocalDateTime end = LocalDateTime.of(2025, 6, 1, 15, 30, 0);
long seconds = ChronoUnit.SECONDS.between(start, end);
System.out.println("Giữa hai sự kiện đã trôi qua " + seconds + " giây");
Ví dụ: tính tuổi của nhiệm vụ
import java.time.LocalDate;
import java.time.Period;
LocalDate created = LocalDate.of(2025, 5, 20);
LocalDate today = LocalDate.now();
Period period = Period.between(created, today);
System.out.println("Nhiệm vụ được tạo " +
period.getDays() + " ngày, " +
period.getMonths() + " tháng và " +
period.getYears() + " năm trước.");
Ví dụ: thời lượng giữa hai Instant
import java.time.Instant;
import java.time.Duration;
Instant start = Instant.now();
// ... làm gì đó ...
Instant end = Instant.now();
Duration duration = Duration.between(start, end);
System.out.println("Thực thi mất " + duration.toMillis() + " mili giây");
5. Đặc thù khi sử dụng Period và Duration
Đặc điểm của Period
- Period tính chênh lệch theo lịch: giữa 28 tháng 2 và 1 tháng 3 — 1 ngày, ngay cả khi là khác tháng.
- Có thể lấy riêng lẻ số năm, tháng, ngày: getYears(), getMonths(), getDays().
- Nếu cần tổng số ngày giữa các ngày — hãy dùng ChronoUnit.DAYS.between().
Đặc điểm của Duration
- Duration làm việc với thời gian chính xác (giờ, phút, giây).
- Có thể làm việc với LocalTime, LocalDateTime, Instant.
- Không dùng cho ngày không có thời gian (LocalDate) — sẽ ném ngoại lệ.
Ví dụ: sự khác biệt giữa Period và Duration
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.Duration;
import java.time.Period;
LocalDate date1 = LocalDate.of(2025, 2, 28);
LocalDate date2 = LocalDate.of(2025, 3, 1);
Period period = Period.between(date1, date2); // 2 ngày (năm nhuận)
long days = java.time.temporal.ChronoUnit.DAYS.between(date1, date2); // 2 ngày
System.out.println("Period: " + period.getDays() + " ngày");
System.out.println("ChronoUnit: " + days + " ngày");
LocalDateTime dt1 = LocalDateTime.of(2025, 6, 1, 23, 0);
LocalDateTime dt2 = LocalDateTime.of(2025, 6, 2, 1, 0);
Duration duration = Duration.between(dt1, dt2); // 2 giờ
System.out.println("Duration: " + duration.toHours() + " giờ");
6. Sử dụng Duration và Period để hiển thị chênh lệch ở dạng thuận tiện
Khi bạn tính chênh lệch giữa ngày hoặc thời gian cho người dùng, không phải lúc nào hiển thị “123456 giây” cũng tiện. Thường ta muốn thứ gì đó như “2 ngày 3 giờ 15 phút”.
Ví dụ: hiển thị chênh lệch giữa ngày và thời gian
import java.time.LocalDateTime;
import java.time.Duration;
LocalDateTime start = LocalDateTime.of(2025, 6, 1, 14, 0);
LocalDateTime end = LocalDateTime.of(2025, 6, 3, 16, 30);
Duration duration = Duration.between(start, end);
long days = duration.toDays();
long hours = duration.minusDays(days).toHours();
long minutes = duration.minusDays(days).minusHours(hours).toMinutes();
System.out.println("Chênh lệch: " + days + " ngày " +
hours + " giờ " + minutes + " phút");
7. Đặc thù khi làm việc với ZonedDateTime và Instant
- Chênh lệch giữa hai ZonedDateTime ở các múi giờ khác nhau được tính theo thời gian tuyệt đối, không phải “thời gian mang tính con người” ở từng múi giờ.
- Chuyển đổi giờ mùa hè/mùa đông có thể ảnh hưởng đến chênh lệch giờ giữa các ngày.
Ví dụ: chênh lệch giữa các ZonedDateTime
import java.time.ZonedDateTime;
import java.time.ZoneId;
import java.time.Duration;
ZonedDateTime MinskTime = ZonedDateTime.of(2025, 6, 1, 12, 0, 0, 0, ZoneId.of("Europe/Minsk"));
ZonedDateTime nyTime = ZonedDateTime.of(2025, 6, 1, 5, 0, 0, 0, ZoneId.of("America/New_York"));
Duration diff = Duration.between(nyTime, MinskTime);
System.out.println("Chênh lệch: " + diff.toHours() + " giờ");
8. Các lỗi thường gặp khi tính toán với ngày giờ
Lỗi №1: Nhầm lẫn giữa Period và Duration.
Nếu cố dùng Duration cho LocalDate, bạn sẽ nhận ngoại lệ. Với ngày hãy dùng Period, với thời gian — Duration.
Lỗi №2: Không tính đến múi giờ.
Nếu so sánh ngày và giờ ở các múi giờ khác nhau, bạn có thể nhận kết quả bất ngờ. Tốt hơn nên quy về cùng một múi giờ hoặc dùng Instant.
Lỗi №3: Kỳ vọng các phương thức plus/minus thay đổi đối tượng.
Tất cả các đối tượng java.time đều bất biến! Đừng quên gán kết quả vào biến mới (ví dụ, today = today.plusDays(1)).
Lỗi №4: Dùng getDays() của Period và nghĩ đó là tổng số ngày.
Thực ra đó chỉ là số ngày còn lại sau khi trừ năm và tháng. Để lấy tổng số ngày, hãy dùng ChronoUnit.DAYS.between().
Lỗi №5: So sánh các đối tượng khác kiểu.
Các phương thức isBefore, isAfter, isEqual chỉ hoạt động cho các đối tượng cùng một kiểu (ví dụ, LocalDate với LocalDate).
GO TO FULL VERSION