-2

A duration is given. Ex: Jan 15-March 15

I want to count the number of days which belongs to each month, in that given duration. In this example, number of days of January in that duration; 15 number of days of February in that duration; 28 number of days of March in that duration; 15

I'm looking for a solution other that traversing through each date of the duration and checking if Date.getMonth() = "Month I want to check against"

Is there an easier way of doing this using methods in Java Date or Java SQL Date or using any other Date type?

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
direndd
  • 642
  • 2
  • 16
  • 47
  • 1
    SQL appears irrelevant in this context – Strawberry Dec 28 '19 at 18:45
  • 1
    is it a string which is given and you need to count the number of days? Do you need number of days for each month? Year is not present in the String? – dassum Dec 28 '19 at 18:50
  • Is start date inclusive? What about end date? In the example, do you want to count 14 or 15 days of Match? And 28 or 29 days of February? Can we tell the year so we can determine whether it’s a leap year? – Ole V.V. Dec 28 '19 at 19:00
  • 1
    Yes, it’s possible, but the question seems very broad and poorly researched. Use `LocalDate` and `ChronoUnit.DAYS.between()`. Don’t use `java.util.Date`. That class is poorly designed and long outdated and despite the name does not represent a date. – Ole V.V. Dec 28 '19 at 19:01
  • (1-) What does this have to do with Swing? – camickr Dec 28 '19 at 19:32

3 Answers3

2

Map < YearMonth , Long > with lambda syntax

Here is a solution using a bit of terse code using streams and lambdas. While this solution does traverse each date of the time range, the simplicity and clarity of the code may outweigh that inefficiency.

Use LocalDate for the starting and stopping date. Use YearMonth to track each month.

LocalDate start = LocalDate.of( 2019 , 1 , 15 );
LocalDate stop = LocalDate.of( 2019 , 3 , 16 );

Make a Map to keep a number of days for each month.

Map < YearMonth, Long > map =
        start
                .datesUntil( stop )
                .collect(
                        Collectors.groupingBy(
                                ( LocalDate localDate ) -> YearMonth.from( localDate ) ,
                                TreeMap::new ,
                                Collectors.counting()
                        )
                );

Dump to console.

{2019-01=17, 2019-02=28, 2019-03=15}

System.out.println( map );
  1. Given a starting date, LocalDate::datesUntil provides a Stream of LocalDate objects, incremented by days.

  2. Then just do a grouping into a SortedMap (a TreeMap) to keep months in chronological order, classified by the YearMonth and counting the days for that month in the range.

If you want the total days you can just do

long totalDays = d.datesUntil(LocalDate.of(2019, 3, 16)).count();
Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
WJS
  • 36,363
  • 4
  • 24
  • 39
  • 2
    Impressive. It is *traversing through each date of the duration*, which the questioner wanted to avoid, but given the terseness it might be worth it in this case. – Ole V.V. Dec 28 '19 at 21:12
  • Clever solution. I changed your code to use `YearMonth` rather than `Month`, in case the date range went over 12 months. – Basil Bourque Dec 28 '19 at 21:55
  • Good idea. I didn't really have time to think about this until I posted the answer, having looked up `localDate`. Notice that I handled the `exclusive` part of `datesUntil` rather clumsily which also should be refined. Perhaps by adding a day using `plus()` – WJS Dec 28 '19 at 21:59
  • As an option, the second argument to the `groupingBy` could be `LinkedHashMap::new` to allow the dates to be displayed in chonological order. – WJS Dec 28 '19 at 22:53
  • 1
    @WJS If you mean `datesUntil` running up to, but not including, the limit of March 16th, that is a feature, not a bug. The Half-Open approach is commonly used for date-time handling and generally the wisest way to go. – Basil Bourque Dec 28 '19 at 22:53
  • I know it's a feature and I depend on it in all range cases. I meant that I didn't handle it well. A method should be provided to calculate this where a single day is added to the end date, transparent to the user ( if that is what the OP wants). – WJS Dec 28 '19 at 22:56
  • Ah, TreeMap. A much better choice. – WJS Dec 28 '19 at 22:58
  • Instantiating a `SortedMap` in the 2nd argument worked. I went with `TreeMap` instead of `LinkedHashMap`, implementing the `SortedMap`/`NavigableMap` interface rather than depending on original insertion order. – Basil Bourque Dec 28 '19 at 22:59
1

This is just a simple example I threw together with some basic research.

LocalDate from = LocalDate.of(2019, Month.JANUARY, 15);
LocalDate to = LocalDate.of(2019, Month.MARCH, 15);

