Skip to content
On this page

Date and Calendar

How should we represent dates and times in computing?

Common representations of dates and times include:

  • 2019-11-20 0:15:00 GMT+00:00
  • 2019年11月20日8:15:00
  • 11/19/2019 19:15:00 America/New_York

If stored directly as strings, different formats and languages can make representation cumbersome.

Before understanding how to represent dates and times, we first need to grasp data storage and display.

When we define an integer variable and assign a value:

java
int n = 123400;

The compiler compiles this string (the program source code is essentially a string) into bytecode. At runtime, the memory pointed to by variable n is actually a 4-byte area:

┌──┬──┬──┬──┐
│00│01│e2│08│
└──┴──┴──┴──┘

Notice that computer memory contains only binary 0/1, with no other format. The hexadecimal representation is just for simplification.

When we print this integer using System.out.println(n), the println() method internally converts the int type to a String, printing the string 123400.

Similarly, we can print this integer in hexadecimal or format it as currency:

java
import java.text.*;
import java.util.*;

public class Main {
    public static void main(String[] args) {
        int n = 123400;
        // 123400
        System.out.println(n);
        // 1e208
        System.out.println(Integer.toHexString(n));
        // $123,400.00
        System.out.println(NumberFormat.getCurrencyInstance(Locale.US).format(n));
    }
}

The integer 123400 is the data storage format, which is very simple. The various strings we print are the display formats. There are many forms for display, but essentially, they represent a conversion method:

java
String toDisplay(int n) { ... }

Now that we understand data storage and display, let's look at the following date and time representations:

2019-11-20 0:15:01 GMT+00:00
2019年11月20日8:15:01
11/19/2019 19:15:01 America/New_York

These are actually display formats for the same moment, shown according to the British, Chinese, and New York time zones. This "same moment" is stored in the computer as an integer, known as Epoch Time.

Epoch Time is the number of seconds elapsed since midnight on January 1, 1970 (GMT+00:00). For example:

1574208900 represents the total seconds from midnight on January 1, 1970, to that moment. Converting to London, Beijing, and New York time gives:

1574208900 = 北京时间2019-11-20 8:15:00
           = 伦敦时间2019-11-20 0:15:00
           = 纽约时间2019-11-19 19:15:00

Therefore, in computing, we only need to store the integer 1574208900 for a certain moment. When we need to display it in a local time, we format it as a string:

java
String displayDateTime(int n, String timezone) { ... }

Epoch Time, also known as a timestamp, can be stored in several ways in different programming languages:

  • As an integer in seconds: 1574208900, which has a precision of only seconds.
  • As an integer in milliseconds: 1574208900123, where the last three digits represent milliseconds.
  • As a floating-point number in seconds: 1574208900.123, where the decimal part represents a fraction of a second.

Conversions among these formats are straightforward. In Java, timestamps are usually represented as long milliseconds:

java
long t = 1574208900123L;

Converting to Beijing time results in 2019-11-20T8:15:00.123. To get the current timestamp, we can use System.currentTimeMillis(), which is the most common way to retrieve timestamps in Java.

Standard Library API

Now, let's look at the APIs provided by the Java standard library. The Java standard library offers two sets of APIs for handling dates and times:

  1. One set is defined in the java.util package, mainly including classes like Date, Calendar, and TimeZone.
  2. The other set is the new API introduced in Java 8, defined in the java.time package, including LocalDateTime, ZonedDateTime, ZoneId, etc.

Why do we have both old and new APIs? Due to historical reasons, the old APIs have many issues, which is why the new APIs were introduced.

Can we skip the old API and use only the new one? Not if we deal with legacy code, as many legacy systems still use the old API. Thus, it's essential to understand the old API and often convert between the two.

This section will briefly introduce the common types and methods of the old API.

Date

java.util.Date is used to represent a date and time object, distinct from java.sql.Date, which is used in databases. If we look at the source code of Date, we can see it actually stores a long type representing a timestamp in milliseconds:

java
public class Date implements Serializable, Cloneable, Comparable<Date> {

    private transient long fastTime;

    ...
}

Let’s examine the basic usage of Date:

java
import java.util.*;

public class Main {
    public static void main(String[] args) {
        // Get current time:
        Date date = new Date();
        System.out.println(date.getYear() + 1900); // Must add 1900
        System.out.println(date.getMonth() + 1); // 0~11, must add 1
        System.out.println(date.getDate()); // 1~31, do not add 1
        // Convert to String:
        System.out.println(date.toString());
        // Convert to GMT:
        System.out.println(date.toGMTString());
        // Convert to local time:
        System.out.println(date.toLocaleString());
    }
}

Note that the year returned by getYear() must have 1900 added, the month returned by getMonth() is from 0 to 11 (representing January to December), so we add 1, while getDate() returns a range from 1 to 31 and should not be adjusted.

When printing the date and time in local timezone, different computers may yield different results. If we want precise control over the format based on user preferences, we can use SimpleDateFormat to convert a Date. It uses predefined strings to represent formatting:

  • yyyy: year
  • MM: month
  • dd: day
  • HH: hour
  • mm: minute
  • ss: second

Let’s see how to output in a custom format:

java
import java.text.*;
import java.util.*;

public class Main {
    public static void main(String[] args) {
        // Get current time:
        Date date = new Date();
        var sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println(sdf.format(date));
    }
}

Java defines many different formatting options. For example, using MMM and E:

java
import java.text.*;
import java.util.*;

public class Main {
    public static void main(String[] args) {
        // Get current time:
        Date date = new Date();
        var sdf = new SimpleDateFormat("E MMM dd, yyyy");
        System.out.println(sdf.format(date));
    }
}

