Sign in
Log inSign up
How not to get lost in time: DateTime and Calendar

How not to get lost in time: DateTime and Calendar

Alex Yelenevych's photo
Alex Yelenevych
·May 30, 2019

Hi! Today we'll start working with a new data type we haven't encountered before, namely, dates.

I don't think I need to explain what a date is. :) In principle, we could store the current data and time in an ordinary Java String.

public class Main {
   public static void main(String[] args) {

       String date = "June 11, 2018";
       System.out.println(date);
   }
}

But this approach has many drawbacks. The String class is designed to work with text, and its methods are appropriate for this task. If we need to manipulate a date in some way (add 2 hours, for example), String doesn't work so well. Or if we want to display the current date and time when the program is compiled. String doesn't help here either: by the time you write the code and run it, the time will have changed and the console will display the wrong information.

That's why Java's creators provided several classes for working with dates and time.

The first of these is java.util.Date

Date class

We specified its full name, because another Java package has the java.sql.Date class. Don't mix them up!

The first thing you need to know about it is that it stores the date as the number of milliseconds that have passed since January 1, 1970. This time system even has its own name: "Unix-time"

A rather interesting approach, wouldn't you agree? :)

The second thing worth remembering is this: If you create a Date object using the default constructor, the result represents the current date and time at the moment the object was created.

Remember that we said that a date represented as a String would struggle with such a task? The Date class handles it with ease.

public class Main {
   public static void main(String[] args) {

       Date date = new Date();
       System.out.println(date);
   }
}

Run this code several times, and you'll see the time change repeatedly. :) This is possible because the time is stored as milliseconds: they are extremely small units of time, so the results are highly accurate.

The Date class another constructor: you can pass the exact number of milliseconds since 00:00 on January 1, 1970 to the required date, and a corresponding date object will be created:

public class Main {
   public static void main(String[] args) {

       Date date = new Date(1212121212121L);
       System.out.println(date);
   }
}

Console output:

Fri May 30 04:20:12 GMT 2008

We get May 30, 2008. "Fri" indicates the day of the week (Friday, duh), and GMT is the time zone (Greenwich Mean Time).

Milliseconds are passed as longs, because the number of milliseconds does not usually fit into an int.

So, what operations with dates we might we need to perform?

Well, the most obvious, of course, is comparison. To determine whether one date comes before or after another.

This can be done in several ways.

For example, you can call the Date.getTime() method, which returns the number of milliseconds that have elapsed since midnight on January 1, 1970.

Just call it on two Date objects and compare the results:

public class Main {
   public static void main(String[] args) {

       Date date1 = new Date();

       Date date2 = new Date();

       System.out.println((date1.getTime() > date2.getTime())?
               "date1 is later than date2" : "date1 is earlier than date2");
   }
}

Output:

date1 is earlier than date2

But there's also a more convenient way, i.e. by using special methods provided by the Date class: before(), after() and equals().

All of them return a boolean value.

The before() method checks whether our date is earlier than the date passed as an argument:

public class Main {
   public static void main(String[] args) throws InterruptedException {

       Date date1 = new Date();

       Thread.sleep(2000);// Suspend the program for 2 seconds
       Date date2 = new Date();

       System.out.println(date1.before(date2));
   }
}

Console output:

true

Similarly, the after() method checks to see if our date is later than the date passed as an argument:

public class Main {
   public static void main(String[] args) throws InterruptedException {

       Date date1 = new Date();

       Thread.sleep(2000);// Suspend the program for 2 seconds
       Date date2 = new Date();

       System.out.println(date1.after(date2));
   }
}

Console output:

false

In our examples, we "put the program to sleep" for 2 seconds, so that the two dates are guaranteed to be different. On fast computers, the time between the creation of date1 and date2 could be less than one millisecond, causing both before() and after() to return false.

But in this case, the equals() method will return true!

After all, it compares the number of milliseconds since 00:00 on January 1, 1970 for each date. The objects are considered equal only if they match to the millisecond:

public static void main(String[] args) {

   Date date1 = new Date();
   Date date2 = new Date();

   System.out.println(date1.getTime());
   System.out.println(date2.getTime());

   System.out.println(date1.equals(date2));
}

Here's another thing you need to pay attention to.

If you open the documentation for the Date class on the Oracle) website, you'll see that many of its methods and constructors have been marked as Deprecated (i.e. not recommended for use).

Here's what Java's creators have to say about parts of classes that have been deprecated:

"A program element annotated @Deprecated is something programmers are not recommended to use, usually because it's dangerous, or because there is a better alternative."

This doesn't mean that these methods can't be used at all. If you try to run code using deprecated methods in an IDE, it will most likely work

