In the calculation, you are only taking into account the year, but you should also check if the person’s birthday has passed (if it has not passed, you have to subtract 1 from age). Thus:
public static int calculoIdade(Data dataNascimento) {
Calendar hoje = Calendar.getInstance();
int idade = hoje.get(Calendar.YEAR) - dataNascimento.year;
// se ainda não chegou o aniversário, diminui 1 ano
int mesAtual = hoje.get(Calendar.MONTH) + 1;
if ((mesAtual == dataNascimento.month && hoje.get(Calendar.DAY_OF_MONTH) < dataNascimento.day)
|| mesAtual < dataNascimento.month) {
idade--;
}
return idade;
}
One annoying detail is that in class Calendar
the months are indexed to zero (January is zero, February is 1, etc.), so you have to add 1 to get the correct value of the month (since your class Data
seems to use the correct values: January 1, February 2, etc).
Then I see if the person has not yet birthday this year. IE:
- whether it is in the same month and the birthday has not yet arrived, or
- if the birthday month has not yet arrived
If any of the above conditions occur, it is because this year’s birthday has not yet occurred, and in that case should decrease 1 of age.
There are other details to consider.
In the constructor, if any value is invalid, you print the respective error message, but continue creating the date anyway. But if the value is invalid, the Data
nor should it be created. Instead, she should make an exception (read more about this here, here and here).
In addition, it is possible to simplify the logic of checking the day based on the month. It is also worth remembering that a leap year is one that is divisible by 4, except for if divisible by 100 (unless also divisible by 400).
Finally, a suggestion for the constructor and the validation of the date would be:
public Data(int day, int month, int year) {
validarData(day, month, year);
this.day = day;
this.month = month;
this.year = year;
}
private void validarData(int day, int month, int year) {
if (month < 1 || month > 12) {
throw new IllegalArgumentException("Informe um mês válido.");
}
if (year > 2020) {
throw new IllegalArgumentException("Ano inválido");
}
// primeiro calcula a quantidade de dias do mês
int qtdDias;
if (month == 2) {
// ano bissexto, divisível por 4 (mas se for divisível por 100, só é bissexto se for divisível por 400)
if (year % 4 == 0 && ((year % 100) != 0 || (year % 400) == 0)) {
qtdDias = 29;
} else {
qtdDias = 28;
}
} else if (month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12) {
qtdDias = 31;
} else {
qtdDias = 30;
}
// verifica se o dia está no intervalo válido
if (day < 1 || day > qtdDias) {
throw new IllegalArgumentException("Dia tem que ser de 1 a " + qtdDias);
}
}
I left the method validarData
as private
, Apparently, it’s only used by the class itself to validate. If any value is invalid, it throws the exception and the instance is not created (since it makes no sense to validate, inform that the value is wrong and create the instance anyway).
Note that the day and month condition uses the operator ||
("or") and not &&
("and"). That’s because there’s no way that the month is both smaller than 1 and larger than 12 (either one or the other or none, it just doesn’t have to be both at the same time), so use &&
, would never get into the if
.
I also changed the fields of Integer
for int
, because I don’t think there’s any reason to use the Wrappers - read more about here.
Use a Date API
If it’s just an exercise, it’s okay to try to implement a class that represents a date. But if it’s for code in production, use what you already have.
From Java 8 you can use the API java.time
. To calculate age, it would look like this:
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
public static int calculoIdade(int dia, int mes, int ano) {
return (int) ChronoUnit.YEARS.between(LocalDate.of(ano, mes, dia), LocalDate.now());
}
The difference is that the method between
returns a long
, but if you know that the value will always be less than 231-1 (just over 2 billion), can do the cast for int
hassle-free.
And the class itself LocalDate
already checks the values of the day, month and year, if it is leap, etc, and launches a exception if any of them is invalid. You would only need to add the condition of checking if year is higher than 2020, which is specific to your code.
For versions prior to JDK 8, you can use JDK 8 itself Calendar
. Or, for JDK 6 and 7 there is the Threeten Backport, an excellent backport of java.time
. Most of the features of Java 8 are present, but instead of the classes being in the package java.time
, they stay in the package org.threeten.bp
(apart from this, the code above would look the same).
February 29
It is worth remembering that there is a corner case. If the person was born, for example, on 29 February 2020, how old will he be on 28 February 2021?
The above codes consider that the age will be zero, as her birthday has not yet arrived. But as in the year 2021, February only has 28 days, so only from March 1 will be considered that the age is 1.
If you want to consider that in a non-leap year, February 28th is the "birthday", you will have to put this rule in your methods.
In your class Data
, would be:
private static boolean bissexto(int ano) {
return ano % 4 == 0 && ((ano % 100) != 0 || (ano % 400) == 0);
}
public static int calculoIdade(Data dataNascimento) {
Calendar hoje = Calendar.getInstance();
int idade = hoje.get(Calendar.YEAR) - dataNascimento.year;
// se ainda não chegou o aniversário, diminui 1 ano
int mesAtual = hoje.get(Calendar.MONTH) + 1;
int diaAtual = hoje.get(Calendar.DAY_OF_MONTH);
if ((mesAtual == dataNascimento.month && diaAtual < dataNascimento.day) || mesAtual < dataNascimento.month) {
// mas se nasceu em 29/02 e hoje é 28/02 (e o ano não é bissexto), não subtrai
if (!(diaAtual == 28 && mesAtual == 2
&& dataNascimento.day == 29 && dataNascimento.month == 2
&& !bissexto(hoje.get(Calendar.YEAR))))
idade--;
}
return idade;
}
Note that I have devised an auxiliary method to verify that the year is leap.
But with the java.time
you can use the class Year
, which has a ready method to check this:
public static int calculoIdade(int dia, int mes, int ano) {
LocalDate hoje = LocalDate.now();
int idade = (int) ChronoUnit.YEARS.between(LocalDate.of(ano, mes, dia), hoje);
// se nasceu em 29/02 e hoje é 28/02 (e o ano não é bissexto), soma 1 na idade
if (dia == 29 && mes == 2 && hoje.getDayOfMonth() == 28
&& hoje.getMonthValue() == 2 && !Year.isLeap(hoje.getYear()))
idade++;
return idade;
}
For a more complete discussion of leap year verification, see this question.
Nothing may be less than 1 and greater than 31. Perhaps you wish to do
if (day < 1 || day > 31) {
, ditto{if(month < 1 || month > 12) {
, or in place of and.– anonimo