| New file |
| | |
| | | package com.yami.trading.service; |
| | | |
| | | import java.time.*; |
| | | import java.time.temporal.ChronoUnit; |
| | | import java.util.*; |
| | | |
| | | /** |
| | | * 美股交易日统计工具(适配java.util.Date) |
| | | */ |
| | | public class UsStockTradingDayCalculator { |
| | | // 美股纽约时区 |
| | | private static final ZoneId NEW_YORK_ZONE = ZoneId.of("America/New_York"); |
| | | // UTC时区(Date转ZonedDateTime的基准) |
| | | private static final ZoneId UTC_ZONE = ZoneId.of("UTC"); |
| | | |
| | | // ==================== 基础版:仅排除周末 ==================== |
| | | /** |
| | | * 计算两个Date之间的美股交易日天数(仅排除周六/周日) |
| | | * @param startDate 开始日期(java.util.Date) |
| | | * @param endDate 结束日期(java.util.Date) |
| | | * @return 美股交易日天数 |
| | | */ |
| | | public static int countUsStockTradingDays(Date startDate, Date endDate) { |
| | | // 1. 参数校验 |
| | | if (startDate == null || endDate == null) { |
| | | throw new IllegalArgumentException("startDate error"); |
| | | } |
| | | if (startDate.after(endDate)) { |
| | | throw new IllegalArgumentException("endDate error"); |
| | | } |
| | | |
| | | // 2. 将Date转换为纽约时区的LocalDate(核心:按纽约时间算日期) |
| | | LocalDate startLocalDate = convertDateToNyLocalDate(startDate); |
| | | LocalDate endLocalDate = convertDateToNyLocalDate(endDate); |
| | | |
| | | // 3. 遍历日期并统计交易日(非周六/周日) |
| | | int tradingDays = 0; |
| | | LocalDate currentDate = startLocalDate; |
| | | while (!currentDate.isAfter(endLocalDate)) { |
| | | DayOfWeek dayOfWeek = currentDate.getDayOfWeek(); |
| | | // 非周六、非周日即为交易日 |
| | | if (dayOfWeek != DayOfWeek.SATURDAY && dayOfWeek != DayOfWeek.SUNDAY) { |
| | | tradingDays++; |
| | | } |
| | | currentDate = currentDate.plusDays(1); |
| | | } |
| | | return tradingDays; |
| | | } |
| | | |
| | | // ==================== 进阶版:排除周末+美股法定节假日 ==================== |
| | | /** |
| | | * 计算两个Date之间的美股交易日天数(排除周六/周日+美股法定节假日) |
| | | * @param startDate 开始日期 |
| | | * @param endDate 结束日期 |
| | | * @return 美股交易日天数 |
| | | */ |
| | | public static int countUsStockTradingDaysWithHolidays(Date startDate, Date endDate) { |
| | | // 基础参数校验 |
| | | if (startDate == null || endDate == null) { |
| | | throw new IllegalArgumentException("开始日期和结束日期不能为空"); |
| | | } |
| | | if (startDate.after(endDate)) { |
| | | throw new IllegalArgumentException("开始日期不能晚于结束日期"); |
| | | } |
| | | |
| | | // 转换为纽约时区的LocalDate |
| | | LocalDate startLocalDate = convertDateToNyLocalDate(startDate); |
| | | LocalDate endLocalDate = convertDateToNyLocalDate(endDate); |
| | | |
| | | // 预加载周期内涉及年份的美股节假日 |
| | | Set<LocalDate> holidays = new HashSet<>(); |
| | | for (int year = startLocalDate.getYear(); year <= endLocalDate.getYear(); year++) { |
| | | holidays.addAll(getUsStockHolidays(year)); |
| | | } |
| | | |
| | | // 遍历统计(非周末+非节假日) |
| | | int tradingDays = 0; |
| | | LocalDate currentDate = startLocalDate; |
| | | while (!currentDate.isAfter(endLocalDate)) { |
| | | DayOfWeek dayOfWeek = currentDate.getDayOfWeek(); |
| | | if (dayOfWeek != DayOfWeek.SATURDAY && dayOfWeek != DayOfWeek.SUNDAY |
| | | && !holidays.contains(currentDate)) { |
| | | tradingDays++; |
| | | } |
| | | currentDate = currentDate.plusDays(1); |
| | | } |
| | | return tradingDays; |
| | | } |
| | | |
| | | // ==================== 工具方法:Date转纽约时区LocalDate ==================== |
| | | /** |
| | | * 将java.util.Date转换为纽约时区的LocalDate |
| | | * @param date 待转换的Date |
| | | * @return 纽约时区的LocalDate |
| | | */ |
| | | private static LocalDate convertDateToNyLocalDate(Date date) { |
| | | // Date -> Instant -> ZonedDateTime(UTC) -> ZonedDateTime(纽约) -> LocalDate |
| | | Instant instant = date.toInstant(); |
| | | ZonedDateTime utcZoned = instant.atZone(UTC_ZONE); |
| | | ZonedDateTime nyZoned = utcZoned.withZoneSameInstant(NEW_YORK_ZONE); |
| | | return nyZoned.toLocalDate(); |
| | | } |
| | | |
| | | // ==================== 工具方法:获取指定年份的美股法定节假日 ==================== |
| | | /** |
| | | * 获取指定年份的美股法定节假日(含调休规则) |
| | | * @param year 年份 |
| | | * @return 节假日LocalDate集合 |
| | | */ |
| | | private static Set<LocalDate> getUsStockHolidays(int year) { |
| | | Set<LocalDate> holidays = new HashSet<>(); |
| | | |
| | | // 1. 新年(1月1日,周末则调休) |
| | | LocalDate newYear = LocalDate.of(year, 1, 1); |
| | | if (newYear.getDayOfWeek() == DayOfWeek.SATURDAY) { |
| | | newYear = newYear.minusDays(1); |
| | | } else if (newYear.getDayOfWeek() == DayOfWeek.SUNDAY) { |
| | | newYear = newYear.plusDays(1); |
| | | } |
| | | holidays.add(newYear); |
| | | |
| | | // 2. 马丁·路德·金日(1月第三个周一) |
| | | LocalDate mlkDay = LocalDate.of(year, 1, 1); |
| | | while (mlkDay.getDayOfWeek() != DayOfWeek.MONDAY) { |
| | | mlkDay = mlkDay.plusDays(1); |
| | | } |
| | | mlkDay = mlkDay.plusWeeks(2); |
| | | holidays.add(mlkDay); |
| | | |
| | | // 3. 总统日(2月第三个周一) |
| | | LocalDate presidentsDay = LocalDate.of(year, 2, 1); |
| | | while (presidentsDay.getDayOfWeek() != DayOfWeek.MONDAY) { |
| | | presidentsDay = presidentsDay.plusDays(1); |
| | | } |
| | | presidentsDay = presidentsDay.plusWeeks(2); |
| | | holidays.add(presidentsDay); |
| | | |
| | | // 4. 阵亡将士纪念日(5月最后一个周一) |
| | | LocalDate memorialDay = LocalDate.of(year, 5, 31); |
| | | while (memorialDay.getDayOfWeek() != DayOfWeek.MONDAY) { |
| | | memorialDay = memorialDay.minusDays(1); |
| | | } |
| | | holidays.add(memorialDay); |
| | | |
| | | // 5. 独立日(7月4日,周末则调休) |
| | | LocalDate independenceDay = LocalDate.of(year, 7, 4); |
| | | if (independenceDay.getDayOfWeek() == DayOfWeek.SATURDAY) { |
| | | independenceDay = independenceDay.minusDays(1); |
| | | } else if (independenceDay.getDayOfWeek() == DayOfWeek.SUNDAY) { |
| | | independenceDay = independenceDay.plusDays(1); |
| | | } |
| | | holidays.add(independenceDay); |
| | | |
| | | // 6. 劳工节(9月第一个周一) |
| | | LocalDate laborDay = LocalDate.of(year, 9, 1); |
| | | while (laborDay.getDayOfWeek() != DayOfWeek.MONDAY) { |
| | | laborDay = laborDay.plusDays(1); |
| | | } |
| | | holidays.add(laborDay); |
| | | |
| | | // 7. 感恩节(11月第四个周四) |
| | | LocalDate thanksgiving = LocalDate.of(year, 11, 1); |
| | | while (thanksgiving.getDayOfWeek() != DayOfWeek.THURSDAY) { |
| | | thanksgiving = thanksgiving.plusDays(1); |
| | | } |
| | | thanksgiving = thanksgiving.plusWeeks(3); |
| | | holidays.add(thanksgiving); |
| | | |
| | | // 8. 圣诞节(12月25日,周末则调休) |
| | | LocalDate christmas = LocalDate.of(year, 12, 25); |
| | | if (christmas.getDayOfWeek() == DayOfWeek.SATURDAY) { |
| | | christmas = christmas.minusDays(1); |
| | | } else if (christmas.getDayOfWeek() == DayOfWeek.SUNDAY) { |
| | | christmas = christmas.plusDays(1); |
| | | } |
| | | holidays.add(christmas); |
| | | |
| | | return holidays; |
| | | } |
| | | |
| | | // ==================== 测试示例 ==================== |
| | | public static void main(String[] args) { |
| | | // 测试:2025-12-22(周一) ~ 2025-12-28(周日) |
| | | Calendar cal1 = Calendar.getInstance(); |
| | | cal1.set(2025, Calendar.DECEMBER, 22); |
| | | Date start = cal1.getTime(); |
| | | |
| | | Calendar cal2 = Calendar.getInstance(); |
| | | cal2.set(2025, Calendar.DECEMBER, 28); |
| | | Date end = cal2.getTime(); |
| | | |
| | | // 基础版:仅排除周末,共5天(22-26日) |
| | | int basicDays = countUsStockTradingDays(start, end); |
| | | // 进阶版:若2025-12-25是圣诞节(周四),则排除,共4天 |
| | | int advancedDays = countUsStockTradingDaysWithHolidays(start, end); |
| | | |
| | | System.out.println("基础版(仅排周末)交易日数:" + basicDays); // 输出:5 |
| | | System.out.println("进阶版(排周末+节假日)交易日数:" + advancedDays); // 输出:4 |
| | | } |
| | | } |