JAVA - How to make custom holidays and keep track of them - java

Making a Calendar that allows you to add in specific holidays

You should use the java.time package. Specifically the MonthDay class to represent a holiday, as holidays are usually some fixed month + day (however you will need more complicated logic than a Set<MonthDay> if the holidays are "dynamic").
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.Month;
import java.time.MonthDay;
import java.util.HashSet;
import java.util.Set;
public class CustomHolidays {
private final Set<MonthDay> holidays = new HashSet<>();
public void addHoliday(final MonthDay monthDay) {
holidays.add(monthDay);
}
public boolean isHoliday(final LocalDate localDate) {
return isWeekend(localDate) || holidays.contains(toMonthDay(localDate));
}
public int numberOfWorkdaysBetween(final LocalDate startInclusive, final LocalDate endInclusive) {
int c = 0;
for (LocalDate i = startInclusive; !i.isAfter(endInclusive); i = i.plusDays(1)) {
if (!isHoliday(i)) {
c++;
}
}
return c;
}
private boolean isWeekend(final LocalDate localDate) {
final DayOfWeek dow = localDate.getDayOfWeek();
return dow == DayOfWeek.SATURDAY || dow == DayOfWeek.SUNDAY;
}
private static MonthDay toMonthDay(final LocalDate localDate) {
return MonthDay.of(localDate.getMonth(), localDate.getDayOfMonth());
}
public static void main(String[] args) {
final CustomHolidays ch = new CustomHolidays();
ch.addHoliday(MonthDay.of(Month.MAY, 1));
ch.addHoliday(MonthDay.of(Month.MAY, 2));
ch.addHoliday(MonthDay.of(Month.MAY, 3));
System.out.println(ch.numberOfWorkdaysBetween(LocalDate.of(2018, 5, 1), LocalDate.of(2018, 5, 8)));
}
}
Output: 3

As recommended by others, you should go ahead and use java.time.LocalDate instead.
But if you are still curious as to why your current approach is not working, read ahead.
1st Problem: The following while condition will stop once the day is equal, even if months are different (because dayIndex!= yDay will evaluate to false)
while (monthIndex != yMonth && dayIndex != yDay)
Changing the while condition to below one will check for both the day and month. Also compare monthIndex to yMonth-1 since your monthIndex is initialised as xMonth-1
while (!(monthIndex == yMonth-1 && dayIndex == yDay))
2nd problem: Change int calMonth = xMonth; to int calMonth = xMonth-1; for the same reason as mentioned in problem 1.
3rd Problem: Initialise dayIndex to 0 instead of 1, because you are incrementing dayIndex after the if block. If you initialise it to 1, it will increment to 2 and hence 1st date of every month will be skipped.
if (dayIndex == numDaysInMonth+1) {
dayIndex = 0;
monthIndex++;
}
dayIndex++;
Check here for working fiddle

Related

Oracle next_day function equivalent in JAVA

I wanted to make a method which works similar to as oracle's next_day() function, below is my code which accepts date n weekday in MONDAY,TUESDAY etc. and returns a date which falls on the next given day from the input date.
/**
* #param date
* #param weekDay
* #return Gives the date for next weekday specified in the parameter.
*/
public Date getNextDay(Date value, String weekday) {
Calendar date1 = Calendar.getInstance();
date1.setTime(value);
if (weekday.equalsIgnoreCase("MONDAY")) {
while (date1.get(Calendar.DAY_OF_WEEK) != Calendar.MONDAY) {
date1.add(Calendar.DATE, 1);
}
} else if (weekday.equalsIgnoreCase("TUESDAY")) {
while (date1.get(Calendar.DAY_OF_WEEK) != Calendar.TUESDAY) {
date1.add(Calendar.DATE, 1);
}
} else if (weekday.equalsIgnoreCase("WEDNESDAY")) {
while (date1.get(Calendar.DAY_OF_WEEK) != Calendar.WEDNESDAY) {
date1.add(Calendar.DATE, 1);
}
} else if (weekday.equalsIgnoreCase("THURSDAY")) {
while (date1.get(Calendar.DAY_OF_WEEK) != Calendar.THURSDAY) {
date1.add(Calendar.DATE, 1);
}
} else if (weekday.equalsIgnoreCase("FRIDAY")) {
while (date1.get(Calendar.DAY_OF_WEEK) != Calendar.FRIDAY) {
date1.add(Calendar.DATE, 1);
}
} else if (weekday.equalsIgnoreCase("SATURDAY")) {
while (date1.get(Calendar.DAY_OF_WEEK) != Calendar.SATURDAY) {
date1.add(Calendar.DATE, 1);
}
} else {
while (date1.get(Calendar.DAY_OF_WEEK) != Calendar.SUNDAY) {
date1.add(Calendar.DATE, 1);
}
}
return date1.getTime();
}
Please suggest a better way of doing this.
I recommend you do it using the modern java.time date-time API. Learn more about the modern date-time API from Trail: Date Time. The java.util date-time API and SimpleDateFormat are outdated and error-prone. In case you are not using Java-8, you can still use Java-8 date-time API through ThreeTenABP library.
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.temporal.TemporalAdjusters;
public class Main {
public static void main(String[] args) {
// Tests
// Next occurrence
System.out.println(getNextDay(LocalDate.now(), "Monday"));
System.out.println(getNextDay(LocalDate.now(), "Wednesday"));
// Same (if date falls on the given day) or next occurrence
System.out.println(getSameOrNextDay(LocalDate.now(), "Monday"));
System.out.println(getSameOrNextDay(LocalDate.now(), "Wednesday"));
}
static LocalDate getNextDay(LocalDate value, String weekday) {
return value.with(TemporalAdjusters.next(DayOfWeek.valueOf(weekday.toUpperCase())));
}
static LocalDate getSameOrNextDay(LocalDate value, String weekday) {
return value.with(TemporalAdjusters.nextOrSame(DayOfWeek.valueOf(weekday.toUpperCase())));
}
}
Output:
2020-09-28
2020-09-30
2020-09-28
2020-09-23
If you are interested in the next day I'll suggest using a java.time.LocalDate instead of a java.util.Date.
The following code accepts such a LocalDate along with a String (that needs to be a full day of week in upper case letters) and returns the LocalDate representing the nearest future date which has the given day of week:
public static LocalDate nextDay(LocalDate sourceDay, String weekday) {
// parse the day of week to an enum value
DayOfWeek dayOfWeek = DayOfWeek.valueOf(weekday);
// check if the day is the same as the one of the given LocalDate
if (sourceDay.getDayOfWeek().equals(dayOfWeek)) {
// and return the LocalDate that's a week later
return sourceDay.plusWeeks(1);
} else {
// otherwise add a day to the given date
LocalDate nextDayOfWeek = sourceDay.plusDays(1);
// and do that until the day of week of the given date is reached
while (nextDayOfWeek.getDayOfWeek() != dayOfWeek) {
nextDayOfWeek = nextDayOfWeek.plusDays(1);
}
// then return the future date
return nextDayOfWeek;
}
}
You can use it in a main like this:
public static void main(String[] args) {
System.out.println(nextDay(LocalDate.now(), "FRIDAY")
.format(DateTimeFormatter.ofPattern("uuuu-MM-dd '('EEEE')'",
Locale.ENGLISH)));
}
Output (today ⇒ 2020-09-23):
2020-09-25 (Friday)
Use the "new" java.time.DayOfWeek and java.time.LocalDate classes:
public LocalDate getNextDay(
final LocalDate value,
final DayOfWeek day
)
{
int currentDay = value.getDayOfWeek().getValue();
int expectedDay = day.getValue();
if ( currentDay >= expectedDay )
{
expectedDay += 7;
}
return value.plusDays( expectedDay - currentDay );
}

i have set of date ranges, i need to get the combined date range ,if any of the date overlaps in java [duplicate]

