3

I am struggling to count the number of Sundays on the 1st of the months. like input 1 (test case) 1900 1 1 1902 1 1(year, month, day) the output should be 4. Start date is included.

EXPLANATION:

1 April 1900
1 July 1900
1 September 1901
1 December 1901

However, when I tried THIS:

6 4699 12 12 4710 1 1 1988 3 25 1989 7 13 1924 6 6 1925 6 16 1000000000000 2 2 1000000001000 3 2 1925 6 16 1924 6 6 1905 1 1 1905 1 1

OUTPUT SHOULD BE: 18 2 2 1720 0 1

My CODE OUTPUT IS: 18 2 2 **1714** 0 1

In the test input 6 means 6 test cases. 4699 years 12 month, 12 days, so Dec 12, 4699; 4710 end date and so on.

Could you help me to solve this problem?

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.Calendar;

public class nineteen {
    
    public static void main(String[] args) throws ParseException {
        SimpleDateFormat formatter = new SimpleDateFormat("MM/dd/yyyy");
        Scanner sc = new Scanner(System.in);
        int loop = sc.nextInt();
        for (int i = 0; i < loop; i++) {
            long year = sc.nextLong(), month = sc.nextLong(), day = sc.nextLong(), yearto = sc.nextLong(),
                    monthto = sc.nextLong(), dayto = sc.nextLong();
            String day1 = String.valueOf(day);
            String month1 = String.valueOf(month);
            String year1 = String.valueOf(year);
            String dayt = String.valueOf(dayto);
            String montht = String.valueOf(monthto);
            String yeart = String.valueOf(yearto);
            String input_date = month1 + "/" + day1 + "/" + year1; // month day year
            String out_date = montht + "/" + dayt + "/" + yeart; // month day year
            long count = 0;

            Date d1 = formatter.parse(input_date);
            Date d2 = formatter.parse(out_date);
            count = saturdayscount(d1, d2);

            // TODO Auto-generated method stub

        }
        sc.close();
    }

    public static long saturdayscount(Date d1, Date d2) {
        Calendar c1 = Calendar.getInstance();
        c1.setTime(d1);

        Calendar c2 = Calendar.getInstance();
        c2.setTime(d2);

        long sundays = 0;

        while (!c1.after(c2)) {

            if (c1.get(Calendar.DAY_OF_MONTH) != 1) {
                c1.add(Calendar.MONTH, 1);
                c1.set(Calendar.DAY_OF_MONTH, 1);

            }

            if (c1.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) {

                sundays++;
            }

            c1.add(Calendar.MONTH, 1);
            c1.set(Calendar.DAY_OF_MONTH, 1);
        }

        System.out.println(sundays);
        return sundays;
    }

}
Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
Z_Z
  • 127
  • 7
  • 1
    https://stackoverflow.com/questions/20165564/calculating-days-between-two-dates-with-java – FaltFe Mar 11 '21 at 13:31
  • 1
    Are the start and end dates inclusive? – Ole V.V. Mar 11 '21 at 16:03
  • I am not perfectly sure (since you didn’t answer my question), but I think that the numbers 1000000000000 and 1000000001000 are supposed to be years. If so, you are overflowing the range supported by `Date`. This is how confusing the `Date` class is, it doesn’t tip you of the error, it just picks some dates that are within its range and bear no recognizable relation with the dates you intended. – Ole V.V. Mar 12 '21 at 05:11
  • 1
    @OleV.V. This is the test inputs 6 mean is test case 4699 years 12 month, 12 days; 4710 end date and so on. And yes start dates included. I cannot find the range of date class. Could you some link. Thank you for your contributions. – Z_Z Mar 12 '21 at 07:47
  • You are right, it is not documented directly. A `Date` is internally represented as a `long` denoting the difference in milliseconds from the “epoch” of 1970-01-01T00:00:00Z (UTC). `new Date(Long.MAX_VALUE)` prints `Sun Aug 17 08:12:55 CET 292278994` on my computer, so that’s the upper limit. The lower limit is `new Date(Long.MIN_VALUE)` or `Sun Dec 02 17:47:04 CET 292269055` where the year is BCE. – Ole V.V. Mar 12 '21 at 08:02

