如何用Java实现自定义节假日管理及可添加特定节假日的日历?
Alright, let's build a robust custom holiday management and calendar system in Java. I’ve worked on several similar projects, so here’s a practical, scalable approach that balances simplicity with flexibility.
1. Core Data Models
First, we need a clear model to represent holidays. This class captures all essential details—including whether the holiday repeats annually (like New Year's Day) or is a one-off event (like a company anniversary).
import java.time.LocalDate; public class Holiday { private Long id; private String name; private LocalDate date; private boolean isAnnual; // True for fixed-date annual holidays (e.g., Jan 1) private String description; // Constructor for easy holiday creation public Holiday(String name, LocalDate date, boolean isAnnual, String description) { this.name = name; this.date = date; this.isAnnual = isAnnual; this.description = description; } // Getters and setters (omitted for brevity—add these in your actual code) public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public LocalDate getDate() { return date; } public boolean isAnnual() { return isAnnual; } public String getDescription() { return description; } }
2. Holiday Management Service
This is the heart of the system—handling CRUD operations, holiday checks, and annual holiday date adjustment. It keeps our business logic centralized and reusable.
import java.time.LocalDate; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; public class HolidayService { private List<Holiday> holidays = new ArrayList<>(); // Add a new holiday (with duplicate prevention) public void addHoliday(Holiday holiday) { boolean duplicateExists = holidays.stream() .anyMatch(h -> h.getName().equalsIgnoreCase(holiday.getName()) && h.getDate().equals(holiday.getDate())); if (!duplicateExists) { holidays.add(holiday); } } // Remove a holiday by ID public void removeHoliday(Long holidayId) { holidays.removeIf(h -> h.getId().equals(holidayId)); } // Check if a given date is a holiday public boolean isHoliday(LocalDate date) { // For annual holidays, match month + day instead of full date return holidays.stream() .anyMatch(h -> h.isAnnual() ? h.getDate().getMonth() == date.getMonth() && h.getDate().getDayOfMonth() == date.getDayOfMonth() : h.getDate().equals(date)); } // Get all holidays for a specific year (adjusts annual holidays to the target year) public List<Holiday> getHolidaysForYear(int year) { return holidays.stream() .map(holiday -> { if (holiday.isAnnual()) { LocalDate adjustedDate = LocalDate.of(year, holiday.getDate().getMonth(), holiday.getDate().getDayOfMonth()); return new Holiday(holiday.getName(), adjustedDate, true, holiday.getDescription()); } return holiday; }) .filter(holiday -> holiday.getDate().getYear() == year) .collect(Collectors.toList()); } }
3. Calendar Integration Component
Now we’ll tie the holiday service to a calendar component that generates usable calendar data—perfect for rendering in a UI or running date checks.
import java.time.LocalDate; import java.time.YearMonth; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; public class CustomCalendarService { private final HolidayService holidayService; public CustomCalendarService(HolidayService holidayService) { this.holidayService = holidayService; } // Get a full month's calendar with holiday flags public Map<LocalDate, Boolean> getCalendarMonth(int year, int month) { YearMonth yearMonth = YearMonth.of(year, month); Map<LocalDate, Boolean> calendarMap = new HashMap<>(); for (int day = 1; day <= yearMonth.lengthOfMonth(); day++) { LocalDate date = LocalDate.of(year, month, day); calendarMap.put(date, holidayService.isHoliday(date)); } return calendarMap; } // Get detailed info for a specific date (including holiday names) public String getDateDetails(LocalDate date) { List<Holiday> matchingHolidays = holidayService.getHolidaysForYear(date.getYear()) .stream() .filter(h -> h.getDate().equals(date)) .collect(Collectors.toList()); if (matchingHolidays.isEmpty()) { return date.toString() + ": Regular workday"; } else { String holidayNames = matchingHolidays.stream() .map(Holiday::getName) .collect(Collectors.joining(", ")); return date.toString() + ": " + holidayNames; } } }
4. Example Usage
Let’s put it all together with a simple main method to demonstrate how the system works:
public class Main { public static void main(String[] args) { HolidayService holidayService = new HolidayService(); // Add annual holidays holidayService.addHoliday(new Holiday("New Year's Day", LocalDate.of(2024, 1, 1), true, "Start of the calendar year")); // Add a one-off company holiday holidayService.addHoliday(new Holiday("Company 10th Anniversary", LocalDate.of(2024, 5, 15), false, "Celebrating 10 years in business")); CustomCalendarService calendarService = new CustomCalendarService(holidayService); // Check if Jan 1, 2024 is a holiday System.out.println("2024-01-01 is a holiday? " + holidayService.isHoliday(LocalDate.of(2024, 1, 1))); // Output: true // Get May 2024 calendar data Map<LocalDate, Boolean> may2024Calendar = calendarService.getCalendarMonth(2024, 5); may2024Calendar.forEach((date, isHoliday) -> { System.out.println(date + ": " + (isHoliday ? "Holiday" : "Workday")); }); // Get details for May 15, 2024 System.out.println(calendarService.getDateDetails(LocalDate.of(2024, 5, 15))); // Output: 2024-05-15: Company 10th Anniversary } }
5. Scalability & Enhancements
To take this system to production level, consider these upgrades:
- Persistence: Use Spring Data JPA or MyBatis to store holidays in a database (add
@Entityand@Idannotations to theHolidayclass). - Timezone Support: Switch to
ZonedDateTimeinstead ofLocalDateif you need to handle multiple timezones. - Workaround Days: Add support for "work weekends" (e.g., a Saturday where employees work to compensate for a holiday).
- UI Integration: Pair this backend with a frontend framework like Vue.js or Thymeleaf to render an interactive calendar with holiday highlights.
内容的提问来源于stack exchange,提问作者Ashton Cochrane