This code will print a date like Sun Sep 15, 2019 in different locales. Detailed formatting specifications can be found in the JDK documentation. Generally, the longer the letter, the longer the output. For instance, if the current month is September:

  • M: outputs 9
  • MM: outputs 09
  • MMM: outputs Sep
  • MMMM: outputs September

However, the Date object has several serious issues: it cannot convert time zones, and aside from toGMTString(), it always outputs based on the current computer's default time zone. Additionally, performing arithmetic on dates and times, such as calculating the difference in days or finding the first Monday of a month, is difficult.

Calendar

Calendar can be used to get and set years, months, days, hours, minutes, and seconds, providing simple date and time arithmetic features compared to Date.

Let’s look at the basic usage of Calendar:

java
import java.util.*;

public class Main {
    public static void main(String[] args) {
        // Get current time:
        Calendar c = Calendar.getInstance();
        int y = c.get(Calendar.YEAR);
        int m = 1 + c.get(Calendar.MONTH);
        int d = c.get(Calendar.DAY_OF_MONTH);
        int w = c.get(Calendar.DAY_OF_WEEK);
        int hh = c.get(Calendar.HOUR_OF_DAY);
        int mm = c.get(Calendar.MINUTE);
        int ss = c.get(Calendar.SECOND);
        int ms = c.get(Calendar.MILLISECOND);
        System.out.println(y + "-" + m + "-" + d + " " + w + " " + hh + ":" + mm +

 ":" + ss + "." + ms);
    }
}

Note that when retrieving year, month, and day information, it uses get(int field); the year does not require adjustment, but the month still needs to add 1, and the week must be noted as 1-7 representing Sunday to Saturday.

Calendar can only be obtained through Calendar.getInstance(), and it is always set to the current time upon retrieval. To set it to a specific date and time, you must first clear all fields:

java
import java.text.*;
import java.util.*;

public class Main {
    public static void main(String[] args) {
        // Current time:
        Calendar c = Calendar.getInstance();
        // Clear all:
        c.clear();
        // Set to 2019:
        c.set(Calendar.YEAR, 2019);
        // Set to September: Note that 8 represents September:
        c.set(Calendar.MONTH, 8);
        // Set to 2nd:
        c.set(Calendar.DATE, 2);
        // Set time:
        c.set(Calendar.HOUR_OF_DAY, 21);
        c.set(Calendar.MINUTE, 22);
        c.set(Calendar.SECOND, 23);
        System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(c.getTime()));
        // 2019-09-02 21:22:23
    }
}

Using Calendar.getTime() allows converting a Calendar object to a Date, which can then be formatted with SimpleDateFormat.

TimeZone

Compared to Date, Calendar provides time zone conversion functionality. Time zones are represented by TimeZone objects:

java
import java.util.*;

public class Main {
    public static void main(String[] args) {
        TimeZone tzDefault = TimeZone.getDefault(); // Current time zone
        TimeZone tzGMT9 = TimeZone.getTimeZone("GMT+09:00"); // GMT+9:00 time zone
        TimeZone tzNY = TimeZone.getTimeZone("America/New_York"); // New York time zone
        System.out.println(tzDefault.getID()); // Asia/Shanghai
        System.out.println(tzGMT9.getID()); // GMT+09:00
        System.out.println(tzNY.getID()); // America/New_York
    }
}

Time zone IDs are represented as strings. We obtain specific TimeZone objects using these IDs. Valid time zone IDs include GMT+09:00 and Asia/Shanghai. To list all supported IDs, use TimeZone.getAvailableIDs().

With time zones, we can convert a specified time. For example, the following code demonstrates converting Beijing time 2019-11-20 8:15:00 to New York time:

java
import java.text.*;
import java.util.*;

public class Main {
    public static void main(String[] args) {
        // Current time:
        Calendar c = Calendar.getInstance();
        // Clear all:
        c.clear();
        // Set to Beijing time zone:
        c.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
        // Set date and time:
        c.set(2019, 10 /* November */, 20, 8, 15, 0);
        // Display time:
        var sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        sdf.setTimeZone(TimeZone.getTimeZone("America/New_York"));
        System.out.println(sdf.format(c.getTime()));
        // 2019-11-19 19:15:00
    }
}

The steps for time zone conversion using Calendar are:

  1. Clear all fields.
  2. Set the specified time zone.
  3. Set the date and time.
  4. Create a SimpleDateFormat and set the target time zone.
  5. Format the resulting Date object (note that the Date object lacks time zone information; it’s stored in the SimpleDateFormat).

Thus, fundamentally, time zone conversion can only be completed during display via SimpleDateFormat.

Calendar can also perform simple arithmetic on dates and times:

java
import java.text.*;
import java.util.*;

public class Main {
    public static void main(String[] args) {
        // Current time:
        Calendar c = Calendar.getInstance();
        // Clear all:
        c.clear();
        // Set date and time:
        c.set(2019, 10 /* November */, 20, 8, 15, 0);
        // Add 5 days and subtract 2 hours:
        c.add(Calendar.DAY_OF_MONTH, 5);
        c.add(Calendar.HOUR_OF_DAY, -2);
        // Display time:
        var sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date d = c.getTime();
        System.out.println(sdf.format(d));
        // 2019-11-25 6:15:00
    }
}

Summary

Computers represent time using integer timestamps, specifically Epoch Time. Java uses long type to represent timestamps in milliseconds, accessed via System.currentTimeMillis().

Java provides two sets of date and time APIs:

  • The old Date, Calendar, and TimeZone.
  • The new LocalDateTime, ZonedDateTime, ZoneId, etc.

These are located in the java.util and java.time packages.

Date and Calendar has loaded