4 Answers4

2

Your program is failing because the input 1000000000000 exceeds the maximum valid date in the Java APIs. Which when using:

  • the deprecated java.util.Date API is year 292278994
  • the new java.time API is year 999999999

So you need to validate your inputs yourself with simple if-else statements. Or let the new api handle it, like in the answer shown by Arvind Kumar Avinash

Lino
  • 19,604
  • 6
  • 47
  • 65
2

Solution: how to handle out of range dates

As I said in a comment already, your problem is that 1000000000000 (year 1 000 000 000 000, year one trillion or in classical English year one billion) is out of range for Java’s date libraries. This in combination with the poorly designed and long outdated Date and SimpleDateFormat classes not giving you any notification of the error and just tacitly giving you incorrect data. The answer by Arvind Kumar Avinash demonstrates that java.time, the modern Java date and time API, does notify you of the error. The exact ranges are in the answer by Lino. If you need to handle such years, here’s how.

Since the standard library doesn’t do the job for us, we will need to do at least some of the work ourselves, “by hand” so to speak. You may of course write your own date code from scratch, but no one should want to do that. Instead observe:

  • Leap years come in a 400 years cycle.
  • January 1, 1900 was a Monday. 400 years later January 1, 2300 will also be a Monday. This means that the days of the week follow the same 400 years cycle.

So to handle years after year 2300, first calculate the number of Sundays on the first of a month in a full 400 years cycle. java.time, the modern Java date and time API, can do this, as shown in other answers. Now subtract a whole multiple of 400 from the start year to get a year between 1900 and 2300. Count Sundays from this modified start date until January 1, 2300. Similarly subtract a multiple of 400 from the end year. Then count Sundays from 1900. Add the two numbers and adjust for how many cycles at 400 years you subtracted from each of start and end years.

Remember to use long or BigInteger for years since an int cannot hold such large numbers either.

I have assumed that years are always 1900 or later, as I believe was also the premise in the original Project Euler challenge (link below). When going back in time, calendar systems tend to disagree, so whether a given Sunday falls on the 1st of a month is no longer well-defined. I am leaving that out of consideration.

Link: Project Euler.net Counting Sundays Problem 19

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
1

I recommend you to do it using the modern date-time API*

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.temporal.TemporalAdjusters;
import java.util.Locale;

public class Main {
    public static void main(String[] args) {
        // Tests
        System.out.println("Count: " + countSundaysBetween("1900 1 1", "1902 1 1"));
        System.out.println();
        System.out.println("Count: " + countSundaysBetween("1000000000000 2 2", "1000000001000 3 2"));
    }

    static int countSundaysBetween(String strStartDate, String strEndDate) {
        DateTimeFormatter dtfInput = DateTimeFormatter.ofPattern("uuuu M d", Locale.ENGLISH);
        DateTimeFormatter dtfOutput = DateTimeFormatter.ofPattern("d MMMM uuuu", Locale.ENGLISH);
        int count = 0;

        LocalDate start, end;

        System.out.printf("Processing start date: %s and end date: %s%n", strStartDate, strEndDate);

        try {
            start = LocalDate.parse(strStartDate, dtfInput);
        } catch (DateTimeParseException e) {
            System.out.printf("The start date %s can not be processed%n", strStartDate);
            return 0;
        }

        try {
            end = LocalDate.parse(strEndDate, dtfInput);
        } catch (DateTimeParseException e) {
            System.out.printf("The end date %s can not be processed%n", strEndDate);
            return 0;
        }

        for (LocalDate date = start.with(TemporalAdjusters.firstDayOfMonth()); !date.isAfter(end); date = date
                .plusMonths(1)) {
            if (date.getDayOfWeek() == DayOfWeek.SUNDAY) {
                System.out.println(date.format(dtfOutput));
                count++;
            }
        }

        return count;
    }
}