For example, consider the deprecated Date.getHours() method, which returns the number of hours associated with a Date object.

public static void main(String[] args) {

   Date date1 = new Date();

   System.out.println(date1.getHours());
}

If you start the code at 14:21 (2:21 PM), it will display the number 14.

As you can see, the deprecated method is crossed out, but it still works.

These methods aren't removed in order to not break the huge body of existing code that uses them.

In other words, these methods are neither "broken" nor "removed". They are simply not recommended for use because a more convenient alternative is available.

Incidentally, the documentation specifically mentions this alternative:

pic2

Most of the Date class's methods have been moved the improved and extended Calendar class. We'll get acquainted with that class next. :)

Calendar class

JDK 1.1 introduced a new class: Calendar. It made working with dates in Java somewhat easier than before.

The only implementation of the class Calendar that we'll work with is the GregorianCalendar class. It implements the Gregorian calendar, which is observed by most countries of the world.

Its main advantage is that it can work with dates in a more convenient format.

For example, it can:

  • Add a month or day to the current date
  • Check whether the year is a leap year;
  • Return individual components of the date (for example, extract the month number from a whole date)
  • It also contains a very convenient system of constants (many of which we will see below).

Another important enhancement of the Calendar class is its Calendar.ERA constant: you can indicate a date before the common era (BC - before Christ) or in the common era (AD - Anno Domini).

Let's look at all this with examples.

Let's create a calendar object with the date of January 25, 2017:

public static void main(String[] args) {

  Calendar calendar = new GregorianCalendar(2017, 0 , 25);
}

In the Calendar class (as well as the Date class for that matter), months start from zero, so we pass the number 0 as the second argument.

When working with the Calendar class, it's important to understand that this is just that, a calendar, not an individual date.

pic3

A date is just a few numbers that indicate a specific time interval. A calendar is a whole system that lets you do a lot of things with dates. :)

This is abundantly apparent if you try to display the Calendar object:

Output:

