计算包括节假日在内的工作日
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 中获取它们。在下面的代码中,我将它们硬编码为 Set
的 LocalDate
s。
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);
然后我们必须生成一个整数序列,其长度与开始日期和结束日期之间的天数完全一样,然后从它创建LocalDate
s,从开始日期开始。
IntStream.iterate(0, i -> i + 1)
.limit(numOfDaysBetween)
.mapToObj(startDate::plusDays)
现在我们有了Stream<LocalDate>
,然后您可以使用Java 9 代码的剩余部分。您还需要替换 Set.of()
的用法,因为它在 Java 8 中不可用。可能是 new HashSet<>(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。
【讨论】:
以上是关于计算包括节假日在内的工作日的主要内容,如果未能解决你的问题,请参考以下文章