Output:

Processing start date: 1900 1 1 and end date: 1902 1 1
1 April 1900
1 July 1900
1 September 1901
1 December 1901
Count: 4

Processing start date: 1000000000000 2 2 and end date: 1000000001000 3 2
The start date 1000000000000 2 2 can not be processed
Count: 0

ONLINE DEMO

Learn more about the modern date-time API from Trail: Date Time.

Note: I have used LocalDate#parse to keep the demo clean and focussed on the main problem i.e. how to count Sundays between two given dates. If you need to create an instance of LocalDate using year, month and day-of-month as the input, you will use LocalDate#of. And, as you can understand from the documentation, in that case, you will need to replace DateTimeParseException with DateTimeException. The rest of the solution will remain unchanged.


* The java.util date-time API and their formatting API, SimpleDateFormat are outdated and error-prone. It is recommended to stop using them completely and switch to the modern date-time API. For any reason, if you have to stick to Java 6 or Java 7, you can use ThreeTen-Backport which backports most of the java.time functionality to Java 6 & 7. If you are working for an Android project and your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring and How to use ThreeTenABP in Android Project.

Arvind Kumar Avinash
  • 71,965
  • 6
  • 74
  • 110
  • To reach the correct result for big years we need to make some pre-calculations. Not forget to some years Feb=29. – Z_Z Mar 12 '21 at 12:05
  • 1
    Catching error is good but not enough to handle huge numbers. Can't process is not wanted answer sorry. – Z_Z Mar 12 '21 at 23:07
  • @Z_Z - The message, `...can not be processed` is just for the logging purpose. The behaviour I've put in my function for this case is that it returns `0`. By the way, what is the expected behaviour for the numbers beyond the allowed values? – Arvind Kumar Avinash Mar 12 '21 at 23:31
1

I would recommend a solution with the new java.time APIs. You can calculate whether or not the day you're looking at is the first day of the month, and if it's a Sunday, and then increment a simple day counter.

Here's some sample code for that:

class nineteen {
    // 6 4699 12 12 4710 1 1 1988 3 25 1989 7 13 1924 6 6 1925 6 16 2020 2 2 3020 3 2 1925 6 16 1924 6 6 1905 1 1 1905 1 1
    public static void main(String[] args) throws ParseException {
        Scanner sc = new Scanner(System.in);
        int loop = sc.nextInt();
        for (int i = 0; i < loop; i++) {
            long year = sc.nextLong();
            int month = sc.nextInt();
            int day = sc.nextInt();
            long yearto = sc.nextLong();
            int monthto = sc.nextInt();
            int dayto = sc.nextInt();
            LocalDate startDate = LocalDate.of(0, month, day).plusYears(year);
            LocalDate endDate = LocalDate.of(0, monthto, dayto).plusYears(yearto);
            LocalDate currentDay = startDate;
            int sundaysOnFirstDayOfMonth = 0;
            while (currentDay.isBefore(endDate) || currentDay.isEqual(endDate)) {
                if (currentDay.getDayOfMonth() == 1 && currentDay.getDayOfWeek().equals(DayOfWeek.SUNDAY)) {
                    sundaysOnFirstDayOfMonth++;
                }
                currentDay = currentDay.plusDays(1L);
            }
            System.out.println(sundaysOnFirstDayOfMonth);
        }
    }
}

Note that I use sample data between the years 2020 and 3020 to get the exact answers you're looking for. Other millennial counts may not produce this exact result. Notwithstanding, there is a maximum year which your test data beyond exceeds, so this API will do the right thing and fail to run as opposed to silently overflow.

Makoto
  • 104,088
  • 27
  • 192
  • 230
  • 1
    I think you could directly use `currentDay = currentDay.plusMonths(1);`, you'd just have to ensure that the first day before the iteration is also tje `1.` of the month – Lino Mar 12 '21 at 16:39