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.

Post History

84%
+9 −0
Q&A How to verify if a year is leap in Java?

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 i...

posted 2y ago by hkotsubo‭  ·  edited 2y ago by hkotsubo‭

Answer
#6: Post edited by user avatar hkotsubo‭ · 2023-02-16T18:24:04Z (almost 2 years ago)
  • 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](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/time/Year.html), which has the [static method `isLeap`](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/time/Year.html#isLeap(long)). This is the "faster" and more straighforward, no need to create instances of any object, it's just a static method call:
  • ```java
  • int year = 2000;
  • System.out.println(Year.isLeap(year)); // true
  • ```
  • For **Java <= 7**, use [`Calendar`](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/Calendar.html). 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:
  • ```java
  • 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`](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/GregorianCalendar.html) and use the [`isLeapYear` method](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/GregorianCalendar.html#isLeapYear(int)):
  • ```java
  • int year = 2000;
  • GregorianCalendar cal = new GregorianCalendar();
  • System.out.println(cal.isLeapYear(year)); // true
  • ```
  • ---
  • Both `Calendar` and `GregorianCalendar` use the [Gregorian Calendar's rules](https://en.wikipedia.org/wiki/Gregorian_calendar): 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:
  • ```java
  • 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](https://en.wikipedia.org/wiki/Coordinated_Universal_Time). This means that, for any dates before that, `GregorianCalendar` uses the [Julian Calendar's rules](https://en.wikipedia.org/wiki/Julian_calendar), 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:
  • ```java
  • 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:
  • ```java
  • 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()`](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/Date.html#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:
  • ```java
  • 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](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/time/LocalDate.html#isLeapYear()):
  • ```java
  • 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:
  • ```java
  • 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`):
  • ```java
  • 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:
  • ```java
  • 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](https://docs.oracle.com/javase/tutorial/datetime/iso/overview.html).
  • But the `java.time` API also supports another calendars: the implementations can be found in the [`java.time.chrono` package](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/time/chrono/package-summary.html). For example, the class `ThaiBuddhistChronology` (implementation of [Buddhist Calendar](https://en.wikipedia.org/wiki/Buddhist_calendar)) has a different rule for leap years:
  • ```java
  • 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](https://www.threeten.org/threeten-extra/apidocs/org.threeten.extra/org/threeten/extra/chrono/package-summary.html) (made by the same creator of `java.time`) and [Time4j](http://www.time4j.net/javadoc-en/): both provide nice support for other calendars.
  • 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](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/time/Year.html), which has the [static method `isLeap`](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/time/Year.html#isLeap(long)). This is the "faster" and more straighforward, no need to create instances of any object, it's just a static method call:
  • ```java
  • int year = 2000;
  • System.out.println(Year.isLeap(year)); // true
  • ```
  • For **Java <= 7**, use [`Calendar`](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/Calendar.html). 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:
  • ```java
  • 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`](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/GregorianCalendar.html) and use the [`isLeapYear` method](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/GregorianCalendar.html#isLeapYear(int)):
  • ```java
  • int year = 2000;
  • GregorianCalendar cal = new GregorianCalendar();
  • System.out.println(cal.isLeapYear(year)); // true
  • ```
  • ---
  • Both `Calendar` and `GregorianCalendar` use the [Gregorian Calendar's rules](https://en.wikipedia.org/wiki/Gregorian_calendar): 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:
  • ```java
  • 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](https://en.wikipedia.org/wiki/Coordinated_Universal_Time) (note: [not all countries changed their calendars in 1582](https://en.wikipedia.org/wiki/Adoption_of_the_Gregorian_calendar), 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](https://en.wikipedia.org/wiki/Julian_calendar), 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:
  • ```java
  • 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:
  • ```java
  • 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()`](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/Date.html#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:
  • ```java
  • 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](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/time/LocalDate.html#isLeapYear()):
  • ```java
  • 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:
  • ```java
  • 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`):
  • ```java
  • 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:
  • ```java
  • 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](https://docs.oracle.com/javase/tutorial/datetime/iso/overview.html).
  • But the `java.time` API also supports another calendars: the implementations can be found in the [`java.time.chrono` package](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/time/chrono/package-summary.html). For example, the class `ThaiBuddhistChronology` (implementation of [Buddhist Calendar](https://en.wikipedia.org/wiki/Buddhist_calendar)) has a different rule for leap years:
  • ```java
  • 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](https://www.threeten.org/threeten-extra/apidocs/org.threeten.extra/org/threeten/extra/chrono/package-summary.html) (made by the same creator of `java.time`) and [Time4j](http://www.time4j.net/javadoc-en/): both provide nice support for other calendars.
#5: Post edited by user avatar hkotsubo‭ · 2023-02-16T18:14:23Z (almost 2 years ago)
  • 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](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/time/Year.html), which has the [static method `isLeap`](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/time/Year.html#isLeap(long)). This is the "faster" and more straighforward, no need to create instances of any object, it's just a static method call:
  • ```java
  • int year = 2000;
  • System.out.println(Year.isLeap(year)); // true
  • ```
  • For **Java <= 7**, use [`Calendar`](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/Calendar.html). 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:
  • ```java
  • 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`](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/GregorianCalendar.html) and use the [`isLeapYear` method](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/GregorianCalendar.html#isLeapYear(int)):
  • ```java
  • int year = 2000;
  • GregorianCalendar cal = new GregorianCalendar();
  • System.out.println(cal.isLeapYear(year)); // true
  • ```
  • ---
  • Both `Calendar` and `GregorianCalendar` use the [Gregorian Calendar's rules](https://en.wikipedia.org/wiki/Gregorian_calendar): 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:
  • ```java
  • 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](https://en.wikipedia.org/wiki/Coordinated_Universal_Time). This means that, for any dates before that, `GregorianCalendar` uses the [Julian Calendar's rules](https://en.wikipedia.org/wiki/Julian_calendar), 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:
  • ```java
  • 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:
  • ```java
  • 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()`](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/Date.html#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:
  • ```java
  • 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](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/time/LocalDate.html#isLeapYear()):
  • ```java
  • 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:
  • ```java
  • 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`):
  • ```java
  • 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:
  • ```java
  • 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](https://docs.oracle.com/javase/tutorial/datetime/iso/overview.html).
  • But the `java.time` API also supports another calendars: the implementations can be found in the [`java.time.chrono` package](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/time/chrono/package-summary.html). For example, there's the class `ThaiBuddhistChronology` (implementation of [Buddhist Calendar](https://en.wikipedia.org/wiki/Buddhist_calendar)) has a different rule for leay years:
  • ```java
  • 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](https://www.threeten.org/threeten-extra/apidocs/org.threeten.extra/org/threeten/extra/chrono/package-summary.html) (made by the same creator of `java.time`) and [Time4j](http://www.time4j.net/javadoc-en/): both provide nice support for other calendars.
  • 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](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/time/Year.html), which has the [static method `isLeap`](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/time/Year.html#isLeap(long)). This is the "faster" and more straighforward, no need to create instances of any object, it's just a static method call:
  • ```java
  • int year = 2000;
  • System.out.println(Year.isLeap(year)); // true
  • ```
  • For **Java <= 7**, use [`Calendar`](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/Calendar.html). 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:
  • ```java
  • 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`](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/GregorianCalendar.html) and use the [`isLeapYear` method](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/GregorianCalendar.html#isLeapYear(int)):
  • ```java
  • int year = 2000;
  • GregorianCalendar cal = new GregorianCalendar();
  • System.out.println(cal.isLeapYear(year)); // true
  • ```
  • ---
  • Both `Calendar` and `GregorianCalendar` use the [Gregorian Calendar's rules](https://en.wikipedia.org/wiki/Gregorian_calendar): 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:
  • ```java
  • 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](https://en.wikipedia.org/wiki/Coordinated_Universal_Time). This means that, for any dates before that, `GregorianCalendar` uses the [Julian Calendar's rules](https://en.wikipedia.org/wiki/Julian_calendar), 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:
  • ```java
  • 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:
  • ```java
  • 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()`](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/Date.html#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:
  • ```java
  • 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](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/time/LocalDate.html#isLeapYear()):
  • ```java
  • 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:
  • ```java
  • 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`):
  • ```java
  • 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:
  • ```java
  • 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](https://docs.oracle.com/javase/tutorial/datetime/iso/overview.html).
  • But the `java.time` API also supports another calendars: the implementations can be found in the [`java.time.chrono` package](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/time/chrono/package-summary.html). For example, the class `ThaiBuddhistChronology` (implementation of [Buddhist Calendar](https://en.wikipedia.org/wiki/Buddhist_calendar)) has a different rule for leap years:
  • ```java
  • 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](https://www.threeten.org/threeten-extra/apidocs/org.threeten.extra/org/threeten/extra/chrono/package-summary.html) (made by the same creator of `java.time`) and [Time4j](http://www.time4j.net/javadoc-en/): both provide nice support for other calendars.
#4: Post edited by user avatar hkotsubo‭ · 2023-02-16T18:09:52Z (almost 2 years ago)
  • 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](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/time/Year.html), which has the [static method `isLeap`](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/time/Year.html#isLeap(long)). This is the "faster" and more straighforward, no need to create instances of any object, it's just a static method call:
  • ```java
  • int year = 2000;
  • System.out.println(Year.isLeap(year)); // true
  • ```
  • For **Java <= 7**, use [`Calendar`](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/Calendar.html). 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:
  • ```java
  • 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`](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/GregorianCalendar.html) and use the [`isLeapYear` method](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/GregorianCalendar.html#isLeapYear(int)):
  • ```java
  • int year = 2000;
  • GregorianCalendar cal = new GregorianCalendar();
  • System.out.println(cal.isLeapYear(year)); // true
  • ```
  • ---
  • Both `Calendar` and `GregorianCalendar` use the [Gregorian Calendar's rules](https://en.wikipedia.org/wiki/Gregorian_calendar): 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:
  • ```java
  • 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](https://en.wikipedia.org/wiki/Coordinated_Universal_Time). This means that, for any dates before that, `GregorianCalendar` uses the [Julian Calendar's rules](https://en.wikipedia.org/wiki/Julian_calendar), 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 change this behaviour by setting a different cut-off date:
  • ```java
  • 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:
  • ```java
  • 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()`](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/Date.html#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:
  • ```java
  • 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](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/time/LocalDate.html#isLeapYear()):
  • ```java
  • 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:
  • ```java
  • 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`):
  • ```java
  • 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:
  • ```java
  • 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](https://docs.oracle.com/javase/tutorial/datetime/iso/overview.html).
  • But the `java.time` API also supports another calendars: the implementations can be found in the [`java.time.chrono` package](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/time/chrono/package-summary.html). For example, there's the class `ThaiBuddhistChronology` (implementation of [Buddhist Calendar](https://en.wikipedia.org/wiki/Buddhist_calendar)) has a different rule for leay years:
  • ```java
  • 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](https://www.threeten.org/threeten-extra/apidocs/org.threeten.extra/org/threeten/extra/chrono/package-summary.html) (made by the same creator of `java.time`) and [Time4j](http://www.time4j.net/javadoc-en/): both provide nice support for other calendars.
  • 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](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/time/Year.html), which has the [static method `isLeap`](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/time/Year.html#isLeap(long)). This is the "faster" and more straighforward, no need to create instances of any object, it's just a static method call:
  • ```java
  • int year = 2000;
  • System.out.println(Year.isLeap(year)); // true
  • ```
  • For **Java <= 7**, use [`Calendar`](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/Calendar.html). 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:
  • ```java
  • 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`](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/GregorianCalendar.html) and use the [`isLeapYear` method](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/GregorianCalendar.html#isLeapYear(int)):
  • ```java
  • int year = 2000;
  • GregorianCalendar cal = new GregorianCalendar();
  • System.out.println(cal.isLeapYear(year)); // true
  • ```
  • ---
  • Both `Calendar` and `GregorianCalendar` use the [Gregorian Calendar's rules](https://en.wikipedia.org/wiki/Gregorian_calendar): 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:
  • ```java
  • 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](https://en.wikipedia.org/wiki/Coordinated_Universal_Time). This means that, for any dates before that, `GregorianCalendar` uses the [Julian Calendar's rules](https://en.wikipedia.org/wiki/Julian_calendar), 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:
  • ```java
  • 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:
  • ```java
  • 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()`](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/Date.html#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:
  • ```java
  • 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](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/time/LocalDate.html#isLeapYear()):
  • ```java
  • 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:
  • ```java
  • 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`):
  • ```java
  • 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:
  • ```java
  • 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](https://docs.oracle.com/javase/tutorial/datetime/iso/overview.html).
  • But the `java.time` API also supports another calendars: the implementations can be found in the [`java.time.chrono` package](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/time/chrono/package-summary.html). For example, there's the class `ThaiBuddhistChronology` (implementation of [Buddhist Calendar](https://en.wikipedia.org/wiki/Buddhist_calendar)) has a different rule for leay years:
  • ```java
  • 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](https://www.threeten.org/threeten-extra/apidocs/org.threeten.extra/org/threeten/extra/chrono/package-summary.html) (made by the same creator of `java.time`) and [Time4j](http://www.time4j.net/javadoc-en/): both provide nice support for other calendars.
#3: Post edited by user avatar hkotsubo‭ · 2023-02-16T18:08:41Z (almost 2 years ago)
  • 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](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/time/Year.html), which has the [static method `isLeap`](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/time/Year.html#isLeap(long)). This is the "faster" and more straighforward, no need to create instances of any object, it's just a static method call:
  • ```java
  • int year = 2000;
  • System.out.println(Year.isLeap(year)); // true
  • ```
  • For **Java <= 7**, use [`Calendar`](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/Calendar.html). 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:
  • ```java
  • 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`](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/GregorianCalendar.html) and use the [`isLeapYear` method](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/GregorianCalendar.html#isLeapYear(int)):
  • ```java
  • int year = 2000;
  • GregorianCalendar cal = new GregorianCalendar();
  • System.out.println(cal.isLeapYear(year)); // true
  • ```
  • ---
  • Atention: both `Calendar` and `GregorianCalendar` use the [Gregorian Calendar's rules](https://en.wikipedia.org/wiki/Gregorian_calendar): 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:
  • ```java
  • public static boolean bissexto(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](https://en.wikipedia.org/wiki/Coordinated_Universal_Time). This means that, for any dates before that, `GregorianCalendar` uses the [Julian Calendar's rules](https://en.wikipedia.org/wiki/Julian_calendar), 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 change this behaviour by setting a different cut-off date:
  • ```java
  • 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:
  • ```java
  • 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()`](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/Date.html#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:
  • ```java
  • 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](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/time/LocalDate.html#isLeapYear()):
  • ```java
  • 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:
  • ```java
  • 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`):
  • ```java
  • 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:
  • ```java
  • 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](https://docs.oracle.com/javase/tutorial/datetime/iso/overview.html).
  • But the `java.time` API also supports another calendars: the implementations can be found in the [`java.time.chrono` package](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/time/chrono/package-summary.html). For example, there's the class `ThaiBuddhistChronology` (implementation of [Buddhist Calendar](https://en.wikipedia.org/wiki/Buddhist_calendar)) has a different rule for leay years:
  • ```java
  • 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](https://www.threeten.org/threeten-extra/apidocs/org.threeten.extra/org/threeten/extra/chrono/package-summary.html) (made by the same creator of `java.time`) and [Time4j](http://www.time4j.net/javadoc-en/): both provide nice support for other calendars.
  • 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](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/time/Year.html), which has the [static method `isLeap`](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/time/Year.html#isLeap(long)). This is the "faster" and more straighforward, no need to create instances of any object, it's just a static method call:
  • ```java
  • int year = 2000;
  • System.out.println(Year.isLeap(year)); // true
  • ```
  • For **Java <= 7**, use [`Calendar`](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/Calendar.html). 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:
  • ```java
  • 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`](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/GregorianCalendar.html) and use the [`isLeapYear` method](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/GregorianCalendar.html#isLeapYear(int)):
  • ```java
  • int year = 2000;
  • GregorianCalendar cal = new GregorianCalendar();
  • System.out.println(cal.isLeapYear(year)); // true
  • ```
  • ---
  • Both `Calendar` and `GregorianCalendar` use the [Gregorian Calendar's rules](https://en.wikipedia.org/wiki/Gregorian_calendar): 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:
  • ```java
  • 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](https://en.wikipedia.org/wiki/Coordinated_Universal_Time). This means that, for any dates before that, `GregorianCalendar` uses the [Julian Calendar's rules](https://en.wikipedia.org/wiki/Julian_calendar), 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 change this behaviour by setting a different cut-off date:
  • ```java
  • 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:
  • ```java
  • 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()`](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/Date.html#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:
  • ```java
  • 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](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/time/LocalDate.html#isLeapYear()):
  • ```java
  • 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:
  • ```java
  • 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`):
  • ```java
  • 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:
  • ```java
  • 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](https://docs.oracle.com/javase/tutorial/datetime/iso/overview.html).
  • But the `java.time` API also supports another calendars: the implementations can be found in the [`java.time.chrono` package](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/time/chrono/package-summary.html). For example, there's the class `ThaiBuddhistChronology` (implementation of [Buddhist Calendar](https://en.wikipedia.org/wiki/Buddhist_calendar)) has a different rule for leay years:
  • ```java
  • 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](https://www.threeten.org/threeten-extra/apidocs/org.threeten.extra/org/threeten/extra/chrono/package-summary.html) (made by the same creator of `java.time`) and [Time4j](http://www.time4j.net/javadoc-en/): both provide nice support for other calendars.
#2: Post edited by user avatar hkotsubo‭ · 2023-02-16T18:07:21Z (almost 2 years ago)
  • 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`, for instance), and **is using Java >= 8**, you can use the [`java.time.Year` class](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/time/Year.html), which has the [static method `isLeap`](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/time/Year.html#isLeap(long)). This is the "faster" and more straighforward, no need to create instances of any object, it's just a static method call:
  • ```java
  • int year = 2000;
  • System.out.println(Year.isLeap(year)); // true
  • ```
  • For **Java <= 7**, use [`Calendar`](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/Calendar.html). 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:
  • ```java
  • 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`](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/GregorianCalendar.html) and use the [`isLeapYear` method](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/GregorianCalendar.html#isLeapYear(int)):
  • ```java
  • int year = 2000;
  • GregorianCalendar cal = new GregorianCalendar();
  • System.out.println(cal.isLeapYear(year)); // true
  • ```
  • ---
  • Atention: both `Calendar` and `GregorianCalendar` use the [Gregorian Calendar's rules](https://en.wikipedia.org/wiki/Gregorian_calendar): 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:
  • ```java
  • public static boolean bissexto(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](https://en.wikipedia.org/wiki/Coordinated_Universal_Time). This means that, for any dates before that, `GregorianCalendar` uses the [Julian Calendar's rules](https://en.wikipedia.org/wiki/Julian_calendar), 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 change this behaviour by setting a different cut-off date:
  • ```java
  • 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:
  • ```java
  • 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()`](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/Date.html#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:
  • ```java
  • 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](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/time/LocalDate.html#isLeapYear()):
  • ```java
  • 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:
  • ```java
  • 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`):
  • ```java
  • 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:
  • ```java
  • 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](https://docs.oracle.com/javase/tutorial/datetime/iso/overview.html).
  • But the `java.time` API also supports another calendars: the implementations can be found in the [`java.time.chrono` package](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/time/chrono/package-summary.html). For example, there's the class `ThaiBuddhistChronology` (implementation of [Buddhist Calendar](https://en.wikipedia.org/wiki/Buddhist_calendar)) has a different rule for leay years:
  • ```java
  • 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](https://www.threeten.org/threeten-extra/apidocs/org.threeten.extra/org/threeten/extra/chrono/package-summary.html) (made by the same creator of `java.time`) and [Time4j](http://www.time4j.net/javadoc-en/): both provide nice support for other calendars.
  • 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](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/time/Year.html), which has the [static method `isLeap`](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/time/Year.html#isLeap(long)). This is the "faster" and more straighforward, no need to create instances of any object, it's just a static method call:
  • ```java
  • int year = 2000;
  • System.out.println(Year.isLeap(year)); // true
  • ```
  • For **Java <= 7**, use [`Calendar`](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/Calendar.html). 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:
  • ```java
  • 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`](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/GregorianCalendar.html) and use the [`isLeapYear` method](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/GregorianCalendar.html#isLeapYear(int)):
  • ```java
  • int year = 2000;
  • GregorianCalendar cal = new GregorianCalendar();
  • System.out.println(cal.isLeapYear(year)); // true
  • ```
  • ---
  • Atention: both `Calendar` and `GregorianCalendar` use the [Gregorian Calendar's rules](https://en.wikipedia.org/wiki/Gregorian_calendar): 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:
  • ```java
  • public static boolean bissexto(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](https://en.wikipedia.org/wiki/Coordinated_Universal_Time). This means that, for any dates before that, `GregorianCalendar` uses the [Julian Calendar's rules](https://en.wikipedia.org/wiki/Julian_calendar), 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 change this behaviour by setting a different cut-off date:
  • ```java
  • 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:
  • ```java
  • 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()`](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/Date.html#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:
  • ```java
  • 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](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/time/LocalDate.html#isLeapYear()):
  • ```java
  • 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:
  • ```java
  • 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`):
  • ```java
  • 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:
  • ```java
  • 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](https://docs.oracle.com/javase/tutorial/datetime/iso/overview.html).
  • But the `java.time` API also supports another calendars: the implementations can be found in the [`java.time.chrono` package](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/time/chrono/package-summary.html). For example, there's the class `ThaiBuddhistChronology` (implementation of [Buddhist Calendar](https://en.wikipedia.org/wiki/Buddhist_calendar)) has a different rule for leay years:
  • ```java
  • 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](https://www.threeten.org/threeten-extra/apidocs/org.threeten.extra/org/threeten/extra/chrono/package-summary.html) (made by the same creator of `java.time`) and [Time4j](http://www.time4j.net/javadoc-en/): both provide nice support for other calendars.
#1: Initial revision by user avatar hkotsubo‭ · 2023-02-16T18:06:47Z (almost 2 years ago)
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`, for instance), and **is using Java >= 8**, you can use the [`java.time.Year` class](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/time/Year.html), which has the [static method `isLeap`](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/time/Year.html#isLeap(long)). This is the "faster" and more straighforward, no need to create instances of any object, it's just a static method call:

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

For **Java <= 7**, use [`Calendar`](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/Calendar.html). 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:

```java
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`](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/GregorianCalendar.html) and use the [`isLeapYear` method](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/GregorianCalendar.html#isLeapYear(int)):

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

Atention: both `Calendar` and `GregorianCalendar` use the [Gregorian Calendar's rules](https://en.wikipedia.org/wiki/Gregorian_calendar): 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:

```java
public static boolean bissexto(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](https://en.wikipedia.org/wiki/Coordinated_Universal_Time). This means that, for any dates before that, `GregorianCalendar` uses the [Julian Calendar's rules](https://en.wikipedia.org/wiki/Julian_calendar), 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 change this behaviour by setting a different cut-off date:

```java
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:

```java
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()`](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/Date.html#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:

```java
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](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/time/LocalDate.html#isLeapYear()):

```java
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:

```java
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`):

```java
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:

```java
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](https://docs.oracle.com/javase/tutorial/datetime/iso/overview.html).

But the `java.time` API also supports another calendars: the implementations can be found in the [`java.time.chrono` package](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/time/chrono/package-summary.html). For example, there's the class `ThaiBuddhistChronology` (implementation of [Buddhist Calendar](https://en.wikipedia.org/wiki/Buddhist_calendar)) has a different rule for leay years:

```java
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](https://www.threeten.org/threeten-extra/apidocs/org.threeten.extra/org/threeten/extra/chrono/package-summary.html) (made by the same creator of `java.time`) and [Time4j](http://www.time4j.net/javadoc-en/): both provide nice support for other calendars.