使用Java構(gòu)建一個簡潔清晰的日期API
說起日期類型,相對其他原始數(shù)據(jù)類型(int/long/boolean/string)處理起來是比較棘手的,原因也是多方面的,首先日期是個籠統(tǒng)的概念,年月日可以說成是日期,年月日時分秒也可以說成日期,或者說單獨的時分秒也是日期,——那你到底說著哪個日期呢,是不是?Java 傳統(tǒng)日期類型java.util.Date就是這么干的,一個類型囊括上述所有的日期概念,也是造成日期 API 混亂的根源;其次日期的表達(dá)方式,也稱之為“格式 Format”,可以2025-12-22 11:05,也可以2025-12-22 11-05甚至3 Jun 2025 11:05,種類繁多;最后日期還有時區(qū)的概念,比如2024-12-03T10:15:30+01:00[Europe/Paris],Tue, 3 Jun 2025 11:05:30 GMT等等。
關(guān)于Date類型
早期 Java 版本中就是基于java.util.Date類型一統(tǒng)天下,很多日期處理方法都是圍繞它進(jìn)行的(Java 1.1 之前)。后來版本重構(gòu)中廢棄了很多的這些方法,改由java.util.Calendar與java.text.DateFormat完成。Calendar 解決了日期的棘手問題了嗎?并沒有,雖然 Calendar 處理日期起來更靈活但是使用仍然過于復(fù)雜,設(shè)置、比較、格式化仍舊比較麻煩、冗長,所以社區(qū)又有了代替品 Joda-Time,被許多項目所采用。于是這個優(yōu)秀的開源項目漸漸地成為 Java 標(biāo)準(zhǔn)的一部分,便是 JSR310,最后集成到 Java 8 中變成java.time!
總之,我們主張使用的日期類型就是java.time里面的 Instant 及 LocalDateTime,其中 Instant 表示某一時刻的時間,大致對應(yīng)時間戳,可支持納秒的級別。LocalDateTime 可能更我們更常用一些,以及它衍生的其他精確類型。
新 Java 常用日期時間類型
| 類型 | 所在包 | Java 版本 | 說明 | 是否推薦 |
|---|---|---|---|---|
| Instant | java.time.Instant | 8+ | 表示時間線上的一個瞬時點(UTC),精確到納秒 | 推薦用于時間戳 |
| LocalDate | java.time.LocalDate | 8+ | 只包含日期(年-月-日),如 2025-03-08 | 強(qiáng)烈推薦用于“純?nèi)掌?rdquo;場景(如列車出發(fā)日) |
| LocalTime | java.time.LocalTime | 8+ | 只包含時間(時:分:秒.納秒),如 14:30:00 | 推薦用于“純時間”場景(如發(fā)車時間) |
| LocalDateTime | java.time.LocalDateTime | 8+ | 日期 + 時間(無時區(qū)),如 2025-03-08T14:30:00 | 推薦用于本地時間(如列車時刻表) |
| ZonedDateTime | java.time.ZonedDateTime | 8+ | 帶時區(qū)的日期時間,支持夏令時等 | 推薦用于跨時區(qū)系統(tǒng) |
| OffsetDateTime | java.time.OffsetDateTime | 8+ | 帶偏移量的時間(如 +08:00) | 適合存儲數(shù)據(jù)庫時間 |
| Duration | java.time.Duration | 8+ | 表示時間段(秒、納秒),如 2 小時 | 推薦用于計算時間差 |
| Period | java.time.Period | 8+ | 表示時間段(年、月、日),如 1個月后 | 推薦用于日歷周期計算 |
| DateTimeFormatter | java.time.format.DateTimeFormatter | 8+ | 線程安全的格式化/解析工具 | 替代 SimpleDateFormat |
那么 Date 類型就不用,完全廢棄了嗎?——并不是,雖然圍繞 Date 的相關(guān) API 早在 Java 1.1 之后就被廢棄,但是不代表 Date 本身被廢棄。如果廢棄了,為什么你在新版 JDK17 上找不到@Deprecated注解?說明該類型還是保留著的。作為值對象(Value Object)使用 Date 本身沒什么問題,例如兩種構(gòu)造器的用法仍保留:new Date()以及new Date(long timestamp)。
API 簡介
這套筆者封裝的 日期 API,主要功能有以下三點:
- 日期類型的轉(zhuǎn)換。面對如此繁多的日期類型:Date、Instant、Int/Long/String、LocalDate/LocalDateTime/LocalTime、ZonedDateTime 等等,給出一個相互之間都能夠轉(zhuǎn)換的方法,即輸入
T 返回的日期 = input(Object anyType).to(Class<T> 期望類型)。 - 通用的日期格式化。
- 返回當(dāng)前日期的
now()函數(shù)及其他工具函數(shù)。
源碼在:gitee.com/lightweight-components/aj-util/tree/main/aj-util/src/main/java/com/ajaxjs/util/date。
此 API屬于 aj-util 庫的一部分,而 aj-util 庫又是 aj-framework 框架的一部分,——敬請參閱了解。
萬能日期類型轉(zhuǎn)換
起初想到的轉(zhuǎn)換方式是這樣的,一個類型對應(yīng)著一個類型逐個轉(zhuǎn)換。
@SuppressWarnings("unchecked")
public <T> T to(Class<T> clz, ZoneId zoneId) {
if (input != null) {
if (clz == LocalDate.class) {
LocalDate localDate = input.toInstant()
.atZone(zoneId == null ? ZoneId.systemDefault() : zoneId)
.toLocalDate();
return (T) localDate;
}
}
if (localDate != null) {
if (clz == Date.class) {
Date date = Date.from(localDate.atStartOfDay(zoneId == null ? ZoneId.systemDefault() : zoneId).toInstant());
return (T) date;
}
}
throw new UnsupportedOperationException("Can not transform this date type to another date type");
}
可是這樣的方式太累了,代碼也啰嗦。于是咨詢了一下 AI 的意見,改為下面清爽的代碼。
/**
* Convert the input to the specified date type
*
* @param clz The target date type
* @param zoneId The time zone. Optional, defaults to system default if passed null
* @param <T> The target date type
* @return The converted date
*/
@SuppressWarnings("unchecked")
public <T> T to(Class<T> clz, ZoneId zoneId) {
ZoneId zone = zoneId != null ? zoneId : ZoneId.systemDefault();
Instant baseInstant;
if (input != null) {/* SB way, actually u can set any values to Instant by constructor or setter method */
baseInstant = input.toInstant();
} else if (sqlDate != null) {
baseInstant = sqlDate.toLocalDate().atStartOfDay(zone).toInstant();
} else if (sqlTimestamp != null) {
baseInstant = sqlTimestamp.toInstant();
} else if (localDate != null) {
baseInstant = localDate.atStartOfDay(zone).toInstant();
} else if (localDateTime != null) {
baseInstant = localDateTime.atZone(zone).toInstant();
} else if (zonedDateTime != null) {
baseInstant = zonedDateTime.toInstant();
} else if (offsetDateTime != null) {
baseInstant = offsetDateTime.toInstant();
} else if (offsetTime != null) {
baseInstant = offsetTime.atDate(LocalDate.now()).toInstant();
} else if (instant != null) {
baseInstant = instant;
} else if (timestamp != 0L) {
baseInstant = Instant.ofEpochMilli(timestamp);
} else
throw new UnsupportedOperationException("No input date/time set.");
// Convert baseInstant to target
if (clz == Instant.class) {
return (T) baseInstant;
} else if (clz == Date.class) {
return (T) Date.from(baseInstant);
} else if (clz == java.sql.Date.class) {
return (T) java.sql.Date.valueOf(baseInstant.atZone(zone).toLocalDate());
} else if (clz == Timestamp.class) {
return (T) Timestamp.from(baseInstant);
} else if (clz == LocalDate.class) {
return (T) baseInstant.atZone(zone).toLocalDate();
} else if (clz == LocalTime.class) {
return (T) baseInstant.atZone(zone).toLocalTime();
} else if (clz == LocalDateTime.class) {
return (T) baseInstant.atZone(zone).toLocalDateTime();
} else if (clz == ZonedDateTime.class) {
return (T) baseInstant.atZone(zone);
} else if (clz == OffsetDateTime.class) {
return (T) baseInstant.atOffset(zone.getRules().getOffset(baseInstant));
} else if (clz == OffsetTime.class) {
return (T) baseInstant.atZone(zone).toOffsetDateTime().toOffsetTime();
} else if (clz == Calendar.class) {
Calendar calendar = Calendar.getInstance();
baseInstant.atZone(zone);
calendar.setTimeInMillis(baseInstant.toEpochMilli());
return (T) calendar;
}
throw new UnsupportedOperationException("Unsupported target type: " + clz.getName());
}
主要就是不管什么輸入類型,先統(tǒng)一轉(zhuǎn)換到Instant類再轉(zhuǎn)為目標(biāo)類型。
下面是一些用法例子:
long timestamp = System.currentTimeMillis(); Instant expected = Instant.ofEpochMilli(timestamp); Instant result = new DateTypeConvert(timestamp).to(Instant.class, null); assertEquals(expected, result); Instant instant = Instant.now(); LocalDateTime expected = instant.atZone(zone).toLocalDateTime(); LocalDateTime result = new DateTypeConvert(instant).to(LocalDateTime.class, null); assertEquals(expected, result); LocalDate localDate = LocalDate.of(2025, 10, 23); Date expected = Date.from(localDate.atStartOfDay(zone).toInstant()); Date result = new DateTypeConvert(localDate).to(Date.class, null); assertEquals(expected, result);
日期格式化
新版推薦使用DateTimeFormatter替代SimpleDateFormat,使之線程安全與 API 清晰。同時DateTimeFormatter.ofPattern()實例有一定開銷,尤其是自定義模式(如 "yyyy-MM-dd HH:mm:ss"),于是我們可以緩存 DateTimeFormatter 實例的方式來優(yōu)化。
Formatter 用法如下:
new Formatter(TemporalAccessor temporal).format(); new Formatter(TemporalAccessor temporal).format(String format)
接口TemporalAccessor實現(xiàn)都是常見的那些日期類型,于是我們在構(gòu)造器傳入即可,然后指定日期格式的字符串。
工具函數(shù)
主要是now()返回當(dāng)前時間字符串的工具函數(shù)。
/**
* Obtain current time by specified format.
*
* @param formatter The formatter object
* @return The current time
*/
public static String now(DateTimeFormatter formatter) {
return LocalDateTime.now().format(formatter);
}
/**
* Obtain current time by specified format.
*
* @param format The format string
* @return The current time
*/
public static String now(String format) {
return now(Formatter.getDateFormatter(format));
}
/**
* Obtain current time, which is formatted by default like "yyyy-MM-dd HH:mm:ss".
*
* @return The current time
*/
public static String now() {
return now(Formatter.getDateTimeFormatter());
}
/**
* Obtain current time, which is formatted like "yyyy-MM-dd HH:mm".
*
* @return The current time
*/
public static String nowShort() {
return now(Formatter.getDateTimeShortFormatter());
}
/**
* 請求的時間戳,格式必須符合 RFC1123 的日期格式
*
* @return The current time
*/
public static String nowGMTDate() {
return Formatter.GMT_FORMATTER.format(Instant.now());
}
/**
* 請求的時間戳。按照 ISO8601 標(biāo)準(zhǔn)表示,并需要使用 UTC 時間,格式為 yyyy-MM-ddTHH:mm:ssZ
*
* @return The current time
*/
public static String newISO8601Date() {
return Formatter.ISO8601_FORMATTER.format(Instant.now());
}
另外還包括一個字符串轉(zhuǎn)日期的函數(shù),其主要使用正則來匹配是否日期字符串然后進(jìn)行轉(zhuǎn)換。
小結(jié)
這套 API 原理并不是太復(fù)雜,主要是通過封裝來厘清 Java 日期 API 的使用,務(wù)求更簡單、清晰。
到此這篇關(guān)于使用Java構(gòu)建一個簡潔清晰的日期API的文章就介紹到這了,更多相關(guān)Java構(gòu)建日期API內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java web將數(shù)據(jù)導(dǎo)出為pdf格式文件代碼片段
這篇文章主要為大家詳細(xì)介紹了java web將數(shù)據(jù)導(dǎo)出為pdf格式文件代碼片段,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-01-01
Spring中@Autowired和@Resource注解的使用區(qū)別詳解
這篇文章主要介紹了Spring中@Autowired和@Resource注解的使用區(qū)別詳解,@Autowired默認(rèn)根據(jù)type進(jìn)行注入,找到與指定類型兼容的?Bean?并進(jìn)行注入,如果無法通過type匹配到對應(yīng)的?Bean?的話,會根據(jù)name進(jìn)行匹配,如果都匹配不到則拋出異常,需要的朋友可以參考下2023-11-11
intellij IDEA配置springboot的圖文教程
Spring Boot是由Pivotal團(tuán)隊提供的全新框架,其設(shè)計目的是用來簡化新Spring應(yīng)用的初始搭建以及開發(fā)過程。接下來通過本文給大家介紹intellij IDEA配置springboot的圖文教程,感興趣的朋友一起看看吧2018-03-03
MybatisPlus自帶的queryWrapper實現(xiàn)時間倒序方式
這篇文章主要介紹了MybatisPlus自帶的queryWrapper實現(xiàn)時間倒序方式,具有很好的參考價值,希望對的有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-01-01
SpringCloud之Zuul網(wǎng)關(guān)原理及其配置講解
這篇文章主要介紹了SpringCloud之Zuul網(wǎng)關(guān)原理及其配置講解,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03