This question already has answers here:
Determine Whether Two Date Ranges Overlap
(39 answers)
Closed 5 years ago.
I have set of date ranges, I need to get the combined date range if any of the dates overlap in Java.
Given three sets of date ranges, if any of the dates overlap with another range of dates need to be combined.
Example:
20170101-20170331
20170101-20170430
20170430-20170501
Expected result is:
20170101-20170430
20170430-20170501
I have all the dates in String Variable. Can please any one help me to how to write the code for that. I have pasted below my code.
I want to achieve the expected results. I couldn't find out how I need to modify this code. I am a beginner, please help me to do that. I have got this sample program from StackOverflow.
package com.kkkkk.Combine;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
public class Ideone {
public static void main(String[] args) throws java.lang.Exception {
ArrayList<Interval> x = new ArrayList<>();
x.add(new Interval("20170430", "20170501")); // "20170101", "20170430"
x.add(new Interval("20170101", "20170430"));// 20170101-20170430
x.add(new Interval("20170101", "20170331"));
x = merge(x);
for (Interval i1 : x) {
System.out.println(i1.getStartDate() + " " + i1.getEndDate());
}
}
public static ArrayList<Interval> merge(ArrayList<Interval> intervals) {
if (intervals.size() == 0 || intervals.size() == 1)
return intervals;
ArrayList<Interval> result = new ArrayList<Interval>();
Collections.sort(intervals, new IntervalComparator());
System.out.println("intervals ggggg\n" + intervals + "\n");
Interval first = intervals.get(0);
String start = first.getStartDate();
String end = first.getEndDate();
Date startDateF = null;
Date endDateF = null;
try {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
startDateF = sdf.parse(start);
endDateF = sdf.parse(end);
// ArrayList<Interval> result = new ArrayList<Interval>();
for (int i = 1; i < intervals.size(); i++) {
Interval current = intervals.get(i);
Date currentEndDate = sdf.parse(current.getEndDate());
Date currentStartDate = sdf.parse(current.getStartDate());
// if ((current.getStartDate().after(endDateF)) ||
Date d1 = minDate(endDateF, currentStartDate);
if ((currentStartDate).compareTo(endDateF) <= 0) {
endDateF = maxDate(currentEndDate, endDateF);
} else {
result.add(new Interval(start, (sdf.format(endDateF))));
// start = current.start;
// end = current.end;
start = sdf.format(currentStartDate);
endDateF = (currentEndDate);
enter code here
}
}
result.add(new Interval(start, end));
// result.add(new Interval(start, (sdf.format(endDateF))));
}
catch (ParseException ex) {
ex.printStackTrace();
}
// result.add(new Interval(start, end));
return result;
// return intervals;
}
public static Date minDate(Date date1, Date date2) {
// if date1 before date2 then return date1 else return date2
return date1.before(date2) ? date1 : date2;
}
/**
* find Max Dates
*
* #param date1
* #param date2
* #return
*/
public static Date maxDate(Date date1, Date date2) {
// if date1 after date2 then return date1 else return date2
System.out.println("date max");
return date1.after(date2) ? date1 : date2;
}
}
ISO 8601
Use standard ISO 8601 formats when serializing date-time values to text. Your format complies with the “basic” version of the standard, but better to use the full format when possible:
YYYY-MM-DD
Use the standard format for a date range, using a slash character as separator:
YYYY-MM-DD/YYYY-MM-DD
If you cannot alter the input strings, split the string on the hyphen. Parse each piece as a LocalDate. Use those objects to instantiate a LocalDateRange.
LocalDate ld = LocalDate.parse( "20170101" , DateTimeFormatter.BASIC_ISO_DATE ) ;
LocalDateRange
Use the LocalDateRange class from the ThreeTen-Extra project which extends java.time class functionality. Uses the standard format when parsing and generating text.
LocalDateRange range = LocalDateRange.parse( "2017-01-01/2017-03-31" ) ;
Collect in a List<LocalDateRange>.
To sort, write a comparator that calls LocalDateRange::getStart.
Compare to another range to see if they overlap. If so, combine with a call to union.
if ( range.overlaps( otherRange ) ) {
range = range.union( otherRange ) ;
}
If they do not overlap, you have finished that round. Store this result in another List<LocalDateRange>. Start another round with the next range.
Lather, rinse, repeat.
I assume your Interval is something like this:
private static class Interval {
private String begin;
private String end;
public Interval(String begin, String end) {
this.begin = begin;
this.end = end;
}
public String getStartDate() {
return begin;
}
public String getEndDate() {
return end;
}
}
What you need to do is to merge a Interval list. A solution is sort list with start date then end date. And then store the earliest start time and latest end time in a cursor variable. A example:
public List<Interval> merge(List<Interval> intervals) {
Collections.sort(intervals, new Comparator<Interval>() {
#Override
public int compare(Interval o1, Interval o2) {
if (o1.getStartDate().equals(o2.getStartDate())) {
return o1.getEndDate().compareTo(o2.getEndDate());
}
return o1.getStartDate().compareTo(o2.getStartDate());
}
});
List<Interval> ret = new ArrayList<>();
String MAX_VAL = "99999999";
String MIN_VAL = "00000000";
String start = MAX_VAL, end = MIN_VAL;
for (Interval interval : intervals) {
if (interval.getStartDate().compareTo(end) > 0) {
if (start.compareTo(MAX_VAL) < 0) {
ret.add(new Interval(start, end));
}
start = interval.getStartDate();
end = interval.getEndDate();
} else {
if (start.compareTo(interval.getStartDate()) < 0) {
start = interval.getStartDate();
}
if (end.compareTo(interval.getEndDate()) > 0) {
end = interval.getEndDate();
}
}
}
if (start.compareTo(MAX_VAL) < 0) {
ret.add(new Interval(start, end));
}
return ret;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
ArrayList<MainLab.Interval> list = new ArrayList<MainLab.Interval>();
list.add(new MainLab.Interval("20170430", "20170501"));
list.add(new MainLab.Interval("20170101", "20170430"));
list.add(new MainLab.Interval("20170101", "20170331"));
for (Iterator iterator = mergeInterval(list).iterator(); iterator.hasNext();) {
Interval interval = (Interval) iterator.next();
System.out.println(interval.getStart()+ "==="+interval.getEnd());
}
}
public static List<Interval> mergeInterval(ArrayList<MainLab.Interval> list){
/*
* Sort the list , Interval class have implemented Comparable Interface.
* So we will get sorted intervals. Intervals sorted based on start of interval
*/
Collections.sort(list);
Set<MainLab.Interval> resultlist = new TreeSet<MainLab.Interval>();
List<MainLab.Interval> mergedIntervals = new ArrayList<MainLab.Interval>();
//declare date formate to parse and format date from string to and from
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
if(list.size() == 1){
//resultlist = list
return list;
}
if(list.size() > 1){
// get first interval Object. conside it as first interval
Interval mergeInterval = list.get(0);
// loop other intervals from second in the list
for(int i=1; i< list.size() ; i++){
Interval interval2 = list.get(i);
try{
Date startDate1 = sdf.parse(mergeInterval.getStart());
Date endDate1 = sdf.parse(mergeInterval.getEnd());
Date startDate2 = sdf.parse(interval2.getStart());
Date endDate2 = sdf.parse(interval2.getEnd());
// compare if current interval's start date is before merging interval's end date
// then the two intervals are overlaping
if(startDate2.compareTo(endDate1) < 0 ){
// check whether end date of current loop interval is after the merging interval.
// then we need to update the end date of merging interval with looping interval's end date
if(endDate2.compareTo(endDate1) > 0 ){
mergeInterval.setEnd(interval2.getEnd());
}
}else{
// compare if current interval's start date is after merging interval's end date
// then it must be a new interval start so swap mergInterval variable with current looping interval
mergeInterval = interval2;
}
//add merge interval to set.
resultlist.add(mergeInterval);
}catch(Exception ex){
ex.printStackTrace();
}
}
}
mergedIntervals.addAll(resultlist);
return mergedIntervals;
}
public static class Interval implements Comparable<Interval>{
private String start;
private String end;
public String getStart() {
return start;
}
public void setStart(String start) {
this.start = start;
}
public String getEnd() {
return end;
}
public void setEnd(String end) {
this.end = end;
}
public Interval(){
}
public Interval(String start,String end){
this.start = start;
this.end = end;
}
#Override
public boolean equals(Object obj) {
// TODO Auto-generated method stub
Interval inteval = (Interval)obj;
return this.getStart().equals(inteval.getStart()) && this.getEnd().equals(inteval.getEnd()) ;
}
#Override
public int compareTo(Interval o) {
// TODO Auto-generated method stub
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
try{
Date startDate = sdf.parse(start);
Date endDate = sdf.parse(end);
Date pstartDate = sdf.parse(o.start);
Date pendDate = sdf.parse(o.end);
return startDate.compareTo(pstartDate);
}catch(Exception ex){
ex.printStackTrace();
}
return 0;
}
}

How to sort dates by Month & Day but exclude year

