2

Here is a response to a question about calculating age in Java.

/**
 * This Method is unit tested properly for very different cases , 
 * taking care of Leap Year days difference in a year, 
 * and date cases month and Year boundary cases (12/31/1980, 01/01/1980 etc)
**/

public static int getAge(Date dateOfBirth) {

    Calendar today = Calendar.getInstance();
    Calendar birthDate = Calendar.getInstance();

    int age = 0;

    birthDate.setTime(dateOfBirth);
    if (birthDate.after(today)) {
        throw new IllegalArgumentException("Can't be born in the future");
    }

    age = today.get(Calendar.YEAR) - birthDate.get(Calendar.YEAR);

    // If birth date is greater than todays date (after 2 days adjustment of leap year) then decrement age one year   
    if ( (birthDate.get(Calendar.DAY_OF_YEAR) - today.get(Calendar.DAY_OF_YEAR) > 3) ||
            (birthDate.get(Calendar.MONTH) > today.get(Calendar.MONTH ))){
        age--;

     // If birth date and todays date are of same month and birth day of month is greater than todays day of month then decrement age
    }else if ((birthDate.get(Calendar.MONTH) == today.get(Calendar.MONTH )) &&
              (birthDate.get(Calendar.DAY_OF_MONTH) > today.get(Calendar.DAY_OF_MONTH ))){
        age--;
    }

    return age;
}

This code works just fine, but why does it have this comparison: (birthDate.get(Calendar.DAY_OF_YEAR) - today.get(Calendar.DAY_OF_YEAR) > 3)

I've gone so far as to create a giant spreadsheet of all the day differences in a year to try to see what cases it might be covering, but I don't see anything that the other comparisons don't cover. Can anyone explain the purpose behind including this comparison? Is it more efficient in some way?

Community
  • 1
  • 1
Jed Schaaf
  • 940
  • 1
  • 8
  • 19
  • 6
    Was the comment (*If birth date is greater than todays date (after 2 days adjustment of leap year) then decrement age one year*) unclear? – Elliott Frisch Mar 09 '16 at 21:41
  • It seems to me that the day-of-year-check is unnecessary because age-calculation is about handling the year- or month-boundaries (and day-of-month-comparisons) and not about day-of-year-details. – Meno Hochschild Mar 10 '16 at 10:26
  • @ElliottFrisch If it were comparing for the possible 2-day difference due to leap years, then the comparison would be `>2`, wouldn't it? – Jed Schaaf Mar 10 '16 at 17:38

1 Answers1

3

Following code example from ThreetenBP (backport of Java-8) supports the statement that a day-of-year-check is unnecessary:

@Override 
public long until(Temporal endExclusive, TemporalUnit unit) { 
LocalDate end = LocalDate.from(endExclusive); 
    if (unit instanceof ChronoUnit) { 
         switch ((ChronoUnit) unit) { 
             case DAYS: return daysUntil(end); 
             case WEEKS: return daysUntil(end) / 7; 
             case MONTHS: return monthsUntil(end); 
             case YEARS: return monthsUntil(end) / 12; 
             case DECADES: return monthsUntil(end) / 120; 
             case CENTURIES: return monthsUntil(end) / 1200; 
             case MILLENNIA: return monthsUntil(end) / 12000; 
             case ERAS: return end.getLong(ERA) - getLong(ERA); 
         } 
         throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit); 
     } 
     return unit.between(this, end); 
} 

[...]     

private long monthsUntil(LocalDate end) { 
   long packed1 = getProlepticMonth() * 32L + getDayOfMonth();  // no overflow 
   long packed2 = end.getProlepticMonth() * 32L + end.getDayOfMonth();  // no overflow 
   return (packed2 - packed1) / 32; 
} 

The line case YEARS: return monthsUntil(end) / 12; (the expressions birthday.until(today, YEARS) and YEARS.between(birthday, today) are equivalent - one delegating to other) exploits the same algorithm as following reduced code cited by the OP and does not refer to any day-of-year-check:

age = today.get(Calendar.YEAR) - birthDate.get(Calendar.YEAR);

if (birthDate.get(Calendar.MONTH) > today.get(Calendar.MONTH)) {
    age--;
}else if ((birthDate.get(Calendar.MONTH) == today.get(Calendar.MONTH )) &&
          (birthDate.get(Calendar.DAY_OF_MONTH) > today.get(Calendar.DAY_OF_MONTH ))){
    age--;
}

The question arises: Why the day-of-year-check?

a) the poster had originally taken the day-of-year-idea seriously and then forgotten to clean up in a later version

b) the poster hopes to "improve" the performance

Following Java-8-code demonstrates the problem of day-of-year-based algorithm if taken seriously and as complete version (the choice of library is not relevant here, only the algorithm matters):

LocalDate birthday = LocalDate.of(2001, 3, 6);
LocalDate today = LocalDate.of(2016, 3, 5); // leap year

int age = today.getYear() - birthday.getYear();
if (birthday.getDayOfYear() > today.getDayOfYear()) {
    age--;
}
System.out.println("age based on day-of-year: " + age); // 15 (wrong)
System.out.println("age based on month and day-of-month: " 
  + ChronoUnit.YEARS.between(birthday, today)); // 14 (correct)

Conclusion:

The proposed day-of-year-clause you had cited is only noise since the rest of the algorithm corresponds to what Java-8 does. Maybe the day-of-year-check originates from some earlier day-of-year-based versions of proposed code and had not been cleaned up yet.

In order to answer your last question: An unnecessary check like this is not good resp. efficient in terms of performance (although we talk here about micro-optimization).

Meno Hochschild
  • 38,305
  • 7
  • 88
  • 115
  • @Holger To be more specific: The condition `(birthDate.get(Calendar.DAY_OF_YEAR) - today.get(Calendar.DAY_OF_YEAR) > 3)` can be removed without any negative impact on the result (and Jed Schaaf has proved it himself by his spreadsheet). – Meno Hochschild Mar 16 '16 at 16:10
  • @Holger An [example](http://stackoverflow.com/a/4982150/2491410) for a wrong day-of-year-check is given by the author of Date4J in the same thread. There are only two possible reasons why the code in question has such a condition: a) the poster had originally taken the day-of-year-idea seriously and then forgotten to clean up in a later version b) the poster hopes to "improve" the performance. – Meno Hochschild Mar 16 '16 at 16:24
  • @Holger Yes, I have adjusted my answer. Sorry, that I am too deep in the matter and forget that some details are not so obvious to other users. – Meno Hochschild Mar 16 '16 at 17:07
  • 1
    Never mind. That’s what the comment function is for. My theory regarding that check is, that’s the check that was there first, then after realizing that it doesn’t work (in all cases) the code got expanded up to the point that the original check became obsolete. But obsolete code has a tendency to survive very long… – Holger Mar 16 '16 at 17:10