java.util.GregorianCalendar[time=?,areFieldsSet=false,areAllFieldsSet=false,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Europe/London",offset=0,dstSavings=0,useDaylight=false,transitions=79,lastRule=null],firstDayOfWeek=2,minimalDaysInFirstWeek=1,ERA=?,YEAR=2017,MONTH=0,WEEK_OF_YEAR=?,WEEK_OF_MONTH=?,DAY_OF_MONTH=25,DAY_OF_YEAR=?,DAY_OF_WEEK=?,DAY_OF_WEEK_IN_MONTH=?,AM_PM=0,HOUR=0,HOUR_OF_DAY=0,MINUTE=0,SECOND=0,MILLISECOND=?,ZONE_OFFSET=?,DST_OFFSET=?]

See how much information you get! A calendar has a bunch of properties that a normal date does not have, and all of them are displayed (this is how the toString() method works in the Calendar class).

If you just need to get a simple date from the calendar, i.e. a Date object, use the Calendar.getTime() method (the name isn't the most logical, but what can you do?):

public static void main(String[] args) {

   Calendar calendar = new GregorianCalendar(2017, 0 , 25);
   Date date = calendar.getTime();
   System.out.println(date);
}

Output:

Wed Jan 25 00:00:00 GMT 2017

Now we have taken the calendar and "reduced it" to an ordinary date.

Let's go further.

In addition to designating months by their number, you can use the Calendar class's constant field values.

These constants are static fields of the Calendar class with a preset value that cannot be changed.

This is actually an even better option, because using them improves the readability of your code.

public static void main(String[] args) {
   GregorianCalendar calendar = new GregorianCalendar(2017, Calendar.JANUARY , 25);
}

Calendar.JANUARY is one of the constants that represent the months of the year.

Using these named constants, no one will forget, for example, that the number 3 means April, and not the third month, which we like to call March. Just write Calendar.APRIL, and you're done. :)

All calendar fields (number, month, minutes, seconds, etc.) can be specified separately using the set() method.

This method is very convenient, because the Calendar class has a constant for each field, and the resulting code is very easy to read.

In the last example, we created a date, but did not set a time for it. Let's set the time 19:42:12

public static void main(String[] args) {
   Calendar calendar = new GregorianCalendar();
   calendar.set(Calendar.YEAR, 2017);
   calendar.set(Calendar.MONTH, 0);
   calendar.set(Calendar.DAY_OF_MONTH, 25);
   calendar.set(Calendar.HOUR_OF_DAY, 19);
   calendar.set(Calendar.MINUTE, 42);
   calendar.set(Calendar.SECOND, 12);

   System.out.println(calendar.getTime());
}

Output:

Wed Jan 25 19:42:12 GMT 2017

We call the set() method, passing a constant (depending on the field we want to change) and the new value for the field. It turns out that this set() method is a kind of "super-setter" that knows how to set the value not just for one field, but for many fields. :)

The Calendar class uses the add() method to add and subtract values. You pass in the field you want to change, and a number (exactly how much you want to add/subtract from the current value).

For example, let's get a date that is 2 months before the date we created:

public static void main(String[] args) {
   Calendar calendar = new GregorianCalendar(2017, Calendar.JANUARY , 25);
   calendar.set(Calendar.HOUR, 19);
   calendar.set(Calendar.MINUTE, 42);
   calendar.set(Calendar.SECOND, 12);

   calendar.add(Calendar.MONTH, -2); // To subtract, pass a negative number
   System.out.println(calendar.getTime());
}

Output:

Fri Nov 25 19:42:12 GMT 2016

Very good! We got the date 2 months ago. This didn't only cause the month to change: the year also changed from 2017 to 2016. Of course, when converting dates, the current year is calculated automatically without any need for you to keep track of it manually.

But if for some reason you need to disable this behavior, you can do so.

The roll() method can add and subtract values without affecting the remaining values. For example, like this:

public static void main(String[] args) {
   Calendar calendar = new GregorianCalendar(2017, Calendar.JANUARY , 25);
   calendar.set(Calendar.HOUR, 10);
   calendar.set(Calendar.MINUTE, 42);
   calendar.set(Calendar.SECOND, 12);

   calendar.roll(Calendar.MONTH, -2);
   System.out.println(calendar.getTime());
}

We did exactly the same thing as in the previous example: we took 2 months from the current date. But now the code does something different: the month has changed from January to November, but the year remains unchanged—2017!

Output:

Sat Nov 25 10:42:12 GMT 2017

Moving along.

As we said above, we can get all the Calendar fields separately. We do this with the get() method:

public static void main(String[] args) {
   GregorianCalendar calendar = new GregorianCalendar(2017, Calendar.JANUARY , 25);
   calendar.set(Calendar.HOUR, 10);
   calendar.set(Calendar.MINUTE, 42);
   calendar.set(Calendar.SECOND, 12);

   System.out.println("Year: " + calendar.get(Calendar.YEAR));
   System.out.println("Month: " + calendar.get(Calendar.MONTH));
   System.out.println("Week in the month: " + calendar.get(Calendar.WEEK_OF_MONTH));// Week in this month?

   System.out.println("Day: " + calendar.get(Calendar.DAY_OF_MONTH));

   System.out.println("Hours: " + calendar.get(Calendar.HOUR));
   System.out.println("Minutes: " + calendar.get(Calendar.MINUTE));
   System.out.println("Seconds: " + calendar.get(Calendar.SECOND));
   System.out.println("Milliseconds: " + calendar.get(Calendar.MILLISECOND));

}

Output:

*Year: 2017

Month: 0

Week in the month: 5

Day: 25

Hours: 10

Minutes: 42

Seconds: 12

Milliseconds: 0*

So, in addition to the Calendar class's "super-setter", there is also a "super-getter". :)

Of course, another interesting aspect of this class is working with eras.

To create a "BC" date, you'll need to use the Calendar.ERA field

For example, let's create a date for the Battle of Cannae, where Hannibal defeated the Roman army. This happened on August 2, 216 BC:

public static void main(String[] args) {
   GregorianCalendar cannae = new GregorianCalendar(216, Calendar.AUGUST, 2);
   cannae.set(Calendar.ERA, GregorianCalendar.BC);

   DateFormat df = new SimpleDateFormat("MMM dd, yyy GG");
   System.out.println(df.format(cannae.getTime()));
}

Here we used the SimpleDateFormat class to print the date in a format that's easier for us to understand (the letters "GG" indicate we want the era to be displayed).

Output:

Aug. 02, 216 BC.

The Calendar class has many more methods and constants. You can read about them in the documentation.

If don't like this date format

Sat Nov 25 10:42:12 GMT 2017

then you can use SimpleDateFormat to easily make it what you want it to be.

public static void main(String[] args) {

   SimpleDateFormat dateFormat = new SimpleDateFormat("EEEE, MMMM d, yyyy");
   Calendar calendar = new GregorianCalendar(2017, Calendar.JANUARY , 25);
   calendar.set(Calendar.HOUR, 10);
   calendar.set(Calendar.MINUTE, 42);
   calendar.set(Calendar.SECOND, 12);

   calendar.roll(Calendar.MONTH, -2);
   System.out.println(dateFormat.format(calendar.getTime()));
}

Output:

Saturday, November 25, 2017

That's much better, isn't it? :)