Home > Net >  I am trying to figure out the difference in days between two dates manually
I am trying to figure out the difference in days between two dates manually

Time:04-03

I have used these two websites to measure how close my code gets.

I use days since year zero to normalise the two entered dates, then I find the difference between those dates.

import java.util.Scanner;

public class DateDiff {
    private static final int[] monthsDay = {31,28,31,30,31,30,31,31,30,31,30,31};

    public static String dateChecker() {
        boolean b = true;
        int dateC = 0;
        String date = "";

        do {
            Scanner scanner = new Scanner(System.in);
            date = scanner.nextLine();
            try {
                if (date.charAt(2) == '/' && date.charAt(5) == '/') {
                    date = date.replace("/", "");
                    dateC = Integer.parseInt(date);
                    b = false;
                } else {
                    System.out.println("Reenter date in the dd/mm/yyyy format");
                }
            } catch (Exception e) {
                System.out.println("Reenter date in the dd/mm/yyyy format");
            }

        } while (b);

        return date;
    }

    public static int daysForMonth(int months, int year) {

        int days = 0;

        for (int i = 0; i < months; i  )
            if (i == 1)
                days  = ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)
                        ? monthsDay[i]   1
                        : monthsDay[i];
            else
                days  = monthsDay[i];

        return days;
    }

    public static int daysForYears(int year) {

        int days = 0;

        for (int i = 0; i < year; i  )
            if ((i % 4 == 0 && (i % 100 != 0)) || (i % 400 == 0))
                days  = 366;
            else
                days  = 365;

        return days;
    }

    public static int daysSinceYearZero(String date) {
        int day = Integer.parseInt(date.substring(0,2));
        int month = Integer.parseInt(date.substring(2,4));
        int year = Integer.parseInt(date.substring(4,8));

        int daysMonth = daysForMonth(month-1, year);
        int daysYear = daysForYears(year);

        return day   daysMonth   daysYear;
    }

    public static void main(String[] args) {
        System.out.println("Enter first date");
        String date1 = dateChecker();
        System.out.println("Enter second date");
        String date2 = dateChecker();

        int firstDate = daysSinceYearZero(date1);
        int secondDate = daysSinceYearZero(date2);

        System.out.println("First Date days since Year Zero: "   firstDate);
        System.out.println("Second Date days since Year Zero: "   secondDate);
        System.out.println("Difference: "   Math.abs(firstDate-secondDate));

    }
}

My code gets close, but always seems to miss by a few days and I can't figure out why. I have confirmed the days and daysMonth are correct, but do not understand where I am going wrong in calculating the number of days since year zero using years (the daysYear variable)

CodePudding user response:

There are date libraries to do this. I am not very sure about the necessity of writing this code. In any case if you want to completely code the solution without using any libraries then here is the code to do that.

import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

public class DateDiff {
    private static final Map<Integer, Integer> monthsMap = new HashMap<>();
    static {
        monthsMap.put(0, 0);
        monthsMap.put(1, 31);
        monthsMap.put(2, 28);
        monthsMap.put(3, 31);
        monthsMap.put(4, 30);
        monthsMap.put(5, 31);
        monthsMap.put(6, 30);
        monthsMap.put(7, 31);
        monthsMap.put(8, 31);
        monthsMap.put(9, 30);
        monthsMap.put(10, 31);
        monthsMap.put(11, 30);
        monthsMap.put(12, 31);
    }

    public static String dateChecker() {
        boolean incorrectDateFormat =false;
        String date;
        do {
            Scanner scanner = new Scanner(System.in);
            date = scanner.nextLine();
            if (date.charAt(2) != '/' || date.charAt(5) != '/') {
                System.out.println("Re-enter date in the dd/mm/yyyy format");
                incorrectDateFormat =true;
            } else {
                incorrectDateFormat = false;
            }
        } while(incorrectDateFormat);
        return date;
    }

    public static int daysSinceYearZero(String dateStr) {
        int year = Integer.parseInt(dateStr.substring(dateStr.lastIndexOf("/") 1));
        int totalNumberOfCompleteYearsSinceYear0 = year; //As year zero is also a complete year, so completed number of years will be equal given year in the date.
        int leapYearCount = 0;
        for(int i=1; i<=totalNumberOfCompleteYearsSinceYear0; i  ) { //year zero is not a leap year. so starting the loop from 4 as year 4 is the first leap year.
            if((i % 4 == 0 && (i % 100 != 0)) || (i % 400 == 0)) {
                leapYearCount  ;
            }
        }
        int totalNumberOfDaysInCompletedYears = totalNumberOfCompleteYearsSinceYear0*365   leapYearCount;
        int monthFromGivenDate = Integer.parseInt(dateStr.substring(dateStr.indexOf("/") 1,dateStr.lastIndexOf("/")));
        int completedMonth = monthFromGivenDate - 1;
        int daysForCompletedMonthsOfCurrentYear = 0;
        for(int i=0; i<=completedMonth;i  ) {
            daysForCompletedMonthsOfCurrentYear = daysForCompletedMonthsOfCurrentYear   monthsMap.get(i);
            if(i==2 && year%4==0) {
                daysForCompletedMonthsOfCurrentYear  ;
            }
        }

        int numberOfDaysCompletedInCurrentMonth = Integer.parseInt(dateStr.substring(0,dateStr.indexOf("/")));

        int totalNumberOfDaysTillGivenDate = totalNumberOfDaysInCompletedYears   daysForCompletedMonthsOfCurrentYear   numberOfDaysCompletedInCurrentMonth;
        return totalNumberOfDaysTillGivenDate;
    }