DateTimeFormatter monthFormatter = DateTimeFormatter.ofPattern("MMM");

LocalDate date = from;
while (date.isBefore(to)) {
    LocalDate endOfMonth = date.withDayOfMonth(date.lengthOfMonth());
    if (endOfMonth.isAfter(to)) { 
        endOfMonth = to;
    }

    // Inclusive to exclusive comparison
    long days = ChronoUnit.DAYS.between(date, endOfMonth.plusDays(1));
    System.out.println(days + " days in " + date.format(monthFormatter));

    date = date.plusMonths(1).withDayOfMonth(1);
}

This will output

17 days in Jan.
28 days in Feb.
15 days in Mar.

There are probably better ways to achieve the same result, but as I said, I just threw it together with a little bit of Googling and trial and error.

As has already been stated, you should avoid using the older, out-of-date and effectively deprecated Date, Calendar and associated classes.

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
-1

Try this. May be something like this you want. So it set a startdate and enddate, then loop for each moth till the end date and calculate the day count. I have not tested it thoroughly, but should be close to your concept.

public static void main(String[] args) throws ParseException {
    String startDateS = "01/15/2019";
    String endDateS = "03/15/2019";

    SimpleDateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy");
    Date startDate = dateFormat.parse(startDateS);
    Date endDate = dateFormat.parse(endDateS);

    while (endDate.compareTo(startDate) > 0) {
        Calendar c = Calendar.getInstance();
        c.setTime(startDate);
        c.set(Calendar.DAY_OF_MONTH, c.getActualMaximum(Calendar.DAY_OF_MONTH));
        Date endOfMonth = c.getTime();
        if( endDate.compareTo(endOfMonth) > 0 )
            System.out.println("Count Month " + getMonthForInt(c.get(Calendar.MONTH)) + " " + getDifferenceDays(startDate, endOfMonth));
        else
            System.out.println("Count Month " + getMonthForInt(c.get(Calendar.MONTH)) + " " + getDifferenceDays(startDate, endDate));
        c.add(Calendar.DAY_OF_MONTH, 1);
        startDate = c.getTime();
    }
}

static String getMonthForInt(int num) {
    String month = "wrong";
    DateFormatSymbols dfs = new DateFormatSymbols();
    String[] months = dfs.getMonths();
    if (num >= 0 && num <= 11) {
        month = months[num];
    }
    return month;
}

public static int getDifferenceDays(Date d1, Date d2) {
    int daysdiff = 0;
    long diff = d2.getTime() - d1.getTime();
    long diffDays = diff / (24 * 60 * 60 * 1000) + 1;
    daysdiff = (int) diffDays;
    return daysdiff;
}

You can do the same using Java.time in Java 8.

public static void main(String[] args) throws ParseException {
        String startDateS = "01/15/2019";
        String endDateS = "03/15/2019";

        DateTimeFormatter format1 = DateTimeFormatter.ofPattern("MM/dd/yyyy");
        LocalDate startDate = LocalDate.parse(startDateS, format1);
        LocalDate endDate = LocalDate.parse(endDateS, format1);

        while (endDate.compareTo(startDate) > 0) {
            LocalDate endOfMonth = startDate.minusDays(startDate.getDayOfMonth()).plusMonths(1);
            if( endDate.compareTo(endOfMonth) > 0 )
                System.out.println("Count Month " + getMonthForInt(startDate) + " " + getDifferenceDays(startDate, endOfMonth));
            else
                System.out.println("Count Month " + getMonthForInt(startDate) + " " + getDifferenceDays(startDate, endDate));
            startDate = endOfMonth.plusDays(1);
        }
    }

    static String getMonthForInt(LocalDate startDate) {
        return startDate.getMonth().getDisplayName(
                TextStyle.FULL , 
                Locale.US 
            );
    }

    public static long getDifferenceDays(LocalDate d1, LocalDate d2) {
       // return Duration.between(d2.atStartOfDay(), d1.atStartOfDay()).toDays();
        return ChronoUnit.DAYS.between(d1, d2) + 1;
    }
A Paul
  • 8,113
  • 3
  • 31
  • 61
  • 4
    Please don’t teach the young ones to use the long outdated and notoriously troublesome `SimpleDateFormat` class. At least not as the first option. And not without any reservation. Today we have so much better in [`java.time`, the modern Java date and time API,](https://docs.oracle.com/javase/tutorial/datetime/) and its `DateTimeFormatter`. – Ole V.V. Dec 28 '19 at 19:13