计算包括节假日在内的工作日

Posted

技术标签:

【中文标题】计算包括节假日在内的工作日【英文标题】:calculate business days including holidays 【发布时间】:2012-06-01 16:31:04 【问题描述】:

我需要计算两个日期之间的工作日。 例如:我们在 7 月 4 日放假(在美国)。所以如果我的日期是 日期 1 = 07/03/2012 日期 2 = 07/06/2012

由于 7 月 4 日是假期,因此这些日期应该是 1 个工作日。

我有一个下面的方法来计算工作日,它只计算周末而不是假期。 还有什么方法可以计算假期....请帮助我。

  public static int getWorkingDaysBetweenTwoDates(Date startDate, Date endDate)   
    Calendar startCal;  
    Calendar endCal;  
    startCal = Calendar.getInstance();  
    startCal.setTime(startDate);  
    endCal = Calendar.getInstance();  
    endCal.setTime(endDate);  
    int workDays = 0;  

    //Return 0 if start and end are the same  
    if (startCal.getTimeInMillis() == endCal.getTimeInMillis())   
        return 0;  
      

    if (startCal.getTimeInMillis() > endCal.getTimeInMillis())   
        startCal.setTime(endDate);  
        endCal.setTime(startDate);  
      

    do   
        startCal.add(Calendar.DAY_OF_MONTH, 1);  
        if (startCal.get(Calendar.DAY_OF_WEEK) != Calendar.SATURDAY   
       && startCal.get(Calendar.DAY_OF_WEEK) != Calendar.SUNDAY)   
            ++workDays;  
          
     while (startCal.getTimeInMillis() < endCal.getTimeInMillis());  

    return workDays;  

【问题讨论】:

您需要手动跟踪哪些日子是假期的知识,AFAIK 没有内置功能。另外,现在谁在 java 中使用 do-while?? 首先,您需要一个一年中所有假期的列表,因为没有 java 类(即Locale 特定类)提供此功能。然后你需要从这个列表中找出有多少落在指定的日期之间,这是相当简单的。然后你可以从上面代码的结果中删除那么多天。 感谢您的回复。假设我有一个包含所有假期的列表,您能否建议我如何使用该列表或如何检查条件中的列表日期。 重复的问题Date api for managing off days in an year? 但这个有更好的答案。 【参考方案1】:

由于接受的答案仍然使用过时的 Calendar 类 - 这是我使用 java.time 包中提供的更新的 Java 日期和时间 API 的两分钱。

您必须从某个地方获取假期的日期,没有标准的 Java 库。无论如何,这将过于本地化,因为假期在很大程度上取决于您所在的国家和地区(众所周知的假期除外,例如圣诞节或复活节)。 例如,您可以从假期 API 中获取它们。在下面的代码中,我将它们硬编码为 SetLocalDates。

Java 9

LocalDate startDate = LocalDate.of(2012, 3, 7);
LocalDate endDate = LocalDate.of(2012, 6, 7);

// I've hardcoded the holidays as LocalDates
// and put them in a Set
final Set<LocalDate> holidays = Set.of(
    LocalDate.of(2018, 7, 4)
);
// For the sake of efficiency, I also put the business days into a Set.
// In general, a Set has a better lookup speed than a List.
final Set<DayOfWeek> businessDays = Set.of(
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY
);

List<LocalDate> allDates =

    // Java 9 provides a method to return a stream with dates from the
    // startdate to the given end date. Note that the end date itself is
    // NOT included.
    startDate.datesUntil(endDate)

        // Retain all business days. Use static imports from
        // java.time.DayOfWeek.*
        .filter(t -> businessDays.contains(t.getDayOfWeek()))

        // Retain only dates not present in our holidays list
        .filter(t -> !holidays.contains(t))

         // Collect them into a List. If you only need to know the number of
         // dates, you can also use .count()
        .collect(Collectors.toList());

Java 8

LocalDate.datesUntil 方法在 Java 8 中不可用,因此您必须以不同的方式获取这两个日期之间的所有日期的流。我们必须先计算使用ChronoUnit.DAYS.between 方法之间的总天数。

long numOfDaysBetween = ChronoUnit.DAYS.between(startDate, endDate);

然后我们必须生成一个整数序列,其长度与开始日期和结束日期之间的天数完全一样,然后从它创建LocalDates,从开始日期开始。

IntStream.iterate(0, i -> i + 1)
    .limit(numOfDaysBetween)
    .mapToObj(startDate::plusDays)

