Deprecating java.util.Date, java.util.Calendar and java.text.DateFormat and their subclasses

Victor Williams Stafusa da Silva victorwssilva at gmail.com
Tue May 10 03:00:24 UTC 2022


Well, I tried to track every case in the public API of the JDK individually
and analyze each of them, one by one.

TL;DR - If you are concerned only with the harder cases (like Brian Goetz
pointed out), skip directly to items 11, 12 and 13 below.

[1] First, those cases below should be fairly easy, just overload the
methods and constructors with versions taking java.time.Instant instead of
the legacy classes:

- java.security.Timestamp(Date, CertPath)
- java.security.cert.CertificateRevokedException(Date, other parameters)
- java.util.Timer.schedule(TimerTask, Date)
- java.util.Timer.schedule(TimerTask, Date, long)
- java.util.Timer.scheduleAtFixedRate(TimerTask, Date, long)
-
java.util.concurrent.locks.AbstractQueuedLongSynchronizer.ConditionObject.awaitUntil(Date)
-
java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject.awaitUntil(Date)
- java.util.concurrent.locks.Condition.awaitUntil(Date)
- javax.management.timer.Timer.addNotification(many overload versions)
- javax.management.timer.TimerMBean.addNotification(many overload versions)
- javax.print.attribute.DateTimeSyntax(Date)
- javax.print.attribute.DateTimeAtCompleted(Date)
- javax.print.attribute.DateTimeAtCreation(Date)
- javax.print.attribute.DateTimeAtProcessing(Date)
- javax.print.attribute.JobHoldUntil(Date)
- javax.security.auth.kerberos.KerberosTicket(a lot of parameters)
- javax.swing.RowFilter.dateFilter(ComparisonType, Date, int...)
- javax.swing.SpinnerDateModel(Date, Comparable<Date>, Comparable<Date>,
int)
- javax.xml.datatype.Duration.addTo(Date)
- javax.xml.datatype.Duration.getTimeInMillis(Date)
- javax.xml.datatype.Duration.getTimeInMillis(Calendar)

[2] Those are the methods that return java.util.Date. Maybe in some cases
the best name was really be already taken, but they are still easy to solve:

- java.security.KeyStoreSpi.getCreationDate(String) - Add a
getCreationInstant(String) method.
- java.security.Timestamp.getTimestamp() - Add a getInstant() method.
- java.security.cert.CertificateRevokedException.getInvalidityDate() - Add
a getInvalidityInstant() method.
- java.security.cert.CertificateRevokedException.getRevocationDate() - Add
a getRevocationInstant() method.
- javax.management.timer.Timer.getDate(Integer) - Add a getInstant(Integer)
method.
- javax.management.timer.TimerMBean.getDate(Integer) - Add a
getInstant(Integer) method.
- javax.print.attribute.DateTimeSyntax.getValue() - Add a getInstant()
method.
- javax.security.auth.kerberos.KerberosTicket.getAuthTime() - Add a
getAuthInstant() method.
- javax.security.auth.kerberos.KerberosTicket.getEndTime() - Add a
getEndInstant() method.
- javax.security.auth.kerberos.KerberosTicket.getStartTime() - Add a
getStartInstant() method.
- javax.security.auth.kerberos.KerberosTicket.getRenewTill() - Add a
getLatestExpiration() method.
- javax.swing.SpinnerDateModel.getDate() - Add a getInstant() method.

[3] Those are getter and setter pairs of java.util.Date (or something with
Date within its generics):

- java.security.cert.PKIXParameters.getDate() and setDate(Date) - Add a
getValidity() and setValidity(Instant) methods.
- java.security.cert.X509CRLSelector.getDateAndTime() and
setDateAndTime(Date) - Add differently named methods.
- javax.swing.SpinnerDateModel.getStart() and setStart(Comparable<Date>) -
Add getStartInstant() and setStartInstant(Comparable<Instant>).
- javax.swing.SpinnerDateModel.getEnd() and setEnd(Comparable<Date>) - Add
getStartInstant() and setEndInstant(Comparable<Instant>).

For them, setting the Instant would also set the Date and vice-versa.
Granted, the best names were already taken, but this isn't a big deal and
definitely not a reason for keeping using java.util.Date.

[4] There are other few special easy cases:

