Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users
Tabletop RPGs
Tabletop RPGs
Community Proposals
Community Proposals
tag:snake search within a tag
answers:0 unanswered questions
user:xxxx search by author id
score:0.5 posts with 0.5+ score
"snake oil" exact phrase
votes:4 posts with 4+ votes
created:<1w created < 1 week ago
post_type:xxxx type of post
Search help
Notifications
Mark all as read See all your notifications »
Q&A

Welcome to Software Development on Codidact!

Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.

How to verify if a year is leap in Java?

+7
−0

How to verify if a year is leap?

Given a numeric value (such as 2023) or some object that represents a date (such as Date, Calendar, LocalDate, etc), how can I do it?

History
Why does this post require moderator attention?
You might want to add some details to your flag.
Why should this post be closed?

0 comment threads

1 answer

+9
−0

There are many ways to do it, it depends on what data you already have and/or the Java version.


I have only the year's numeric value

If you already have a value as a number (int or long), and is using Java >= 8, you can use the java.time.Year class, which has the static method isLeap. This is the "faster" and more straighforward, no need to create instances of any object, it's just a static method call:

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

For Java <= 7, use Calendar. Unfortunately it's not as straightforward as Java 8's solution, you have to create an instance and then check how many days the year has:

int year = 2000;
Calendar cal = Calendar.getInstance();
cal.set(Calendar.YEAR, year);
// the year is leap if it has more than 365 days
System.out.println(cal.getActualMaximum(Calendar.DAY_OF_YEAR) > 365); // true

Another alternative is to create an instance of GregorianCalendar and use the isLeapYear method:

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

Both Calendar and GregorianCalendar use the Gregorian Calendar's rules: if the year is divisible by 100, it's leap only if it's also divisible by 400. If it's not divisible by 100, it's leap if it's divisible by 4. You could implement this rule by yourself:

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

But it already exists in the native API, so I don't see a reason to reinvent the wheel.

