2

My method works, as I only want to know the difference in years, my problem is that I am so inefficient in doing this. I have a feeling there is a much more simple and aesthetically pleasing way to write the method.

Edit: I have to write my own method. I also prefer to not go out of my way and use advanced things, something within the range of a first year programmer.

public int differenceInYears(MyDate comparedDate) {
        int difference = 0;
        if (this.year > comparedDate.year) {
            if (this.month > comparedDate.month) {
                difference = this.year - comparedDate.year;
            }
            else if (this.month == comparedDate.month) {
                if (this.day >= comparedDate.day) {
                    difference = this.year - comparedDate.year;
                }
                else {
                    difference = this.year - comparedDate.year - 1;
                }
            }
            else {
                difference = this.year - comparedDate.year - 1;
            }
        }
        if (comparedDate.year > this.year) {
            if (comparedDate.month > this.month) {
                difference = comparedDate.year - this.year;
            }
            else if (comparedDate.month == this.month) {
                if (comparedDate.day >= this.day) {
                    difference = comparedDate.year - this.year;
                }
                else {
                    difference = comparedDate.year - this.year - 1;
                }
            }
            else {
                difference = comparedDate.year - this.year - 1;
            }
        }
        return difference;
    }

I will add the MyDate class below for context:

public class MyDate {

    private int day;
    private int month;
    private int year;

    public MyDate(int day, int montd, int year) {
        this.day = day;
        this.month = montd;
        this.year = year;
    }

    public String toString() {
        return this.day + "." + this.month + "." + this.year;
    }