- Field javax.management.openmbean.SimpleType.DATE - Just provide
SimpleType.INSTANT, SimpleType.LOCAL_DATE, SimpleType.LOCAL_DATE_TIME, etc.
- Package javax.security.cert (not to be confused with java.security.cert)
- Deprecated for removal. It is time for it to go.

[5] Getting into the SQL realm, the classes java.sql.CallableStatement,
java.sql.PreparedStatement, java.sql.ResultSet, javax.sql.RowSet and
javax.sql.rowset.BaseRowSet feature the following methods:

- getDate(int)
- getDate(String)
- setDate(int, java.sql.Date)
- setDate(String, java.sql.Date)
- getTime(int)
- getTime(String)
- setTime(int, java.sql.Time)
- setTime(String, java.sql.Time)
- getTimestamp(int)
- getTimestamp(String)
- setTimestamp(int, java.sql.Timestamp)
- setTimestamp(String, java.sql.Timestamp)

Those are mostly straightforward. Just add methods taking or returning
java.time.LocalDate instead of java.sql.Date, java.time.LocalTime instead
of java.sql.Time and java.time.LocalDateTime instead of java.sql.Timestamp.

Also, it is tempting to create alternatives for other classes within
java.time package, like Instant, OffsetDateTime, ZonedDateTime or
ChronoLocalDate, if possible.

[6] The java.sql.SQLInput, java.sql.SQLOutput,
javax.sql.rowset.serial.SQLInputImpl and
javax.sql.rowset.serial.SQLOutputImpl also features those methods:

- readDate()
- readTime()
- readTimestamp()
- writeDate(java.sql.Date)
- writeTime(java.sql.Time)
- writeTimestamp(java.sql.Timestamp)

The solution would be the same as in [5].

[7] All the methods listed in [5] also have an overload taking a
java.util.Calendar parameter, which is used for time zone information as
far as I can tell. I am not really sure of what to do with them, but I'm
pretty sure that it should not be something complicated. Without looking
deep in the issue, maybe adding a version which uses java.time.ZoneId
instead of java.util.Calendar is enough. But maybe ZonedDateTime and
OffsetDateTime come to play here.

[8] Those methods:

- java.text.spi.DateFormatProvider.getDateInstance(int, Locale)
- java.text.spi.DateFormatProvider.getDateTimeInstance(int, int, Locale)
- java.text.spi.DateFormatProvider.getTimeInstance(int, Locale)

We just overload them with versions returning
java.time.format.DateTimeFormatter and taking enums instead of ints.

[9] Those:

- javax.swing.text.DateFormatter(DateFormat)
- javax.swing.text.DateFormatter.setFormat(DateFormat)

Just overload them with java.time.format.DateTimeFormatter.

[10] This method:

- javax.swing.JSpinner.DateEditor.getFormat()

Just add a getFormatter() method returning a
java.time.format.DateTimeFormatter.

[11] There are those pair of getters and setters:

- java.security.cert.X509CRLSelector.getCertificateValid() and
setCertificateValid(Date)
- java.security.cert.X509CertSelector.getPrivateKeyValid() and
setPrivateKeyValid(Date)

Adding just differently named methods is ok and simple enough, although
their names could be a bit cumbersome. However, the X509CRLSelector and
X509CertSelector classes suffer from the same problem that SimpleDateFormat
suffers. Users might think that sharing them between threads without
locking as long as no setters are called is safe, a presumption that is
false as stated in the javadocs. A better alternative, but way off of the
scope of this thread, would be to provide immutable alternative for those
classes.

[12] Now, let's see the problematic case pointed out by Brian Goetz, since
those are abstract methods:

- java.security.cert.X509CRL.getNextUpdate()
- java.security.cert.X509CRL.getThisUpdate()
- java.security.cert.X509CRLEntry.getRevocationDate()
- java.security.cert.X509Certificate.getNotAfter()
- java.security.cert.X509Certificate.getNotBefore()

The X509CRL, X509CRLEntry and X509Certificate classes should really been
interfaces. Anyway, users aren't expected to be directly extending those
classes and none of them have public subclasses. Instead, users should rely
on java.security.cert.CertificateFactory class to get instances of these.
Hence, adding Instant returning methods should not have be a big problem
for 99% of the expected use cases. It is also noteworthy that the
X509Certificate class feature the deprecated abstract getIssuerDN() method,
so this won't be the first time that it runs to a similar design problem.

[13] And to close it up, there are those abstract methods also:

