With the \'old\', pre-Java 8 SimpleDateFormat
I can do:
new java.text.SimpleDateFormat(\"MMM yyyy\", new java.util.Locale(\"es\", \"ES\")).parse
The call to ofLocalizedDateTime()
can be removed, because in the end you call ofPattern()
, creating another formatter with a totally different pattern (and the pattern returned by ofLocalizedDateTime(FormatStyle.FULL)
is very different from just month year
, so that's not really what you want).
Another detail is that Mayo
is the full month name, so the pattern must be MMMM
(check the javadoc for more details). Also, the DateTimeFormatter
by default accepts only lowercase names (at least in the tests I've made with Spanish locale), so you must set the formatter to be case insensitive.
You can do that by using a java.time.format.DateTimeFormatterBuilder
:
DateTimeFormatter fmt = new DateTimeFormatterBuilder()
// case insensitive
.parseCaseInsensitive()
// pattern with full month name (MMMM)
.appendPattern("MMMM yyyy")
// set locale
.toFormatter(new Locale("es", "ES"));
// now it works
fmt.parse("Mayo 2017");
Optionally, you can directly parse it to a java.time.YearMonth
object, as it seems to be the best choice for this case (because the input has only year and month):
YearMonth ym = YearMonth.parse("Mayo 2017", fmt);
System.out.println(ym); // 2017-05
When the input doesn't have all the fields, SimpleDateFormat
simply uses some defaults for them. In this case, the input has only year and month, so the parsed Date
will be equivalent to the parsed month/year, but the day will be set to 1 and the time to midnight (at the JVM default timezone).
The new API is very strict about that and doesn't create default values unless you tell it to do so. One way to configure it is to use parseDefaulting
with a java.time.temporal.ChronoField
:
DateTimeFormatter fmt = new DateTimeFormatterBuilder()
// case insensitive
.parseCaseInsensitive()
// pattern with full month name (MMMM)
.appendPattern("MMMM yyyy")
// default value for day of month
.parseDefaulting(ChronoField.DAY_OF_MONTH, 1)
// default value for hour
.parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
// default value for minute
.parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
// set locale
.toFormatter(new Locale("es", "ES"));
With this, you can parse it to a LocalDateTime
and the missing fields will be assigned to the respective default values:
LocalDateTime dt = LocalDateTime.parse("Mayo 2017", fmt);
System.out.println(dt); // 2017-05-01T00:00
If you need to get a java.util.Date
with the same value as the one created by SimpleDateFormat
, you can convert this LocalDateTime
to the JVM default timezone and then convert it to Date
:
Date javaUtilDate = Date.from(dt.atZone(ZoneId.systemDefault()).toInstant());
Note that I had to explicity use the JVM default timezone (ZoneId.systemDefault()
), which is implicity used by SimpleDateFormat
.
Another alternative is to manually set the values in the YearMonth
value:
// in this case, the formatter doesn't need the default values
YearMonth ym = YearMonth.parse("Mayo 2017", fmt);
ZonedDateTime z = ym
// set day of month to 1
.atDay(1)
// midnight at JVM default timezone
.atStartOfDay(ZoneId.systemDefault());
Date javaUtilDate = date.from(z.toInstant());
The default timezone can be changed without notice, even at runtime, so it's better to always make it explicit which one you're using.
The API uses IANA timezones names (always in the format Region/City
, like America/New_York
or Europe/Berlin
), so you can call ZoneId.of("America/New_York")
for example.
Avoid using the 3-letter abbreviations (like CST
or PST
) because they are ambiguous and not standard.
You can get a list of available timezones (and choose the one that fits best your system) by calling ZoneId.getAvailableZoneIds()
.