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.
1 answer
The following users marked this post as Works for me:
User | Comment | Date |
---|---|---|
LAFK | (no comment) | Jun 20, 2023 at 17:26 |
There are many ways to do it, it depends on what data you already have and/or the Java version.
I have only the year's numeric value
If you already have a value as a number (int
or long
), and is using Java >= 8, you can use the java.time.Year
class, which has the static method isLeap
. This is the "faster" and more straighforward, no need to create instances of any object, it's just a static method call:
int year = 2000;
System.out.println(Year.isLeap(year)); // true
For Java <= 7, use Calendar
. Unfortunately it's not as straightforward as Java 8's solution, you have to create an instance and then check how many days the year has:
int year = 2000;
Calendar cal = Calendar.getInstance();
cal.set(Calendar.YEAR, year);
// the year is leap if it has more than 365 days
System.out.println(cal.getActualMaximum(Calendar.DAY_OF_YEAR) > 365); // true
Another alternative is to create an instance of GregorianCalendar
and use the isLeapYear
method:
int year = 2000;
GregorianCalendar cal = new GregorianCalendar();
System.out.println(cal.isLeapYear(year)); // true
Both Calendar
and GregorianCalendar
use the Gregorian Calendar's rules: if the year is divisible by 100, it's leap only if it's also divisible by 400. If it's not divisible by 100, it's leap if it's divisible by 4. You could implement this rule by yourself:
public static boolean isLeap(int ano) {
return (ano % 4 == 0) && (ano % 100 != 0 || ano % 400 == 0);
}
But it already exists in the native API, so I don't see a reason to reinvent the wheel.
But beware: GregorianCalendar
has a "cut-off date", which is the year 1582 (when the Gregorian Calendar was introduced) - to be more precise, the date is 1582-10-15T00:00:00Z
: October 15th, 1582, at midnight, in UTC (note: not all countries changed their calendars in 1582, but that's the date configured in the class when an instance is created).
This means that, for any dates before that, GregorianCalendar
uses the Julian Calendar's rules, in which any year divisible by 4 is leap.
Example: according to GregorianCalendar
, 1900 isn't a leap year, because it's greater than 1582 and if follows the current rule (it's divisible by 100, but no by 400), but 1500 is leap because it's before 1582, so the divisible-by-400 rule doesn't apply.
But you can change this behaviour by setting a different cut-off date:
int year = 1500;
GregorianCalendar cal = new GregorianCalendar();
// 1500 is before the cut-off date (1582) and uses the "old" rule (divisible by 4 == leap)
System.out.println(cal.isLeapYear(year)); // true
cal.setGregorianChange(new Date(-62135758799190L)); // change cut-off date to January 1st, 0001
// 1500 isn't leap anymore (uses the current rule: if divisible by 100, it's leap only if it's also divisible by 400)
System.out.println(cal.isLeapYear(year)); // false
Another detail: only GregorianCalendar
does that. But Calendar.getInstance()
might or might not do it, because the getInstance
method can return either a GregorianCalendar
, or some other sub-classes, depending on the default locale configured in the JVM. For example: if the default locale is th_TH
(Thailand), getInstance
returns a BuddhistCalendar
, and for that calendar, 1500 isn't a leap year:
Locale.setDefault(new Locale("th", "TH"));
int year = 2000;
// for locale th_TH, it doesn't create a GregorianCalendar
Calendar cal2 = Calendar.getInstance();
System.out.println(cal2.getClass()); // class sun.util.BuddhistCalendar
cal2.set(Calendar.YEAR, year);
System.out.println(cal2.getActualMaximum(Calendar.DAY_OF_YEAR) > 365); // false
GregorianCalendar cal = new GregorianCalendar();
System.out.println(cal.isLeapYear(year)); // true
Of course you could force it to create a GregorianCalendar
, by using a specific locale (such as Calendar.getInstance(new Locale("pt", "BR"))
- for most locales, it returns a GregorianCalendar
).
On the other hand, Year.isLeap
can't be configured (there's no such thing as changing the cut-off date), so it will always say that 1500 isn't leap.
That's what you'd use for any numeric value. If you want the current year, though, you could use Year.now().isLeap()
for Java >= 8 (in that case, we can't avoid the creation of an instance). And for Calendar
, simply don't set the year (don't call set(Calendar.YEAR, ano)
), as getInstance()
already returns the current date.
What if I have an object that represents a date?
Java <= 7
If you have a Calendar
instance, use the methods above.
if you have a java.util.Date
, don't use getYear()
, for two reasons:
- it returns a value that is the result of subtracting 1900 from the year. Therefore, if the current year is 2023,
new Date().getYear()
returns123
, and to check if it's leap, you have to remember to add 1900 - it's deprecated since Java 1.1 and the documentation recommends using
Calendar
Therefore, if you have a Date
instance, do like this:
Date date = // some Date instance
// create a Calendar and set the Date
Calendar cal = Calendar.getInstance();
cal.setTime(date);
// use the Calendar's methods already explained above
And once again, if you want the current date, don't use Date
, just Calendar.getInstance()
.
Java >= 8 (java.time
)
For LocalDate
, just use the isLeapYear
method:
LocalDate date = // some LocalDate instance
System.out.println(date.isLeapYear());
For other java.time
types, you can get the year numeric value and pass it to Year.isLeap
, or get a LocalDate
if applicable (or get a Year
instance from the object). Example:
LocalDateTime dt = // some LocalDateTime instance
// get the year numeric value and pass it to Year.isLeap
System.out.println(Year.isLeap(dt.getYear()));
// or get a LocalDate instance
System.out.println(dt.toLocalDate().isLeapYear());
// or get a Year instance
System.out.println(Year.from(dt).isLeap());
The first one (getYear
) is more straightforward and - IMO - simpler. All API native date/time types have this getter (except, of course, the ones that don't make sense to have a year: LocalTime
, OffsetTime
, MonthDay
, Instant
, Month
and DayOfWeek
).
The second one (dt.toLocalDate().isLeapYear()
) might seem "bad" because it creates a LocalDate
instance, but in the current implementation, LocalDateTime
uses composition and encapsulates an instance of LocalDate
, which is returned by toLocalDate
. Anyway, it's an implementation detail: don't blindly rely on it if you wish to minimize the creation of new instances.
The third one (Year.from
) is preffered if you're working with a TemporalAccessor
(an interface implemented by all API date/time types), without caring too much about the actual type: if it has a year field, that's all that matters. With this, you could create a method that works with any date/time type (and any other that you create, as long as it implements TemporalAccessor
):
static boolean isLeapYear(TemporalAccessor t) {
return Year.from(t).isLeap();
}
...
// it works with any type that implements TemporalAccessor and has a year field
System.out.println(isLeapYear(LocalDate.now()));
System.out.println(isLeapYear(LocalDateTime.now()));
String string = "10/01/2023"; // some date in day/month/year format
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("dd/MM/uuuu");
// I don't need to create a LocalDate instance, just pass the result of parse (which is a TemporalAccessor) directly to the method
System.out.println(isLeapYear(fmt.parse(string)));
As a side note, in this case, you could also create a TemporalQuery
and pass it directly to the parse
method:
TemporalQuery<Boolean> isLeapYear = (t) -> Year.from(t).isLeap();
String string = "10/01/2020"; // some date in day/month/year format
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("dd/MM/uuuu");
System.out.println(fmt.parse(string, isLeapYear)); // true
Other calendars
All the rules described above are valid for the ISO 8601 calendar. For short, it's like applying the Gregorian Calendar rules retroactively, for dates before October 1582 (the "cut-off date", as we saw above). That's the default calendar used by java.time
classes.
But the java.time
API also supports another calendars: the implementations can be found in the java.time.chrono
package. For example, the class ThaiBuddhistChronology
(implementation of Buddhist Calendar) has a different rule for leap years:
int year = 2020;
System.out.println(IsoChronology.INSTANCE.isLeapYear(year)); // true
System.out.println(ThaiBuddhistChronology.INSTANCE.isLeapYear(year)); // false
year = 2543;
System.out.println(IsoChronology.INSTANCE.isLeapYear(year)); // false
System.out.println(ThaiBuddhistChronology.INSTANCE.isLeapYear(year)); // true
Anyway, for other calendar systems, just use the respective class in java.time.chrono
. Or, if you need a different one (and don't want to implement them), you can use external libraries. Two alternatives are the ThreeTen Extra project (made by the same creator of java.time
) and Time4j: both provide nice support for other calendars.
0 comment threads