- javax.xml.datatype.Duration.addTo(Calendar)
- javax.xml.datatype.Duration.normalizeWith(Calendar)
-
javax.xml.datatype.DatatypeFactory.newXMLGregorianCalendar(GregorianCalendar)
- javax.xml.datatype.DatatypeFactory.getTimeZone(int)
- javax.xml.datatype.XMLGregorianCalendar.toGregorianCalendar()
- javax.xml.datatype.XMLGregorianCalendar.toGregorianCalendar(TimeZone,
Locale, XMLGregorianCalendar)

XMLGregorianCalendar is tightly coupled with GregorianCalendar (not
unexpected considering the class name). But, since users are expected to
get instances of Duration and XMLGregorianCalendar only through the
DatatypeFactory which in turn should only be instantiated through its
static factory methods, this means that only JAXP implementors are expected
to be directly extending those classes and 99% of the use cases are ok.

Further, these classes really deserved to be better bridged to java.time
classes.

[FINISH] And, as far as I can tell, those are all the places which should
be changed.

The following classes would be then be deprecated: java.sql.Date,
java.sql.Time, java.sql.Timestamp, java.text.DateFormat,
java.text.SimpleDateFormat, java.util.Date, java.util.Calendar,
java.util.GregorianCalendar, java.util.TimeZone, java.util.SimpleTimeZone.

The package javax.security.cert removed (not to be confused with
java.security.cert).

Also, even if you decide that java.util.Date, java.util.Calendar and
java.util.GregorianCalendar should not be deprecated, the refactorings
listed above seem to be very worth to perform anyway. Maybe deprecating
only some of them (like java.sql.Date, java.sql.Time and
java.sql.Timestamp) is a good start.

Finally, I don't think that just because that are a few abstract methods in
the deep guts of the JDK which few programmers use directly and because the
best possible name method were taken over in roughly those same deep guts
classes that this is a sufficient justification to stop deprecating these
horrible classes.

Em seg., 9 de mai. de 2022 às 18:39, Ethan McCue <ethan at mccue.dev> escreveu:

