Why should I use Joda-Time or not?

The question comes mainly based on the comment of one of our most active users currently and who has already demonstrated extraordinary knowledge in time use, especially in Java.

To java API.time would be more "inspired" by Joda-Time, Than A Simple Copy

Some ideas and concepts were harnessed, but other things he considered "wrong" or bad, he took the opportunity to correct

I I was wondering if Java, which I know improved its API, took advantage of something from Joda-Time.

So I wanted to better understand what's wrong with her and if she still has any reason to use her if the Java API took advantage of her ideas.

Interests me as curious because I do not use Java, but I use NodaTime (inspired by Joda-Time) that I consider good and I have seen some things that it is better than Joda-Time, who knows I could have a better view of NodaTime understanding more about Joda-Time. It would be an interesting addendum if it is pertinent and knowledgeable.

Author: hkotsubo, 2019-10-01

1 answers

In general, the decision whether or not to use a language/framework/library/technology depends on several factors (from technical aspects to personal taste). In the specific case of Joda-Time, I think it is worth "giving a general" in the library, seeing its history and listing some similarities and differences with respect to java.time, and then each draws its own conclusions.

Brief - but not so much-Joda-Time summary

For a long time the Joda-Time was the a better alternative to the native API of the date of Java, because at that time, all we had was for the class java.util.Date and java.util.Calendar (in addition to your friends": java.text.SimpleDateFormat, java.util.TimeZone and, in the class Date, Time and Timestamp package java.sql). They all have a long list of problems and design failures (I will not list one by one, but some will be quoted below, when relevant to the discussion).

One of the innovations of Joda-Time (which today may seem "beaten" and trivial, but at the time it was a reason for " WoW!") it was the fluid and simplest API to use. For example, to create a specific date and add a few days and months to it:

// antes do Joda-Time
Calendar cal = Calendar.getInstance();
cal.set(2019, Calendar.JANUARY, 20); // 20 de janeiro de 2019
cal.add(Calendar.DAY_OF_MONTH, 5); // somar 5 dias
cal.add(Calendar.MONTH, 3); // somar 3 meses
System.out.println(cal.getTime()); // Thu Apr 25 16:15:27 BRT 2019

// depois do Joda-Time
LocalDate data = new LocalDate(2019, 1, 20) // 20 de janeiro de 2019
    .plusDays(5) // somar 5 dias
    .plusMonths(3); // somar 3 meses
System.out.println(data); // 2019-04-25

In the code above we can notice other details. The native API only had two classes to represent dates and times:

Joda-Time has already created several different classes to represent concepts such as " only one date (day, month and year)"," only time"," date and time with (or without) time zone", etc. This was another great innovation, since with Date and Calendar, you are always dealing with timestamps directly or indirectly. Calendar still has a built-in timezone, always acting "behind the cloths", so adjustments like Daylight Saving Time can occur without you noticing. Already in Joda-Time you can choose which fields you will use and have greater control over the dates and hour.

Look at the example above, for example. When printing cal.getTime(), the return is a java.util.Date, which when printed calls the method toString(), which in turn uses the JVM's timezone default to know which date and time to display - behavior that is explained Here and Here (in the "dates and timezones"section). And since I only set the day, month, and year, the time fields continued to be set to the current time (at the time the Calendar was created with getInstance()). Already use a org.joda.time.LocalDate, such problems do not occur. Since this class does not have the time fields, they are not even shown when printing the date. And since this class also has no time zone information, it does not suffer interference from the JVM's timezone default.

Another big improvement was using the correct values for the months. In the native API, the months were indexed to zero (January is zero, February is 1, etc), and to this day there are times when I forget and end up using the wrong value. So I preferred to use the constant Calendar.JANUARY (whose value is zero , by the way), which is less prone to errors. It may seem like a beast detail, but the JavaScript API has this same problem , and to this day causes confusion among developers.

Other features are immutable classes and thread safe , a formatting API and parsing much superior to SimpleDateFormat (which fixes several bizarre problems of this, as well as being more flexible and complete), more detailed timezones support, support for other calendars, as well as classes to represent durations (amounts of time), something that did not exist in the native API. And also other facilities, such as the comparison methods isBefore and isAfter (clearer and more semantic than compareTo), specific getters to get the day, month, year, etc (versus the generic" getter "of Calendar, which forces you to do things like calendar.get(Calendar.MONTH), conversions from / to the native API and many other "little things" that added up, made Joda-Time the preferred date API of this one that writes to you (today is no longer).


In the comments was indicated this question about the disadvantages of Joda-Time. Jon Skeet's answer says that one of the problems was when he tried to write your own timezone. Well, I'm yet to see an API that allows you to do this easily (I've also tried it with Joda-Time and other APIs, and it's not something trivial depending on what you want to do), but anyway it's a rare use case (I just did it out of curiosity) and I wouldn't consider it such a serious problem. Because it's not really simple, and I understand that no API has provided an easy way to do it. For the vast majority of cases, use the timezones that already exist is enough.

Another point that you might consider an advantage or disadvantage is that the timezone information is in the Joda-Time JAR itself, separated from the JVM. On the one hand it can be good, because you can update them separately, without having to mess with the installation of Java. On the other hand it can be bad, because it's more of a place to change (since usually updates - or should update - the JVM as well, because that's where the native API reads this information). Anyway, this "disadvantage" I consider debatable: there are cases where the update of the JVM is in charge of another team and you can not expect them, so using Joda-Time would guarantee that at least your application will be with the updated information (but if it does conversions from/to the legacy API, there is no way, you have to update the JVM too).

Anyway, I will not comment on all the answers one by one, not least because some may outdated (the question is from 2008), but one of the disadvantages is precisely the fact that it is an old API, of a project that declared itself closed. Seeing the latest versions , most are timezone updates or minor fixes. Also, on your own website there is a warning :

Note that Joda-Time is considered to be a largely "finished" project. No major enhancements are planned. If using Java SE 8, please migrate to java.time (JSR-310).

In free translation:

Joda-Time is considered a "closed" project. There are no major improvements planned. If you are using Java 8, please migrate to java.time (JSR-310).

That is, Joda-Time itself is saying " do not use me, go to the java.time". Then let's go...


The path to the API java.time

When it was announced that Stephen Colebourne (the creator of Joda-Time) would be one of the leaders of JSR-310 (which would give rise to a new date API in Java, the java.time), a lot of people - including me - thought he would just copy or adapt Joda-Time, make some adjustments here and there, and that's it. But that's not quite what happened.

In his blog, Stephen explains some of the things he considers flaws of design in Joda-Time, and that he decided to fix in the new API. The first is the dichotomy between the ways in which humans and machines "see" the concept of "time".

Basically, humans have created several different concepts, such as calendar systems (we have the Gregorian calendar, Buddhist, Hebrew, Chinese, Japanese, Hindu, etc.), which divide the timeline into arbitrary pieces: years, months, days, hours, minutes, and seconds. Not to mention the great mess that time zones are, with their constant changes of rules (you always have someone wanting to change the time zone of your country/state/city/province/etc, or wanting to adopt / abolish daylight saving time, so that "the people have more hours of sunshine" or whatever reason it is).

Thanks to these concepts and definitions, the same instant can represent a completely different date and time depending on the system you use (whether it's a different time zone or a different calendar). For example, when it is October 1, 2019, at 18:00 in the time of Brasilia, in Tokyo, it is already Day 2, at 6 in the morning. But if we consider the Coptic calendar , this same date corresponds to the 20th of Month 1 of the Year 1736 (to find out this, I used this class, also made by Stephen Colebourne, so if it is wrong complain to him :P).

Anyway, the human view of time is one side of the coin. The other is the "machine View", which would be the aforementioned timestamp : a number representing the amount of seconds (or milliseconds, or nanoseconds, varies by implementation) elapsed since the Unix Epoch (1970-01-01T00:00Z - January 1, 1970 at midnight on UTC). This number is "absolute", in the sense of being the same for everyone, regardless of time zone, calendar, etc. Although it is possible to convert a timestamp to a date and time in a given calendar and timezone (and vice versa), they are 2 separate concepts.

No Joda-Time there are the classes org.joda.time.Instant (representing the "machine View") andorg.joda.time.DateTime (which represents the "human vision"), but both implement the interface org.joda.time.ReadableInstant (which also represents the "machine View"). That is, we have a class that represents one concept (DateTime), but that implements an interface that represents a completely different one. This is one of the design flaws that he wanted to fix in the new API.


The other fault (according to the author) is that all the date and time classes of Joda-Time have a pluggable chronology (being that" chronology "is a synonym for" calendar system", since there is a chronology class for each calendar). Therefore, there are some cases in which this can affect some fields, such as the maximum value that the month can have (some calendars have years with 13 months). In several classes it is possible to change the chronology, giving room for confusing codes and prone to errors (since almost no one remembers to check if the chronology used is the correct one - most do not even conceive of the possibility of being using a different calendar).

Since most of the usual applications will end up using the calendar ISO 8601 (which is what we use on a day-to-day basis), he thought it best to leave this as the default of the new API, and dates that use other calendars would have their own classes (as we can see in the java.time.chrono).

The other faults mentioned are:

  • use of null at many points of the API, which depending on the context can mean the Unix Epoch, or a duration of zero value, which according to the author was not a good decision, because it causes many bugs. In the new API, many classes and methods check that the values are null and throw an exception.
  • because of the problems already mentioned (especially the first), many details implementation internals turned out to be quite complex. He mentions that he also took the opportunity to better consolidate some concepts and behaviors of the API, such as what to do in daylight saving time transitions (Joda-Time can give error in some cases, while java.time makes some automatic adjustments) and in some corner cases in date arithmetic (Ex: how many years are there between 29/02/2020 and 28/02/2021? For Joda-Time, the answer is 1, for java.time, it is zero) - the latter is not mentioned on the blog, but do not believe that it has changed for nothing.

Finally, the author himself mentions that "JSR - 310 started from scratch, but with an API inspired by Joda-Time".


Similarities and differences between Joda-Time and java.time

Here and here has a great summary, and from the tables of the first link you can see that several Joda-Time concepts are present in java.time:

  • specific classes for each situation : one for date only (day, month and year), one for time, one for date and time (no time zone), one for date and time in a time zone, etc. It is worth noting that some classes have been added, especially the enum'S for the months and the days of the week.
  • Joda-Time has mutable versions of the classes, which have been removed in java.time (in the new API, all classes are immutable and thread safe )
  • in java.time the concepts of timezone and offset they were separated, which is a very rare thing to see. The vast majority of API's treat them as if they were the same thing, but there is a subtle difference: the offset is simply the difference with respect to UTC (like -03:00 to indicate 3 hours behind UTC), since a timezone contains the change history of the offset of a certain region of the planet (like America/Sao_Paulo, which contains changes from the official time of Brasilia, in which it is used -03:00 during part of the year, and changes to -02:00 during daylight saving time). For more details, see the timezone tag wiki (in the "differences between timezone and offset"section).
  • one thing I didn't understand was the removal of duration formatting (although less common than date formatting, it's still a relatively common use case)

Though many classes (mainly the core of the APIs) have the same names and methods, some details in their operation are different. One that stands out from the guy is the use of factory methods instead of constructors:

// Java 8, uso dos factory methods "of()" e "now()"
java.time.LocalDate.of(2018, 2, 1);
java.time.LocalDate.now(); // data atual

// Joda-Time, uso de construtor
new org.joda.time.LocalDate(2018, 2, 1);
new org.joda.time.LocalDate(); // data atual

Note that I used the full name of the classes so as not to get confused .

Another difference is the already quoted enum's for the month and day of the week. In Joda-Time, the getDayOfWeek() method, for example, returns a int whose values are defined in the class org.joda.time.DateTimeConstants. This class lists the days of the week according to the definition of ISO 8601, which considers Monday as the first day of the week. So, Monday (DateTimeConstants.MONDAY) has the value 1, Tuesday is 2, and so on, until Sunday, whose value is 7.

The fact that there is a type java.time.DayOfWeek in Java 8 makes the code clearer and less prone to errors, since we do not run the risk of passing an invalid value if a method is waiting for a day of the week, as only those defined in enum will be accepted. The same goes for java.time.Month, which is the enum for the months (and January has the value 1 \o/).


One of the main differences between API's is in the way the DST gaps are treated (when Daylight Saving Time starts and the clock is advanced by one hour). Let's use timezone America/Sao_Paulo as an example. In this timezone, on October 15, 2017, at midnight, the clocks were advanced at hour. This means that this time has been "skipped", that is, every minute between 00:00 and 00:59 does not exist on this day, for the timezone in question.

In java.time this is solved by setting the time to the next valid time. For example, when creating a ZonedDateTime for "October 15, 2017 at midnight and a half in São Paulo", the time is automatically set to 01:30 (the next valid time):

// 15 de outubro de 2017, meia-noite e meia, timezone America/Sao_Paulo
ZonedDateTime z = ZonedDateTime.of(2017, 10, 15, 0, 30, 0, 0, ZoneId.of("America/Sao_Paulo"));
// Devido ao horário de verão, 00:30 é ajustado para 01:30
System.out.println(z); // 2017-10-15T01:30-02:00[America/Sao_Paulo]

This is an interesting approach to the API as it simulates a person manually advancing the clock when noticing that the time was incorrect. Already in Joda-Time, this adjustment is not made and the attempt to create this date throws an exception:

// 15 de outubro de 2017, meia-noite e meia, timezone America/Sao_Paulo
DateTime d = new DateTime(2017, 10, 15, 0, 30, 0, 0, DateTimeZone.forID("America/Sao_Paulo"));

This code throws the following exception:

Org.joda.time.IllegalInstantException: Illegal instant due to time zone offset transition( daylight savings time 'gap'): 2017-10-15T00:30:00.000 (America/Sao_Paulo)

Note that the exception description reports that there was a DST gap and so the time reported does not exist that day, for the timezone in question. To create a valid DateTime, you must first check that the date and time are in a DST gap, and if so, set the time to the next time. To do this, let's create a org.joda.time.DateTimeZone for timezone America/Sao_Paulo and a org.joda.time.LocalDateTime to check if the date and time are in a DST gap :

// timezone America/Sao_Paulo
DateTimeZone zone = DateTimeZone.forID("America/Sao_Paulo");
// 15 de outubro de 2017, meia-noite e meia (sem timezone)
LocalDateTime localDt = new LocalDateTime(2017, 10, 15, 0, 30, 0, 0);
// se o localDt está em um DST gap, ajustar para a hora seguinte
if (zone.isLocalDateTimeGap(localDt)) {
    localDt = localDt.plusHours(1);
}
// criar o DateTime: 15 de outubro de 2017, à 01:30, timezone America/Sao_Paulo
DateTime dt = localDt.toDateTime(zone);

The isLocalDateTimeGap() method checks if the LocalDateTime is in a DST gap , and if applicable, we set it to the next valid time. In practice, we are manually making the adjustment that ZonedDateTime does automatically. there is only one but .

The above code can work for most cases, but not all DST gaps are 1 hour. There are timezones, such as Australia/Lord_Howe, which advance the clock by 30 minutes during daylight saving time . Or even Asia/Kuching, which in the 30s advanced the clock by only 20 minutes .

Also, not all gaps are because of daylight saving time. There are cases where a certain region simply changes its time zone " definitively "(in quotes because nothing guarantees that it will not change again). An example is North Korea, which on May 5, 2018 at midnight moved the clock forward by 30 minutes, to align its schedule with South Korea. It is a 30-minute gap, but not related to daylight saving time. But because it is a gap , it can also cause a IllegalInstantException.

Another notable example is the case of Samoa: on December 30, 2011 at midnight they changed their offset from -10:00 to +14:00. With this, the entire 30th day was skipped , that is, a 24-hour gap ! All times for December 30, 2011 do not exist in this timezone. This was only possible because the country is located near the International Date Line .

By this, the previous solution (add 1 hour to LocalDateTime) does not work well for these cases. In the case of Samoa, for example, we would have to add up at least 24 hours to make sure we are no longer within the gap. But by adding up 24 hours, we run the risk of getting too "early" a date. One way around this would be to add up to 1 minute, until you find a valid time:

// ajustar para o minuto seguinte, até encontrar uma hora que não está em um gap
while (zone.isLocalDateTimeGap(localDt)) {
    localDt = localDt.plusMinutes(1);
}

With this, LocalDateTime will be incremented until we find a valid time that is not in a gap , it does not matter whether it is related to Daylight Saving Time or not, nor whether it is 1 hour, 20 minutes or a whole day. java.time makes these adjustments automatically, always advancing to a valid date and time.


The java.time also changed some things to formatting and parsing of dates. Some patterns do not work in the same way: in Joda-Time, the letter e represents the day of the week according to the values of the respective constants in class DateTimeConstants, which we have seen previously: Monday has value 1, Tuesday is 2 etc. However, in java.time, the letter e represents the day of the week according to the Locale used (and if none Locale is reported, the default JVM locale is used). According to Locale, the week can start on Saturday, Sunday or Monday, so a DateTimeFormatter with the letter e can return a different value for the same date, depending on the Locale used (something that does not occur in Joda-Time, which uses a week definition that does not depend on Locale). here has a very detailed explanation about this.

Another thing that has been - in my opinion - improved are TemporalField and TemporalUnit, which correspond respectively to fields of a date (such as Day, Month, Year, etc.) and units of a duration (days, months, hours, minutes, etc.). These concepts also exist in Joda-Time but I have always found it a bit confusing to use them, and in java.time it seems to me that it has become easier to understand and use (but there is already my opinion).

There are still some news that Joda-Time does not have:

  • A interface TemporalQuery, that allows to obtain information of a date in a more customized way. Ex:

    TemporalQuery<MeuResponseCustomizado> info = (t) -> {
        DayOfWeek dow = DayOfWeek.from(t); // obtém o dia da semana
        MeuResponseCustomizado resp;
        if (condicaoComplexa(dow)) {
            resp = fazAlgoBemComplicado(t);
        } else {
            resp = respostaDefault(t);
        }
        return resp;
    };
    MeuResponseCustomizado resposta = LocalDate.now().query(info);
    

    The detail is that a TemporalQuery takes as a parameter a TemporalAccessor (an interface that all API date classes implement), making it very flexible (it is possible to use the same TemporalQuery with several different classes). Of course in Joda-Time it would be enough to create a static method in a utility class, but TemporalQuery facilitates some things (such as using the default strategy, being able to pass the query as a parameter, exchanging it for mocks in tests, etc)

  • A interface TemporalAdjuster, for useful adjustments, such as getting the last day of the month, the third Thursday of the month, among others. These that I cited, including, are already ready in the API .

  • java.time.Clock, with which it is possible to change the current date/time (very useful in tests , for example). In Joda-Time this was done with a static method , which changed the current date/time for the entire application, already in java.time you can have several different Clock's at the same time, they can be configured by the dependency injection container, etc.

Another difference is that the java.time it has nanosecond accuracy (9 decimal places in the fraction of seconds), while Joda-Time accuracy is milliseconds (3 decimal places). Depending on what you need, this may or may not be a big plus.

Anyway, there are several other details that are different ( I have a class on GitHub that better details these and other points), but in short:

  • some concepts and ideas of Joda-Time were harnessed in java.time
  • some things have been improved and don't work exactly the same way
  • others were completely modified, and some new concepts added
  • the new API is inspired by the old one, but is not a mere copy

But should I use it or not?

In my opinion: in new projects using JDK > = 8, use java.time. The API meets most of the needs that Joda-Time provided, and the support of the "famous" APIs only increases: Jackson already has a own module, JDBC 4.2 already supports these classes (make sure your database already has a compatible driver) and so on.

If you are using JDK 6 or 7, use the ThreeTen backport, a backport that has most of the functionality of java.time. The API is pretty much the same (except for some details, such as the conversion from / to Date and Calendar, which in Java 8 is done through new methods , and in backport is done by a utility class ). In addition to having the same features, this facilitates a future migration to JDK 8, since for most cases it would be enough to change the import from org.threeten.bp to java.time.

For Android, java.time is also available (here has instructions for using it), but you can also use backport (here has instructions to use it on Android).

But in case you are still stuck to JDK 5 (I don't doubt that there is no prospect of changing the version, and want to use something better than Date and Calendar, in this case I recommend Joda-Time. Another case is when there is a lot of legacy code using Joda-Time and it is unviable to migrate it (being that "too much" and "unviable" can be subjective, but whether you are considering continuing to use it or not, it is an analysis that has to be done).


this question from SOen also compares the 2 API'S. enough to say something meaningful.

 20
Author: hkotsubo, 2020-10-07 17:10:15