现在我们有了Stream&lt;LocalDate&gt;,然后您可以使用Java 9 代码的剩余部分。您还需要替换 Set.of() 的用法,因为它在 Java 8 中不可用。可能是 new HashSet&lt;&gt;(Arrays.asList(MONDAY...FRIDAY))

【讨论】:

【参考方案2】:

假设您有一个包含所有假期的列表,正如您所提到的。

ArrayList<Integer> holidays = ...

只需在您的do-while 中为您的if 条件添加一个条件:

do 
          startCal.add(Calendar.DAY_OF_MONTH, 1);
          if (startCal.get(Calendar.DAY_OF_WEEK) != Calendar.SATURDAY
          && startCal.get(Calendar.DAY_OF_WEEK) != Calendar.SUNDAY
          && !holidays.contains((Integer) startCal.get(Calendar.DAY_OF_YEAR))) 
              ++workDays;
          
 while (startCal.getTimeInMillis() < endCal.getTimeInMillis());

为简单起见,我假设holiday 包含的日期格式与Calendar.DAY_OF_YEAR 相同。

【讨论】:

非常感谢...我不知道我是怎么错过这个简单的逻辑的。但在这里,我不是 Calendar.DAY_OF_YEAR,而是将它与返回完整日期的 Calendar.getTime() 进行比较。它现在工作正常 干杯,很高兴为您服务。 值得注意的是,这里的两个示例都将计算 1 天的差异 1 毫秒或更多。改为在循环中使用 day_of_year 和 year 比较。【参考方案3】:

Nager.Date

您可以使用Nager.Date 项目的JSON API。它支持美国、加拿大和欧洲。您可以将每年可用的数据保存在自己的数据库中。

示例

//https://github.com/FasterXML/jackson-databind/
ObjectMapper mapper = new ObjectMapper();
MyValue value = mapper.readValue(new URL("http://date.nager.at/api/v1/get/US/2017"), PublicHoliday[].class);

PublicHoliday.class

public class PublicHoliday

    public String date;
    public String localName;
    public String name;
    public String countryCode;
    public Boolean fixed;
    public Boolean countyOfficialHoliday;
    public Boolean countyAdministrationHoliday;
    public Boolean global;
    public String[] counties;
    public int launchYear;

检索到的示例 JSON 数据。

[
  
    "date": "2017-01-01",
    "localName": "New Year's Day",
    "name": "New Year's Day",
    "countryCode": "US",
    "fixed": true,
    "countyOfficialHoliday": true,
    "countyAdministrationHoliday": true,
    "global": true,
    "counties": null,
    "launchYear": null
  ,
  
    "date": "2017-01-16",
    "localName": "Martin Luther King, Jr. Day",
    "name": "Martin Luther King, Jr. Day",
    "countryCode": "US",
    "fixed": true,
    "countyOfficialHoliday": true,
    "countyAdministrationHoliday": true,
    "global": true,
    "counties": null,
    "launchYear": null
  ,
  
    "date": "2017-01-20",
    "localName": "Inauguration Day",
    "name": "Inauguration Day",
    "countryCode": "US",
    "fixed": true,
    "countyOfficialHoliday": true,
    "countyAdministrationHoliday": true,
    "global": false,
    "counties": [
      "US-DC",
      "US-LA",
      "US-MD",
      "US-VA"
    ],
    "launchYear": null
  ,
  
    "date": "2017-02-20",
    "localName": "Washington's Birthday",
    "name": "Presidents' Day",
    "countryCode": "US",
    "fixed": true,
    "countyOfficialHoliday": true,
    "countyAdministrationHoliday": true,
    "global": true,
    "counties": null,
    "launchYear": null
  ,
  
    "date": "2017-05-29",
    "localName": "Memorial Day",
    "name": "Memorial Day",
    "countryCode": "US",
    "fixed": true,
    "countyOfficialHoliday": true,
    "countyAdministrationHoliday": true,
    "global": true,
    "counties": null,
    "launchYear": null
  ,
  
    "date": "2017-07-04",
    "localName": "Independence Day",
    "name": "Independence Day",
    "countryCode": "US",
    "fixed": true,
    "countyOfficialHoliday": true,
    "countyAdministrationHoliday": true,
    "global": true,
    "counties": null,
    "launchYear": null
  ,
  
    "date": "2017-09-04",
    "localName": "Labor Day",
    "name": "Labor Day",
    "countryCode": "US",
    "fixed": true,
    "countyOfficialHoliday": true,
    "countyAdministrationHoliday": true,
    "global": true,
    "counties": null,
    "launchYear": null
  ,
  
    "date": "2017-09-09",
    "localName": "Columbus Day",
    "name": "Columbus Day",
    "countryCode": "US",
    "fixed": true,
    "countyOfficialHoliday": true,
    "countyAdministrationHoliday": true,
    "global": false,
    "counties": [
      "US-AL",
      "US-AZ",
      "US-CO",
      "US-CT",
      "US-DC",
      "US-GA",
      "US-ID",
      "US-IL",
      "US-IN",
      "US-IA",
      "US-KS",
      "US-KY",
      "US-LA",
      "US-ME",
      "US-MD",
      "US-MA",
      "US-MS",
      "US-MO",
      "US-MT",
      "US-NE",
      "US-NH",
      "US-NJ",
      "US-NM",
      "US-NY",
      "US-NC",
      "US-OH",
      "US-OK",
      "US-PA",
      "US-RI",
      "US-SC",
      "US-TN",
      "US-UT",
      "US-VA",
      "US-WV"
    ],
    "launchYear": null
  ,
  
    "date": "2017-11-10",
    "localName": "Veterans Day",
    "name": "Veterans Day",
    "countryCode": "US",
    "fixed": false,
    "countyOfficialHoliday": true,
    "countyAdministrationHoliday": true,
    "global": true,
    "counties": null,
    "launchYear": null
  ,
  
    "date": "2017-12-23",
    "localName": "Thanksgiving Day",
    "name": "Thanksgiving Day",
    "countryCode": "US",
    "fixed": true,
    "countyOfficialHoliday": true,
    "countyAdministrationHoliday": true,
    "global": true,
    "counties": null,
    "launchYear": 1863
  ,
  
    "date": "2017-12-25",
    "localName": "Christmas Day",
    "name": "Christmas Day",
    "countryCode": "US",
    "fixed": true,
    "countyOfficialHoliday": true,
    "countyAdministrationHoliday": true,
    "global": true,
    "counties": null,
    "launchYear": null
  
]

