How to check if a year is leap in Java?

Asked

Viewed 348 times

6

How to know if the current year - indeed, if any year - is leap?

Since I can have only the numerical value of the year, or an object representing a date (i.e. Date, Calendar, LocalDate, etc), the way to check would be the same for all cases?

  • 2

    Before anyone wonders that I even answered my own question, this is perfectly acceptable and within the rules. Including, the ask question page has a option to post your own answer along with the question. Of course, if someone has a better and more complete answer, they should definitely post it. The goal is to bring this knowledge to the site (because I did a search and had found nothing about it).

  • This answers your question? Calculation of age in Java

  • @Ricardopunctual I believe it is not dup: in the other question the main focus is the calculation of age (that of course, has to consider the leap years), but the verification of the leap year itself is a detail there (and in addition, for seeming an exercise, only the "manual" calculation was suggested). Here the focus is only the verification of the leap year (and I don’t touch on the subject of age calculation or any other case of date arithmetic)

  • 1

    @hkotsubo would summarize (editing the other question) and in the other would put a link to that answer, based on the proposal here, which is "whether you already use a date API or not", at the reader’s discretion. ps: congratulations on the initiative.

  • 4

    I’d answer, but the guy who responded knows a lot more than I do and I had to give him a +1. Joking aside, I get uncomfortable when we have to give explanations of our actions already predicting retaliation due to the community’s ignorance about the functioning of the site, not talking about the older users or novices already engaged but about those users that a guidance on the use of the platform is a deadly offense.

1 answer

6


There are several ways to do it, and it varies according to the data you already have and/or the Java version.


I have only the numerical value of the year

If you already have the numerical value of the year (in a int or long, for example), and is using Java >= 8, can use the class java.time.Year, who owns the static method isLeap (this is the most "fast" and direct, and do not need to create any object as it is just a static method call):

int ano = 2000;
System.out.println(Year.isLeap(ano)); // true

To Java <= 7, use Calendar (unfortunately it is not so direct as it has to create an instance and then get the amount of days of the year):

int ano = 2000;
Calendar cal = Calendar.getInstance();
cal.set(Calendar.YEAR, ano);
// se a quantidade de dias do ano for maior que 365, é bissexto
System.out.println(cal.getActualMaximum(Calendar.DAY_OF_YEAR) > 365); // true

Another option is to use GregorianCalendar (also need to create an instance), which has the method isLeapYear:

int ano = 2000;
GregorianCalendar cal = new GregorianCalendar();
System.out.println(cal.isLeapYear(ano)); // true

Remembering that the above classes use the rule of Gregorian calendar: if the year is divisible by 100, it shall be leap only if it is divisible by 400, and if it is not divisible by 100, leap only if it is divisible by 4). Of course, if you want, you can implement this rule manually:

public static boolean bissexto(int ano) {
    return (ano % 4 == 0) && (ano % 100 != 0 || ano % 400 == 0);
}

But if it already exists in the native API, I see no reason to reinvent the wheel...

There’s only one detail: GregorianCalendar has a "cut-off date", which in this case is 1582 (when the Gregorian Calendar was made official - more precisely, 1582-10-15T00:00:00Z: 15 October 1582 at midnight in UTC). That means that for dates prior to this, he considers the rule of Julian calendar, in which it is sufficient to be divisible by 4 for it to be leap: that is, using GregorianCalendar, 1900 is not leap because it is greater than 1582 and falls into the rule of being multiple of 100 but not 400; but 1500 is considered leap because it is less than 1582, so the rule of the multiple of 400 does not apply.

But it is possible to change this behavior by setting a different cut-off date:

int ano = 1500;
GregorianCalendar cal = new GregorianCalendar();
// 1500 é anterior à data de corte (1582) e usa a regra antiga (basta ser divísivel por 4)
System.out.println(cal.isLeapYear(ano)); // true

cal.setGregorianChange(new Date(-62135758799190L)); // mudar data de corte para 1 de janeiro do ano 1

// agora 1500 não é mais considerado bissexto (usa a regra atual: múltiplos de 100 só são bissextos se forem divisíveis por 400)
System.out.println(cal.isLeapYear(ano)); // false

Remembering that only GregorianCalendar do it. Now Calendar.getInstance() may or may not as the method getInstance can return a GregorianCalendar, but can also return other implementations, according to the default locale which is configured in the JVM. For example, for the locale th_TH (thai), getInstance returns a BuddhistCalendar (implementing the Buddhist Calendar), and in this case considers that 1500 is not leap:

Locale.setDefault(new Locale("th", "TH"));
int ano = 2000;

// no locale th_TH, não é criado um GregorianCalendar
Calendar cal2 = Calendar.getInstance();
System.out.println(cal2.getClass()); // class sun.util.BuddhistCalendar
cal2.set(Calendar.YEAR, ano);
System.out.println(cal2.getActualMaximum(Calendar.DAY_OF_YEAR) > 365); // false

