2

Am very beginner and new to Java platform. I have the below 3 simple Java date difference calculation functions. I wanted to exclude weekends on the below calculations in all the 3 methods. Can anyone please help how to exclude weekends for the below dateDiff calculations?

public static String getDatesDiff(String date1, String date2) {

    String timeDiff = "";
    SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    Date d1 = null;
    Date d2 = null;

    try {
        d1 = format.parse(date1);
        d2 = format.parse(date2);
        long diff = d2.getTime() - d1.getTime();
        timeDiff = ""+diff;
    } catch (Exception e) {
        // TODO: handle exception
        e.printStackTrace();
    }
    return timeDiff;
}
public static String getDatesDiffAsDays(String date1, String date2) {
    String timeDiff = "";
    SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    Date d1 = null;
    Date d2 = null;

    try {
        d1 = format.parse(date1);
        d2 = format.parse(date2);
        long diff = d2.getTime() - d1.getTime();
        String days = ""+(diff / (24 * 60 * 60 * 1000));
            
        timeDiff = days;
        timeDiff = timeDiff.replaceAll("-", "");
        timeDiff = timeDiff+" days";
            
    } catch (Exception e) {
        // TODO: handle exception
        e.printStackTrace();
    }
    return timeDiff;
}
public static String getDatesDiffAsDate(String date1, String date2) {
    String timeDiff = "";
    SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    Date d1 = null;
    Date d2 = null;

    try {
        d1 = format.parse(date1);
        d2 = format.parse(date2);
        long diff = d2.getTime() - d1.getTime();
        String days = (diff / (24 * 60 * 60 * 1000))+" days";
        String hours = (diff / (60 * 60 * 1000) % 24)+"h";
        String minutes = (diff / 1000 % 60)+"mts";
        String seconds = (diff / (60 * 1000) % 60)+"sec";
    
        timeDiff = days;
        timeDiff = timeDiff.replaceAll("-", "");
    } catch (Exception e) {
        // TODO: handle exception
        e.printStackTrace();
    }
    return timeDiff;
}
deHaar
  • 17,687
  • 10
  • 38
  • 51
