classDiagram
direction LR
class Temporal {
+ minus()
+ plus()
+ with()
+ get()
}
Temporal <|-- Instant
Temporal <|-- LocalDateTime
Temporal <|-- LocalDate
Temporal <|-- LocalTime
link Temporal "#time-points"
TemporalAmount <|-- Period
TemporalAmount <|-- Duration
link TemporalAmount "#intervals"
TemporalField <|-- ChronoField
note for ChronoField "enumeration"
TemporalUnit <|-- ChronoUnit
note for ChronoUnit "enumeration"
<<interface>> TemporalField
<<interface>> TemporalUnit
class Clock
13 Date and Time
In Java, there are many classes and interfaces used to represent date and time. These have evolved significantly, starting from relatively simple representations to much more sophisticated tools for properly handling calendars, timezones, and time.
Keep in mind that handling date and time is inherently complex.
For example, if in a meeting taking place in Turin someone tells you it’s 5:17 PM, you understand it because you are all in Italy and you know what that means locally. But the same statement, if streamed to someone in the U.S., would be misleading — they’re in a different time zone - so it wouldn’t be 5:17 PM for them. Time zone management is critical in date and calendar handling.
Even something as seemingly trivial as computing “the next day” isn’t as simple as adding one to the day digits. Months have different lengths, and leap years add complexity - like February sometimes having 29 days every fourth year -. Going even further, every so often a leap second is added to adjust for the Earth’s rotational slowdown.
Another importan aspect is daylight saving time. When converting between time zones, it’s not enough to know the offsets, you need to know when daylight saving rules apply. Java handles this using built-in zone databases.
If you ignore these details, you risk inaccurate results. That’s why a simplified or naïve approach to calendar management often doesn’t work except for the most basic operations.
Chronologically date and time in Java evolved through several different APIs:
- Timestamps (in
java.lang.System) java.util.Datejava.util.Calendarjava.timepackage
13.1 System timestamps
The System class provides two methods that can be used to retrieve system timestamps:
currentTimeMillis()the difference, measured in milliseconds, between the current time and midnight, January 1, 1970 UTCnanoTime()current value of the running JVM’s high-resolution time source, in nanosecond. Does not have a fixed reference point and is often reset based on internal CPU timing. It’s useful for measuring elapsed time, but not for real-world timestamps.
13.1.1 Performance measurement
A typical application of these method is to measure the time performance of several algorithms, e.g.
long t0 = System.nanoTime();
algorithm();
long t1 = System.nanoTime();
IO.println("Elapsed: " + (t1-t0));13.1.2 Monitoring
Another typical usage is the monitoring of performance of long running operations.
A monitoring listerner interface can be used to receive apdate about the progress:
interface ProgressListener {
void progress(int current, int total);
}An example of a long running algorithm (concatenating integer into a string in the most inefficient way):
static void monitoredAlgorithm(ProgressListener l){
String s = "";
for(int i=0; i<N; ++i){
1 if(i%1000 == 0) l.progress(i, N);
s+=i;
}
l.progress(N, N);
}- 1
- every given number of iterations (1000 in this case) the listener method is invoked to keep track of the progress.
A possible example of monitor:
long[] time = {-1,-1};
monitoredAlgorithm( (c,t) -> {
1 double prop = c / (double)t * 100;
2 if(time[0] == -1){
time[0] = System.currentTimeMillis();
IO.print(" %.2f%% completed\r".formatted(prop));
}else{
3 time[1] = System.currentTimeMillis();
4 double elapsed = (time[1] - time[0])/1000.0;
5 double throughput = c / elapsed;
6 double residual = t / throughput - elapsed;
7 IO.print(" %.1f%% completed in %.1f s (%.1f s remaining) throughput: %.1f k per sec\r"
.formatted(prop, elapsed, residual, throughput/1000));
}
});
IO.println("\nDone.");- 1
- a proportion of completed work is computed
- 2
- on first invocation, the initial time is recorded and the proportion printed
- 3
- on following invocations, the current last time is recorded
- 4
- the elapsed time is computed
- 5
- the average throughput is computed
- 6
- an estimated residual time is computed
- 7
- all the above information is printed
13.2 Old APIs
13.2.1 Date
In earlier versions of Java, the Date class in package java.util was introduced. It’s essentially a wrapper around a long timestamp (milliseconds since the epoch).
However, Date had serious limitations, especially for time zone conversions and date arithmetic.
Even its constructors were problematic: they referenced the year 1900 as a base, so creating a Date with the value 115 meant the year 2015. Months were zero-based (e.g., 4 for May), but days were one-based, making new Date(115, 4, 6) a cryptic way to represent May 6, 2015. This constructor is deprecated now, and rightly so—it’s very confusing and error-prone.
- 1
- Deprecated
- 2
- “Wed May 06 00:00:00 CEST 2015”
When instantiated with no arguments Date provides the current date. But even that relies on the time zone of the system it is running on.
While older classes like Date are still found in legacy code, they are not recommended. Most of their methods are deprecated, and they’re not well-suited for complex operations. The newer java.time classes are much more robust, consistent, immutable, and suitable for any kind of date/time processing.
13.2.2 Calendar
To improve this, Java introduced – in version 1.1 – an abstract class Calendar and its primary implementation, GregorianCalendar. This class provides standard fields for date components (e.g., YEAR, MONTH, DAY_OF_MONTH, HOUR, …) and lets you get and set these fields. You can also perform operations like adding days, months, or weeks—operations that are calendar-aware.
The main method for interacting with Calendar are:
get(field)set(field, value)add(field, delta)
13.3 New Date and Time
Starting with Java 8, a new date/time API was introduced in the java.time package, which provides a comprehensive and consistent set of classes designed according to a few guiding principles:
- Simplicity
- Consistency
- Immutable classes
The classes and interfaces fall into two main categories:
Temporal points: such as
Instant,LocalDate,LocalTime,LocalDateTime, andZonedDateTime. These represent moments in time, either in absolute terms or relative to a time zone.Temporal amounts: such as
Duration(time-based intervals) andPeriod(date-based intervals).
13.3.1 Time points
These types are immutable and do not expose public constructors. Instead, they use factory methods like of() or parse(). This applies uniformly—for instance, LocalDate.of(2025, 5, 6) clearly constructs May 6, 2025, without any weird base-year offsets.
| Method | Purpose |
|---|---|
of() |
Create instance from a set of specific parameters, with validation |
from() |
Convert from another class with possible loss of information |
parse() |
Parse a string to build an instance |
now() |
Create an instance representing the current time / date. Can accept a ZoneId |
Comparison
| Method | Purpose |
|---|---|
isBefore() |
Checks if this time/date is before the specified time/date |
isAfter() |
Checks if this time/date is after the specified time/date |
isEqual() |
Checks if this time/date is the same as the specified time/date |
compareTo() |
Compares to to other time/date |
13.3.2 Changing
| Method | Purpose |
|---|---|
minus() |
Returns a new date/time built by removing a specific amount of date/time |
plus() |
Returns a new date/time built by adding a specific amount of date/time |
with() |
Returns a new date/time modified as specified by a temporal adjuster |
To manipulate dates or times, methods like plus() and minus() are available. These come in two flavors:
- You pass a
longand aChronoUnit(e.g.,DAYS,MONTHS,YEARS). - You use a
TemporalAmountlike aPeriodorDuration.
You can also use TemporalAdjusters to adjust a date to meaningful calendar positions thought method with(), for instance:
firstDayOfMonth()firstDayOfNextMonth()firstInMonth(DayOfWeek dayOfWeek)lastDayOfMonth()next(DayOfWeek dayOfWeek)previous(DayOfWeek dayOfWeek)
In addition there are class specific method, e.g., plusDays(long toAdd).
Day of Week and Month are represented by enums:
DayOfWeekMonth
Can be converted to string using: getDisplayName(style,locale), where style is one of:
TextStyle.FULLTextStyle.NARROWTextStyle.SHORT
Examples:
LocalDate today = LocalDate.now();
LocalDate tomorrow = today.plus(1,ChronoUnit.DAYS);
LocalDate inTwoWeeks = today.plusDays(14);
LocalDate lastMonday = oggi.with(TemporalAdjusters.previous(DayOfWeek.MONDAY));13.3.3 Locale
Class Locale represents a specific geographical, political, or cultural region Used to perform locale-sensitive operations:
- Date formats
- DoW, Month names
- Decimal separators
- Locale definition
Predefined constants, e.g., Locale.US, Locale.ITALIAN
Constructors
- Language: 2 or 3 chars code
- Country: 2 chars or 3 digits
In addition a variant allows optional additional specifications.
13.3.4 ISO-8601
A general recommendation is to use the ISO 8601 date format: YYYY-MM-DD, with four-digit years and two-digit months and days. This standard avoids ambiguity—unlike the European format (DD/MM/YYYY) or the U.S. format (MM/DD/YYYY), which can easily be misinterpreted.
The full ISO 8601 format also supports date and time, separated by a 'T' (e.g., 2025-06-06T15:30:00+02:00). The time zone is included as either 'Z' for UTC or a +/- offset.
13.3.5 Intervals
For time intervals, use Duration and Period. You can create these with factory methods or with between(start, end), which calculates the elapsed time between two points.
Intervals can be created with the folowing factory methods:
| Method | Purpose |
|---|---|
of() |
Creates interval from specified amount of TemporalUnits |
ofXxxx() |
Creates interval from specified amount of units (Xxxx is : Days, Hours, etc.) |
between() |
Creates interval between two temporal points |
Example: measuring elasped time for
Instant start = Instant.now();
// here some long running operation
Instant end = Instant.now();
Duration elapsed = Duration.between(start, end);
IO.println(elapsed);This prints a result like PT0.003S (3 milliseconds), using ISO 8601 duration format, where PT stands for Period of Time.
13.4 Testing
Testing code that is time dependent can be difficult. If in code that depend on time (e.g., booking an exam session) now() is hardcoded, it makes testing difficult.
For this, Java provides the Clock class, which you can pass to time-based methods. A Clock can be fixed to a specific instant or offset from the actual system time, making it ideal for testing. A clock object can be used as argument of now().
Clocks can be created with the following factory methods:
fixed(instant, zone)returns a clock that always returns the same instant, it is mainly used to testing purposes.offset(base, offset)returns a clock yields instants from the specified clock with the specified duration added, used to simulate past or future events w.r.t. a the given base clock.systemDefaultZone()obtains a clock aligned with the system clock in the system time zone, be careful since it implies a dependency to the default time-zone into your application.systemUTC()obtains a clock aligned with the system clock in the UTC time zone.
As a simple example of method that has dependency on the current time is the computation of a total amount due to return an initial capital plus a monthly rate, given the initial date.
Given an initial sum (\(amount\)) and a monthly interest rate (\(0 < rate < 1\)), the total due (\(due\)) can be computed using the compount interest rate after a given number of months (\(months\)):
\[ due = amount \cdot (1+rate)^{months} \]
An example of code used to compute the amount due today is:
static double totalDue(double amount, LocalDate begin, double monthlyRate){
LocalDate today = LocalDate.now();
Period interval = Period.between(begin, today);
int months = interval.getMonths();
double compoundRate = Math.pow(1.0+monthlyRate, months);
return amount*compoundRate;
}Of course this method will return a different value based on the day of execution of the test, thus making testing more difficult.
A testable version of the previous date-based computation should include a Clock in the computation that enable faking the date of execution:
static Clock clock = Clock.system();
static double totalDue(double amount, LocalDate begin, double monthlyRate){
LocalDate today = LocalDate.now(clock);
Period interval = Period.between(begin, today);
int months = interval.getMonths();
double compoundRate = Math.pow(1.0+monthlyRate, months);
return amount*compoundRate;
}With the testable version it is possible to write the following test
@Test
public static void testTotalDue(){
1 LocalDate begin = LocalDate.of(2025,4,10);
double r = 0.01;
int amount = 1000;
2 LocalDate in4 = begin.plusMonths(4));
3 clock = Clock.fixed(in4.atStartOfDay(zone).toInstant(),
ZoneId.systemDefault());
4 double t = totalDue(amount, begin, r);
assertEquals(amount*Math.pow(1+r, 4), t, 1);
}- 1
- the values of the arguments to test the method
- 2
- compute the day four mounths after the begin date
- 3
- create a fixed clock with the latter date
- 4
- call the method regularly
Wrap-up
Old
Dateclass does not handle time zones correctly. If all you need is a simple date container, storing year/month/day in a custom class may be fine.If you plan to calculate time intervals or work with time zones, using the
java.timeAPI is strongly advised.New classes provide a consistent structure for both time and date measures:
- They are immutable
- Operations can be performed using existing methods
Testing time and date based operations can be complex, the use of
Clockis advised to make them testable