I have to sort a collection of person objects by Birthdate so I created the following pojo.
public class Person implements Comparable<Person> {
private long id;
private String name;
private Date birthDate;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getBirthDate() {
return birthDate;
}
public void setBirthDate(Date birthDate) {
this.birthDate = birthDate;
}
#Override
public int compareTo(Person person) {
if (getBirthDate() == null || person.getBirthDate() == null)
return 0;
return getBirthDate().compareTo(person.getBirthDate());
}
}
This does the job for sorting the birth dates but the additional requirement I have is to sort them by which birth date is closest to today ascending. So for example, if today is October 11, then the birthdays would be sorted as an example.
October 20th
November 5th
January 3rd...
In the way I currently have it since the year is part of the comparable the dates are showing from the oldest year to most recent year.
How can I sort these birthdates without the year?
This does the job for sorting the birth dates but the additional
requirement I have is to sort them by which birth date is closest to
today ascending. So for example, if today is October 11, then the
birthdays would be sorted as an example
If i understood you right, while sorting you are not considering the year e.g., 1975 or is it?. it seems to me that you are after a Birth Day remembering functionality. However, If you are sorting based upon month and day, Following comparedTo() function should give you some idea to compare based upon month and day excluding year:
class Person implements Comparable<Person>
{
Date birthDay;
static SimpleDateFormat formatter = new SimpleDateFormat("MMM dd, yyyy");
public Person(String birthDay) throws ParseException
{
this.birthDay = formatter.parse(birthDay);
}
#Override
public int compareTo(Person o) {
Calendar cal1 = Calendar.getInstance();
cal1.setTime(this.birthDay);
Calendar cal2 = Calendar.getInstance();
cal2.setTime(o.birthDay);
int month1 = cal1.get(Calendar.MONTH);
int month2 = cal2.get(Calendar.MONTH);
if(month1 < month2)
return -1;
else if(month1 == month2)
return cal1.get(Calendar.DAY_OF_MONTH) - cal2.get(Calendar.DAY_OF_MONTH);
else return 1;
}
}
Trying with following code Test:
List<Person>persons = new ArrayList<>();
persons.add(new Person("MAR 2, 2001"));
persons.add(new Person("JAN 7, 1972"));
persons.add(new Person("JAN 2, 1976"));
persons.add(new Person("MAR 4, 1985"));
Collections.sort(persons);
for(Person p : persons)
System.out.println(p.formatter.format(p.birthDay));
//Person.formatter is SimpleDateFormat with format "MMM dd, yyyy"
// in person class, i declared it as static
Output:
Jan 02, 1976
Jan 07, 1972
Mar 02, 2001
Mar 04, 1985
Getting the list sorted according to a pivot e.g, OCT 11
I think after sorting you just could round the list, thinking it is circular. For example suppose the sorted list:
JAN 20, FEB 5, SEP 18, OCT 9, OCT 20, NOV 23
If our pivot is OCT 11 choosing the immediate larger(smallest date larger than pivot) date to it, would be OCT 20. You can find it just using a for loop. Now, we just need to round it thinking it is circular:
OCT 20, NOV 23 --> JAN 20, FEB 5, SEP 18, OCT 9
Formally, find the index i of immediate larger date comparing to our pivot based upon month and day(try using the compareTo example), then create a new list, insert the element starting from the index i to n-1 and then 0 to i-1, here n is the size of the Birth Date list.
I hope that helps.
Write a Comparator
You can specify a comparator for Persons, which include the year, and another which excludes it.
You will need to sort your List, by calling:
Collections.sort(list, new BirthdayWithoutYearComparatr());
You have a compound series of problems.
First, I would not, personally, make the Person class Comparable, the main problem (I see with this), is that it's not really comparing the Person object, but just one of it's properties. This locks you in to a comparison that might not meet all of your requirements all of the time.
I would, personally, instead, set up a series of Comparators that did very specific jobs, for example, comparing the birth date of the Person.
The next problem is the comparable API doesn't support mismatched parameters, okay, it does, but it's messy...
What we need is some way that we can compare a object (or more specifically, a property of an object) with some other value.
Now, because the core API is based around comparing like values, we need to provide our own implementation to support our requirements.
So, the below, is a quick sort implementation that allows you to provide a List of objects a ISortMatcher and a value to be compared with (this is the base value that all the values in the list will be compared to)
import java.util.Collections;
import java.util.List;
public class QuickSort {
public static <O, M> void sort(List<O> values, M value, ISortMatcher<O, M> matcher) {
sort(values, value, matcher, 0, values.size() - 1);
}
protected static <O, M> void sort(List<O> values, M value, ISortMatcher<O, M> matcher, int low, int high) {
int i = low, j = high;
// Get the pivot element from the middle of the list
int pivot = matcher.compare(values.get(low + (high - low) / 2), value);
// Divide into two lists
while (i <= j) {
// If the current value from the left list is smaller then the pivot
// element then get the next element from the left list
while (matcher.compare(values.get(i), value) < pivot) {
i++;
}
// If the current value from the right list is larger then the pivot
// element then get the next element from the right list
while (matcher.compare(values.get(j), value) > pivot) {
j--;
}
// If we have found a values in the left list which is larger then
// the pivot element and if we have found a value in the right list
// which is smaller then the pivot element then we exchange the
// values.
// As we are done we can increase i and j
if (i <= j) {
Collections.swap(values, i, j);
i++;
j--;
}
}
// Recursion
if (low < j) {
sort(values, value, matcher, low, j);
}
if (i < high) {
sort(values, value, matcher, i, high);
}
}
public static interface ISortMatcher<O, M> {
public int compare(O o, M m);
}
}
Okay, this is a very specific requirement for the sort and acts a little like a binary search, but it will do...
What this allows us to do is define the base value we want to match, the algorithm by which the comparison is carried out with and the list of values to be sorted. It works on a similar concept to the core API, returning values less then, equal to or greater then 0 to indicate the weight of the result.
Now, we can start comparing...
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
public class SortDates {
public static final SimpleDateFormat SDF = new SimpleDateFormat("dd/MM/yyyy");
public static void main(String[] args) {
new SortDates();
}
public SortDates() {
List<Person> people = new ArrayList<Person>(5);
for (int index = 0; index < 10; index++) {
Date date = getDate(
(int)(Math.random() * 30) + 1,
(int)(Math.random() * 12),
(int)(Math.random() * 113) + 1900);
people.add(new Person(date));
}
Collections.sort(people, new DateOfBirthComparator());
System.out.println("By date of birth");
for (Person p : people) {
System.out.println(p);
}
Collections.shuffle(people);
System.out.println("");
System.out.println("Shuffled");
for (Person p : people) {
System.out.println(p);
}
QuickSort.ISortMatcher matcher = new QuickSort.ISortMatcher<Person, Date>() {
#Override
public int compare(Person o, Date m) {
Calendar with = Calendar.getInstance();
with.setTime(m);
Calendar to = Calendar.getInstance();
to.setTime(o.getBirthDate());
to.set(Calendar.YEAR, with.get(Calendar.YEAR));
int withDOY = with.get(Calendar.DAY_OF_YEAR);
int toDOY = to.get(Calendar.DAY_OF_YEAR);
int result = 0;
if (withDOY < toDOY) {
result = toDOY - withDOY;
} else if (withDOY > toDOY) {
result = withDOY - toDOY;
}
return result;
}
};
QuickSort.sort(people, new Date(), matcher);
System.out.println("");
System.out.println("To today (" + SDF.format(new Date()) + ")");
for (Person p : people) {
System.out.println(p);
}
}
public Date getDate(int day, int month, int year) {
Calendar cal = Calendar.getInstance();
cal.set(Calendar.DATE, day);
cal.set(Calendar.MONTH, month);
cal.set(Calendar.YEAR, year);
return cal.getTime();
}
public class Person {
private Date birthDate;
public Person(Date birthDate) {
this.birthDate = birthDate;
}
#Override
public String toString() {
return SDF.format(birthDate);
}
public Date getBirthDate() {
return birthDate;
}
}
public class DateOfBirthComparator implements Comparator<Person> {
#Override
public int compare(Person o1, Person o2) {
return o1.getBirthDate().compareTo(o2.getBirthDate());
}
}
}
At the heart of the example, is the QuickSort Matcher. This determines the weight to be applied between a given value and the value we want to use as the base for the comparison.
QuickSort.ISortMatcher matcher = new QuickSort.ISortMatcher<Person, Date>() {
#Override
public int compare(Person o, Date m) {
Calendar with = Calendar.getInstance();
with.setTime(m);
Calendar to = Calendar.getInstance();
to.setTime(o.getBirthDate());
to.set(Calendar.YEAR, with.get(Calendar.YEAR));
int withDOY = with.get(Calendar.DAY_OF_YEAR);
int toDOY = to.get(Calendar.DAY_OF_YEAR);
int result = 0;
if (withDOY < toDOY) {
result = toDOY - withDOY;
} else if (withDOY > toDOY) {
result = withDOY - toDOY;
}
return result;
}
};
Now, I've taken you requirement literally, this will sort the values based on their distance from the given Date, so you may end up with dates before the specified date appearing after dates after the specified date...
For example,
By date of birth
05/10/1905
01/10/1906
13/03/1921
11/04/1942
07/12/1944
27/04/1953
05/07/1988
15/12/1988
19/03/1995
12/07/2001
Shuffled
13/03/1921
01/10/1906
05/07/1988
12/07/2001
11/04/1942
27/04/1953
19/03/1995
15/12/1988
07/12/1944
05/10/1905
To today (12/10/2013)
05/10/1905
01/10/1906
07/12/1944
15/12/1988
12/07/2001
05/07/1988
27/04/1953
11/04/1942
19/03/1995
13/03/1921
You can see that 05/10 appears before 07/12 as it is closer to the target date...

Calculate number of weekdays between two dates in Java