【讨论】:

您应该遵循 Java 命名约定:变量名始终以小写字母开头。【参考方案4】:

我没有任何代码示例或类似的东西,但我搜索了您并发现了这个 Stack Overflow 线程,其中包含一些可以为您返回假期日期的 Web 服务链接,这可能会帮助您获得到你需要去的地方:National holiday web service

该线程中的最佳答案链接到此 Web 服务:http://www.holidaywebservice.com/

我不确定为这类事情使用网络服务是否是矫枉过正,但肯定有更好的方法。抱歉,我不是最有经验的程序员,所以我无法为您提供尽可能多的帮助。

【讨论】:

这并不是矫枉过正。您需要从其他地方获取信息,因为它总是会发生变化。【参考方案5】:

我发现最快的方法是使用一些数学(借用 SO 帖子):

public static int totalBusinessDaysBetween(LocalDate start, LocalDate end) 
    Objects.requireNonNull(start, "Start date must not be null");
    Objects.requireNonNull(end, "End date must not be null");
    long daysBetweenWithoutWeekends = calculateNumberOfDaysBetweenMinusWeekends(start, end);
    final Set<LocalDate> holidayForYearRange = getUSFederalHolidayForYearRange(start.getYear(), end.getYear());
    for (LocalDate localDate : holidayForYearRange) 
        if (localDate.isAfter(start) && localDate.isBefore(end)) 
            daysBetweenWithoutWeekends--;
        
    
    return (int) daysBetweenWithoutWeekends;

还有:

private static long calculateNumberOfDaysBetweenMinusWeekends(LocalDate start, LocalDate end) 

    final DayOfWeek startW = start.getDayOfWeek();
    final DayOfWeek endW = end.getDayOfWeek();

    final long days = ChronoUnit.DAYS.between(start, end);
    final long daysWithoutWeekends = days - 2 * ((days + startW.getValue()) / 7);

    //adjust for starting and ending on a Sunday:
    return daysWithoutWeekends + (startW == DayOfWeek.SUNDAY ? 1 : 0) + (endW == DayOfWeek.SUNDAY ? 1 : 0);

我把更完整的东西放在一起here。

【讨论】:

以上是关于计算包括节假日在内的工作日的主要内容,如果未能解决你的问题,请参考以下文章

在相邻单元格中找到的日期之后的十个工作日(不包括节假日)突出显示相邻单元格

根据工作日/节假日收货要求计算预计发货时间

计算两个日期之间的工作日(去掉周末和节假日)

JAVA判断当前日期是节假日还是工作日

moment.js能判断是不是为节假日吗

JAVA判断当前日期是节假日还是工作日