Date and Time Manipulation in Java Using JodaTime
Date and time manipulations are one of the hardest things to do right when programming for a global user base. What seems like a simple task, such as “record when a change was made” turns into a nightmare when you have to account for time zones and daylight saving. How can you “add a day” to a timestamp when a day can be 23 hours long?
Java’s core time classes can handle some of the confusion, particularly from Java7 on, but JodaTime offers a richer and more versatile way of coping with these issues that is also compatible with earlier Java versions.
A Short History of Time (Measurement)
Once upon a time, the ancient Sumerians recorded the passing of days and years using a 12-month calendar. The Mayans, Greeks, Celts, Asians, Hindu, each had their own calendars. In 46 BC, Julius Caesar introduced the calendar that would be named after him. This Julian calendar was used in most of Europe and its early colonies until it was replaced by the Gregorian calendar, starting in 1582. The last European countries to adopt it did so in 1923.
Today, the Gregorian calendar is usually the only calendar your software applications have to worry about, fortunately. The Java Core Classes also support the Japanese Imperial calendar and the Buddhist one, but only inasmuch as it calculates the years according to the “start of eras” of those calendars. To use any other calendar, you must use an external library such as Joda Time.
Date and time measurement is surprisingly complex. Once, timekeeping was a very local affair, using shadow clocks and sundials, so a particular reading of the time was valid only at a specific location. But as civilization expanded, and communications and travel became faster and more commonplace, standards had to be established.
Some countries set up their own standards (which may remind you of the computer industry’s own “any standard as long as it’s mine” history). In October 1884, the international 24-hour time zone system, with the Greenwich Mean Time (GMT) as its prime meridian, was first adopted by a few countries. By 1920, it was generally adopted the world over. Large countries modified it slightly so that time zones followed political boundaries rather than strict meridians. Some added time zones on the half-hour. China even set a single time zone for the whole country. It is now possible to know what time it is, right now, anywhere in the world, as long as you know what time zone covers that location. This is nice and makes perfect sense, right? But wait! There’s more! Because Daylight Saving Time makes the time-and-date calculations even more complex.
During the First World War, Germany decided that clocks should be moved forward so that the sun would effectively set one hour later. This would “save daylight,” so less energy would have to be allocated to artificial lighting – all the more for the war effort. Britain thought it was a good idea, and copied it; the U.S. and more countries followed for the duration of World War I. Until the next World War, when the U.S. went on continuous daylight saving time (DST). From 1945 to 1966, time zones were an absolute pandemonium as states and localities were allowed to choose whether and when they observed DST. (If you want to learn more details, I am sure you will enjoy The History of Daylight Saving Time.)
In 1966, Congress took charge so that all the states would change to/from DST at the same time, unless they chose not to change at all. The “spring forward/fall back” dance was formalized for good. Many other countries go through the same rigmarole, some at the same time as the U.S., others at different times. Others do not observe DST at all.
So, it is now possible to know what time it is, anywhere in the world, as long as you know the time zone it is in, whether that location changes to DST, and whether DST is currently in effect in that location.
That is a lot of information to know for a simple question! Fortunately, there exist databases of time zones that you can use. For example, the Java Runtime Environment comes with one, and when some time zone definitions change, Sun releases patches that you can apply to your JRE to get the new definitions. Joda Time also ships with such a database, and it can be updated by regenerating the jar file.
The Java Core Classes offer some way to manipulate dates and times. The java.util.Date object represents a specific moment in time, but it isn’t overly useful. Most of its methods are deprecated in favor of Calendar, which is useful for date/time manipulation (“date math”). Yet if you want to represent a date/time as a String, you use the DateFormat, which can format Dates, but not Calendars. It gets cumbersome.
If the Calendar you’re manipulating uses a time zone that’s different from the system time zone, not only do you have to transform it to a Date first, you also have to tell the DateFormat what time zone to use to format it:
Calendar c =
c.set(2013, Calendar.JUNE, 10, 9, 0);
SimpleDateFormat format =
new SimpleDateFormat("yyyy-MMM-dd HH:mm (z)");
2013-Jun-10 12:00 (EDT)
2013-Jun-10 09:00 (PDT)
Java is fully aware of DST idiosyncrasies and handles the math correctly. In my time zone, as in most of the United States, daylight saving starts on the second Sunday of March at 2:00am, so that an hour after 1:58 is really 3:58. The last change was March 10th:
Calendar c = Calendar.getInstance();
c.set(2013, Calendar.MARCH, 10, 1, 58);
SimpleDateFormat format =
new SimpleDateFormat("EEE, MMM d, yyyy 'at' hh:mm");
Sun, Mar 10, 2013 at 01:58
Sun, Mar 10, 2013 at 03:58
Speaking of idiosyncrasies, Calendar expects the month of March to be sent as “2.” The months start at 0 for January and go to 11 for December, so use the predefined constants instead.
I doubt there’s anything you can’t do in terms of date and time manipulation using the standard JRE classes, but I find it to be difficult and complicated to use. This is why I strongly encourage you to use Joda Time instead.
Joda Time is a library that makes date and time manipulation a little easier. It will never be easy, particularly when you need to account for all the various locations and locales your users may come from, but at least with Joda Time, the tools help you, rather than get in the way.
To use Joda Time, download the jar from SourceForge and add it to your project the same way you would any other jar (or with maven, use the group joda-time and the artifact joda-time).
The primary class used to represent an instant in time is the org.joda.time.DateTime. A DateTime, as it name implies, encodes both the date and the time. It also includes time zone information so that it knows how to interpret it in terms of hours and minutes. This object is immutable, which means it is thread-safe. It also means that when you perform “date math” on that object, you get a brand new DateTime as a result
DateTime today = new DateTime();
DateTime tomorrow = today.plusDays(1);
This is not unlike working with Strings:
String s1 = "hello, ";
String s2 = s1.concat("world!");
You can extract data from a DateTime and you can effectively set its fields, but remember to use the return value of with* methods, because, as I just told you, DateTime is immutable.
DateTime firstOfTheMonth = today.withDayOfMonth(1);
Another nicety of the API is that Joda Time lets you use the intuitive numbers for months, with 1 meaning January and 12 December.
Joda Time has another class you may want to use to represent a particular date and time, LocalDateTime. It is very similar to the DateTime, except that it does not include any time zone information (it does not even default to the system time zone; there is no time zone at all). This small difference has a huge impact, since while LocalDateTime has methods such as addHours() (as DateTime does), it cannot possibly calculate things correctly around DST changes, since it is totally unaware of DST.
String format = "EEE, MMM d, yyyy, 'at' HH:mm";
DateTime beforeDST = new DateTime(2013, 3, 10, 1, 58);
DateTime afterDST = beforeDST.plusHours(1);
LocalDateTime localBeforeDST =
new LocalDateTime(2013, 3, 10, 1, 58);
LocalDateTime localAfterDST = localBeforeDST.plusHours(1);
Sun, Mar 10, 2013, at 01:58
Sun, Mar 10, 2013, at 03:58
Sun, Mar 10, 2013, at 01:58
Sun, Mar 10, 2013, at 02:58
There are times when using LocalDateTime, LocalTime, or LocalDate can make sense. For example, if an American travels to Japan for her birthday on August 6th, when does her birthday start? As soon as it turns August 6th in Japan, of course! She doesn’t have to wait until 1pm to start celebrating! Similarly, if I ask you at what time you wake up in the morning, it usually doesn’t matter where in the world you are.
When it comes time to choosing which time zone to use, consider your user’s location, of course, but also consider the context. For example, to show the operating hours of a brick-and-mortar store, it makes sense to show the times in the store’s time zone (and it is wise to mention the time zone, in case someone wants to phone). However, if you’re letting your users know about a planned system interruption for maintenance, display that in the user’s time zone if you have it. Similarly, transaction time stamps should be expressed in the user’s time zone.
If you receive input from a user, assume they’re entering it in their time zone. If you do not know what that is, ask. You can have them choose from the list you get by calling
Set<String> ids = DateTimeZone.getAvailableIDs();
When you store a DateTime, use a consistent time zone. In many cases, your best option is using UTC because it’s free of DST silliness, but also have to consider how else that data will be used. For example, if you’re using a database that is set to the Eastern time zone, you don’t need to convert to UTC only to have the database convert it back! If you need to send timestamps to another program, make sure it can handle what you send it.
You may be tempted to store the DateTimes or other timestamps as seconds or milliseconds since Jan 1st, 1970, at 00:00:00.0000, but that leads to trouble, because it makes debugging tedious and difficult. And it’s frustrating, as you have to convert the timestamps back and forth.
Durations, Periods, and Intervals
Joda Time distinguishes between Durations, Periods, and Intervals. This may seems like adding complexity for little gain, but it is necessary for accurate representations of different situations.
A duration is a length of time, which Joda Time encodes as the number of milliseconds. This can be how long you slept, or how long you spent on the treadmill, or even how long it has been since your last chocolate cake binge. A period is a more tangible representation of that time, such as 7 hours, 25 minutes, or 3 weeks, 4 days, 9 hours and 22 minutes. Joda Time uses periods to do the date math on DateTime:
Is equivalent to
Periods and Durations can be very smart. For example, if you specify a Period or Duration using DateTimes that cross a DST change, or from different time zones, it does “the right thing:”
DateTime beforeDST = new DateTime(2013, 3, 10, 1, 45);
DateTime afterDST = new DateTime(2013, 3, 10, 3, 45);
Period p = new Period(beforeDST, afterDST);
System.out.println("#hours between 1:45 and 3:45: " +
DateTime departure =
new DateTime(2013, 4, 1, 5, 0,
DateTime arrival =
new DateTime(2013, 4, 1, 8, 0,
Period tripLength = new Period(departure, arrival);
System.out.println("Trip length in hours: " +
#hours between 1:45 and 3:45: 1
Trip length in hours: 6
Intervals are another concept altogether. They represent a specific span of time. You can think of this in terms of mathematical intervals, closed at the start (the time at the start is part of the interval) and open at the end (the endpoint is not part of the interval). You can compare intervals, find out if they overlap, and find out how long they last, but that length of time is expressed as a Duration or a Period. For example, an Interval may represent the time when an employee is at work, as recorded when he punches in and out. Then we can compare intervals to find out when two employees were at work at the same time:
Interval aliceAtWork =
new Interval(new DateTime(2013, 6, 10, 9, 0),
new DateTime(2013, 6, 10, 14, 0));
Interval bobAtWork =
new Interval(new DateTime(2013, 6, 10, 12, 0),
new DateTime(2013, 6, 10, 19, 0));
Interval workedTogether = aliceAtWork.overlap(bobAtWork);
" to " +
System.out.println("# hours worked together: " +
12:00 to 14:00
# hours worked together: 2
Durations, Periods and Intervals are immutable, and thus thread-safe. Again, remember to use the return values of with* methods to get the modified values.
When it comes to handling dates and times, the Java Core Objects try to shield you from all the complexity related to date/time manipulation, and that’s tempting, but concienscious programmers need to care about the details, and when it comes to dates and times, the sane solution is Joda Time. It makes it possible to go into as much details as necessary, yet it abstracts the rest so you don’t get too caught up trying to get work done. Using Joda Time should improve both your results and productivity, as well as your sanity.
Further reading on Joda Time:
Nancy Deschênes has been developing for the Web for more than 15 years. In that time, she has worn many hats, acting at times as a front-end developer, database specialist, and (her favorite) application architect. She has used various technologies, mostly Java and the Spring MVC framework, but has recently spent most of her time using the Grails framework. She is the technical co-founder of myTurn.com, a platform for online rental of physical goods.
The State of Code Quality 2016
The State of Code Quality 2016 is an industry report designed to establish benchmarks for how organizations are developing and maintaining the quality of software in 2016.
The report includes insights from 600 software developers, testers, IT/operations professionals, and business leaders representing more than 30 different industries. Participants in the survey work on software teams ranging from less than five employees to more than 50 employees, and work for companies ranging from small businesses, with less than 25 employees, to enterprise organizations, with 10,000 employees or more.
The following report will cover:
- Perceptions on Code Quality
- Approaches to Code Review
- Code Review Tools and Decision Making
- Software Development Tools
Get your copy!