Can anyone point me to some Java snippet wherein i can get business (except Sat and Sun) days between two dates.
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;
}
Start date and end date are exclusive, Only the days between given
dates will be counted. Start date and end date will not be included.
Solution without loop:
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;
}
Solution without loop in 5 lines of code
Days between are defined in the same way as ChronoUnit.DAYS.between(start, end) which means there are 4 days between Monday and Friday. Since we are only interested in weekdays we have to subtract weekends, therefore from Friday until Tuesday there will be 2 weekdays(just compute endDay - startDay and subtract 2 for the weekend). Add 1 to the result if you want an inclusive result, i.e. not days between.
I present two solutions.
First solution (5-liner, short and cryptic):
import java.time.*;
import java.time.temporal.*;
public static long calcWeekDays1(final LocalDate start, final 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);
}
Second solution:
public static long calcWeekDays2(final LocalDate start, final LocalDate end) {
final int startW = start.getDayOfWeek().getValue();
final int endW = end.getDayOfWeek().getValue();
final long days = ChronoUnit.DAYS.between(start, end);
long result = days - 2*(days/7); //remove weekends
if (days % 7 != 0) { //deal with the rest days
if (startW == 7) {
result -= 1;
} else if (endW == 7) { //they can't both be Sunday, otherwise rest would be zero
result -= 1;
} else if (endW < startW) { //another weekend is included
result -= 2;
}
}
return result;
}
java.time
The modern way is with the java.time classes.
LocalDate
The LocalDate class represents a date-only value without time-of-day and without time zone.
LocalDate start = LocalDate.of( 2016 , 1 , 23 );
LocalDate stop = start.plusMonths( 1 );
DayOfWeek enum
The DayOfWeek enum provides a singleton instance for each of the sever days of the week.
DayOfWeek dow = start.getDayOfWeek();
if( dow.equals( DayOfWeek.SATURDAY ) || dow.equals( DayOfWeek.SUNDAY ) ) …
We can collect the desired dates in a 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 );
…
An EnumSet is an extremely efficient, fast and low-memory, implementation of Set. We can use an EnumSet instead of the if statement seen above.
Set<DayOfWeek> weekend = EnumSet.of( DayOfWeek.SATURDAY , DayOfWeek.SUNDAY ) ;
…
if( weekend.contains( dayOfWeek ) ) …
Put that all together.
LocalDate date = start ;
while( date.isBefore( stop ) ) {
if( ! weekend.contains( date.getDayOfWeek() ) ) { // If not weekend, collect this LocalDate.
dates.add( date ) ;
}
// Prepare for next loop.
date = date.plusDays( 1 ); // Increment to next day.
}
nextWorkingDay TemporalAdjuster
Another approach uses the ThreeTen-Extra project to add classes that work with java.time.
The Temporals class adds additional implementations of TemporalAdjuster for manipulating date-time values. We want the nextWorkingDay adjuster to increment the date while skipping over Saturday & Sunday.
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( org.threeten.extra.Temporals.nextWorkingDay() );
// Double-check ending date as the `nextWorkingDay` adjuster could move us past the stop date.
if( date.isBefore( stop ) ) {
dates.add( date ) ;
}
}
Performance
I am curious about the performance of the various approach in various Answers on this page. I am considering only the modern java.time code, not the code using troublesome legacy Date/Calendar classes.
Here are four methods that each return the number of days elapsed.
One uses the clever math-based approach seen in the Answer by Roland.
private long countWeekDaysMath ( LocalDate start , LocalDate stop ) {
// Code taken from Answer by Roland.
// https://stackoverflow.com/a/44942039/642706
long count = 0;
final DayOfWeek startW = start.getDayOfWeek();
final DayOfWeek stopW = stop.getDayOfWeek();
final long days = ChronoUnit.DAYS.between( start , stop );
final long daysWithoutWeekends = days - 2 * ( ( days + startW.getValue() ) / 7 );
//adjust for starting and ending on a Sunday:
count = daysWithoutWeekends + ( startW == DayOfWeek.SUNDAY ? 1 : 0 ) + ( stopW == DayOfWeek.SUNDAY ? 1 : 0 );
return count;
}
Two use approaches seen in this Answer of mine: (a) Visit each date, incrementing one-by-one in a conventional loop.
private long countWeekDaysVisit ( LocalDate start , LocalDate stop ) {
// Code taken from Answer by Basil Bourque.
// https://stackoverflow.com/a/40369140/642706
long count = 0;
Set < DayOfWeek > weekend = EnumSet.of( DayOfWeek.SATURDAY , DayOfWeek.SUNDAY );
LocalDate ld = start;
while ( ld.isBefore( stop ) ) {
if ( ! weekend.contains( ld.getDayOfWeek() ) ) { // If not weekend, collect this LocalDate.
count++;
}
// Prepare for next loop.
ld = ld.plusDays( 1 ); // Increment to next day.
}
return count;
}
…and, (b) Using the TemporalAdjuster implementation org.threeten.extra.Temporals.nextWorkingDay().
private long countWeekDaysAdjuster ( LocalDate start , LocalDate stop ) {
// Code taken from Answer by Basil Bourque.
// https://stackoverflow.com/a/40369140/642706
long count = 0;
Set < DayOfWeek > weekend = EnumSet.of( DayOfWeek.SATURDAY , DayOfWeek.SUNDAY );
TemporalAdjuster nextWorkingDayTA = org.threeten.extra.Temporals.nextWorkingDay();
LocalDate ld = start;
if ( weekend.contains( ld.getDayOfWeek() ) ) {
ld = ld.with( nextWorkingDayTA );
}
while ( ld.isBefore( stop ) ) {
count++;
// Prepare for next loop.
ld = ld.with( nextWorkingDayTA ); // Increment to next working day (non-weekend day).
}
return count;
}
The last uses Java Streams approach seen in the Answer by Ravindra Ranwala.
private long countWeekDaysStream ( LocalDate start , LocalDate stop ) {
// Code taken from the Answer by Ravindra Ranwala.
// https://stackoverflow.com/a/51010738/642706
long count = 0;
Set < DayOfWeek > weekend = EnumSet.of( DayOfWeek.SATURDAY , DayOfWeek.SUNDAY );
final long weekDaysBetween = start.datesUntil( stop )
.filter( d -> ! weekend.contains( d.getDayOfWeek() ) )
.count();
return count;
}
And the test harness.
Caveats:
Well, the usual caveats about micro-benchmarking being untrustworthy, prone to unjustified or unrealistic conclusions.
I wish I'd learned to use the JMH micro-benchmarking framework.
I have not bothered to try optimizing any of this code. For example, in real work, the TemporalAdjuster could be cached outside our method.
Test harness.
LocalDate start = LocalDate.of( 2018 , Month.JANUARY , 1 );
LocalDate stop = start.plusYears( 1 );
int runs = 100_000;
long go = System.nanoTime();
for ( int i = 1 ; i <= runs ; i++ ) {
long count = this.countWeekDaysMath( start , stop );
}
long elapsedMath = ( System.nanoTime() - go );
go = System.nanoTime();
for ( int i = 1 ; i <= runs ; i++ ) {
long count = this.countWeekDaysVisit( start , stop );
}
long elapsedVisit = ( System.nanoTime() - go );
go = System.nanoTime();
for ( int i = 1 ; i <= runs ; i++ ) {
long count = this.countWeekDaysStream( start , stop );
}
long elapsedAdjuster = ( System.nanoTime() - go );
go = System.nanoTime();
for ( int i = 1 ; i <= runs ; i++ ) {
long count = this.countWeekDaysStream( start , stop );
}
long elapsedStream = ( System.nanoTime() - go );
System.out.println( "math: " + elapsedMath + " each: " + ( elapsedMath / runs ) );
System.out.println( "visit: " + elapsedVisit + " each: " + ( elapsedVisit / runs ) );
System.out.println( "adjuster: " + elapsedAdjuster + " each: " + ( elapsedAdjuster / runs ) );
System.out.println( "stream: " + elapsedStream + " each: " + ( elapsedStream / runs ) );
When run on my MacBook Pro (Sierra) with Oracle JDK 10.0.1 and ThreeTen-Extra version 1.3.2, I get results consistently close to the following. The math solution is a tiny fraction of the others at a couple hundred nanos versus several thousand, as we would expect obviously. Of the other three, the TemporalAdjuster is the longest, always over 10,000 nanos each. The visit and stream both come in well under that 10,000 nanos each, with visit being noticeably faster than streams. As seen in other examples around the internets, Java Streams usually make for nifty short code while often running significantly longer, about 20% longer in this case.
math: 18313309 each: 183
visit: 708420626 each: 7084
adjuster: 1002157240 each: 10021
stream: 924724750 each: 9247
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.
The Joda-Time project, now in maintenance mode, advises migration to java.time.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
Where to obtain the java.time classes?
Java SE 8 and SE 9 and later
Built-in.
Part of the standard Java API with a bundled implementation.
Java 9 adds some minor features and fixes.
Java SE 6 and SE 7
Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
Android
The ThreeTenABP project adapts ThreeTen-Backport (mentioned above) for Android specifically.
See How to use….
The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.
I used Shengyuan Lu's solution, but I needed to make a fix for the case where the method is called when one of the dates is on a Saturday and the other a Sunday - otherwise the answer is off by a day:
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;
}
Almost all the solutions are pretty much obsoleted and narrative. However here's a much condensed and readable solution.
This approach uses a Java Stream provided by the LocalDate::datesUntil method built into in Java 9 and later.
LocalDate startDate = LocalDate.of(2018, 5, 2);
LocalDate endDate = LocalDate.now();
Set<DayOfWeek> weekend = EnumSet.of(DayOfWeek.SATURDAY, DayOfWeek.SUNDAY);
final long weekDaysBetween = startDate.datesUntil(endDate)
.filter(d -> !weekend.contains(d.getDayOfWeek()))
.count();
.datesUntil returns a sequential ordered stream of dates. The
returned stream starts from this date (inclusive) and goes to
endExclusive (exclusive) by an incremental step of 1 day.
Then all the Saturdays and Sundays are filtered out. Final step is to get the count of the remaining week days.
Java-9 has been released one year ago, since using it now seems reasonable to me.
This thread is filled with failing solutions... I started by writing a little test file which met my needs, and saw that Roland's both solutions fails, Amir's too. I wanted a solution that uses java 8 and that does not uses loops because, do I have to say why ?
So here's the test file :
#Test
public void test() {
LocalDate d1 = LocalDate.of(2018, 8, 1);
LocalDate d2 = LocalDate.of(2018, 8, 2);
LocalDate d3 = LocalDate.of(2018, 8, 3);
LocalDate d4 = LocalDate.of(2018, 8, 4);
LocalDate d5 = LocalDate.of(2018, 8, 5);
LocalDate d6 = LocalDate.of(2018, 8, 6);
LocalDate d7 = LocalDate.of(2018, 8, 7);
LocalDate d8 = LocalDate.of(2018, 8, 8);
LocalDate d9 = LocalDate.of(2018, 8, 9);
LocalDate d10 = LocalDate.of(2018, 8, 10);
LocalDate d15 = LocalDate.of(2018, 8, 15);
LocalDate dsep = LocalDate.of(2018, 9, 5);
// same day : 0 days between
Assert.assertEquals(0, DateUtils.calcWeekDays1(d1, d1));
Assert.assertEquals(1, DateUtils.calcWeekDays1(d1, d2));
Assert.assertEquals(2, DateUtils.calcWeekDays1(d1, d3));
// end on week-end
Assert.assertEquals(2, DateUtils.calcWeekDays1(d1, d4));
Assert.assertEquals(2, DateUtils.calcWeekDays1(d1, d5));
// next week
Assert.assertEquals(3, DateUtils.calcWeekDays1(d1, d6));
Assert.assertEquals(4, DateUtils.calcWeekDays1(d1, d7));
Assert.assertEquals(5, DateUtils.calcWeekDays1(d1, d8));
Assert.assertEquals(6, DateUtils.calcWeekDays1(d1, d9));
Assert.assertEquals(7, DateUtils.calcWeekDays1(d1, d10));
// start on saturday
Assert.assertEquals(0, DateUtils.calcWeekDays1(d4, d5));
Assert.assertEquals(0, DateUtils.calcWeekDays1(d4, d6));
Assert.assertEquals(1, DateUtils.calcWeekDays1(d4, d7));
// start on sunday
Assert.assertEquals(0, DateUtils.calcWeekDays1(d5, d5));
Assert.assertEquals(0, DateUtils.calcWeekDays1(d5, d6));
Assert.assertEquals(1, DateUtils.calcWeekDays1(d5, d7));
// go to next week
Assert.assertEquals(10, DateUtils.calcWeekDays1(d1, d15));
// next month
Assert.assertEquals(25, DateUtils.calcWeekDays1(d1, dsep));
// start sat, go to next month
Assert.assertEquals(22, DateUtils.calcWeekDays1(d4, dsep));
}
And here is my proposed solution, quite simple. Just let java count the number of weeks, multiply by five, and add the number of days needed to compensate the difference ; the only trick is adjusting the start and end to avoid week-ends :
public static long calcWeekDays1(LocalDate start, LocalDate end) {
if (start.getDayOfWeek().getValue() > 5) {
start = start.with(TemporalAdjusters.next(DayOfWeek.MONDAY));
}
if (end.getDayOfWeek().getValue() > 5) {
end = end.with(TemporalAdjusters.previous(DayOfWeek.FRIDAY));
}
if (start.isAfter(end)) { // may happen if you start sat. and end sunday
return 0;
}
long weeks = ChronoUnit.WEEKS.between(start, end);
if (start.getDayOfWeek().getValue() > end.getDayOfWeek().getValue()) {
weeks += 1;
}
return 5 * weeks + end.getDayOfWeek().getValue() - start.getDayOfWeek().getValue();
}
And now I will look stupid if my code fails too :)
I don't have a Java based solution, but have a PHP one, hope it helps:
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()));
}
This is my example without looping. Algorithm is same as 卢声远 Shengyuan Lus one but I used some features of 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);
}
The do while in the solution of Piyush is wrong, it should be :
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());
The startCal.add should add onto the Calendar.DATE field, not the Calendar.DAY_OF_MONTH, I was getting weird results with over Decemeber / January period.
This is my example without looping. It is a class in this example because I serialize it in some JSON output. Basically I work out the number of days between the two dates, divide by 7 and assign to a long to have a integer value for the number of weeks. Take the original number of days and subtract the number of weekends*2. This isn't quite perfect - you need to work out if there is a 'hangover' where the start is close to the end of the week and goes over the weekend. To correct for this I find the day of the week at the start and find the remainder of the number of days, and add those together to find the 'hangover' - and if it is more than 5 it is a weekend. It isn't quite perfect, and does not account for holidays at all. And no Joda in sight. That said there is also a issue with timezones.
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;
}
}
Solution for Java 8 without loop and INCLUSIVE intervals:
public long getDaysWithoutSundays(LocalDate startDate, LocalDate endDate) {
long numberOfDays = ChronoUnit.DAYS.between(startDate, endDate) + 1;
long numberOfSundays = numberOfDays / 7;
long rest = numberOfDays % 7;
if (rest > 0) {
int startToEnd = startDate.getDayOfWeek().getValue() - endDate.getDayOfWeek().getValue();
if (startToEnd > 0) {
numberOfSundays++;
}
else {
if (endDate.getDayOfWeek().equals(DayOfWeek.SUNDAY)) {
numberOfSundays++;
}
}
}
return numberOfDays - numberOfSundays;
}
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
/**
*
* #author varun.vishwakarma
*
*/
public class FindWeekendsInDateRange {
static HashMap<Integer, String> daysOfWeek=null;
static {
daysOfWeek = new HashMap<Integer, String>();
daysOfWeek.put(new Integer(1), "Sun");
daysOfWeek.put(new Integer(2), "Mon");
daysOfWeek.put(new Integer(3), "Tue");
daysOfWeek.put(new Integer(4), "Wed");
daysOfWeek.put(new Integer(5), "Thu");
daysOfWeek.put(new Integer(6), "Fri");
daysOfWeek.put(new Integer(7), "Sat");
}
/**
*
* #param from_date
* #param to_date
* #return
*/
public static List<Date> calculateWeekendsInDateReange(Date fromDate, Date toDate) {
List<Date> listOfWeekends = new ArrayList<Date>();
Calendar from = Calendar.getInstance();
Calendar to = Calendar.getInstance();
from.setTime(fromDate);
to.setTime(toDate);
while (from.getTimeInMillis() < to.getTimeInMillis()) {
if (daysOfWeek.get(from.get(Calendar.DAY_OF_WEEK)) == "Sat") {
Date sat = from.getTime();
listOfWeekends.add(sat);
} else if (daysOfWeek.get(from.get(Calendar.DAY_OF_WEEK)) == "Sun") {
Date sun = from.getTime();
listOfWeekends.add(sun);
}
from.add(Calendar.DAY_OF_MONTH, 1);
}
return listOfWeekends;
}
public static void main(String[] args) {
String fromDate = "7-Oct-2019";
String toDate = "25-Oct-2019";
System.out.println(FindWeekendsInDateRange.calculateWeekendsInDateReange(new Date(fromDate), new Date(toDate)));
}
}
I'm surprised that every solution given here fails to account for business holidays, which occur in most countries every few months at least, so any span more than a few months will give an incorrect answer. Fortunately, Ravindra's answer using Streams is, despite the inconsequential (for spans under ~10 years) overhead, the best one because it is trivial to correct:
import java.time.*;
import java.util.*;
import static java.time.temporal.ChronoUnit.DAYS;
public class WeekDaysLeft {
public static void main(String[] args) {
LocalDate startDate = LocalDate.now(); // Early 2023
LocalDate endDate = LocalDate.of(2023, 6, 23);
Set<DayOfWeek> weekendDays =
EnumSet.of(DayOfWeek.SATURDAY, DayOfWeek.SUNDAY);
Set<LocalDate> holidays =
Set.of(LocalDate.of(2023,4,7),
LocalDate.of(2023,4,10));
final long weekDaysBetween =
startDate.datesUntil(endDate)
.filter(d -> !weekendDays.contains(
d.getDayOfWeek()))
.filter(d -> !holidays.contains(d))
.count();
System.out.println(weekDaysBetween);
}
}
In regard to my claim that the overhead is inconsequential:
Run my solution under time(1) on Unix/Linux
e.g., time java WeekDaysLeft.java
Change the end-date to 2033, and repeat step 1.
On my system, the user time went DOWN slightly (user time up trivially).
And, of course, if you're running this after June, 2023, bump all the years given.
This program considers loop approach but consider activities happened on after work hours to next working day office start hour
public 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);
}
}
In 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
}
Using java 8 it can be easily done, example function:
long getBusinessDaysDifference(LocalDate startDate, LocalDate endDate) {
EnumSet<DayOfWeek> weekend = EnumSet.of(DayOfWeek.SATURDAY, DayOfWeek.SUNDAY);
List<LocalDate> list = Lists.newArrayList();
LocalDate start = startDate;
while (start.isBefore(endDate)) {
list.add(start);
start = start.plus(1, ChronoUnit.DAYS);
}
long numberOfDays = list.stream().filter(d -> !weekend.contains(d.getDayOfWeek())).count();
return numberOfDays;
}
Description:
Define your off-days in an EnumSet (weekends in this case).
Create a list holding all the days between the startDate and endDate.
Reduce the outcome list by removing any occurrence of a day from the EnumSet.
Then finally count the size of this reduced list.
Note: this function can be optimized, but might be helpful as a starting point.
public long getNumberOfWeekDayBetweenDates(LocalDate startDate, LocalDate endDate, String dayOfWeek) {
long result = -1;
if (startDate != null && endDate != null && dayOfWeek != null && (startDate.isBefore(endDate) || startDate.isEqual(endDate))) {
java.time.DayOfWeek givenDayOfWeek = java.time.DayOfWeek.valueOf(dayOfWeek);
// find the first given day of week in the interval
LocalDate firstOccurrence = startDate.with(TemporalAdjusters.nextOrSame(givenDayOfWeek));
// similarly find last Monday
LocalDate lastOccurrence = endDate.with(TemporalAdjusters.previousOrSame(givenDayOfWeek));
if (firstOccurrence != null && lastOccurrence != null) {
// count the number of weeks between the first and last occurrence, then add 1 as end day is exclusive
result = ChronoUnit.WEEKS.between(firstOccurrence, lastOccurrence) + 1;
} else if (firstOccurrence == null && lastOccurrence == null) {
// no occurrence
result = 0;
} else {
result = 1;
}
}
return result;
}
Here is a set-based solution that completes in constant time for any given subset of weekdays, not just Monday-Friday. It splits the problem into counting full weeks and counting the days in the residual week. If you are interested, here's a detailed explanation and a formal proof that the algorithm is correct. Note that the intervals are inclusive, i.e. startDate and endDate are counted in. If startDate is after endDate, the result is zero rather than negative.
long countWeekDays(LocalDate startDate, LocalDate endDate, Set<DayOfWeek> daysOfWeek) {
long periodLength = Math.max(0, ChronoUnit.DAYS.between(startDate, endDate) + 1);
long fullWeeks = periodLength / 7;
long residualWeekLength = periodLength % 7;
Set<DayOfWeek> residualWeekDays = LongStream.range(0, residualWeekLength)
.mapToObj(offset -> startDate.plusDays(offset).getDayOfWeek())
.collect(Collectors.toSet());
residualWeekDays.retainAll(daysOfWeek);
return fullWeeks * daysOfWeek.size() + residualWeekDays.size();
}
For the original problem (Monday-Friday) it is called e.g. with:
countWeekDays(
LocalDate.of(2016, 2, 8),
LocalDate.of(2016, 2, 26),
new HashSet(Arrays.asList(
DayOfWeek.MONDAY,
DayOfWeek.TUESDAY,
DayOfWeek.WEDNESDAY,
DayOfWeek.THURSDAY,
DayOfWeek.FRIDAY
)
)
)
This assumes you are working with inclusive intervals. If you want to skip the first day of the interval, just add one day to the first parameter:
countWeekDays(
LocalDate.of(2016, 2, 8).plusDays(1),
LocalDate.of(2016, 2, 26),
new HashSet(Arrays.asList(
DayOfWeek.MONDAY,
DayOfWeek.TUESDAY,
DayOfWeek.WEDNESDAY,
DayOfWeek.THURSDAY,
DayOfWeek.FRIDAY
)
)
)
Likewise, if you want to skip the last day of the inclusive interval, subtract one day from the second parameter:
countWeekDays(
LocalDate.of(2016, 2, 8),
LocalDate.of(2016, 2, 26).minusDays(1),
new HashSet(Arrays.asList(
DayOfWeek.MONDAY,
DayOfWeek.TUESDAY,
DayOfWeek.WEDNESDAY,
DayOfWeek.THURSDAY,
DayOfWeek.FRIDAY
)
)
)
Finally, if you need to skip both interval-delimiting days, combine the two previous modifications:
countWeekDays(
LocalDate.of(2016, 2, 8).plusDays(1),
LocalDate.of(2016, 2, 26).minusDays(1),
new HashSet(Arrays.asList(
DayOfWeek.MONDAY,
DayOfWeek.TUESDAY,
DayOfWeek.WEDNESDAY,
DayOfWeek.THURSDAY,
DayOfWeek.FRIDAY
)
)
)
For LocalDate supported by latest java version you can try below function.
It provides support of functiongetDayOfWeek().
The getDayOfWeek() method of LocalDate class in Java gets the day-of-week field, which is an enum DayOfWeek.
public static int getWeekEndCount(LocalDate fromDate, LocalDate toDate) {
int saturday = 0;
int sunday = 0;
while (!fromDate.isAfter(toDate)) {
if (fromDate.getDayOfWeek().equals(DayOfWeek.SATURDAY))
saturday++;
else if (fromDate.getDayOfWeek().equals(DayOfWeek.SUNDAY))
sunday++;
fromDate = fromDate.plusDays(1);
}
System.out.println("Saturday count=="+saturday);
System.out.println("Sunday count=="+sunday);
return saturday+sunday;
}

