I have been successfully using SimpleDateFormat
for the last couple of years. I built a bunch of time utility classes using it.
As I ran into problems with SimpleDateFormat
(SDF) not being thread safe, I spent the last couple of days refactoring these utility classes to internally use DateTimeFormatter
(DTF) now. Since both classes' time patterns are almost identical, this transition seemed a good idea at the time.
I now have problems obtaining EpochMillis
(milliseconds since 1970-01-01T00:00:00Z
): While SDF would e.g. interpret 10:30
parsed using HH:mm
as 1970-01-01T10:30:00Z
, DTF does not do the same. DTF can use 10:30
to parse a LocalTime
, but not a ZonedDateTime
which is needed to obtain EpochMillis
.
I understand that the objects of java.time
follow a different philosophy; Date
, Time
, and Zoned
objects are kept separately. However, in order for my utility class to interpret all strings as it did before, I need to be able to define the default parsing for all missing objects dynamically. I tried to use
DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder();
builder.parseDefaulting(ChronoField.YEAR, 1970);
builder.parseDefaulting(ChronoField.MONTH_OF_YEAR, 1);
builder.parseDefaulting(ChronoField.DAY_OF_MONTH, 1);
builder.parseDefaulting(ChronoField.HOUR_OF_DAY, 0);
builder.parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0);
builder.parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0);
builder.append(DateTimeFormatter.ofPattern(pattern));
but this does not work for all patterns. It seems to only allow defaults for parameters that are not defined in pattern
. Is there a way to test which ChronoField
s are defined in pattern
to then selectively add defaults?
Alternatively, I tried
TemporalAccessor temporal = formatter.parseBest(time,
ZonedDateTime::from,
LocalDateTime::from,
LocalDate::from,
LocalTime::from,
YearMonth::from,
Year::from,
Month::from);
if ( temporal instanceof ZonedDateTime )
return (ZonedDateTime)temporal;
if ( temporal instanceof LocalDateTime )
return ((LocalDateTime)temporal).atZone(formatter.getZone());
if ( temporal instanceof LocalDate )
return ((LocalDate)temporal).atStartOfDay().atZone(formatter.getZone());
if ( temporal instanceof LocalTime )
return ((LocalTime)temporal).atDate(LocalDate.of(1970, 1, 1)).atZone(formatter.getZone());
if ( temporal instanceof YearMonth )
return ((YearMonth)temporal).atDay(1).atStartOfDay().atZone(formatter.getZone());
if ( temporal instanceof Year )
return ((Year)temporal).atMonth(1).atDay(1).atStartOfDay().atZone(formatter.getZone());
if ( temporal instanceof Month )
return Year.of(1970).atMonth((Month)temporal).atDay(1).atStartOfDay().atZone(formatter.getZone());
which does not cover all cases either.
What is the best strategy to enable dynamic date / time / date-time / zone-date-time parsing?