Appearance
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:
- One set is defined in the
java.util
package, mainly including classes likeDate
,Calendar
, andTimeZone
. - The other set is the new API introduced in Java 8, defined in the
java.time
package, includingLocalDateTime
,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
: yearMM
: monthdd
: dayHH
: hourmm
: minutess
: 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
: outputs9
MM
: outputs09
MMM
: outputsSep
MMMM
: outputsSeptember
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:
- Clear all fields.
- Set the specified time zone.
- Set the date and time.
- Create a
SimpleDateFormat
and set the target time zone. - Format the resulting
Date
object (note that theDate
object lacks time zone information; it’s stored in theSimpleDateFormat
).
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
, andTimeZone
. - The new
LocalDateTime
,ZonedDateTime
,ZoneId
, etc.
These are located in the java.util
and java.time
packages.