Four basic types describing a temporal entity
- PlainDate - calendar date based on gregorian calendar for all times without any timezone reference
- PlainTime - wall time of an analoguous clock without any timezone reference
- PlainTimestamp - combination of calendar date and wall time without any timezone reference
- Moment - absolute moment as global timestamp anchored at UTC+00:00
What is common to these types?
Immutability
Objects of these basic types cannot be changed themselves. Instead you create changed copies
via with(...)
-methods and need to assign the results to new variables - like in the
Java-class java.math.BigDecimal
: So never write date.with(DAY_OF_MONTH, 5);
but date = date.with(DAY_OF_MONTH, 5);
You can also chain these manipulation methods,
but finally there must be an assignment to a variable.
A final note about immutability: This feature does not mean that you always get a new instance for manipulated entities. Sometimes you can also get a cached instance for better performance. The immutability just denotes that you cannot change the original object itself. Another aspect is that all immutable classes are final to ensure that nobody can make mutable classes out of it. This design enables complete safety in multiple-thread-environments.
Value object semantics
All temporal entities represent value objects. That means that they have identities based
on their temporal state and not on object identity. So all entities must be compared by the use
of equals(Object)
-method and never by identity comparison operator ==
.
Furthermore: Although there are interfaces or abstract super classes the basic types implement, applications should not use these interfaces for assignments and then work with the interfaces respective super classes. Instead applications always work directly with the final immutable types. You will also get more flexibility and power using the concrete classes to benefit from many extra methods. Note also that the abstract classes in Time4J are strongly generified. You can avoid using type parameters in most cases if you always use the concrete classes. Examples:
// Bad example in old Java: Wide-spread use of java.util.Calendar, NOT RECOMMENDED Calendar cal = Calendar.getInstance(); // do something with cal // Bad example in Time4J: DON'T DO THIS Calendrical<?, ?> date = PlainDate.of(2014, 3, 21); int dayOfMonth = date.get(PlainDate.DAY_OF_MONTH); TimePoint<?, ?> tsp = ... // difficult to construct from date! // Good example: PlainDate date = PlainDate.of(2014, 3, 21); int dayOfMonth = date.get(PlainDate.DAY_OF_MONTH); PlainTimestamp tsp = date.atStartOfDay();
Moral: Super types in Time4J are not designed for direct use by applications but for the construction of new chronological types or calendar systems. This is also strongly discouraged by excessive use of generic type parameters. However, the abstract super classes define common methods which can be used in concrete final types.
ISO-8601
All basic types closely follow the ISO-8601-standard. This is represented by the canonical
output of its toString()
-method. If you sort a list of temporal objects by this
representation you will always get a chronological order. ISO-8601 is well designed for the
international exchange of chronological data independent from locale settings.
Special details of ISO-8601-standard like the three different date representations (calendar date,
ordinal date and week date) or the wall-time 24:00 are also implemented by PlainDate
and PlainTime
. Examples:
System.out.println(PlainDate.of(2014, 4, 10)); // output: 2014-04-10 System.out.println(PlainTime.midnightAtEndOfDay()); // output: T24 System.out.println(PlainDate.of(2014, 4, 10).atTime(11, 45).atUTC()); // output: 2014-04-10T11:45:00Z
How to create objects?
Generally, there are no public constructors because of the value object semantics and sometimes for
better caching behaviour. Instead all temporal types offer static factory methods with prefix "of".
That is the application-controlled way of creation. Another way is using a TimeSource
as clock (machine-controlled) producing global timestamps. Examples:
import static net.time4j.Month.MARCH; // application-controlled PlainDate date = PlainDate.of(2014, MARCH, 5); // 2014-03-05 PlainTime time = PlainTime.of(14, 45); // 14:45 PlainTimestamp tsp = PlainTimestamp.of(2014, 3, 5, 14, 45); // 2014-03-05T14:45 Moment utc = Moment.of(86400, TimeScale.POSIX); // 1970-01-02T00:00:00,000000000Z // machine-controlled PlainDate today = SystemClock.inLocalView().today(); // current date created by a clock Moment now = SystemClock.currentMoment(); // machine timestamp created by a clock
All types inherit from the super class ChronoEntity
A ChronoEntity
is simply a collection of partial temporal values. These partial values
are called "elements". PlainDate consists mainly of year, month and day-of-month, but also
contains other secondary elements like day-of-week. In a similar way, PlainTime
contains
various elements like hour-of-day, minute-of-hour etc. PlainTimestamp
as combination of
date and time contains both kinds of elements. Moment
does not refer directly to these
elements, but needs an additional timezone to process such local elements.
The partial values resp. element values of an entity are accessed by a simple getter. There
are also various with(...)
-methods as kind of immutable setters. In every case: You
refer to a special element by giving an element-id (as constant). Such partial values are not limited
to int- or long-primitives. They can also be enums or any other immutable type V. Examples:
import static net.time4j.PlainDate.DAY_OF_WEEK; import static net.time4j.Month.MARCH; import static net.time4j.Weekday.MONDAY; PlainDate date = PlainDate.of(2014, MARCH, 5); Weekday dayOfWeek = date.get(DAY_OF_WEEK); // getter for a partial value: here the day of week date = date.with(DAY_OF_WEEK, MONDAY; // immutable setter: here go to monday within the same ISO-week
All types inherit from the super class TimePoint
, too
There is also a time axis associated with the four basic types. A time axis in Time4J is defined as unique and bijective mapping from the internal state of such a type to a single long counter (sometimes technically represented as combination of two integers). Every time axis has an associated unit type U which controls the scaling steps how to measure distances on the axis.
PlainDate
uses EpochDays as counter and is associated with the unit typeIsoDateUnit
.PlainTime
usesPlainTime.NANO_OF_DAY
as counter and is associated with the unit typeIsoTimeUnit
.PlainTimestamp
uses a combination ofEpochDays
andPlainTime.NANO_OF_DAY
as counter and is associated with the unit typeIsoUnit
.Moment
uses a combination ofgetElapsedTime(TimeScale.UTC)
andgetNanosecond()
as counter and is associated with the unit typejava.util.concurrent.TimeUnit
. The unit typeSI
handling real SI-seconds is useable, too.
You can apply a time arithmetic along such a time axis. That means, you can add or subtract time units creating changed immutable copies. Technically the associated counter of the time axis is incremented or decremented. Users can also calculate the distance on a given time axis in terms of units.
Adding time units is performed by methods with prefix "plus", subtracting by methods with
prefix "minus". Distances on such a time axis can be calculated either in long primitives using
a single time unit by the method start.until(end, {U - unit type})
or as time span (duration)
by the method start.until(end, {TimeMetric})
. In most cases unit types offer methods with
the signature between(start, end)
as alternative to the until(...)
-methods.
Examples:
import static net.time4j.CalendarUnit.DAYS; import static net.time4j.Month.MARCH; // plus-arithmetic PlainDate start = PlainDate.of(2014, MARCH, 5); PlainDate end = start.plus(35, DAYS); System.out.println(end); // 2014-04-09 // reverse way (minus) PlainDate earlier = end.minus(35, DAYS); System.out.println(earlier); // 2014-03-05 // calculate the distance by until(...) long distance1 = start.until(end, DAYS); System.out.println(distance1 + " days"); // 35 days // alternative way of calculating the distance in days long distance2 = DAYS.between(start, end); System.out.println(distance2 + " days"); // 35 days
Format support
All basic types offer a print- and a static parse-method with the signatures
public String print(TemporalFormatter<T> formatter)
or
public static T static parse(String input, TemporalFormatter<T> formatter)
.
Example:
String s = PlainDate.of(2014, 3, 21).print( ChronoFormatter.ofDatePattern( "MMMM/dd/uuuu", PatternType.CLDR, Locale.ENGLISH ) ); System.out.println(s); // March/21/2014
More details see the page The format and parse engine.
What is the main difference between these types?
Local versus global
The types PlainDate
, PlainTime
and PlainTimestamp
represent
local types which do not contain any timezone reference. Such local types can adapt the same representation
in different regions of the world but nevertheless not happen at the same moment. For example the wall time
"13:45" at the same calendar date is earlier in Australia than in Europe.
The type Moment
is a global type which really represents an instant and can be expressed in
different time scales, preferrably in POSIX and UTC. Technicaly spoken, a Moment
is pure UTC,
that is a UniversalTime
which always refers to timezone offset UTC+00:00. However, such a
global type can process local elements or format and parse any local input if you pass an additional timezone
or offset information.
How to convert between the basic types?
In order to get a PlainTimestamp
as combination of PlainDate
and
PlainTime
, the class PlainDate
offers various methods with prefix "at".
Conversions between local and global types however require timezone-related methods. More details see the
page Conversions between basic types.