tl;dr
parse string in MM-dd format … in current year
MonthDay // Represent a month-day as such, in a class designed for that purpose.
.parse ( // By default parses strings in standard ISO 8601 format.
"--" + "02-29" // Prepending a double-hyphen to make this input comply with ISO 8601.
) // Returns a `MonthDay` object.
.atYear( // Get the date of this month-day in a specified year.
Year.now( ZoneId.of( "Asia/Tokyo" ) ).getValue() // Getting current year requires a time zone.
) // Returns a `LocalDate` object, a year-month-day without time zone and without time-of-day.
See this code run live at IdeOne.com.
2019-02-28
java.time
The modern solution uses the industry-leading java.time classes built into Java 8 and later, with a back-port available for Java 6 & 7 and early Android.
MonthDay
A month-with-day is represented by the appropriately-named MonthDay
class.
The standard format for a month-day defined in ISO 8601 is a --MM-DD
where the first dash is a placeholder for year. The ISO 8601 formats are used by default in the java.time classes for parsing/generating strings.
Your input nearly complies. You could define a formatting pattern with a DateTimeFormatter
object. But I would just prepend a --
onto the input.
String input = "02-29" ;
String inputModified = "--" + input ;
And then parse by default.
MonthDay md = MonthDay.parse( inputModified ) ;
See this code run live at IdeOne.com.
md.toString(): --02-29
Leap year
Note that your leap year problem goes away. By use an appropriate type that truly represents a month-and-day instead of a moment, we need not worry about leap year.
To get a date for this month-day, simply call MonthDay::atYear
to obtain a LocalDate
object. Pass a year number.
LocalDate leapYear2012 = md.atYear( 2012 ) ;
leapYear2012.toString(): 2012-02-29
Current year
Getting a date in the current year has a twist that may be surprising to you. Note that getting the current year requires getting the current date. And getting the current date requires a time zone.
A time zone is crucial in determining a date. For any given moment, the date varies around the globe by zone. For example, a few minutes after midnight in Paris France is a new day while still “yesterday” in Montréal Québec.
If no time zone is specified, the JVM implicitly applies its current default time zone. That default may change at any moment during runtime(!), so your results may vary. Better to specify your desired/expected time zone explicitly as an argument. If you want to use the JVM’s current default time zone, make your intention clear by calling ZoneId.systemDefault()
. If critical, confirm the zone with your user.
Specify a proper time zone name in the format of Continent/Region
, such as America/Montreal
, Africa/Casablanca
, or Pacific/Auckland
. Never use the 2-4 letter abbreviation such as EST
or IST
as they are not true time zones, not standardized, and not even unique(!).
ZoneId z = ZoneId.of( "America/Montreal" ) ;
LocalDate today = LocalDate.now( z ) ;
In our case, we care only about the year. So we can use the Year
class rather than LocalDate
. But same idea with the time zone. If the current moment happens to be around New Years Eve/Day cutover, the year will vary around the globe by time zone.
ZoneId z = ZoneId.of( "Africa/Tunis" ) ;
Year y = Year.now( z ) ;
LocalDate currentYear = md.atYear( y.getValue() ) ;
currentYear.toString(): 2019-02-28
Notice in the result above that leap year is handled automatically. There is no February 29th in 2019, so java.time adjusted to the 28th.
Parse as LocalDate
Alternatively, you could parse directly into a LocalDate
. You would need to use the DateTimeFormatterBuilder
class to build a DateTimeFormatter
that defaults to a certain year.
Something like this:
ZoneId zKolkata = ZoneId.of( "Asia/Kolkata" ) ;
long yearNumber = Year.now( zKolkata ).getValue() ;
DateTimeFormatter formatter = new DateTimeFormatterBuilder().parseDefaulting( ChronoField.YEAR , yearNumber ).appendPattern( "MM-dd").toFormatter() ;
LocalDate ld = LocalDate.parse( "02-28" , formatter ) ;
System.out.println( "ld.toString(): " + ld ) ;
But I do not recommend this. The approach with MonthDay
object is much more clear as to your problem, solution, and intention. Another benefit: if you are getting such inputs, I suspect you will likely need to be working with the month-day as such, and with MonthDay
class you have an object at hand to do the job.
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date
, Calendar
, & SimpleDateFormat
.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.*
classes.
Where to obtain the java.time classes?
SimpleDateFormat
,Date
andCalendar
. Those classes are poorly designed and long outdated, the first in particular notoriously troublesome. Instead useLocalDate
,DateTimeFormatterBuilder
andDateTimeFormatter
, all from java.time, the modern Java date and time API. – Aprilette