What is this about?
Chronological queries and manipulations can be extremely complex. Many different variations are
possible. In order to avoid API-bloat by an excessive count of specialized query- and manipulation-methods,
Time4J has choosen a design mainly based on chronological elements which serve as main parameters to common
queries and manipulations. Every element implements the basic interface ChronoElement
. It also
has a value type indicated by its type parameter V. This page serves as general guideline how to use elements
in Time4J. As side remark: Other libraries use the term "field" instead of "element".
The traditional Java-class java.util.Calendar
also uses a parameter-driven approach, but
it is not type-safe at all, unfortunately.
The libraries java.time.*
(built in Java 8) and Joda-Time have gone another way:
They want the user to call specialized methods like getHourOfDay()
and consider the
element-centric approach as low-level, designed mainly for framework-developers. Users will therefore
find many more such special methods in classes like java.time.ZonedDateTime
. If you
are a beginner then you might value that design as more convenient on first glance. But wait: You can easily
get lost in method jungle, and the count of offered specialized methods is despite of the size of the API
often not sufficient. As we will see later, the element-centric approach enables far more features with
less methods.
Direct access to temporal state
In contrast to most other libraries, Time4J only knows a very limited count of direct getters, in practice only for the primary temporal state of a chronological entity. Example:
PlainDate date = PlainDate.of(2014, 3, 21); int dayOfMonth = date.getDayOfMonth(); int month = date.getMonth(); int year = date.getYear(); // now you might process these primary state values and send to external interfaces
Query or manipulation
If you have any kind of chronological problem/task you must first decide if you want to query for
chronological details or manipulate it. When querying you usually use get(...)
-methods,
in some few boolean-orientated cases also methods with "is"-prefix. Otherwise you use
with(...)
-methods. Element identifiers serve as main parameter.
Support for elements
You can find out if any element is supported by the concrete temporal type in question as follows:
ChronoElement<Integer> element = ...; PlainDate date = PlainDate.of(2014, 3, 21); boolean supported = date.contains(element);
Not every element is supported by every type. For example: PlainDate
does not support
the element PlainTime.MINUTE_OF_HOUR
. The javadoc of every basic type class documents the
supported elements in detail.
How to query: Choose the right element for your problem
As said before, in most cases you use getters. If you are interested in the day of a calendar month
then you choose the element PlainDate.DAY_OF_MONTH
. Finally you just call the method
date.get(DAY_OF_MONTH)
. This expression uses a static import for the constant to assist
for shorter readable code. The result of such a getter has always the value return type of given element.
Many elements have the value type Integer
. But it can also be any other kind of immutable
type, for example enums like net.time4j.Month
or net.time4j.Weekday
. Examples:
PlainDate date = PlainDate.of(2014, 3, 21); ChronoElement<Integer> element = Weekmodel.of(Locale.US).weekOfYear(); int usWeekOfYear = date.get(element); // 12th week of year in US Weekday dayOfWeek = date.get(PlainDate.DAY_OF_WEEK); // FRIDAY
Now the question arises where to find such element identifiers. You find the most common elements as
static constants in the surrounding chronological entity. If the element is calendrical then you find it in
most cases directly in the class PlainDate
. If the element is related to wall time then you
usually find it in the class PlainTime
. Week-related elements are located in the extra
class Weekmodel
via methods due to their localized background. Most of these elements can
also be used in the classes PlainTimestamp
and Moment
. In case of doubt just
refer to the documentation of the classes or call the method contains(element)
.
Minimum and maximum of element values
All elements have a default minimum and maximum. You can get these estimated range constraints
by calling getDefaultMinimum()
or getDefaultMaximum()
on a given element. These
constraints only represent typical values for minimum and maximum. To get the real context-dependent minima
and maxima, you can do this:
PlainDate date = PlainDate.of(2012, 2, 4); int minDayOfFebruary = date.getMinimum(DAY_OF_MONTH); // 1 int maxDayOfFebruary = date.getMaximum(DAY_OF_MONTH); // 29
Manipulations - part one: Setting element values
Like with queries, you have to decide which element you want to manipulate. The standard way is just to set the partial value associated with the element. This can have side effects however. For example setting the month to another month can have impact on the day of the month if latter one does not fit into new month length.
import static net.time4j.PlainDate.MONTH_OF_YEAR; import static net.time4j.Month.FEBRUARY; PlainDate date = PlainDate.of(2014, 3, 31); date = date.with(MONTH_OF_YEAR, FEBRUARY); // 2014-02-28
Note that also the with(ChronoElement<V>, V)
-method allows and even requires values
of the type V what the element is designed for, not just integers. In given example above, the value type
is the enum net.time4j.Month
.
Manipulations - part two: Using chronological operators
The simple use-case of just setting element values is often not sufficient. More complex manipulations
are needed. Time4J offers many element-based operators of type ChronoOperator
. Its
type parameter determines on which temporal types such an operator can be applied. If you want to know
which operators are offered by a given element then look at the type of the element interface which is
a subtype of ChronoElement
and defines additional operator methods. Or just use the auto
completion feature of your IDE and type the dot key... After having done this, you can use this operator
as argument for the alternative method with(ChronoOperator<T>)
.
Let us now consider the frequent use-case of setting a date to the end of a month. This problem is
related to the day-of-month which must be adjusted. That means we have to choose the element
PlainDate.DAY_OF_MONTH
which is of type ProportionalElement
. Its super type
AdjustableElement
defines the method maximized()
which exactly does what
is needed, maximizing the day of month in context of current month:
import static net.time4j.PlainDate.DAY_OF_MONTH; PlainDate date = PlainDate.of(2014, 4, 14); date = date.with(DAY_OF_MONTH.maximized()); // 2014-04-30
Manipulations - part three: Timezone-related operators
Many operators are not just of type ChronoOperator<T>
but of type
ElementOperator<T>
. These operators have extra methods for the conversion
to a version which is either applicable on PlainTimestamp
or - by help of a timezone -
on Moment
. Examples:
import static net.time4j.PlainTime.DIGITAL_HOUR_OF_DAY; PlainTimestamp tsp = PlainDate.of(2014, 10, 19).atTime(13, 45); tsp = tsp.with(DIGITAL_HOUR_OF_DAY.incremented()); // 2014-10-19T14:45 TZID tz = AMERICA.SAO_PAULO; // Brazil timezone Moment moment = tsp.inTimezone(tz); // 2014-10-19T16:45:00,000000000Z moment = moment.with(DIGITAL_HOUR_OF_DAY.minimized().inTimezone(tz)); // note: change to summer time in Brazil, the minimum hour is T01, not midnight System.out.println(moment.toZonalTimestamp(tz)); // 2014-10-19T01:45
Summary
A standard approach for solving many problems and tasks looks like:
- Describe your task in terms of chronological elements
- Do you want to query or manipulate? => get() or with()
- Decide which element to use for your problem
- In case of manipulation: Set the element value or use an operator?
- If you use an operator then look at the offered operator methods in the element interface.
A parameter-based approach is of course an old idea which was already realized in the class
java.util.Calendar
using integer constants. Time4J has introduced following innovations,
however:
- Instead of integer constants, Time4J uses singleton constants as element identifiers. Every such singleton is represented as a specialized element interface. This is extra type-safety.
- Another piece of type-safety is the definition of a value type V for a given element so you cannot choose the wrong value type for a new element value.
- Each element interface offers additional chronological operators for more complex manipulations.
This feature is different from the
TemporalAdjuster
-concept of Java-8 (JSR-310) because the underlying operators are directly associated with the elements and not hidden in a helper class within a sub-package (in Java-8:java.time.temporal.TemporalAdjusters
). - The combination of elements and operators via dedicated element interfaces shows the real power of manipulations in Time4J. Not so many methods, but a huge amount of ways how to change a date or time.