如何在 Java 中計算“時間前”
一、概述
計算兩個時間點之間的相對時間和持續時間是軟件系統中的常見用例。例如,我們可能希望向用戶顯示自從在社交媒體平台上發布新圖片之類的事件以來已經過去了多少時間。此類“時間前”文本的示例是“5 分鐘前”、“1 年前”等。
雖然詞的語義和選擇完全依賴於上下文,但總體思路是相同的。
在本教程中,我們將探討幾種用 Java 計算時間前的解決方案。由於 Java 8 中引入了新的日期和時間 API,我們將分別討論版本 7 和版本 8 的解決方案。
2.Java 版本 7
Java 7 中有幾個與時間相關的類。而且,由於 Java 7 Date API 的不足,也提供了幾個第三方時間和日期庫。
首先,讓我們使用純 Java 7 來計算“時間前”。
2.1。純 Java 7
我們定義了一個enum
,它持有不同的時間粒度並將它們轉換為毫秒:
public enum TimeGranularity {
SECONDS {
public long toMillis() {
return TimeUnit.SECONDS.toMillis(1);
}
}, MINUTES {
public long toMillis() {
return TimeUnit.MINUTES.toMillis(1);
}
}, HOURS {
public long toMillis() {
return TimeUnit.HOURS.toMillis(1);
}
}, DAYS {
public long toMillis() {
return TimeUnit.DAYS.toMillis(1);
}
}, WEEKS {
public long toMillis() {
return TimeUnit.DAYS.toMillis(7);
}
}, MONTHS {
public long toMillis() {
return TimeUnit.DAYS.toMillis(30);
}
}, YEARS {
public long toMillis() {
return TimeUnit.DAYS.toMillis(365);
}
}, DECADES {
public long toMillis() {
return TimeUnit.DAYS.toMillis(365 * 10);
}
};
public abstract long toMillis();
}
我們使用了java.util.concurrent.TimeUnit
enum
,它是一個強大的時間轉換工具。使用TimeUnit
枚舉,我們為TimeGranularity
enum
的每個值覆蓋toMillis()
抽象方法,以便它返回與每個值等效的毫秒數。例如,對於“decade”,它返回 3650 天的毫秒數。
作為定義TimeGranularity
枚舉的結果,我們可以定義兩個方法。第一個接受一個java.util.Date
對象和一個TimeGranularity
實例,並返回一個“time ago”字符串:
static String calculateTimeAgoByTimeGranularity(Date pastTime, TimeGranularity granularity) {
Date currentTime = new Date();
long timeDifferenceInMillis = currentTime.getTime() - pastTime.getTime();
return timeDifferenceInMillis / granularity.toMillis() + " " +
granularity.name().toLowerCase() + " ago";
}
此方法將當前時間與給定時間的差TimeGranularity
值(以毫秒為單位)。因此,我們可以粗略地計算出從給定時間到指定時間粒度以來經過的時間量。
讓我們測試一下方法:
Assert.assertEquals("5 hours ago",
TimeAgoCalculator.calculateTimeAgoByTimeGranularity(
new Date(System.currentTimeMillis() - (5 * 60 * 60 * 1000)), TimeGranularity.HOURS));
此外,我們還可以編寫一個自動檢測最大合適時間粒度並返回更人性化輸出的方法:
static String calculateHumanFriendlyTimeAgo(Date pastTime) {
Date currentTime = new Date();
long timeDifferenceInMillis = currentTime.getTime() - pastTime.getTime();
if (timeDifferenceInMillis / TimeGranularity.DECADES.toMillis() > 0) {
return "several decades ago";
} else if (timeDifferenceInMillis / TimeGranularity.YEARS.toMillis() > 0) {
return "several years ago";
} else if (timeDifferenceInMillis / TimeGranularity.MONTHS.toMillis() > 0) {
return "several months ago";
} else if (timeDifferenceInMillis / TimeGranularity.WEEKS.toMillis() > 0) {
return "several weeks ago";
} else if (timeDifferenceInMillis / TimeGranularity.DAYS.toMillis() > 0) {
return "several days ago";
} else if (timeDifferenceInMillis / TimeGranularity.HOURS.toMillis() > 0) {
return "several hours ago";
} else if (timeDifferenceInMillis / TimeGranularity.MINUTES.toMillis() > 0) {
return "several minutes ago";
} else {
return "moments ago";
}
}
現在,讓我們看一個測試以查看示例用法:
Assert.assertEquals("several hours ago",
TimeAgoCalculator.calculateHumanFriendlyTimeAgo(new Date(System.currentTimeMillis() - (5 * 60 * 60 * 1000))));
根據上下文,我們可以使用不同的詞,例如“少數”、“幾個”、“許多”,甚至是確切的值。
2.2.喬達時代圖書館
在 Java 8 發布之前,Joda-Time 是 Java 中各種時間和日期相關操作的事實上的標準。我們可以使用 Joda-Time 庫的三個類來計算“時間前”:
-
org.joda.time.Period
接受org.joda.time.DateTime
的兩個對象併計算這兩個時間點之間的差異 -
org.joda.time.format.PeriodFormatter
,它定義了打印Period
對象的格式 -
org.joda.time.format.PeriodFormatuilder
這是一個構建器類,用於創建自定義PeriodFormatter
我們可以使用這三個類來輕鬆獲取現在和過去之間的確切時間:
static String calculateExactTimeAgoWithJodaTime(Date pastTime) {
Period period = new Period(new DateTime(pastTime.getTime()), new DateTime());
PeriodFormatter formatter = new PeriodFormatterBuilder().appendYears()
.appendSuffix(" year ", " years ")
.appendSeparator("and ")
.appendMonths()
.appendSuffix(" month ", " months ")
.appendSeparator("and ")
.appendWeeks()
.appendSuffix(" week ", " weeks ")
.appendSeparator("and ")
.appendDays()
.appendSuffix(" day ", " days ")
.appendSeparator("and ")
.appendHours()
.appendSuffix(" hour ", " hours ")
.appendSeparator("and ")
.appendMinutes()
.appendSuffix(" minute ", " minutes ")
.appendSeparator("and ")
.appendSeconds()
.appendSuffix(" second", " seconds")
.toFormatter();
return formatter.print(period);
}
讓我們看一個示例用法:
Assert.assertEquals("5 hours and 1 minute and 1 second", TimeAgoCalculator.calculateExactTimeAgoWithJodaTime(new Date(System.currentTimeMillis() - (5 * 60 * 60 * 1000 + 1 * 60 * 1000 + 1 * 1000))));
也可以生成更人性化的輸出:
static String calculateHumanFriendlyTimeAgoWithJodaTime(Date pastTime) {
Period period = new Period(new DateTime(pastTime.getTime()), new DateTime());
if (period.getYears() != 0) {
return "several years ago";
} else if (period.getMonths() != 0) {
return "several months ago";
} else if (period.getWeeks() != 0) {
return "several weeks ago";
} else if (period.getDays() != 0) {
return "several days ago";
} else if (period.getHours() != 0) {
return "several hours ago";
} else if (period.getMinutes() != 0) {
return "several minutes ago";
} else {
return "moments ago";
}
}
我們可以運行一個測試,看看這個方法返回了一個更人性化的“time ago”字符串:
Assert.assertEquals("several hours ago",
TimeAgoCalculator.calculateHumanFriendlyTimeAgoWithJodaTime(new Date(System.currentTimeMillis() - (5 * 60 * 60 * 1000))));
同樣,我們可以根據用例使用不同的術語,例如“一個”、“少數”、“幾個”等。
2.3. Joda-Time 時TimeZone
使用 Joda-Time 庫在“時間前”的計算中添加時區非常簡單:
String calculateZonedTimeAgoWithJodaTime(Date pastTime, TimeZone zone) {
DateTimeZone dateTimeZone = DateTimeZone.forID(zone.getID());
Period period = new Period(new DateTime(pastTime.getTime(), dateTimeZone), new DateTime(dateTimeZone));
return PeriodFormat.getDefault().print(period);
}
3.Java 8
Java 8 引入了一個新的改進的日期和時間 API,它採用了 Joda-Time 庫的許多想法。我們可以使用原生java.time.Duration
和java.time.Period
類來計算“時間前”:
static String calculateTimeAgoWithPeriodAndDuration(LocalDateTime pastTime, ZoneId zone) {
Period period = Period.between(pastTime.toLocalDate(), LocalDate.now(zone));
Duration duration = Duration.between(pastTime, LocalDateTime.now(zone));
if (period.getYears() != 0) {
return "several years ago";
} else if (period.getMonths() != 0) {
return "several months ago";
} else if (period.getDays() != 0) {
return "several days ago";
} else if (duration.toHours() != 0) {
return "several hours ago";
} else if (duration.toMinutes() != 0) {
return "several minutes ago";
} else if (duration.getSeconds() != 0) {
return "several seconds ago";
} else {
return "moments ago";
}
}
上面的代碼片段支持時區並且僅使用原生 Java 8 API。
4. PrettyTime 圖書館
PrettyTime 是一個強大的庫,專門提供支持 i18n 的“time ago”功能。此外,它是高度可定制的、易於使用的,並且可以與 Java 版本 7 和 8 一起使用。
首先,讓我們將它的依賴添加到我們的pom.xml
中:
<dependency>
<groupId>org.ocpsoft.prettytime</groupId>
<artifactId>prettytime</artifactId>
<version>3.2.7.Final</version>
</dependency>
現在以人類友好的格式獲取“時間前”非常容易:
String calculateTimeAgoWithPrettyTime(Date pastTime) {
PrettyTime prettyTime = new PrettyTime();
return prettyTime.format(pastTime);
}
5. Time4J 庫
最後,Time4J 是另一個用於在 Java 中處理時間和日期數據的優秀庫。它有一個PrettyTime
類,可以用來計算時間。
讓我們添加它的依賴項:
<dependency>
<groupId>net.time4j</groupId>
<artifactId>time4j-base</artifactId>
<version>5.9</version>
</dependency>
<dependency>
<groupId>net.time4j</groupId>
<artifactId>time4j-sqlxml</artifactId>
<version>5.8</version>
</dependency>
添加此依賴項後,計算時間之前非常簡單:
String calculateTimeAgoWithTime4J(Date pastTime, ZoneId zone, Locale locale) {
return PrettyTime.of(locale).printRelative(pastTime.toInstant(), zone);
}
與 PrettyTime 庫一樣,Time4J 也支持 i18n 開箱即用。
六,結論
在本文中,我們討論了在 Java 中計算前時間的不同方法。
純 Java 和第三方庫都有解決方案。由於 Java 8 中引入了新的 Date and Time API,所以純 Java 解決方案對於 8 之前和之後的版本是不同的。
與往常一樣,示例的源代碼可在 GitHub 上獲得。