Sheila
  • 21
  • 5
  • Which Java version are you using? Could you provide some example dates along with their desired result? – deHaar Oct 07 '21 at 10:27
  • 2
    Does this help? [Calculate number of weekdays between two dates in Java](https://stackoverflow.com/questions/4600034/calculate-number-of-weekdays-between-two-dates-in-java) – Abra Oct 07 '21 at 10:32
  • 1
    You are using terrible date-time classes that are now legacy, supplanted years ago by the modern *java.time* classes defined in JSR 310. – Basil Bourque Oct 07 '21 at 13:20

2 Answers2

6

This code is fundamentally broken. java.util.Date doesn't represent a date, it represents a timestamp. But if you're working with moments in time, you have a problem: not all days are exactly 24 hours long. For example, daylight savings exists, making some days 25 or 23 hours. At specific moments in time in specific places on the planet, entire days were skipped, such as when a place switches which side of the international date line it is on, or when Russia was the last to switch from Julian to Gregorian (the famed October Revolution? Yeah, that happened in November actually!)

Use LocalDate which represents an actual date, not a timestamp. Do not use Date, or SimpleDateFormat – these are outdated and mostly broken takes on dates and times. The java.time package is properly thought through.

When is 'the weekend'? In some places, Friday and Saturday are considered the weekend, not Saturday and Sunday.

If you're excluding weekends, presumably you'd also want to exclude mandated holidays. Many countries state that Jan 1st, regardless of what day that is, counts as a Sunday, e.g. for the purposes of government buildings and services being open or not.

Lessons you need to take away from this:

  • Dates are incredibly complicated, and as a consequence, are a horrible idea for teaching basic principles.
  • Do not use java.util.Date, Calendar, GregorianCalendar, or SimpleDateFormat, ever. Use the stuff in java.time instead.
  • If you're writing math like this, you're probably doing it wrong – e.g. ChronoUnit.DAYS.between(date1, date2) does all that math for you.
  • You should probably just start at start date, and start looping: check if that date counts as a working day or not (and if it is, increment a counter), then go to the next day. Keep going until the day is equal to the end date, and then return that counter. Yes, this is 'slow', but a computer will happily knock through 2 million days (that covers over 5000 years worth) in a heartbeat for you. The advantage is that you can calculate whether or not a day counts as a 'working day' (which can get incredibly complicated. For example, most mainland European countries and I think the US too mandates that Easter is a public holiday. Go look up and how to know when Easter is. Make some coffee first, though).
  • If you really insist on going formulaic and defining weekends as Saturday and Sunday, it's better to separately calculate how many full weeks are between the two dates and multiply that by 5, and then add separately the half-week 'on the front of the range' and the half-week at the back. This will be fast even if you ask for a hypothetical range of a million years.
  • That is not how you handle exceptions. Add throws X if you don't want to deal with it right now, or, put throw new RuntimeException("unhandled", e); in your catch blocks. Not this, this is horrible. It logs half of the error and does blindly keeps going, with invalid state.
  • Almost all interesting questions, such as 'is this date a holiday?' are not answerable without knowing which culture/locale you're in. This includes seemingly obvious constants such as 'is Saturday a weekend day?'.
MC Emperor
  • 22,334
  • 15
  • 80
  • 130
rzwitserloot
  • 85,357
  • 5
  • 51
  • 72
3

rzwitserloot has already brought up many valid points about problems in your code.

This is an example of how you could count the working days:

LocalDate startDate = ...;
LocalDate endDateExclusive = ...;
long days = startDate.datesUntil(endDateExclusive)
    .filter(date -> isWorkingDay(date))
    .count();

And, of course, you need to implement the isWorkingDay method. An example would be this:

public static boolean isWorkingDay(LocalDate date) {
    DayOfWeek dow = date.getDayOfWeek();
    return (dow != DayOfWeek.SATURDAY && dow != DayOfWeek.SUNDAY);
}
  • I used LocalDate to illustrate the example. LocalDate fits well if you are working with concepts like weekend days and holidays. However, if you want to also include the time component, then you should also take clock adjustments like DST into account; otherwise a "difference" does not make sense.

  • I assume the user to input an object representing some datetime value, not a String. The parsing of a string does not belong to this method, but should be handled elsewhere.

  • Already been said, but I repeat: don't use Date, Calendar and SimpleDateFormat. They're troublesome. Here are some reasons why.


If you want to take the time into consideration, it'll get a little more complex. For instance, ChronoUnit.DAYS.between(date1, date2) only supports a single, contiguous timespan. Gaps in the timespan, like excluding certain periods of time, is not. Then you have to walk over each date and get the associated duration of that portion of date.

  • First, we could create a LocalTimeRange class, which represents a time span at a certain day.

    public record LocalTimeRange(LocalTime start, LocalTime endExclusive) {
    
        public static final LocalTimeRange EMPTY = new LocalTimeRange(null, null);
    
        public Duration toDuration(LocalDate date, ZoneId zone) {
            if (this.equals(EMPTY)) {
                return Duration.ZERO;
            }
    
            var s = ZonedDateTime.of(date, Objects.requireNonNullElse(start, LocalTime.MIN), zone);
            var e = (endExclusive != null ? ZonedDateTime.of(date, endExclusive, zone) : ZonedDateTime.of(date.plusDays(1), LocalTime.MIN, zone));
            return Duration.between(s, e);
        }
    }
    

    Calculations are not done immediately, because the duration in between the two wall clock times, depends on the date and timezone. The toDuration method calculates this.

  • Then we'll create a method which defines what times on each day are counted as a non-weekend day. In this example, I have defined a weekend to be from Friday, 12:00 (noon) until Sunday, 23:59 (midnight).

    private static Duration nonWeekendHours(LocalDate date, ZoneId zone) {
        var result = switch (date.getDayOfWeek()) {
            case MONDAY,
                 TUESDAY,
                 WEDNESDAY,
                 THURSDAY   -> new LocalTimeRange(LocalTime.MIDNIGHT, null);
            case FRIDAY     -> new LocalTimeRange(LocalTime.MIDNIGHT, LocalTime.NOON);
            case SATURDAY,
                 SUNDAY     -> new LocalTimeRange(null, null);
        };
        return result.toDuration(date, zone);
    }
    

    The LocalTimeRange::toDuration method is called with the passed LocalDate and ZoneId arguments.

    Note that passing null as LocalTimeRange's second argument means 'until the end of the day'.

  • At last we could stream over all dates of a certain period and calculate how much time are the non-weekend hours for each day, and then reduce them to get the total amount of time:

    LocalDate startDate = ...;
    LocalDate endDate = ...;
    ZoneId zone = ...;
    Duration result = startDate.datesUntil(endDate)
        .map(date -> nonWeekendHours(date, zone))
        .reduce(Duration.ZERO, Duration::plus);
    

With the retrieved Duration instance, you can easily get the time parts with the get<unit>Part() methods,

Online demo

MC Emperor
  • 22,334
  • 15
  • 80
  • 130