GregorianCalendar cal = new GregorianCalendar();
System.out.println(cal.isLeapYear(ano)); // true

In case, you can force the creation of a GregorianCalendar setting the locale (Calendar.getInstance(new Locale("pt", "BR")), for example - the vast majority of locales returns a GregorianCalendar).

Already Year.isLeap considers that 1500 is not leap, and there is no way to set a cut-off date (the multiple rule of 400 is always used).

But that’s for any numerical value. If you want the year current, use Year.now().isLeap() (in this case there is no way, need to create an instance of Year corresponding to the current year). And for Calendar, Just don’t set the year (don’t call set(Calendar.YEAR, ano)), for getInstance() already returns the current date.


And if I already have an object that represents a date?

Java <= 7

In case you have a Calendar, just use the above methods.

If you have a Date, nay recommend using the method getYear(), for 2 reasons:

  • this method returns the normalized year value in 1900. That is, it subtracts 1900 from the year (for example, for the current date - year 2021 - new Date().getYear() returns 121), then you have to remember to add 1900 (if you want to use any of the above methods)

  • this method is deprecated since Java 1.1 and the documentation itself recommends the use of Calendar, therefore, the correct one would be:

    Date date = // um Date qualquer
    // cria um Calendar usando o Date
    Calendar cal = Calendar.getInstance();
    cal.setTime(date);
    // usar os métodos já indicados acima
    

Remembering that if you want the current date, you don’t need to create a Date, just use Calendar.getInstance().

Java >= 8 (java.time)

To LocalDate, just use the method isLeapYear:

LocalDate date = // um LocalDate qualquer;
System.out.println(date.isLeapYear());

And for the other guys from java.time, you can get the value of the year and pass to Year.isLeap, or get the LocalDate where applicable (or where Year from the object):

LocalDateTime dt = // um LocalDateTime qualquer

// passar o valor do ano para Year.isLeap
System.out.println(Year.isLeap(dt.getYear()));

// ou obter o LocalDate
System.out.println(dt.toLocalDate().isLeapYear());

// ou obter o Year a partir do objeto
System.out.println(Year.from(dt).isLeap());

The first form (use getYear) is more direct and - in my opinion - simpler. The advantage is that the native date types of the API (the ones that have the year, of course) already have this getter (except, of course, LocalTime and OffsetTime, which only has the time fields, MonthDay, which has only day and month, plus Instant, Month and DayOfWeek, which also do not have the year).

The second form may seem more disadvantageous by creating an instance LocalDate, but in the current implementation, the types use composition and encapsulate an instance of LocalDate, which is returned by toLocalDate (that is, there is no creation of a new instance). Anyway, as it is a detail of implementation, it is not advisable to rely blindly on this feature, if it is not desirable to create new instances unnecessarily.

The third form (Year.from) is most indicated when you are working with a TemporalAccessor (an interface that all date types implement), not caring much about the specific type of the object in question: all that matters is that it has the field for the year. Thus, it would be possible to have, for example, a method that takes any of the date classes of the API (and any other that implements TemporalAccessor) and can check if the year is leap:

static boolean bissexto(TemporalAccessor t) {
    return Year.from(t).isLeap();
}

...
// funciona com qualquer tipo que implemente TemporalAccessor e tenha o campo referente ao ano
System.out.println(bissexto(LocalDate.now()));
System.out.println(bissexto(LocalDateTime.now()));

String string = // uma string qualquer contendo a data
DateTimeFormatter fmt = // um DateTimeFormatter que faz parsing da string acima
// não preciso transformar o resultado do parse em uma instância específica de nenhuma das classes da API
// posso passar o TemporalAccessor diretamente para o método "bissexto"
System.out.println(bissexto(fmt.parse(string)));

Other calendars

The above rules apply to the ISO 8601 calendar (briefly, it is as if we applied the Gregorian Calendar rule retroactively, for dates prior to October 1582 - which is the "cut-off date" mentioned above). Basically, this is the calendar default of java.time, used implicitly and internally by API core classes.

But the java.time also supports other calendars - the available implementations are on bundle java.time.chrono. For example, the class ThaiBuddhistChronology (that implements the Buddhist calendar) has a different rule for leap years. See below for an example comparing to IsoChronology (implementing the above mentioned rules):

int ano = 2020;
System.out.println(IsoChronology.INSTANCE.isLeapYear(ano)); // true
System.out.println(ThaiBuddhistChronology.INSTANCE.isLeapYear(ano)); // false

ano = 2543;
System.out.println(IsoChronology.INSTANCE.isLeapYear(ano)); // false
System.out.println(ThaiBuddhistChronology.INSTANCE.isLeapYear(ano)); // true

Anyway, for other calendars, just use the respective package class java.time.chrono. Or, if you need other calendars (and you don’t want to implement them), the way is to turn to external libraries. Two examples are the project Threeten Extra (of the same creator of java.time) and the Time4j: both have support for several other calendars.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.