How can I add business days to the current date in Java?

How can I add business days to the current date in Java?
public Calendar addBusinessDate(Calendar cal, int days) {
//
// code goes over here
//
}
Note:
It should consider weekends too.
You may want to consider using ObjectLab Kit to do the heavy lifting for you.
Assuming the requirement is simply to return the next business day when the computed date falls on a non-business day:
package bizdays.example;
import java.time.LocalDate;
import java.util.HashSet;
import net.objectlab.kit.datecalc.common.DateCalculator;
import net.objectlab.kit.datecalc.common.DefaultHolidayCalendar;
import net.objectlab.kit.datecalc.common.HolidayHandlerType;
import net.objectlab.kit.datecalc.jdk8.LocalDateKitCalculatorsFactory;
import static org.junit.Assert.assertThat;
import org.junit.Before;
import org.junit.Test;
import static org.hamcrest.Matchers.equalTo;
public class BizDayTest {
private DateCalculator<LocalDate> dateCalculator;
private final LocalDate startDate = LocalDate.of(2009, 12, 23);
#Before
public void setUp() {
HashSet<LocalDate> holidays = new HashSet<LocalDate>();
holidays.add(LocalDate.of(2009, 12, 25)); // Friday
DefaultHolidayCalendar<LocalDate> holidayCalendar =
new DefaultHolidayCalendar<LocalDate>(holidays);
LocalDateKitCalculatorsFactory.getDefaultInstance()
.registerHolidays("example", holidayCalendar);
dateCalculator = LocalDateKitCalculatorsFactory.getDefaultInstance()
.getDateCalculator("example", HolidayHandlerType.FORWARD);
dateCalculator.setStartDate(startDate);
}
#Test
public void should_not_change_calendar_start_date_even_after_moving() {
assertThat(
dateCalculator.moveByBusinessDays(6).getStartDate(),
equalTo(startDate));
}
#Test
public void moveByBusinessDays_will_return_24_dec_2009_as_next_business_day() {
assertThat(
dateCalculator.moveByBusinessDays(1).getCurrentBusinessDate(),
equalTo(LocalDate.of(2009, 12, 24)));
}
#Test
public void moveByBusinessDays_will_return_28_dec_2009_as_two_business_days_later() {
assertThat(
dateCalculator.moveByBusinessDays(2).getCurrentBusinessDate(),
equalTo(LocalDate.of(2009, 12, 28)));
}
#Test
public void moveByDays_will_also_return_28_dec_2009_as_two_business_days_later() {
assertThat(
dateCalculator.moveByDays(2).getCurrentBusinessDate(),
equalTo(LocalDate.of(2009, 12, 28)));
}
#Test
public void moveByBusinessDays_will_exclude_25_26_and_27_dec_when_computing_business_days() {
assertThat(
dateCalculator.moveByBusinessDays(5).getCurrentBusinessDate(),
equalTo(LocalDate.of(2009, 12, 31)));
}
#Test
public void moveByDays_will_include_25_26_and_27_dec_when_computing_business_days() {
assertThat(
dateCalculator.moveByDays(5).getCurrentBusinessDate(),
equalTo(LocalDate.of(2009, 12, 28)));
}
}
The library defaults the working week to be from Monday to Friday, but you can change the defaults by supplying a custom WorkingWeek to DateCalculator's setWorkingWeek().
As shown in the last two examples, moveByDays() includes the weekends when moving the days, whereas moveByBusinessDays() excludes weekends.
The library also allows you to use java.util.Calendar or Joda Time's LocalDate. The examples use JDK8's java.time.LocalDate because it is the preferred way since JDK8.
Edit: Updated examples to use java.time.LocalDate
Use:
public Calendar addBusinessDate(Calendar cal, int numBusinessDays) {
int numNonBusinessDays = 0;
for(int i = 0; i < numBusinessDays; i++) {
cal.add(Calendar.DATE, 1);
/*
It's a Canadian/American custom to get the Monday (sometimes Friday) off
when a holiday falls on a weekend.
*/
for(int j = 0; j < holidays; j++) { //holidays is list of dates
if(cal.getTime() == (Date)holidays.get(j)) {
numNonBusinessDays++;
}
}
if(cal.get(Calendar.DAY_OF_WEEK) == 1 ||
cal.get(Calendar.DAY_OF_WEEK) == 7) {
numNonBusinessDays++;
}
}
if(numNonBusinessDays > 0) {
cal.add(Calendar.DATE, numNonBusinessDays);
}
return cal;
}
You'd have to populate a list of dates in order to handle holidays. There's common ones like New Years, but Thanksgiving is different between Canada & the US for instance. Also mind that holidays can fall on a weekend, so the weekend becomes a 3 day weekend.
Reference:
Calendar
Calendar Constant Values
PS: There isn't really a need to return the Calendar instance if you are updating the value as in the example. But it is valid if you want to create a separate Calendar instance, use:
public Calendar addBusinessDate(Calendar cal, int numBusinessDays) {
Calendar cal2 = Calendar.getInstance();
cal2.setTime(cal.getTime());
int numNonBusinessDays = 0;
for(int i = 0; i < numBusinessDays; i++) {
cal2.add(Calendar.DATE, 1);
/*
It's a Canadian/American custom to get the Monday (sometimes Friday) off
when a holiday falls on a weekend.
*/
for(int j = 0; j < holidays; j++) { //holidays is list of dates
if(cal2.getTime() == (Date)holidays.get(j)) {
numNonBusinessDays++;
}
}
if(cal2.get(Calendar.DAY_OF_WEEK) == 1 ||
cal2.get(Calendar.DAY_OF_WEEK) == 7) {
numNonBusinessDays++;
}
}
if(numNonBusinessDays > 0) {
cal2.add(Calendar.DATE, numNonBusinessDays);
}
return cal2;
}
Here is the modified version to find date calculation.
public Calendar algorithm2(int businessDays){
Calendar cal2 = Calendar.getInstance();
Calendar cal = Calendar.getInstance();
int totalDays= businessDays/5*7;
int remainder = businessDays % 5;
cal2.add(cal2.DATE, totalDays);
switch(cal.get(Calendar.DAY_OF_WEEK)){
case 1:
break;
case 2:
break;
case 3:
if(remainder >3)
cal2.add(cal2.DATE,2);
break;
case 4:
if(remainder >2)
cal2.add(cal2.DATE,2);
break;
case 5:
if(remainder >1)
cal2.add(cal2.DATE,2);
break;
case 6:
if(remainder >1)
cal2.add(cal2.DATE,2);
break;
case 7:
if(remainder >1)
cal2.add(cal2.DATE,1);
break;
}
cal2.add(cal2.DATE, remainder);
return cal2;
}
//supports negative numbers too.
private Calendar addBusinessDay(final Calendar cal, final Integer numBusinessDays)
{
if (cal == null || numBusinessDays == null || numBusinessDays.intValue() == 0)
{
return cal;
}
final int numDays = Math.abs(numBusinessDays.intValue());
final int dateAddition = numBusinessDays.intValue() < 0 ? -1 : 1;//if numBusinessDays is negative
int businessDayCount = 0;
while (businessDayCount < numDays)
{
cal.add(Calendar.DATE, dateAddition);
//check weekend
if (cal.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY || cal.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY)
{
continue;//adds another day
}
//check holiday
if (isHoliday(cal))//implement isHoliday yourself
{
continue;//adds another day
}
businessDayCount++;
}
return cal;
}
public static Date addBusinessDays(Date date, int days) {
DateTime result = new DateTime(date);
result = isWeekEnd(result)
? getPreviousBusinessDate(result)
: result;
for (int i = 0; i < days; i++) {
if (isWeekEnd(result)) {
i--;
}
result = result.plusDays(1);
}
return result.toDate();
}
private static boolean isWeekEnd(DateTime dateTime) {
int dayOfWeek = dateTime.getDayOfWeek();
return dayOfWeek == DateTimeConstants.SATURDAY || dayOfWeek == DateTimeConstants.SUNDAY;
}
private static DateTime getPreviousBusinessDate(DateTime result) {
while (isWeekEnd(result)) {
result = result.minusDays(1);
}
return result;
}
Will this work? Of course, this is not handling holidays.
public static Date
addBusinessDays(Date baseDate, int
numberOfDays){
if(baseDate == null){
baseDate = new Date();
}
Calendar baseDateCal = Calendar.getInstance();
baseDateCal.setTime(baseDate);
for(int i = 0; i < numberOfDays; i++){
baseDateCal.add(Calendar.DATE,1);
if(baseDateCal.get(Calendar.DAY_OF_WEEK)
== Calendar.SATURDAY){
baseDateCal.add(Calendar.DATE,2);
}
}
return baseDateCal.getTime();
}
tl;dr
Going forward.
myLocalDate.with(
org.threeten.extra.Temporals.nextWorkingDay()
)
Going backward.
myLocalDate.with(
org.threeten.extra.Temporals.previousWorkingDay()
)
Using java.time
The Question and other Answers use the troublesome old date-time classes, now legacy, supplanted by the java.time classes.
Also, see my Answer to a similar Question.
TemporalAdjuster
In java.time, the TemporalAdjuster interface provides for classes to manipulate date-time values. Using immutable objects, a new instance is created with values based on the original.
nextWorkingDay
The ThreeTen-Extra project extend java.time with additional functionality. That includes a nextWorkingDay adjuster that skips over Saturday and Sunday days. So we can loop, incrementing a date one day at a time, and skip over any weekend days.
The LocalDate class represents a date-only value without time-of-day and without time zone.
LocalDate start = LocalDate.now( ZoneId.of( "America/Montreal" ) ) ;
int businessDaysToAdd = 13 ;
// … ensure that: ( businessDaysToAdd >= 0 )
int daysLeft = businessDaysToAdd ;
LocalDate localDate = start ;
while ( daysLeft > 0 ) {
localDate = localDate.with( Temporals.nextWorkingDay() );
daysLeft = ( daysLeft - 1 ) ; // Decrement as we go.
}
return localDate ;
Holidays
Holidays are an entirely different matter. Obviously there is no simple solution. You must either supply a list of your honored holidays, or obtain a list with which you agree.
Once you have such a list, I suggest writing your own implementation of TemporalAdjuster similar to nextWorkingDay.
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
Where to obtain the java.time classes?
Java SE 8 and SE 9 and later
Built-in.
Part of the standard Java API with a bundled implementation.
Java 9 adds some minor features and fixes.
Java SE 6 and SE 7
Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
Android
The ThreeTenABP project adapts ThreeTen-Backport (mentioned above) for Android specifically.
See How to use ThreeTenABP….
The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.
This algorithm calculates the next business date for a given date
(business days are from monday to friday in my country), you can adapt it to iterate the number of days you need to add.
public Calendar nextBusinessDate(Calendar cal) {
List<Calendar> holidays = ********
// Here get list of holidays from DB or some other service...
GregorianCalendar calCp = new GregorianCalendar();
calCp.setTime(cal.getTime());
calCp.add(Calendar.DAY_OF_MONTH, 1);
boolean isSaturday = (calCp.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY);
boolean isSunday = (calCp.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY);
boolean isHoliday = holidays.contains(calCp);
while (isSaturday || isSunday || isHoliday) {
if (isSaturday) {
calCp.add(Calendar.DAY_OF_MONTH, +2); // is saturday, make it monday
} else {
if (isSunday) {
calCp.add(Calendar.DAY_OF_MONTH, +1); // is sunday, make it monday
} else {
if (isHoliday) {
calCp.add(Calendar.DAY_OF_MONTH, +1); // is holiday, make it next day
}
}
}
calCp = new GregorianCalendar();
calCp.setTime(cal.getTime());
isSaturday = (calCp.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY);
isSunday = (calCp.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY);
isHoliday = holidays.contains(calCp);
} // end while
return calCp;
}
O(1) version that works and supports different weekend patterns and negative days:
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class DateUtil {
//Weekend patterns
public static final int WEEKEND_SAT_SUN = 0;
public static final int WEEKEND_FRI_SAT = 1;
public static final int WEEKEND_THU_FRI = 2;
public static final int WEEKEND_FRI_SUN = 3;
public static final int WEEKEND_FRI = 4;
public static final int WEEKEND_SAT = 5;
public static final int WEEKEND_SUN = 6;
//Weekend pattern by country
//#see https://en.wikipedia.org/wiki/Workweek_and_weekend
public static Map<String,Integer> weekendPatternByCountry = new HashMap<>();
static {
weekendPatternByCountry.put("CO",WEEKEND_SUN); //Colombia
weekendPatternByCountry.put("GQ",WEEKEND_SUN); //Equatorial Guinea
weekendPatternByCountry.put("IN",WEEKEND_SUN); //India
weekendPatternByCountry.put("MX",WEEKEND_SUN); //Mexico
weekendPatternByCountry.put("KP",WEEKEND_SUN); //North Korea
weekendPatternByCountry.put("UG",WEEKEND_SUN); //Uganda
weekendPatternByCountry.put("BN",WEEKEND_FRI_SUN); //Brunei Darussalam
weekendPatternByCountry.put("DJ",WEEKEND_FRI); //Djibouti
weekendPatternByCountry.put("IR",WEEKEND_FRI); //Iran
weekendPatternByCountry.put("AF",WEEKEND_THU_FRI); //Afghanistan
weekendPatternByCountry.put("NP",WEEKEND_SAT); //Nepal
weekendPatternByCountry.put("DZ",WEEKEND_FRI_SAT); //Algeria
weekendPatternByCountry.put("BH",WEEKEND_FRI_SAT); //Bahrain
weekendPatternByCountry.put("BD",WEEKEND_FRI_SAT); //Bangladesh
weekendPatternByCountry.put("EG",WEEKEND_FRI_SAT); //Egypt
weekendPatternByCountry.put("IQ",WEEKEND_FRI_SAT); //Iraq
weekendPatternByCountry.put("IL",WEEKEND_FRI_SAT); //Israel
weekendPatternByCountry.put("JO",WEEKEND_FRI_SAT); //Jordan
weekendPatternByCountry.put("KW",WEEKEND_FRI_SAT); //Kuwait
weekendPatternByCountry.put("LY",WEEKEND_FRI_SAT); //Libya
weekendPatternByCountry.put("MV",WEEKEND_FRI_SAT); //Maldives
weekendPatternByCountry.put("MR",WEEKEND_FRI_SAT); //Mauritania
weekendPatternByCountry.put("MY",WEEKEND_FRI_SAT); //Malaysia
weekendPatternByCountry.put("OM",WEEKEND_FRI_SAT); //Oman
weekendPatternByCountry.put("PS",WEEKEND_FRI_SAT); //Palestine
weekendPatternByCountry.put("QA",WEEKEND_FRI_SAT); //Qatar
weekendPatternByCountry.put("SA",WEEKEND_FRI_SAT); //Saudi Arabia
weekendPatternByCountry.put("SD",WEEKEND_FRI_SAT); //Sudan
weekendPatternByCountry.put("SY",WEEKEND_FRI_SAT); //Syria
weekendPatternByCountry.put("AE",WEEKEND_FRI_SAT); //United Arab Emirates
weekendPatternByCountry.put("YE",WEEKEND_FRI_SAT); //Yemen
}
//Adjustment vectors - precomputed adjustment
static int[][][] adjVector = new int[][][]{
{//WEEKEND_SAT_SUN
//Positive number of days
{1,0,-1,-2,-3,1,1},
{0,0},
{0,0,0,0,0,2,1},
//Negative number of days
{-1,3,2,1,0,-1,-1},
{0,0},
{-1,1,1,1,1,1,0}
},
{//WEEKEND_FRI_SAT
//Positive number of days
{0,-1,-2,-3,1,1,1},
{0,0},
{0,0,0,0,2,1,0},
//Negative number of days
{3,2,1,0,-1,-1,-1},
{0,0},
{1,1,1,1,1,0,-1}
},
{//WEEKEND_THU_FRI
//Positive number of days
{-1,-2,-3,1,1,1,0},
{0,0},
{0,0,0,2,1,0,0},
//Negative number of days
{2,1,0,-1,-1,-1,3},
{0,0},
{1,1,1,1,0,-1,1}
},
{//WEEKEND_FRI_SUN
//Positive number of days
{0,-1,-2,-3,-4,-4,0},
{1,0},
{0,0,0,0,0,-1,1},
//Negative number of days
{4,3,2,1,0,0,4},
{0,-1},
{1,1,1,1,1,0,2}
},
{//WEEKEND_FRI
//Positive number of days
{-1,-2,-3,-4,1,1,0},
{0},
{0,0,0,0,1,0,0},
//Negative number of days
{3,2,1,0,-1,-1,4},
{0},
{1,1,1,1,1,0,1}
},
{//WEEKEND_SAT
//Positive number of days
{0,-1,-2,-3,-4,1,1},
{0},
{0,0,0,0,0,1,0},
//Negative number of days
{4,3,2,1,0,-1,-1},
{0},
{1,1,1,1,1,1,0}
},
{//WEEKEND_SUN
//Positive number of days
{1,0,-1,-2,-3,-4,1},
{0},
{0,0,0,0,0,0,1},
//Negative number of days
{-1,4,3,2,1,0,-1},
{0},
{0,1,1,1,1,1,1}
}
};
//O(1) algorithm to add business days.
public static Date addBusinessDays(Date day, int days,int weekendPattern){
Calendar ret = Calendar.getInstance();
if(day != null) {
ret.setTime(day);
}
if(days != 0) {
int startDayofWeek = ret.get(Calendar.DAY_OF_WEEK)-1; //Zero based to use the vectors bellow.
int idx = days > 0 ? 0 : 3;
int howManyWeekendDays = 0;
int[][] adjV = adjVector[weekendPattern];
int numWeekendDaysInOneWeek = adjV[idx+1].length;
for(int i = 0; i < numWeekendDaysInOneWeek;i++){
int adjustmentA = adjV[idx][startDayofWeek]; //pattern shift
int adjustmentB = adjV[idx+1][i]; //day shift
howManyWeekendDays += (days-adjustmentA-adjustmentB)/(7-numWeekendDaysInOneWeek);
}
int adjustmentC = adjV[idx+2][startDayofWeek]; //f(0) adjustment
howManyWeekendDays += adjustmentC;
ret.add(Calendar.DATE,days + howManyWeekendDays);
//TODO: Extend to support holidays using recursion
// int numHolidays = getNumHolidaysInInterval(day,ret.getTime());
// if(numHolidays > 0) return addBusinessDays(ret.getTime,numHolidays);
}
return ret.getTime();
}
public static Date addBusinessDays(Date day, int days,String country){
Integer weekpat = weekendPatternByCountry.get(country);
return weekpat != null ? addBusinessDays(day,days,weekpat) : addBusinessDays(day,days,WEEKEND_SAT_SUN);
}
}
This is the method I came up with:
private Date addLaborDays(Integer days, Date date){
Collection<Date> holidaysList = getHolidays();
Calendar cal = Calendar.getInstance();
cal.setTime(date);
cal.add(Calendar.DATE, 1);
Date dateTemp = cal.getTime();
if(days == 1) return dateTemp;
if(holidaysList.contains(dateTemp) || DateUtil.isWeekend(dateTemp)){
return addLaborDays(days, dateTemp);
} else {
return addLaborDays(days-1, dateTemp);
}
}
Method getHolidays() queries a custom holidays database table, and method DateUtil.isWeekend(dateTemp) returns true if dateTemp is Saturday or Sunday.
/* To Calculate 10 business days ahead of today's date
*/
public class DueDate {
/**
* #param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
DueDate d = new DueDate();
String dueDate = d.getDueDate(10);
System.out.println("due Date " + dueDate);
}
public String getDueDate(int bday){
Calendar cal = new GregorianCalendar();
SimpleDateFormat fdate = new SimpleDateFormat("MM/dd/yyyy");
while(bday > 0){
cal.add(Calendar.DAY_OF_MONTH, 1);
if(noWeekendsorHolidays(cal)){
bday--;
}
}
return fdate.format(cal.getTime());
}
public boolean noWeekendsorHolidays(Calendar cal){
int day = cal.get(Calendar.DAY_OF_WEEK);
if(day == 1 || day == 7){
return false;
}
return true;
}
}
This one works for me, short and simple:
public static Date getBusinessDay(final Date date, final int businessDaysFromDate) {
final int max = 60;
if (date == null) {
return getBusinessDay(new Date(), businessDaysFromDate);
} else if (date != null && (businessDaysFromDate < 0 || businessDaysFromDate > max)) {
return getBusinessDay(date, 0);
} else {
final Calendar baseDateCal = Calendar.getInstance();
baseDateCal.setTime(date);
for (int i = 1; i <= businessDaysFromDate; i++) {
baseDateCal.add(Calendar.DATE, 1);
while (baseDateCal.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY || baseDateCal.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) {
baseDateCal.add(Calendar.DATE, 1);
}
}
return baseDateCal.getTime();
}
}
Adding two business days to current date:
Date today = new Date();
Calendar cal1 = Calendar.getInstance();
cal1.setTime(today);
switch(cal1.get(Calendar.DAY_OF_WEEK)){
case 1:
cal1.add(Calendar.DATE, 2);
break;
case 2:
cal1.add(Calendar.DATE, 2);
break;
case 3:
cal1.add(Calendar.DATE, 2);
break;
case 4:
cal1.add(Calendar.DATE, 2);
break;
case 5:
cal1.add(Calendar.DATE, 4);
break;
case 6:
cal1.add(Calendar.DATE, 4);
break;
case 7:
cal1.add(Calendar.DATE, 3);
break;
}
// You may also set the time to meet your purpose:
cal1.set(Calendar.HOUR_OF_DAY, 23);
cal1.set(Calendar.MINUTE, 59);
cal1.set(Calendar.SECOND, 59);
cal1.set(Calendar.MILLISECOND, 00);
Date twoWeekdaysAhead = cal1.getTime();
Most of the answer I've found online didn't work as expected, so I tweaked an example on this thread, How to get current date and add five working days in Java. The code below appears to work better.
public static Date addWorkingDays(Date date, int days) {
if (days > 0) {
Calendar cal = Calendar.getInstance();
cal.setTime(date);
int daysAdded = 0;
do {
cal.add(Calendar.DATE, 1);
if (isWorkingDay(cal)) {
daysAdded++;
}
} while (daysAdded < days);
return cal.getTime();;
} else {
return date;
}
}
private static boolean isWorkingDay(Calendar cal) {
int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
if (dayOfWeek == Calendar.SUNDAY || dayOfWeek == Calendar.SATURDAY)
return false;
// tests for other holidays here
return true;
}

Categories

Resources