Java Period
Period 是 Java 8 引入的類別,用於表示兩個日期之間的間隔,以年、月、日為單位。適合處理基於日期的間隔。
引入套件
import java.time.Period;
建立 Period
使用 of 方法
// 指定年數
Period years = Period.ofYears(2); // P2Y
// 指定月數
Period months = Period.ofMonths(6); // P6M
// 指定天數
Period days = Period.ofDays(15); // P15D
// 指定週數(轉換為天數)
Period weeks = Period.ofWeeks(2); // P14D
// 指定年、月、日
Period period = Period.of(1, 6, 15); // P1Y6M15D
從字串解析
// ISO-8601 格式:P[n]Y[n]M[n]D
Period p1 = Period.parse("P1Y"); // 1年
Period p2 = Period.parse("P6M"); // 6個月
Period p3 = Period.parse("P1Y6M15D"); // 1年6個月15天
Period p4 = Period.parse("P2W"); // 2週(14天)
計算兩個日期的間隔
LocalDate start = LocalDate.of(2024, 1, 1);
LocalDate end = LocalDate.of(2024, 12, 31);
Period period = Period.between(start, end);
System.out.println(period); // P11M30D
// 注意:between 計算的是完整的年月日差
LocalDate birth = LocalDate.of(1990, 5, 15);
LocalDate today = LocalDate.of(2024, 12, 10);
Period age = Period.between(birth, today);
System.out.println(age); // P34Y6M25D
取得 Period 資訊
Period period = Period.of(2, 6, 15);
// 取得各部分的值
int years = period.getYears(); // 2
int months = period.getMonths(); // 6
int days = period.getDays(); // 15
// 取得總月數
long totalMonths = period.toTotalMonths(); // 30
// 檢查是否為零
boolean isZero = period.isZero(); // false
// 檢查是否為負數
boolean isNegative = period.isNegative(); // false
// 取得所有單位
List<TemporalUnit> units = period.getUnits();
// [Years, Months, Days]
Period 運算
加減運算
Period p1 = Period.of(1, 6, 0);
Period p2 = Period.of(0, 3, 15);
// 加法
Period sum = p1.plus(p2); // P1Y9M15D
Period plusYears = p1.plusYears(1); // P2Y6M
Period plusMonths = p1.plusMonths(2); // P1Y8M
Period plusDays = p1.plusDays(10); // P1Y6M10D
// 減法
Period diff = p1.minus(p2); // P1Y2M-15D
Period minusYears = p1.minusYears(1); // P6M
Period minusMonths = p1.minusMonths(3); // P1Y3M
乘法和取反
Period p = Period.of(1, 2, 3);
// 乘法
Period doubled = p.multipliedBy(2); // P2Y4M6D
// 取反
Period negated = p.negated(); // P-1Y-2M-3D
正規化
// 將月份超過12的部分轉換為年
Period p = Period.of(1, 15, 10);
Period normalized = p.normalized();
System.out.println(normalized); // P2Y3M10D
// 注意:天數不會被正規化
Period p2 = Period.of(0, 0, 45);
Period normalized2 = p2.normalized();
System.out.println(normalized2); // P45D(天數不變)
應用於日期運算
LocalDate date = LocalDate.of(2024, 1, 15);
Period period = Period.of(1, 2, 10);
// 加上 Period
LocalDate later = date.plus(period); // 2025-03-25
// 減去 Period
LocalDate earlier = date.minus(period); // 2022-11-05
// 分別加減
LocalDate plusYears = date.plusYears(1); // 2025-01-15
LocalDate plusMonths = date.plusMonths(2); // 2024-03-15
LocalDate plusDays = date.plusDays(10); // 2024-01-25
實用範例
計算年齡
public static String calculateAge(LocalDate birthDate) {
LocalDate today = LocalDate.now();
Period age = Period.between(birthDate, today);
return String.format("%d 歲 %d 個月 %d 天",
age.getYears(),
age.getMonths(),
age.getDays()
);
}
LocalDate birthday = LocalDate.of(1990, 5, 15);
System.out.println("年齡: " + calculateAge(birthday));
// 年齡: 34 歲 6 個月 25 天
計算到期日
public static LocalDate calculateExpiryDate(LocalDate issueDate, Period validity) {
return issueDate.plus(validity);
}
LocalDate issued = LocalDate.of(2024, 1, 1);
Period validity = Period.of(1, 0, 0); // 1年有效期
LocalDate expiry = calculateExpiryDate(issued, validity);
System.out.println("到期日: " + expiry); // 2025-01-01
訂閱週期計算
public class Subscription {
private LocalDate startDate;
private Period billingPeriod;
public Subscription(LocalDate startDate, Period billingPeriod) {
this.startDate = startDate;
this.billingPeriod = billingPeriod;
}
public LocalDate getNextBillingDate() {
LocalDate today = LocalDate.now();
LocalDate nextBilling = startDate;
while (!nextBilling.isAfter(today)) {
nextBilling = nextBilling.plus(billingPeriod);
}
return nextBilling;
}
public int getBillingCycleNumber() {
LocalDate today = LocalDate.now();
int cycle = 0;
LocalDate date = startDate;
while (!date.isAfter(today)) {
date = date.plus(billingPeriod);
cycle++;
}
return cycle;
}
}
// 月訂閱
Subscription monthly = new Subscription(
LocalDate.of(2024, 1, 1),
Period.ofMonths(1)
);
System.out.println("下次扣款日: " + monthly.getNextBillingDate());
System.out.println("目前週期: " + monthly.getBillingCycleNumber());
格式化顯示
public static String formatPeriod(Period period) {
StringBuilder sb = new StringBuilder();
if (period.getYears() != 0) {
sb.append(Math.abs(period.getYears())).append(" 年 ");
}
if (period.getMonths() != 0) {
sb.append(Math.abs(period.getMonths())).append(" 個月 ");
}
if (period.getDays() != 0) {
sb.append(Math.abs(period.getDays())).append(" 天");
}
if (sb.length() == 0) {
return "0 天";
}
return sb.toString().trim();
}
Period p = Period.of(2, 3, 15);
System.out.println(formatPeriod(p)); // 2 年 3 個月 15 天
比較兩個日期差距
public static String compareDates(LocalDate date1, LocalDate date2) {
Period period = Period.between(date1, date2);
long totalMonths = period.toTotalMonths();
if (period.isZero()) {
return "相同日期";
} else if (period.isNegative()) {
Period abs = period.negated();
return date1 + " 比 " + date2 + " 晚 " + formatPeriod(abs);
} else {
return date1 + " 比 " + date2 + " 早 " + formatPeriod(period);
}
}
LocalDate d1 = LocalDate.of(2024, 1, 1);
LocalDate d2 = LocalDate.of(2024, 6, 15);
System.out.println(compareDates(d1, d2));
// 2024-01-01 比 2024-06-15 早 5 個月 14 天
專案時程計算
public class Project {
private String name;
private LocalDate startDate;
private Period duration;
public Project(String name, LocalDate startDate, Period duration) {
this.name = name;
this.startDate = startDate;
this.duration = duration;
}
public LocalDate getEndDate() {
return startDate.plus(duration);
}
public Period getRemainingTime() {
LocalDate today = LocalDate.now();
LocalDate endDate = getEndDate();
if (today.isAfter(endDate)) {
return Period.ZERO;
}
return Period.between(today, endDate);
}
public double getProgressPercentage() {
LocalDate today = LocalDate.now();
long totalDays = ChronoUnit.DAYS.between(startDate, getEndDate());
long elapsedDays = ChronoUnit.DAYS.between(startDate, today);
if (elapsedDays >= totalDays) return 100.0;
if (elapsedDays <= 0) return 0.0;
return (elapsedDays * 100.0) / totalDays;
}
}
Project project = new Project(
"系統開發",
LocalDate.of(2024, 1, 1),
Period.of(0, 6, 0) // 6個月
);
System.out.println("專案: " + project.name);
System.out.println("結束日期: " + project.getEndDate());
System.out.println("剩餘時間: " + formatPeriod(project.getRemainingTime()));
System.out.printf("進度: %.1f%%%n", project.getProgressPercentage());
Period vs Duration
| 特性 | Period | Duration |
|---|---|---|
| 單位 | 年、月、日 | 秒、奈秒 |
| 精度 | 天級 | 奈秒級 |
| 用途 | 日期間隔 | 時間間隔 |
| 適用類型 | LocalDate | LocalTime, Instant |
| 月份處理 | 考慮月份天數差異 | 固定秒數 |
重點整理
Period表示基於日期的間隔(年、月、日)- 使用
ofXxx()方法建立各種日期長度 - 使用
between()計算兩個日期的間隔 normalized()可將月份超過 12 的部分轉為年- 是不可變(immutable)類別
- 適合用於
LocalDate、LocalDateTime - 時間間隔(時分秒)應使用
Duration