计算Java中两个date之间的工作日数
任何人都可以指向一些Java片段,我可以在两个date之间获得业务(周六和周日除外)。
public static int getWorkingDaysBetweenTwoDates(Date startDate, Date endDate) { Calendar startCal = Calendar.getInstance(); startCal.setTime(startDate); Calendar 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 { //excluding start date 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()); //excluding end date return workDays; }
开始date和结束date是排他性的,只有在给定date之间的日子才会被计算在内。 开始date和结束date将不包括在内。
无循环解决scheme:
static long days(Date start, Date end){ //Ignore argument check Calendar c1 = Calendar.getInstance(); c1.setTime(start); int w1 = c1.get(Calendar.DAY_OF_WEEK); c1.add(Calendar.DAY_OF_WEEK, -w1); Calendar c2 = Calendar.getInstance(); c2.setTime(end); int w2 = c2.get(Calendar.DAY_OF_WEEK); c2.add(Calendar.DAY_OF_WEEK, -w2); //end Saturday to start Saturday long days = (c2.getTimeInMillis()-c1.getTimeInMillis())/(1000*60*60*24); long daysWithoutWeekendDays = days-(days*2/7); // Adjust days to add on (w2) and days to subtract (w1) so that Saturday // and Sunday are not included if (w1 == Calendar.SUNDAY && w2 != Calendar.SATURDAY) { w1 = Calendar.MONDAY; } else if (w1 == Calendar.SATURDAY && w2 != Calendar.SUNDAY) { w1 = Calendar.FRIDAY; } if (w2 == Calendar.SUNDAY) { w2 = Calendar.MONDAY; } else if (w2 == Calendar.SATURDAY) { w2 = Calendar.FRIDAY; } return daysWithoutWeekendDays-w1+w2; }
我用卢胜源的解决scheme,但是我需要解决的方法是,当一个date是星期六,另一个是星期天的时候调用方法 – 否则答案是closures的一天:
static long days(Date start, Date end){ //Ignore argument check Calendar c1 = GregorianCalendar.getInstance(); c1.setTime(start); int w1 = c1.get(Calendar.DAY_OF_WEEK); c1.add(Calendar.DAY_OF_WEEK, -w1 + 1); Calendar c2 = GregorianCalendar.getInstance(); c2.setTime(end); int w2 = c2.get(Calendar.DAY_OF_WEEK); c2.add(Calendar.DAY_OF_WEEK, -w2 + 1); //end Saturday to start Saturday long days = (c2.getTimeInMillis()-c1.getTimeInMillis())/(1000*60*60*24); long daysWithoutSunday = days-(days*2/7); if (w1 == Calendar.SUNDAY) { w1 = Calendar.MONDAY; } if (w2 == Calendar.SUNDAY) { w2 = Calendar.MONDAY; } return daysWithoutSunday-w1+w2; }
我没有一个基于Java的解决scheme,但有一个PHP的,希望它可以帮助:
function getDate($days) { for ($i = 0; $i < $days; $i ++) { if (date('N' , strtotime('+' . ($i + 1) . ' days')) > 5) { $days++; } } return date('l, F jS', strtotime('+' . $days . ' days', time())); }
没有循环的 Java 8解决scheme 。 请注意,这将计算不包括结束date的差异,因此从周五到周五的一周将返回7
。 如果您想要包含结果,请将结果添加到1
。
public long calcWeekDays(final LocalDate start, final LocalDate end) { final DayOfWeek startWeekDay = start.getDayOfWeek().getValue() < 6 ? start.getDayOfWeek() : DayOfWeek.MONDAY; final DayOfWeek endWeekDay = end.getDayOfWeek().getValue() < 6 ? end.getDayOfWeek() : DayOfWeek.FRIDAY; final long nrOfWeeks = ChronoUnit.DAYS.between(start, end) / 7; final long totalWeekdDays = nrOfWeeks * 5 + Math.floorMod(endWeekDay.getValue() - startWeekDay.getValue(), 5); return totalWeekdDays; }
在Piyush的解决scheme中do
是错误的,它应该是:
do { if (startCal.get(Calendar.DAY_OF_WEEK) != Calendar.SATURDAY && startCal.get(Calendar.DAY_OF_WEEK) != Calendar.SUNDAY) { ++workDays; } startCal.add(Calendar.DAY_OF_MONTH, 1); } while (startCal.getTimeInMillis() < endCal.getTimeInMillis());
这是我没有循环的例子。 algorithm和卢声远盛源鲁斯一样,但是我用了一些JodaTime的特性。
public static int getNumberOfBusinessDays(@Nonnull LocalDate from, @Nonnull LocalDate to) { int fromDateDayOfWeek = from.getDayOfWeek(); int toDateDayOfWeek = to.getDayOfWeek(); int daysWithoutWeekends = 5 * Weeks.weeksBetween( from.withDayOfWeek(DateTimeConstants.MONDAY), to).getWeeks(); if (fromDateDayOfWeek == DateTimeConstants.SUNDAY) { fromDateDayOfWeek = DateTimeConstants.SATURDAY; } if (toDateDayOfWeek == DateTimeConstants.SUNDAY) { toDateDayOfWeek = DateTimeConstants.SATURDAY; } return daysWithoutWeekends - (fromDateDayOfWeek - toDateDayOfWeek); }
java.time
现代的方式是使用java.time类。
LocalDate
LocalDate
类代表没有时间和没有时区的只有date的值。
LocalDate start = LocalDate.of( 2016 , 1 , 23 ); LocalDate stop = start.plusMonths( 1 );
DayOfWeek
枚举
DayOfWeek
枚举为每周的每个服务日提供一个单例实例。
DayOfWeek dow = start.getDayOfWeek(); if( dow.equals( DayOfWeek.SATURDAY ) || dow.equals( DayOfWeek.SUNDAY ) ) …
我们可以收集所需的date是一个List
。
int initialCapacity = Duration.between( start , stop ).toDays() ; List<LocalDate> dates = new ArrayList<>( initialCapacity ); … if( dow.equals( DayOfWeek.SATURDAY ) || dow.equals( DayOfWeek.SUNDAY ) ) { dates.add( date ); …
EnumSet
是Set
一个非常高效,快速和低内存的实现。 我们可以使用EnumSet
而不是上面的if
语句。
Set<DayOfWeek> weekend = EnumSet.of( DayOfWeek.SATURDAY , DayOfWeek.SUNDAY ) ; … if( weekend.contains( dayOfWeek ) ) …
把这一切放在一起。
LocalDate date = start ; while( date.isBefore( stop ) ) { if( ! weekend.contains( date ) ) { // If not weekend, collect this LocalDate. dates.add( date ) ; } // Prepare for next loop. date = date.plusDays( 1 ); // Increment to next day. }
nextWorkingDay
TemporalAdjuster
另一种方法是使用ThreeTen-Extra项目来添加使用java.time的类。
Temporals
类添加了用于操作date时间值的TemporalAdjuster
附加实现。 我们希望下个nextWorkingDay
调整者在星期六和星期天跳过date。
LocalDate start = LocalDate.of( 2016 , 1 , 23 ); LocalDate stop = start.plusMonths( 1 ); int initialCapacity = Duration.between( start , stop ).toDays() ; List<LocalDate> dates = new ArrayList<>( initialCapacity ); LocalDate date = start.minusDays( 1 ); // Start a day ahead. while( date.isBefore( stop ) ) { date = date.with( Temporals.nextWorkingDay() ); // Double-check ending date as the `nextWorkingDay` adjuster could move us past the stop date. if( date.isBefore( stop ) ) { dates.add( date ) ; } }
关于java.time
java.time框架内置于Java 8及更高版本中。 这些类取代了麻烦的旧的遗留date时间类,如java.util.Date
, Calendar
和SimpleDateFormat
。
Joda-Time项目现在处于维护模式 ,build议迁移到java.time。
要了解更多信息,请参阅Oracle教程 。 并search堆栈溢出了很多例子和解释。 规范是JSR 310 。
从何处获取java.time类?
- Java SE 8和SE 9及更高版本
- 内置。
- 带有捆绑实现的标准Java API的一部分。
- Java 9增加了一些小function和修复。
- Java SE 6和SE 7
- 大部分的java.timefunction都被移植到了ThreeTen-Backport中的 Java 6&7中。
- Android的
- ThreeTenABP项目专门针对Android,采用了ThreeTen-Backport (上文提到)。
- 请参阅如何使用…。
ThreeTen-Extra项目将java.time扩展到其他类。 这个项目是未来可能增加java.time的一个试验场。 你可能会在这里find一些有用的类,比如Interval
, YearWeek
, YearQuarter
等等 。
startCal.add
应该添加到Calendar.DATE
字段,而不是Calendar.DAY_OF_MONTH
,我得到奇怪的结果与十二月/ 1月期间。
这是我没有循环的例子。 这是这个例子中的一个类,因为我在一些JSON输出中对它进行了序列化。 基本上我计算两个date之间的天数,除以7,并分配一个长周期的整数值。 采取原来的天数,减去周末数* 2。 这并不完美 – 你需要确定是否有一个“宿醉”开始接近周末,并在周末结束。 为了纠正这个问题,我在一开始就find了一周中的某一天,并且find剩余的天数,并且把它们加在一起来find“宿醉” – 如果超过5,那么这是一个周末。 这是不完美的,并没有计入假期。 没有乔达在眼前。 这就是说,时区也有一个问题。
import java.io.Serializable; import java.util.Date; public class BusinessDayCalculator implements Serializable { private static long DAY = 86400000l; private Date startTime; private Date endTime; public void setStartTime(Date startTime) { this.startTime = startTime; } public Date getStartTime() { return startTime; } public void setEndTime(Date endTime) { this.endTime = endTime; } public Date getEndTime() { return endTime; } public long getHours() { return (this.endTime.getTime() - this.startTime.getTime())/(1000*60*60); } public long getBusinessDays(){ long startDay = getDayFromDate(this.startTime); long endDay = getDayFromDate(this.endTime); long totalDays = endDay-startDay; long totalWeekends = totalDays/7; long day = getDay(this.startTime); long hangover = totalDays % 7; long intoWeekend = day + hangover; if(intoWeekend>5){ totalWeekends++; } long totalBusinessDays = totalDays - (totalWeekends *2); /* System.out.println("Days = " + day ); System.out.println("Hangover = " + hangover ); System.out.println("Total Days = " + totalDays); System.out.println("Total Weekends = " + totalWeekends); System.out.println("Total Business Days = " + totalBusinessDays); */ return totalBusinessDays; } private long getDayFromDate( Date date ){ long d = date.getTime() / DAY; return d; } private long getDay( Date date ){ long daysSinceEpoc = getDayFromDate(date); long day = daysSinceEpoc % 7; day = day + 4; if(day>6) day = day - 7; return day; } }
这个程序考虑循环方法,但考虑在下class后到办公室下class时间发生的活动
公共类BusinessDayCalculator {
private final String DATE_FORMAT = "dd/MM/yyyy HH:mm:ss"; private final int OFFICE_START_HOUR = 9; private final int OFFICE_CLOSE_HOUR = 17; private final int TOTAL_MINS_IN_BUSINESS_DAY = (OFFICE_CLOSE_HOUR - OFFICE_START_HOUR)*60; public void dateDifference(String start, String end){ Date startDate = validateStringToDate(start); Date endDate = validateStringToDate(end); System.out.println(startDate); System.out.println(endDate); Calendar startDay = convertDateToCalendar(startDate); Calendar tempDay = (Calendar) startDay.clone(); Calendar endDay = convertDateToCalendar(endDate); System.out.println(startDay.getTime()); System.out.println(endDay.getTime()); int workDays = -1; int startDayDifference = 0; int endDayDifference = 0; int hours = 0; int minsRemainder = 0; if(!(startDay.get(Calendar.DAY_OF_YEAR) == endDay.get(Calendar.DAY_OF_YEAR) && startDay.get(Calendar.YEAR) == endDay.get(Calendar.YEAR))){ do{ tempDay.add(Calendar.DAY_OF_MONTH, 1); if(tempDay.get(Calendar.DAY_OF_WEEK) != Calendar.SATURDAY && tempDay.get(Calendar.DAY_OF_WEEK) != Calendar.SUNDAY){ workDays++; } }while(tempDay.getTimeInMillis() <= endDay.getTimeInMillis()); if(workDays > 0){ workDays = workDays - 1; } } startDayDifference = hourDifferenceInMinutesOfStartDay(startDay); endDayDifference = hourDifferenceInMinutesOfEndDay(endDay); minsRemainder = (startDayDifference + endDayDifference) % TOTAL_MINS_IN_BUSINESS_DAY; workDays = workDays + ((startDayDifference + endDayDifference) / TOTAL_MINS_IN_BUSINESS_DAY); hours = minsRemainder/60; minsRemainder = minsRemainder % 60; System.out.println(workDays + "d "+ hours + "hrs " + minsRemainder + " mins"); } private int hourDifferenceInMinutesOfEndDay(Calendar endDay) { long endTimestamp = endDay.getTimeInMillis(); System.out.println(endTimestamp); endDay.set(Calendar.HOUR_OF_DAY, OFFICE_START_HOUR); endDay.set(Calendar.MINUTE,0); long endDayOfficeStartTimestamp = endDay.getTimeInMillis(); System.out.println(endDayOfficeStartTimestamp); int difference = (int)((endTimestamp - endDayOfficeStartTimestamp) / 1000) / 60; System.out.println(difference); return difference; } private int hourDifferenceInMinutesOfStartDay(Calendar startDay) { long starttimestamp = startDay.getTimeInMillis(); System.out.println(starttimestamp); startDay.set(Calendar.HOUR_OF_DAY, OFFICE_CLOSE_HOUR); startDay.set(Calendar.MINUTE,0); long startDayOfficeCloseTimestamp = startDay.getTimeInMillis(); System.out.println(startDayOfficeCloseTimestamp); int difference = (int)((startDayOfficeCloseTimestamp - starttimestamp) / 1000) / 60; System.out.println(difference); return difference; } public Calendar convertDateToCalendar(Date date){ Calendar calendar = Calendar.getInstance(); calendar.setTime(date); if(calendar.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY || calendar.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY){ calendar = handleActivityOnAfterWorkHoursOrWeekendOrHolidays(calendar); } if(calendar.get(Calendar.HOUR_OF_DAY) >= OFFICE_CLOSE_HOUR && calendar.get(Calendar.MINUTE) > 0){ calendar = handleActivityOnAfterWorkHoursOrWeekendOrHolidays(calendar); } if(calendar.get(Calendar.HOUR_OF_DAY) < OFFICE_START_HOUR){ calendar.set(Calendar.HOUR_OF_DAY, OFFICE_START_HOUR); calendar.set(Calendar.MINUTE,0); } return calendar; } private Calendar handleActivityOnAfterWorkHoursOrWeekendOrHolidays(Calendar calendar) { do{ calendar.add(Calendar.DAY_OF_MONTH, 1); }while(isHoliday(calendar)); calendar.set(Calendar.HOUR_OF_DAY, OFFICE_START_HOUR); calendar.set(Calendar.MINUTE,0); return calendar; } private boolean isHoliday(Calendar calendar) { if(calendar.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY || calendar.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY){ return true; } return false; } public Date validateStringToDate(String input){ SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT); Date date = null; try{ date = dateFormat.parse(input); }catch(ParseException exception){ System.out.println("invalid date format"); throw new RuntimeException("invalid date format"); } return date; } public static void main(String[] args){ BusinessDayCalculator calc = new BusinessDayCalculator(); String startDate = "27/12/2016 11:38:00"; String endDate = "04/01/2017 12:38:00"; calc.dateDifference(startDate, endDate); }
}
以下是使用公式(wd2 – wd1 + 7)%7来计算任何两个工作日(wd1,wd2)之间的距离的解决scheme。
public long countOccurrences(LocalDate startDate, LocalDate endDate, Set<DayOfWeek> daysOfWeek) { long periodLength = ChronoUnit.DAYS.between(startDate, endDate) + 1; long fullWeeks = periodLength / 7; long residualWeekLength = periodLength % 7; return fullWeeks * daysOfWeek.size() + daysOfWeek.stream().mapToLong( // Yields either 1 or 0, depending on whether the residual week contains the target day or not. weekday -> residualWeekLength > (weekday.getValue() - startDate.getDayOfWeek().getValue() + 7) % 7 ? 1 : 0 ).sum(); }
对于原来的问题(星期一至星期五),它被称为例如:
countOccurrences(LocalDate.of(2016, 2, 8), LocalDate.of(2016, 2, 26), new HashSet(Arrays.asList(DayOfWeek.MONDAY, DayOfWeek.TUESDAY, DayOfWeek.WEDNESDAY, DayOfWeek.THURSDAY, DayOfWeek.FRIDAY)))
在groovy:
public static int getWorkingDaysBetweenDates (Date start, Date end) { def totalDays = (Integer) (end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24) def int workingDays = 0 (0..totalDays).each { def dow = (start + it)[Calendar.DAY_OF_WEEK]; if(dow != Calendar.SATURDAY && dow != Calendar.SUNDAY){workingDays++} } workingDays }