But beware: GregorianCalendar has a "cut-off date", which is the year 1582 (when the Gregorian Calendar was introduced) - to be more precise, the date is 1582-10-15T00:00:00Z: October 15th, 1582, at midnight, in UTC (note: not all countries changed their calendars in 1582, but that's the date configured in the class when an instance is created).

This means that, for any dates before that, GregorianCalendar uses the Julian Calendar's rules, in which any year divisible by 4 is leap.

Example: according to GregorianCalendar, 1900 isn't a leap year, because it's greater than 1582 and if follows the current rule (it's divisible by 100, but no by 400), but 1500 is leap because it's before 1582, so the divisible-by-400 rule doesn't apply.

But you can change this behaviour by setting a different cut-off date:

int year = 1500;
GregorianCalendar cal = new GregorianCalendar();
// 1500 is before the cut-off date (1582) and uses the "old" rule (divisible by 4 == leap)
System.out.println(cal.isLeapYear(year)); // true

cal.setGregorianChange(new Date(-62135758799190L)); // change cut-off date to January 1st, 0001

// 1500 isn't leap anymore (uses the current rule: if divisible by 100, it's leap only if it's also divisible by 400)
System.out.println(cal.isLeapYear(year)); // false

Another detail: only GregorianCalendar does that. But Calendar.getInstance() might or might not do it, because the getInstance method can return either a GregorianCalendar, or some other sub-classes, depending on the default locale configured in the JVM. For example: if the default locale is th_TH (Thailand), getInstance returns a BuddhistCalendar, and for that calendar, 1500 isn't a leap year:

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

// for locale th_TH, it doesn't create a GregorianCalendar
Calendar cal2 = Calendar.getInstance();
System.out.println(cal2.getClass()); // class sun.util.BuddhistCalendar
cal2.set(Calendar.YEAR, year);
System.out.println(cal2.getActualMaximum(Calendar.DAY_OF_YEAR) > 365); // false

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

Of course you could force it to create a GregorianCalendar, by using a specific locale (such as Calendar.getInstance(new Locale("pt", "BR")) - for most locales, it returns a GregorianCalendar).

On the other hand, Year.isLeap can't be configured (there's no such thing as changing the cut-off date), so it will always say that 1500 isn't leap.

That's what you'd use for any numeric value. If you want the current year, though, you could use Year.now().isLeap() for Java >= 8 (in that case, we can't avoid the creation of an instance). And for Calendar, simply don't set the year (don't call set(Calendar.YEAR, ano)), as getInstance() already returns the current date.


What if I have an object that represents a date?

Java <= 7

If you have a Calendar instance, use the methods above.

if you have a java.util.Date, don't use getYear(), for two reasons:

  • it returns a value that is the result of subtracting 1900 from the year. Therefore, if the current year is 2023, new Date().getYear() returns 123, and to check if it's leap, you have to remember to add 1900
  • it's deprecated since Java 1.1 and the documentation recommends using Calendar

Therefore, if you have a Date instance, do like this:

Date date = // some Date instance
// create a Calendar and set the Date
Calendar cal = Calendar.getInstance();
cal.setTime(date);
// use the Calendar's methods already explained above

And once again, if you want the current date, don't use Date, just Calendar.getInstance().

Java >= 8 (java.time)

For LocalDate, just use the isLeapYear method:

LocalDate date = // some LocalDate instance
System.out.println(date.isLeapYear());

For other java.time types, you can get the year numeric value and pass it to Year.isLeap, or get a LocalDate if applicable (or get a Year instance from the object). Example:

LocalDateTime dt = // some LocalDateTime instance

// get the year numeric value and pass it to Year.isLeap
System.out.println(Year.isLeap(dt.getYear()));

// or get a LocalDate instance
System.out.println(dt.toLocalDate().isLeapYear());

// or get a Year instance
System.out.println(Year.from(dt).isLeap());

The first one (getYear) is more straightforward and - IMO - simpler. All API native date/time types have this getter (except, of course, the ones that don't make sense to have a year: LocalTime, OffsetTime, MonthDay, Instant, Month and DayOfWeek).

The second one (dt.toLocalDate().isLeapYear()) might seem "bad" because it creates a LocalDate instance, but in the current implementation, LocalDateTime uses composition and encapsulates an instance of LocalDate, which is returned by toLocalDate. Anyway, it's an implementation detail: don't blindly rely on it if you wish to minimize the creation of new instances.

The third one (Year.from) is preffered if you're working with a TemporalAccessor (an interface implemented by all API date/time types), without caring too much about the actual type: if it has a year field, that's all that matters. With this, you could create a method that works with any date/time type (and any other that you create, as long as it implements TemporalAccessor):

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

...
// it works with any type that implements TemporalAccessor and has a year field
System.out.println(isLeapYear(LocalDate.now()));
System.out.println(isLeapYear(LocalDateTime.now()));

String string = "10/01/2023"; // some date in day/month/year format
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("dd/MM/uuuu");
// I don't need to create a LocalDate instance, just pass the result of parse (which is a TemporalAccessor) directly to the method
System.out.println(isLeapYear(fmt.parse(string)));

As a side note, in this case, you could also create a TemporalQuery and pass it directly to the parse method:

TemporalQuery<Boolean> isLeapYear = (t) -> Year.from(t).isLeap();

String string = "10/01/2020"; // some date in day/month/year format
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("dd/MM/uuuu");
System.out.println(fmt.parse(string, isLeapYear)); // true

Other calendars

All the rules described above are valid for the ISO 8601 calendar. For short, it's like applying the Gregorian Calendar rules retroactively, for dates before October 1582 (the "cut-off date", as we saw above). That's the default calendar used by java.time classes.

But the java.time API also supports another calendars: the implementations can be found in the java.time.chrono package. For example, the class ThaiBuddhistChronology (implementation of Buddhist Calendar) has a different rule for leap years:

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

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

Anyway, for other calendar systems, just use the respective class in java.time.chrono. Or, if you need a different one (and don't want to implement them), you can use external libraries. Two alternatives are the ThreeTen Extra project (made by the same creator of java.time) and Time4j: both provide nice support for other calendars.

History
Why does this post require moderator attention?
You might want to add some details to your flag.

1 comment thread

VERY thorough answer. +1 (1 comment)

Sign up to answer this question »