    public boolean earlier(MyDate compared) {
        if (this.year < compared.year) {
            return true;
        }

        if (this.year == compared.year && this.month < compared.month) {
            return true;
        }

        if (this.year == compared.year && this.month == compared.month
                && this.day < compared.day) {
            return true;
        }

        return false;
    }
Budaika
  • 121
  • 9

3 Answers3

3

I will presume that your method works correctly. How can it be improved? The first thing you can do is eliminate all of the repeated this.year - comparedDate.year and comparedDate.year - this.year calculations. You do them no matter what, so let's put them at the top of their respective if blocks.

public int differenceInYears(MyDate comparedDate) {
    int difference;

    if (this.year > comparedDate.year) {
        difference = this.year - comparedDate.year;

        if (this.month > comparedDate.month) {
        }
        else if (this.month == comparedDate.month) {
            if (this.day >= comparedDate.day) {
            }
            else {
                difference -= 1;
            }
        }
        else {
            difference -= 1;
        }
    }
    if (comparedDate.year > this.year) {
        difference = comparedDate.year - this.year;

        if (comparedDate.month > this.month) {
        }
        else if (comparedDate.month == this.month) {
            if (comparedDate.day >= this.day) {
            }
            else {
                difference -= 1;
            }
        }
        else {
            difference -= 1;
        }
    }

    return difference;
}

Next, let's get rid of those empty branches.

public int differenceInYears(MyDate comparedDate) {
    int difference;

    if (this.year > comparedDate.year) {
        difference = this.year - comparedDate.year;

        if (this.month == comparedDate.month) {
            if (this.day < comparedDate.day) {
                difference -= 1;
            }
        }
        else if (this.month < comparedDate.month) {
            difference -= 1;
        }
    }
    if (comparedDate.year > this.year) {
        difference = comparedDate.year - this.year;

        if (comparedDate.month == this.month) {
            if (comparedDate.day < this.day) {
                difference -= 1;
            }
        }
        else if (comparedDate.month < this.month) {
            difference -= 1;
        }
    }

    return difference;
}

Now let's see if we can't squeeze together some of the conditions with && and ||.

public int differenceInYears(MyDate comparedDate) {
    int difference;

    if (this.year > comparedDate.year) {
        difference = this.year - comparedDate.year;

        if (this.month == comparedDate.month && this.day < comparedDate.day ||
            this.month < comparedDate.month)
        {
            difference -= 1;
        }
    }
    if (comparedDate.year > this.year) {
        difference = comparedDate.year - this.year;

        if (comparedDate.month == this.month && comparedDate.day < this.day ||
            comparedDate.month < this.month)
        {
            difference -= 1;
        }
    }

    return difference;
}

Those two blocks look awfully similar, don't they? We could combine them by conditionally swapping this and comparedDate. Let a and b be the earlier and later date, respectively.

public int differenceInYears(MyDate comparedDate) {
    MyDate a = (this.year < comparedDate.year) ? this : comparedDate;
    MyDate b = (this.year < comparedDate.year) ? comparedDate : this;

    int difference = b.year - a.year;

    if (a.year < b.year) {
        if (a.month == b.month && a.day < b.day ||
            a.month < b.month)
        {
            difference -= 1;
        }
    }

    return difference;
}

And one final squeeze.

public int differenceInYears(MyDate comparedDate) {
    MyDate a = (this.year < comparedDate.year) ? this : comparedDate;
    MyDate b = (this.year < comparedDate.year) ? comparedDate : this;

    int difference = b.year - a.year;

    if (a.year < b.year &&
       (a.month == b.month && a.day < b.day ||
        a.month < b.month))
    {
        difference -= 1;
    }

    return difference;
}
John Kugelman
  • 349,597
  • 67
  • 533
  • 578
1

Okay so after some consideration, I think this may look a bit nicer:

public int differenceInYears(MyDate comparedDate){
    //Calculate days total
    long daysTotalThisDate = this.year * 365 + this.month * 30 + this.day;
    long daysTotalComparedDate = comparedDate.year * 365 + comparedDate.month * 30 + comparedDate.day;

    //Get absolute value
    long differenceInDays = daysTotalThisDate - daysTotalComparedDate;
    if (differenceInDays < 0){
        differenceInDays *= -1;
    }

    //the (int) cast will always round down, so anything under 365 will be 0
    return (int) differenceInDays / 365;
}

This does NOT take into account leap years.

Before people think my calculation of daysTotal... is wrong. You are correct. But I did it wrong for both calculations, so the end result is still ok considering we only need to calculate difference in year, not days.

Roel Strolenberg
  • 2,922
  • 1
  • 15
  • 29
  • I know the calculation isn't correct, but the end result still is. since we're returning an int: whether we return 30/365 or 29/365. In both cases 0 is returned. – Roel Strolenberg Jan 12 '17 at 20:19
  • 1
    It's sheerly by coincidence that this can work. This answer is incomplete without explaining the nuances. Specifically it works because the average number of days for a month(30.417) is bigger than the longest month(31) subtract 1(30). If this fact weren't true you would not be able to do this. – Cruncher Jan 12 '17 at 20:31
  • I see it now. You are correct indeed. Especially when the years are more apart. Thank you for clearing that up! – Roel Strolenberg Jan 12 '17 at 20:39
  • Also using the Math.abs function cleans this up a fair bit – Cruncher Jan 12 '17 at 23:52
  • Yes but I wasn't sure he wanted or could use this – Roel Strolenberg Jan 13 '17 at 06:32
0

The key point here is: sometimes "complex" computations are required.

In that sense: do not worry about being inefficient. You should spend your time on trying to write really readable code; very much like the answer from John is guiding you. In general, you would be looking towards principles as the "single layer of abstraction" principle for example; to avoid having such lengthy if/else cascades.

The other key point: such assignments are perfect for unit testing. In other words: you should start with writing little testcases that simply call your method with various (pre-selected) input dates; to then check that the expected result is returned.

In other words:

You first focus on writing a lot of testcases that you can you to determine that your implementation is correct ... because that enables you to refactor your code without being afraid of breaking it.

Using JUnit is as easy as:

@Test
public testDifferenceIs0 {
  MyDate thisYear = new MyDate(1,1,2017);
  MyDate thisYearToo = new MyDate(1,1,2017);
  assertThat(thisYear.differenceInYears(thisYearToo), is(0));
}

for example.

GhostCat
  • 137,827
  • 25
  • 176
  • 248