> > code checkers, possibly built-in
>
> Maybe instead of tackling Date specifically lets ask "What would it look
> like for the JDK to have a built-in fully featured linter."
>
> The capabilities provided by javac's lints like -Xlint:deprecation are a
> subset of the capabilities of a more fully featured linter like SonarCube.
> There is no reason SonarCube's bottom line should be any of our concern, so
> there isn't any conceptual reason the JDK couldn't include something akin
> to clippy - "jlint"? - and have that be the place to really flesh out how
> tooling should communicate the nuance / "best practices" around older apis.
>
>
>
> On Mon, May 9, 2022 at 4:15 PM Peter Lawrey <peter.lawrey at gmail.com>
> wrote:
>
>> Hi,
>>    The only way forward is to have code checkers, possibly built-in, to
>> suggest alternatives.
>> Getting novices to use them is also a challenge.
>>
>> BTW This is an example of how hard it is to kill poor code. At one point,
>> I
>> edited a few hundred copies of this block of code on StackOverflow, but
>> below is an example from 2022.
>> I assume the original author used DataInputStream.readLine() before
>> realising he should use BufferedReader. I found an example from 2002,
>> possibly the original.
>>
>> A google search suggested there are about 3k copies of this code around.
>> https://www.google.com/search?q=%22Get+the+object+of+DataInputStream%22
>>
>> FileInputStream fstream = new FileInputStream("D:adresse.txt");
>> // Get the object of DataInputStream
>> DataInputStream in = new DataInputStream(fstream);
>> BufferedReader br = new BufferedReader(new InputStreamReader(in));
>>
>> Regards,
>>    Peter.
>>
>> On Mon, 9 May 2022 at 20:44, Brian Goetz <brian.goetz at oracle.com> wrote:
>>
>> > The problem with such a proposal is that applications are not the only
>> > user of Date. Libraries -- including the JDK -- have baked Date into
>> > their APIs.
>> >
>> > A simple example is java.security.Timestamp::getTimestamp, which returns
>> > a Date.  This one is easier because it is a final class, and could
>> > acquire a getTimestampAsLocalDate method, and deprecate getTimestamp.
>> > But even this gives a hint of the pain users are in for; the good name
>> > is taken, and since we don't overload on return types, we'd have to pick
>> > a lesser name.  And users would forever be confronted with the choice of
>> > the less desirable but better-named getTimestamp, and the more desirable
>> > but worse-named getTimestampAsLocalDate.
>> >
>> > A more difficult example is java.security.cert.X509Certificate, with:
>> >
>> >      public abstract Date getNotAfter();
>> >
>> > This is a public abstract method in a public abstract class. This means
>> > that remediating this use is even more difficult.  We can add a default
>> > method that returns LocalDate that converts from Date, and then update
>> > existing implementations to swap what they view their canonical
>> > representation as, but there's still going to be an abstract method
>> > returning Date, that new implementations will be confronted with.  So it
>> > will be pretty weird to have an abstract method in a public abstract
>> > class / interface that returns a deprecated type.
>> >
>> > And, after all this, I still don't see a path to removing it in the next
>> > 10 years.  So this seems like a lot of contortion and rewriting and
>> > putting users in confusion positions for mostly symbolic benefit.
>> >
>> > > The negative impact is expected to be very small. Popular products
>> like
>> > > Spring and Jakarta either already moved on and provided java.time.*
>> > > alternatives to their APIs or could do that quickly and easily. Anyone
>> > who
>> > > is left behind, would only get some [deserved] deprecation warnings.
>> >
>> > As I hope you now see, you're ignoring a whole category of
>> > not-at-all-small impact.
>> >
>> >
>> > On 5/7/2022 10:36 PM, Victor Williams Stafusa da Silva wrote:
>> > > The java.time package was released in Java 8, far back in 2014, more
>> > than 8
>> > > years ago. It has been a long time since then. Before that, we had the
>> > > dreadful infamous java.util.Date, java.util.Calendar,
>> > > java.text.DateFormat and their subclasses java.sql.Date,
>> java.sql.Time,
>> > > java.sql.Timestamp, java.util.GregorianCalendar,
>> > java.text.SimpleDateFormat
>> > > and a few other lesser-known obscure cases.
>> > >
>> > > There are plenty of reasons to avoid using Date, Calendar, DateFormat
>> and
>> > > their subclasses, otherwise there would be few to no reasons for
>> > java.time
>> > > to be conceived.
>> > >
>> > > Applications and libraries which used or relied on those legacy
>> classes
>> > > already had plenty of time to move on and provide java.time.*
>> > alternatives.
>> > >
>> > > No skilled java programmer uses the legacy classes in new applications
>> > > except when integrating with legacy APIs.
>> > >
>> > > Using those classes nowadays should be considered at least a bad
>> > > programming practice, if not something worse (source of bugs, security
>> > > issues, etc).
>> > >
>> > > Novices, unskilled, careless and lazy programmers who should know
>> better
>> > > still happily continue to use the legacy classes, pissing off those
>> who
>> > are
>> > > more enlightened.
>> > >
>> > > So, my proposal is pretty simple: It is time to put a @Deprecated in
>> all
>> > of
>> > > those (not for removal, though).
>> > >
>> > > First, let's deprecate all of them. Second, any method in the JDK
>> > returning
>> > > or receiving any of those as a parameter should be equally
>> deprecated. If
>> > > there is no replacement method using the relevant classes or
>> interfaces
>> > in
>> > > the java.time package, one should be created (which is something
>> probably
>> > > pretty straightforward).
>> > >
>> > > If any of those methods is abstract or is part of an interface, then
>> we
>> > > have a small problem, and it should be solved on a case-by-case
>> analysis,
>> > > preferentially by providing a default implementation. I'm sure that
>> some
>> > > cases should still exist, but I doubt that any of them would be a
>> > > showstopper.
>> > >
>> > > The negative impact is expected to be very small. Popular products
>> like
>> > > Spring and Jakarta either already moved on and provided java.time.*
>> > > alternatives to their APIs or could do that quickly and easily. Anyone
>> > who
>> > > is left behind, would only get some [deserved] deprecation warnings.
>> > >
>> > > On the positive impact side, more than just discouraging the usage of
>> the
>> > > ugly and annoying API of Date, Calendar and DateFormat for people who
>> > > should know better, those classes are a frequent source of bugs that
>> are
>> > > hard do track and to debug due to their mutability and thread
>> unsafety.
>> > > Thus, we are already way past the time to make the compiler give a
>> > warning
>> > > to anyone still using them.
>> > >
>> > > What do you think?
>> >
>>
>


More information about the discuss mailing list