基于 Java 的工作日與節(jié)假日智能識(shí)別
作者:一安
在企業(yè)級(jí)應(yīng)用開(kāi)發(fā)中,經(jīng)常需要判斷某個(gè)日期是否為工作日或節(jié)假日,例如考勤系統(tǒng)、任務(wù)調(diào)度系統(tǒng)、銀行交易系統(tǒng)等,來(lái)看看以下方案。
前言
在企業(yè)級(jí)應(yīng)用開(kāi)發(fā)中,經(jīng)常需要判斷某個(gè)日期是否為工作日或節(jié)假日,例如考勤系統(tǒng)、任務(wù)調(diào)度系統(tǒng)、銀行交易系統(tǒng)等。
效果圖
基礎(chǔ)實(shí)現(xiàn)方案
最基礎(chǔ)的方案是使用Java
內(nèi)置的日期時(shí)間API
,結(jié)合簡(jiǎn)單的周末判斷邏輯:
public class HolidayUtil {
// 法定節(jié)假日集合
private static final Set<LocalDate> HOLIDAYS = new HashSet<>();
// 調(diào)休工作日集合(周末調(diào)休為工作日)
private static final Set<LocalDate> ADJUSTED_WORKDAYS = new HashSet<>();
static {
// 初始化法定節(jié)假日
HOLIDAYS.add(LocalDate.of(2025, 1, 1)); // 元旦
HOLIDAYS.add(LocalDate.of(2025, 1, 28)); // 除夕
// 初始化調(diào)休工作日
HOLIDAYS.add(LocalDate.of(2025, 1, 26)); // 春節(jié)前補(bǔ)班
HOLIDAYS.add(LocalDate.of(2025, 2, 8)); // 春節(jié)后補(bǔ)班
}
/**
* 判斷日期是否為工作日
*/
public static boolean isWeekday(LocalDate date) {
DayOfWeek dayOfWeek = date.getDayOfWeek();
return dayOfWeek != DayOfWeek.SATURDAY && dayOfWeek != DayOfWeek.SUNDAY;
}
/**
* 判斷日期是否為法定節(jié)假日
*/
public static boolean isHoliday(LocalDate date) {
return HOLIDAYS.contains(date);
}
/**
* 判斷日期是否為調(diào)休工作日
*/
public static boolean isAdjustedWorkday(LocalDate date) {
return ADJUSTED_WORKDAYS.contains(date);
}
/**
* 判斷日期是否為需要上班的日子(工作日或調(diào)休工作日)
*/
public static boolean isWorkingDay(LocalDate date) {
// 如果是法定節(jié)假日,不是工作日
if (isHoliday(date)) {
returnfalse;
}
// 如果是調(diào)休工作日,是工作日
if (isAdjustedWorkday(date)) {
returntrue;
}
// 否則根據(jù)周幾判斷
return isWeekday(date);
}
public static void main(String[] args) {
LocalDate date = LocalDate.of(2025, 2, 5); // 春節(jié)
System.out.println(date + " 是否為工作日: " + isWorkingDay(date));
date = LocalDate.of(2025, 1, 26); // 春節(jié)調(diào)休
System.out.println(date + " 是否為工作日: " + isWorkingDay(date));
date = LocalDate.of(2025, 7, 5); // 周日
System.out.println(date + " 是否為工作日: " + isWorkingDay(date));
}
}
基于外部數(shù)據(jù)源的方案
基礎(chǔ)方案的局限性在于節(jié)假日數(shù)據(jù)需要硬編碼在代碼中,不利于維護(hù)和更新。更靈活的方案是將節(jié)假日數(shù)據(jù)存儲(chǔ)在外部文件或數(shù)據(jù)庫(kù)中:
public class HolidayUtil {
private static final String HOLIDAY_FILE = "holidays.txt";
private static final String ADJUSTED_WORKDAY_FILE = "adjusted_workdays.txt";
private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
private final Set<LocalDate> holidays = new HashSet<>();
private final Set<LocalDate> adjustedWorkdays = new HashSet<>();
public HolidayUtil() {
loadHolidays();
loadAdjustedWorkdays();
}
private void loadHolidays() {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(Objects.requireNonNull(
getClass().getClassLoader().getResourceAsStream(HOLIDAY_FILE))))) {
reader.lines()
.map(line -> line.trim())
.filter(line -> !line.isEmpty() && !line.startsWith("#"))
.map(dateStr -> LocalDate.parse(dateStr, DATE_FORMAT))
.forEach(holidays::add);
} catch (IOException e) {
System.err.println("加載節(jié)假日數(shù)據(jù)失敗: " + e.getMessage());
}
}
private void loadAdjustedWorkdays() {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(Objects.requireNonNull(
getClass().getClassLoader().getResourceAsStream(ADJUSTED_WORKDAY_FILE))))) {
reader.lines()
.map(line -> line.trim())
.filter(line -> !line.isEmpty() && !line.startsWith("#"))
.map(dateStr -> LocalDate.parse(dateStr, DATE_FORMAT))
.forEach(adjustedWorkdays::add);
} catch (IOException e) {
System.err.println("加載調(diào)休工作日數(shù)據(jù)失敗: " + e.getMessage());
}
}
/**
* 判斷日期是否為工作日
*/
public boolean isWeekday(LocalDate date) {
DayOfWeek dayOfWeek = date.getDayOfWeek();
return dayOfWeek != DayOfWeek.SATURDAY && dayOfWeek != DayOfWeek.SUNDAY;
}
/**
* 判斷日期是否為法定節(jié)假日
*/
public boolean isHoliday(LocalDate date) {
return holidays.contains(date);
}
/**
* 判斷日期是否為調(diào)休工作日
*/
public boolean isAdjustedWorkday(LocalDate date) {
return adjustedWorkdays.contains(date);
}
/**
* 判斷日期是否為需要上班的日子(工作日或調(diào)休工作日)
*/
public boolean isWorkingDay(LocalDate date) {
// 如果是法定節(jié)假日,不是工作日
if (isHoliday(date)) {
returnfalse;
}
// 如果是調(diào)休工作日,是工作日
if (isAdjustedWorkday(date)) {
returntrue;
}
// 否則根據(jù)周幾判斷
return isWeekday(date);
}
public static void main(String[] args) {
HolidayUtil util = new HolidayUtil();
LocalDate date = LocalDate.now();
System.out.println(date + " 是否為工作日: " + util.isWorkingDay(date));
}
}
使用第三方 API 的方案
/**
* 基于timor.tech節(jié)假日API的工作日判斷工具
*/
public class TimorHolidayApiClient {
private static final String API_BASE_URL = "https://timor.tech/api/holiday/year/";
private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
private static final DateTimeFormatter MONTH_DAY_FORMAT = DateTimeFormatter.ofPattern("MM-dd");
private final HttpClient httpClient = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
.followRedirects(HttpClient.Redirect.NORMAL)
.build();
private final ObjectMapper objectMapper = new ObjectMapper();
// 緩存每年的節(jié)假日數(shù)據(jù),避免頻繁調(diào)用API
private final Map<Integer, HolidayResponse> holidayCache = new ConcurrentHashMap<>();
// 緩存日期到是否為工作日的映射,提升重復(fù)查詢(xún)性能
private final Map<LocalDate, Boolean> workdayCache = new ConcurrentHashMap<>();
/**
* 判斷指定日期是否為工作日
* @param date 要判斷的日期
* @returntrue表示工作日,false表示非工作日
*/
public boolean isWorkingDay(LocalDate date) {
// 先檢查緩存
if (workdayCache.containsKey(date)) {
return workdayCache.get(date);
}
try {
// 獲取該年份的節(jié)假日數(shù)據(jù)
HolidayResponse response = getHolidayData(date.getYear());
if (response == null || response.getHoliday().isEmpty()) {
// 沒(méi)有節(jié)假日數(shù)據(jù)時(shí),按常規(guī)周末判斷
return isRegularWeekday(date);
}
// 格式化日期為"MM-dd"格式,用于API結(jié)果匹配
String monthDay = date.format(MONTH_DAY_FORMAT);
HolidayInfo holidayInfo = response.getHoliday().get(monthDay);
// 處理節(jié)假日情況
if (holidayInfo != null) {
// 如果是節(jié)假日,不是工作日
if (holidayInfo.isHoliday()) {
workdayCache.put(date, false);
returnfalse;
}
// 如果是補(bǔ)班日,是工作日
else {
workdayCache.put(date, true);
returntrue;
}
}
// 非節(jié)假日也非補(bǔ)班日,按常規(guī)周末判斷
else {
boolean isWeekday = isRegularWeekday(date);
workdayCache.put(date, isWeekday);
return isWeekday;
}
} catch (Exception e) {
System.err.println("判斷工作日失敗,日期: " + date + ", 錯(cuò)誤: " + e.getMessage());
// 出錯(cuò)時(shí)按常規(guī)工作日處理
boolean isWeekday = isRegularWeekday(date);
workdayCache.put(date, isWeekday);
return isWeekday;
}
}
/**
* 獲取指定年份的節(jié)假日數(shù)據(jù),帶有緩存機(jī)制
*/
private HolidayResponse getHolidayData(int year) throws IOException, InterruptedException {
if (holidayCache.containsKey(year)) {
return holidayCache.get(year);
}
String url = API_BASE_URL + year;
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Accept", "application/json")
.timeout(java.time.Duration.ofSeconds(10))
.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200) {
HolidayResponse holidayResponse = objectMapper.readValue(response.body(), HolidayResponse.class);
if (holidayResponse.getCode() == 0) { // API返回成功
holidayCache.put(year, holidayResponse);
return holidayResponse;
}
}
System.err.println("獲取節(jié)假日數(shù)據(jù)失敗,年份: " + year + ", 狀態(tài)碼: " + response.statusCode());
return new HolidayResponse(); // 返回空數(shù)據(jù)
}
/**
* 常規(guī)周末判斷(不考慮節(jié)假日和補(bǔ)班)
*/
private boolean isRegularWeekday(LocalDate date) {
DayOfWeek dayOfWeek = date.getDayOfWeek();
return dayOfWeek != DayOfWeek.SATURDAY && dayOfWeek != DayOfWeek.SUNDAY;
}
/**
* 獲取日期對(duì)應(yīng)的節(jié)假日信息
*/
public HolidayInfo getHolidayInfo(LocalDate date) {
try {
HolidayResponse response = getHolidayData(date.getYear());
if (response == null || response.getHoliday().isEmpty()) {
return null;
}
String monthDay = date.format(MONTH_DAY_FORMAT);
return response.getHoliday().get(monthDay);
} catch (Exception e) {
System.err.println("獲取節(jié)假日信息失敗,日期: " + date + ", 錯(cuò)誤: " + e.getMessage());
return null;
}
}
/**
* 清除緩存,適用于需要更新數(shù)據(jù)的場(chǎng)景
*/
public void clearCache() {
holidayCache.clear();
workdayCache.clear();
}
/**
* 示例用法
*/
public static void main(String[] args) {
TimorHolidayApiClient client = new TimorHolidayApiClient();
// 測(cè)試2025年部分日期
LocalDate[] dates = {
LocalDate.of(2025, 1, 1), // 元旦
LocalDate.of(2025, 1, 26), // 春節(jié)前補(bǔ)班
LocalDate.of(2025, 1, 28), // 除夕
LocalDate.of(2025, 2, 8), // 春節(jié)后補(bǔ)班
LocalDate.of(2025, 5, 1), // 勞動(dòng)節(jié)
LocalDate.of(2025, 10, 1), // 國(guó)慶節(jié)
LocalDate.of(2025, 10, 11) // 國(guó)慶節(jié)后補(bǔ)班
};
for (LocalDate date : dates) {
boolean isWorkday = client.isWorkingDay(date);
HolidayInfo info = client.getHolidayInfo(date);
String status = isWorkday ? "工作日" : "非工作日";
String name = info != null ? info.getName() : "普通日";
System.out.printf("%s %s - %s%n",
date,
status,
name);
}
}
}
責(zé)任編輯:龐桂玉
來(lái)源:
一安未來(lái)