Now It's Time to Uncomplicate With the Not-So-New API in Java
Dealing with dates or any operations that deal with time as a unit of measurement is always a big challenge. It is no different within the Java world; however, since Java 8, the new date API brings several improvements.
This article will understand more about the complexity of working with dates and how useful Java's date API is.
After All, Why Is the Date So Complex?
Date APIs are considered very complex, but what is the reason for this complexity? Unlike other units of measurement, dates have physical, cultural, political, historical, and religious complexity.
The physical part of the time is already tricky. In his theory of relativity, Albert Einstein demonstrates that time dilation is possible. In this way, the movie Interstellar's time travels are proven, and it is possible to understand more about these theories in the book "The Science of Interstellar."
In addition to the natural complexity, there are several policy interventions, such as:
Inclusion of two months in the calendar in honor of two emperors: Julius Caesar and Gaius Otávio Augustos. In addition to leaving February with far fewer days, the months of emperors cannot have less than thirty-one days. The consequence of this is that we lose the semantics: November represents the eleventh month from the Latin novem, nine. December, from the Latin decem, ten, represents the month twelve.
Daylight saving time was a political intervention that was modified for a certain period to save energy. It can be activated in a year or not; in Brazil, for example, this feature has not been used in recent years.
December 30, 2011, did not exist in Samoa. That's right; an entire day did not live in this country.
There are many external impacts on time, and this does not occur with other units of measurement; for example, there is no unit of measure of distance modified throughout the year, such as a “summer meter” or a unit that ceases to exist by decree.
Understanding the Concepts of Time
There is already a certain complexity within the data API. Still, you can also see another problem in most people who work with development: they don't understand the unit of time. Knowing these concepts is already complex; it makes them even more problematic to understand.
For example, if someone asks, "What day is today? Or what time is it?" the answer will be it depends, as the big problems in the history of computing or software architecture. After all, knowing the day or time depends on which place I'm referring to; they now can change if I'm referring to Lisbon or San Francisco.
The objective here is to talk about the minimum concepts to facilitate the daily life of devs:
LMT, Local Mean Time: The local time of a city. It was an attempt to standardize the schedules. However, as each town had its program, it generated a lot of confusion.
GMT, Greenwich Mean Time: A solution created by British railway companies to facilitate travel timetables. Countries' time was standardized based on London time, which was set at the most reliable observatory, the Royal Observatory in Greenwich. And the times are related to GMT; for example, San Francisco is seven hours behind Greenwich, GMT-7.
UTC, Coordinated Universal time: It's like the new GMT created in 1972. The unit of measurement within UTC is the offset, of which you can have a compensation of -3:00, which is three hours from UTC. We can think of UTC as GMT 2.0.
Time Zone: The GMT/UCT standard was implemented at different times for each country; it is crucial to have the history before implementation and daylight saving time. Time Zone has precisely that goal: to retain that history. For example, the city of São Paulo used UTC, -3:06:28, and it was only in 1914 that it used UTC -03:00.
Daylight saving time: its operation is to advance one hour and add one to the offset. It varies for countries and which hemisphere it is in. For example, in the north, it happens between March and October. In the south, it is between November and February.
TDTL:
UTC is the standard used today to control the world's time, and its unit of measure is the offset. On the other hand, daylight saving time comes down to putting the clock forward one hour, that is, increasing one in the offset of its time, and the time zone is responsible for containing the history of the times of a region.
Getting to Know the Java Date API
After contextualizing the business and the concept behind time, let's talk about the not-so-new Java 8 date API. There are two attempts with time in the Java world. However, my recommendation is for new systems and functionality does not import any API that is not in the "java.time" package.
The Entities
The first step is to know the types of entities the data API supports; this article will not cover all the details, but you can look for a more detailed reading.
An important point: the API brings representations that cover day, month, day of the week, year, timezone, and offset, in addition to combining all of them!
DayOfWeek dayOfWeek = DayOfWeek.MONDAY;
Month month = Month.JANUARY;
MonthDay monthDay = MonthDay.now();
YearMonth yearMonth = YearMonth.now();
Year year = Year.now();
LocalDate localDate = LocalDate.now();
LocalTime localTime = LocalTime.now();
LocalDateTime localDateTime = LocalDateTime.now();
OffsetDateTime offsetDateTime = OffsetDateTime.now();
ZonedDateTime zonedDateTime = ZonedDateTime.now();
Clock clock = Clock.systemUTC();
Instant instant = Instant.now();
TimeZone timeZone = TimeZone.getDefault();
System.out.println("DayOfWeek: " + dayOfWeek);
System.out.println("month: " + month);
System.out.println("MonthDay: " + monthDay);
System.out.println("YearMonth: " + yearMonth);
System.out.println("Year: " + year);
System.out.println("LocalDate: " + localDate);
System.out.println("LocalTime: " + localTime);
System.out.println("LocalDateTime: " + localDateTime);
System.out.println("OffsetDateTime: " + offsetDateTime);
System.out.println("ZonedDateTime: " + zonedDateTime);
System.out.println("Clock: " + clock.getZone());
System.out.println("Instant: " + instant);
System.out.println("TimeZone: " + timeZone.getDisplayName());
We emphasize here the possibilities that we can use with types or entities representing time in the API instead of using just a single one, as was previously done. For example:
YearMonth to work with the expiration date of a credit card.
Year to deal with the year of publication of a book.
In addition to bringing better semantics into the code, these variables bring clarity and simplify how you perform validations. It is based on those earlier mentioned classic “When Make a Type” by Martin Fowler.
Assertions.assertThrows(DateTimeException.class, () -> Year.of(1_000_000_000));
It is also possible to make combinations between types to create a final instance; for example, we start with year and go to date.
LocalDate myBirthday = Year.of(1988).atMonth(Month.JANUARY).atDay(9);
So, as a first step, I suggest you explore and read a little more about the types that the API supports. Since the date API has timed validations, these APIs are much more efficient at representing time than more generic types like int, long, or String.
Operations With Date
In addition to bringing more semantics and validation to dealing with dates, the API also brings some fascinating operations that aim to make your day easier and save your time. Sorry for the tempting pun.
The most basic operations are adding or removing periods, such as months or days, using methods with the suffixes “plus” or “minus,” respectively.
LocalDate myBirthday = Year.of(1988).atMonth(Month.JANUARY).atDay(9);
LocalDate yesterday = myBirthday.minusDays(1);
LocalDate oneYear = myBirthday.plusDays(365);
The TemporalAdjuster interface allows custom adjustments and some more complex operations, and from there, it is possible to create many customizable and reusable procedures. As a convention, this utility class brings several features; for example, checking the following Monday from the current date, I refer to the TemporalAdjusters class.
LocalDate myBirthday = Year.of(1988).atMonth(Month.JANUARY).atDay(9);
LocalDate newYear = myBirthday.with(TemporalAdjusters.firstDayOfMonth());
LocalDate lastDayOfMonth = myBirthday.with(TemporalAdjusters.lastDayOfMonth());
LocalDate nextMonday = myBirthday.with(TemporalAdjusters.next(DayOfWeek.MONDAY));
It is also possible to make comparisons between dates, nothing very recent compared to the older APIs. However, it is always important to point out that this exists.
LocalDate myBirthday = Year.of(1988).atMonth(Month.JANUARY).atDay(9);
LocalDate now = LocalDate.now();
Assertions.assertTrue(now.isAfter(myBirthday));
Assertions.assertFalse(now.isBefore(myBirthday));
The primary operations are beneficial. However, it is possible to go further with Period and also ChronoUnit. With them, you can see the difference between a specific period.
LocalDate today = LocalDate.now();
LocalDate tomorrow = LocalDate.now().plusDays(1);
Assertions.assertEquals(1, ChronoUnit.DAYS.between(today, tomorrow));
LocalDate dateA = LocalDate.of(2012, Month.APRIL, 7);
LocalDate dateB = LocalDate.of(2015, Month.DECEMBER,5);
Period period = Period.between(dateA, dateB);
Assertions.assertEquals(3, period.getYears());
Assertions.assertEquals(7, period.getMonths());
Assertions.assertEquals(28, period.getDays());
Assertions.assertEquals(43, period.toTotalMonths());
The last step for comparison and verification is the TemporalQuery, which aims to extract some time information.
TemporalQuery<Boolean> weekend = temporal -> {
int dayOfWeek = temporal.get(ChronoField.DAY_OF_WEEK);
return dayOfWeek == DayOfWeek.SATURDAY.getValue()
|| dayOfWeek == DayOfWeek.SUNDAY.getValue();
};
LocalDate date = LocalDate.of(2018, 5, 4);
LocalDateTime sunday = LocalDateTime.of(2018, 5, 6, 17, 0);
Assertions.assertFalse(date.query(weekend));
Assertions.assertTrue(sunday.query(weekend));
The last step for comparison and verification is the TemporalQuery, which aims to extract some time information.
ZoneId saoPaulo = ZoneId.of("America/Sao_Paulo");
ZonedDateTime adenNow = ZonedDateTime.now(saoPaulo);
ZoneOffset offset = adenNow.getOffset();
Assertions.assertEquals(saoPaulo, adenNow.getZone());
Assertions.assertEquals("-03:00", offset.getId());
It is possible to make comparisons of different timezones, for example, comparing a time in Brazil and another in Portugal, which on May 3 are four hours apart.
ZoneId saoPaulo = ZoneId.of("America/Sao_Paulo");
ZoneId portugal = ZoneId.of("Portugal");
LocalDateTime timeSP = Year.of(2021).atMonth(Month.MAY).atDay(3)
.atTime(12,0,0);
LocalDateTime timeLisbon = Year.of(2021).atMonth(Month.MAY).atDay(3)
.atTime(16,0,0);
ZonedDateTime zoneSaoPaulo = ZonedDateTime.of(timeSP, saoPaulo);
ZonedDateTime zoneLisbon = ZonedDateTime.of(timeLisbon, portugal);
Assertions.assertTrue(zoneSaoPaulo.isEqual(zoneLisbon));
It is important to emphasize that what we mentioned is just an overview of the API, and it is worth reading the project documentation, which is extremely rich and detailed.
Conclusion
Despite not being a new API, the Java date API is still little used and little explored for several reasons.
Either because of the complexity in dealing with time, the number of available documents is still tiny compared to legacy APIs or not being a well-known API.
It is essential to mention that this API brings many benefits, from its clarity with the methods to several operation features that are well worth knowing. Our article aims to revive this topic and get you on the rock's path for you to explore the API further.