    public static void main(String[] args) {
        System.out.println("Enter first date");
        String date1 = dateChecker();
        System.out.println("Enter second date");
        String date2 = dateChecker();

        int firstDate = daysSinceYearZero(date1);
        int secondDate = daysSinceYearZero(date2);

        System.out.println("First Date days since Year Zero: "   firstDate);
        System.out.println("Second Date days since Year Zero: "   secondDate);
        System.out.println("Difference: "   Math.abs(firstDate-secondDate));

    }
}

CodePudding user response:

Since you didn't explain any requirements or limitations you can do it like this.

LocalDate earliest = LocalDate.parse("2012-05-17");
LocalDate latest = LocalDate.parse("2022-06-22");
System.out.println(latest.toEpochDay()-earliest.toEpochDay());

prints

3688 (exclusive of the latest date day)

However, here is one way to home grow it. I used lambdas to facilitate the process. And no loops were required in the calculation. So this runs in constant time.

  • First I created an IntTrinaryOperator interface.
interface IntTrinaryOperator {
    public int applyAsInt(int a, int b, int c);
}

Then an array of month days was created (leap years are handled later) the first cell is ignored but required for the following operation.

int daysPerMonth[] =
        { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
int monthSums[] = daysPerMonth.clone();
// this creates a running sum
// looks like [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365]
// the last cell is not used. 
Arrays.parallelPrefix(monthSums, (a, b) -> a   b);

A leap year function

Function<Integer, Boolean> isLeapYear =
        a -> a % 400 == 0 || (a % 100 != 0 && a % 4 == 0);

And the defined Trinary to be used for the actual calculation.

  • (y-1)*365-(y-1)/100 (y-1)/4 (y-1)/400 - computes total leap years starting from previous year.
    • first total days using 365 days per year
    • then subtract century years
    • then add years divisible by 400 back in.
  • monthSums[m-1] d - adds days for this year
  • ((m > 2) && isLeapYear.apply(y) ? 1 : 0) - 1 - adds one more day if after February but subtracts 1 to exclude current day (as in most ranges in Java)
IntTrinaryOperator daysToEpoch = (y, m, d) -> (y - 1) * 365
        - (y - 1) / 100   (y - 1) / 4   (y - 1) / 400  
          monthSums[m - 1]   d
          ((m > 2) && isLeapYear.apply(y) ? 1 : 0) - 1;

Testing

  • generate some dates. Dates are not chronological so the days could be negative, hence the Math.abs()
Random r = new Random();

for (int i = 0; i < 10; i  ) {
    int eYear = r.nextInt(2022)   1;
    int eMonth = r.nextInt(12)   1;
    int eDay = r.nextInt(daysPerMonth[eMonth])
              (eMonth == 2 && isLeapYear.apply(eYear) ? 1 :
                    0);
    
    int sYear = r.nextInt(2022)   1;
    int sMonth = r.nextInt(12)   1;
    int sDay = r.nextInt(daysPerMonth[sMonth])
              (sMonth == 2 && isLeapYear.apply(sYear) ? 1 :
                    0);
    
    int eDaysToEpoch =
            daysToEpoch.applyAsInt(eYear, eMonth, eDay);
    int sDaysToEpoch =
            daysToEpoch.applyAsInt(sYear, sMonth, sDay);
    
    System.out.printf("d/d/d - d/d/d - %,9d total days%n",
            eMonth, eDay, eYear, sMonth, sDay, sYear, Math.abs(eDaysToEpoch-sDaysToEpoch));
}

And the original dates

System.out.println(daysToEpoch.applyAsInt(2022, 6, 22)- 
                   daysToEpoch.applyAsInt(2012, 5, 17));

prints something like.

04/10/1377 - 12/03/1486 -    40,048 total days
02/12/0727 - 03/27/0196 -   193,899 total days
11/26/0457 - 12/09/0307 -    54,775 total days
02/25/0691 - 10/23/1596 -   330,785 total days
03/28/0404 - 01/16/1567 -   424,705 total days
10/18/0372 - 01/15/1316 -   344,512 total days
08/01/1374 - 01/23/1484 -    39,986 total days
03/21/0622 - 07/24/0495 -    46,260 total days
02/05/1167 - 08/05/1558 -   142,991 total days
12/02/1824 - 07/21/0976 -   309,859 total days
3688

This has been tested using the API method first shown above. With over 1M random tests there were no discrepancies.

Here is a date validation method. It checks for leap years and days against months. It also allows single digits for month and day. It does not produce detailed error messages. I continues to re-prompt until a valid date is entered. Otherwise the day, month, and year are returned in an array.

public static int[] getDate(Scanner scanner) {
    String stringDate = "\\d\\d?/\\d\\d?/\\d{4}";
    
    while (true) {
        System.out.println(
                "Please enter date in dd/mm/yyyy format.");
        String date = scanner.nextLine();
        if (date.matches(stringDate)) {
            int[] dmy = Arrays.stream(date.split("/"))
                    .mapToInt(Integer::parseInt).toArray();
            System.out.println(Arrays.toString(dmy));
            int d = dmy[0];
            int m = dmy[1];
            int y = dmy[2];
            if (d > 0 && m > 0 && m < 13 && y > 0) {
                boolean isLeap = isLeapYear.apply(y);
                if (isLeap && d <= 29 && m == 2) {
                    return dmy;
                }
                if (d <= daysPerMonth[m]) {
                    return dmy;
                }
            }
        }
        System.out.print("Illegal date: ");
    }
}
  •  Tags:  
  • java
  • Related