Another important new feature of Java 8 is the introduction of new time and date APIs, which are included in java Time package. With the new time and date API, you can handle time and date in a more concise way.
Before introducing the content of this article, let's first discuss why Java 8 introduces the new date API. What is the difference between the time and date processing methods before it?
Before Java 8, all API s for time and date had various defects in use, mainly including:
- Java Util Date and java Util The calendar class is not easy to use, does not support time zones, and they are not thread safe;
- The class DateFormat for formatting dates is placed in java In the text package, it is an abstract class, so we need to instantiate a SimpleDateFormat object to handle date formatting, and the DateFormat is also non thread safe, which means that if you call the same DateFormat object in a multithreaded program, you will get unexpected results.
- The date calculation method is tedious and error prone, because the month starts from 0, and the month obtained from Calendar needs to be added with one to represent the current month.
Due to these problems, there are some tripartite date processing frameworks, such as joda time, date4j and other open source projects. However, Java needs a standard framework for processing time and date, so a new date API is introduced in Java 8. The new date API is JSR-310 The author of joda time framework is the initiator of JSR-310 specification, so we can see a lot of joda time features from the date API of Java 8.
Java 8 date / time class
The date and time classes in Java 8 include LocalDate, LocalTime, Instant, Duration, and Period. These classes are included in java Time package, let's take a look at the usage of these classes.
LocalDate and LocalTime
The LocalDate class represents a specific date, but does not contain a specific time or time zone information. You can create an instance through the static method of() of LocalDate. LocalDate also contains some methods to obtain the year, month, day, day of week, etc.:
1 2 3 4 5 6 7 |
LocalDate localDate = LocalDate.of(2017, 1, 4); // Initialize a date: January 4, 2017 int year = localDate.getYear(); // Year: 2017 Month month = localDate.getMonth(); // Month: JANUARY int dayOfMonth = localDate.getDayOfMonth(); // Day of the month: 4 DayOfWeek dayOfWeek = localDate.getDayOfWeek(); // Day of the week: WEDNESDAY int length = localDate.lengthOfMonth(); // Days of month: 31 boolean leapYear = localDate.isLeapYear(); // Leap year or not: false |
You can also call the static method now() to get the current date:
1 |
LocalDate now = LocalDate.now(); |
LocalTime and LocalDate are similar. The difference between them is that LocalDate does not contain a specific time, while LocalTime contains a specific time. For example:
1 2 3 4 |
LocalTime localTime = LocalTime.of(17, 23, 52); // Initialization time: 17:23:52 int hour = localTime.getHour(); // Hour: 17 int minute = localTime.getMinute(); // Score: 23 int second = localTime.getSecond(); // Seconds: 52 |
LocalDateTime
The LocalDateTime class is a combination of LocalDate and LocalTime. It can be created directly through the of() method, or it can call the atTime() method of LocalDate or the atDate() method of LocalTime to combine LocalDate or LocalTime into one LocalDateTime:
1 2 3 4 5 |
LocalDateTime ldt1 = LocalDateTime.of(2017, Month.JANUARY, 4, 17, 23, 52); LocalDate localDate = LocalDate.of(2017, Month.JANUARY, 4); LocalTime localTime = LocalTime.of(17, 23, 52); LocalDateTime ldt2 = localDate.atTime(localTime); |
LocalDateTime also provides conversion to LocalDate and LocalTime:
1 2 |
LocalDate date = ldt1.toLocalDate(); LocalTime time = ldt1.toLocalTime(); |
Instant
Instant is used to represent a timestamp, which is different from the system Currenttimemillis() is somewhat similar, but instant can be accurate to nano second, system The currenttimemillis() method is only accurate to milli second. If you check the instant source code, you can find that it uses two constants internally. Seconds represents the number of seconds from 1970-01-01 00:00:00 to the present, and nanos represents the nanosecond part (the value of nanos will not exceed 9999999). In addition to using the now() method, instant can also be created through the ofEpochSecond method:
1 |
Instant instant = Instant.ofEpochSecond(120, 100000); |
The first parameter of ofEpochSecond() method is seconds and the second parameter is nanoseconds. The above code represents the time of 100000 nanoseconds two minutes after 1970-01-01 00:00:00. The output on the console is:
1 |
1970-01-01T00:02:00.000100Z |
Duration
The internal implementation of Duration is similar to that of Instant. It also includes two parts: seconds indicates seconds and nanos indicates nanoseconds. The difference between the two is that Instant represents a timestamp (or a point in time), while Duration represents a period of time. Therefore, the Duration class does not contain the now() static method. You can use the Duration The between () method creates a Duration object:
1 2 3 4 5 6 7 8 9 10 |
LocalDateTime from = LocalDateTime.of(2017, Month.JANUARY, 5, 10, 7, 0); // 2017-01-05 10:07:00 LocalDateTime to = LocalDateTime.of(2017, Month.FEBRUARY, 5, 10, 7, 0); // 2017-02-05 10:07:00 Duration duration = Duration.between(from, to); // It means the period from 2017-01-05 10:07:00 to 2017-02-05 10:07:00 long days = duration.toDays(); // Total number of days in this period long hours = duration.toHours(); // Hours during this period long minutes = duration.toMinutes(); // Minutes of this period long seconds = duration.getSeconds(); // Seconds of this time long milliSeconds = duration.toMillis(); // Number of milliseconds during this period long nanoSeconds = duration.toNanos(); // Nanoseconds of this time |
The Duration object can also be created through the of() method, which accepts a time period length and a time unit as parameters:
1 2 |
Duration duration1 = Duration.of(5, ChronoUnit.DAYS); // 5 days Duration duration2 = Duration.of(1000, ChronoUnit.MILLIS); // 1000 ms |
Period
Period is similar to Duration in concept. The difference is that period measures a time period by month, year, for example, 2 years, 3 months and 6 days:
1 |
Period period = Period.of(2, 3, 6); |
The Period object can also be created through the between() method. It is worth noting that since Period measures the time Period by month, year and day, the between() method can only accept parameters of LocalDate type:
1 2 3 4 |
// From January 5, 2017 to February 5, 2017 Period period = Period.between( LocalDate.of(2017, 1, 5), LocalDate.of(2017, 2, 5)); |
Operation and formatting of date
Date of increase and decrease
The date / time classes in Java 8 are immutable to ensure thread safety. Of course, the new date / time class also provides methods for creating variable versions of objects, such as adding or reducing one day:
1 2 3 4 5 6 7 8 9 |
LocalDate date = LocalDate.of(2017, 1, 5); // 2017-01-05 LocalDate date1 = date.withYear(2016); // Revised to 2016-01-05 LocalDate date2 = date.withMonth(2); // Revised to 2017-02-05 LocalDate date3 = date.withDayOfMonth(1); // Revised to 2017-01-01 LocalDate date4 = date.plusYears(1); // Add one year 2018-01-05 LocalDate date5 = date.minusMonths(2); // Two months less 2016-11-05 LocalDate date6 = date.plus(5, ChronoUnit.DAYS); // Add 5 days 2017-01-10 |
In the above example, the operation of date is relatively simple, but sometimes we have to face more complex time operations, such as adjusting the time to the next working day or the last day of the next month. At this time, we can use another overloaded method of the with() method, which receives a TemporalAdjuster parameter, so that we can adjust the date more flexibly:
1 2 |
LocalDate date7 = date.with(nextOrSame(DayOfWeek.SUNDAY)); // Returns the next Sunday closest to the current time LocalDate date9 = date.with(lastInMonth(DayOfWeek.SATURDAY)); // Return to the last Saturday of the month |
To compile the above code correctly, you need to use the statically imported TemporalAdjusters object:
1 |
import static java.time.temporal.TemporalAdjusters.*; |
The TemporalAdjusters class contains many static methods that can be used directly. The following table lists some methods:
Method name | describe |
---|---|
dayOfWeekInMonth | Returns the day ordinal of a week in the same month |
firstDayOfMonth | Return to the first day of the month |
firstDayOfNextMonth | Return to the first day of the next month |
firstDayOfNextYear | Return to the first day of the next year |
firstDayOfYear | Return to the first day of the year |
firstInMonth | Returns the first day of the month |
lastDayOfMonth | Return to the last day of the month |
lastDayOfNextMonth | Return to the last day of the next month |
lastDayOfNextYear | Return to the last day of the next year |
lastDayOfYear | Return to the last day of the year |
lastInMonth | Returns the last day of the week in the same month |
next / previous | Returns the next / previous given day of the week |
nextOrSame / previousOrSame | Returns the next / previous given day of the week. If this value meets the conditions, it returns directly |
If the methods listed in the above table can not meet your needs, you can also create a custom implementation of the TemporalAdjuster interface. TemporalAdjuster is also a functional interface, so we can use Lambda expression:
1 2 3 4 |
@FunctionalInterface public interface TemporalAdjuster { Temporal adjustInto(Temporal temporal); } |
For example, given a date, calculate the next working day of the date (excluding Saturday and Sunday):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
LocalDate date = LocalDate.of(2017, 1, 5); date.with(temporal -> { // current date DayOfWeek dayOfWeek = DayOfWeek.of(temporal.get(ChronoField.DAY_OF_WEEK)); // Under normal circumstances, each additional day int dayToAdd = 1; // If it is Friday, add three days if (dayOfWeek == DayOfWeek.FRIDAY) { dayToAdd = 3; } // If it is Saturday, add two days if (dayOfWeek == DayOfWeek.SATURDAY) { dayToAdd = 2; } return temporal.plus(dayToAdd, ChronoUnit.DAYS); }); |
format date
The new date API provides a DateTimeFormatter class to handle date formatting operations, which is included in java Time In the format package, the date class of Java 8 has a format() method to format the date into a string, which receives a DateTimeFormatter type parameter:
1 2 3 4 5 6 |
LocalDateTime dateTime = LocalDateTime.now(); String strDate1 = dateTime.format(DateTimeFormatter.BASIC_ISO_DATE); // 20170105 String strDate2 = dateTime.format(DateTimeFormatter.ISO_LOCAL_DATE); // 2017-01-05 String strDate3 = dateTime.format(DateTimeFormatter.ISO_LOCAL_TIME); // 14:20:16.998 String strDate4 = dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); // 2017-01-05 String strDate5 = dateTime.format(DateTimeFormatter.ofPattern("Today is: YYYY year MMMM DD day E", Locale.CHINESE)); // Today is: Thursday, January 5, 2017 |
Similarly, the date class also supports parsing a string into a date object, for example:
1 2 3 4 5 |
String strDate6 = "2017-01-05"; String strDate7 = "2017-01-05 12:30:05"; LocalDate date = LocalDate.parse(strDate6, DateTimeFormatter.ofPattern("yyyy-MM-dd")); LocalDateTime dateTime1 = LocalDateTime.parse(strDate7, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); |
time zone
The time zone operation in Java 8 is greatly simplified. The new time zone class java Time ZoneId is the original java Util A substitute for the timezone class. ZoneId objects can be accessed through ZoneId Of() method, or ZoneId Systemdefault() get the system default time zone:
1 2 |
ZoneId shanghaiZoneId = ZoneId.of("Asia/Shanghai"); ZoneId systemZoneId = ZoneId.systemDefault(); |
The of() method receives a "region / city" string as a parameter. You can obtain all legal "region / city" strings through the getAvailableZoneIds() method:
1 |
Set<String> zoneIds = ZoneId.getAvailableZoneIds(); |
For the old TimeZone class TimeZone, Java 8 also provides a conversion method:
1 |
ZoneId oldToNewZoneId = TimeZone.getDefault().toZoneId(); |
With ZoneId, we can convert a LocalDate, LocalTime, or LocalDateTime object into a ZonedDateTime object:
1 2 |
LocalDateTime localDateTime = LocalDateTime.now(); ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime, shanghaiZoneId); |
Print zonedDateTime to the console as:
1 |
2017-01-05T15:26:56.147+08:00[Asia/Shanghai] |
The ZonedDateTime object consists of two parts, LocalDateTime and ZoneId. The 2017-01-05T15:26:56.147 part is LocalDateTime, and the +08:00[Asia/Shanghai] part is ZoneId.
Another way to represent the time zone is to use ZoneOffset, which is calculated based on the deviation between the current time and world standard time (UTC) / Greenwich mean time (GMT), for example:
1 2 3 |
ZoneOffset zoneOffset = ZoneOffset.of("+09:00"); LocalDateTime localDateTime = LocalDateTime.now(); OffsetDateTime offsetDateTime = OffsetDateTime.of(localDateTime, zoneOffset); |
Other calendars
The calendar used in Java is the ISO 8601 calendar system, which is the world civil calendar, also known as the Gregorian calendar. There are 365 days in normal years and 366 days in leap years. Leap year is defined as non century year, which can be divided by 4; Century years can be divided by 400. For consistency of calculation, the year before year 1 is regarded as year 0, and so on.
In addition, Java 8 also provides four other calendars (it is strange that there is no lunar calendar used by the Han people). Each calendar contains a date class, which is:
- Thai buddhistdate: Thai Buddhist calendar
- MinguoDate: calendar of the Republic of China
- JapaneseDate: Japanese calendar
- HijrahDate: Islamic calendar
Each date class inherits the ChronoLocalDate class, so you can operate without knowing the specific calendar. However, these calendars are not commonly used, unless there are some special needs.
These different calendars can also be used to convert to the Gregorian calendar:
1 2 |
LocalDate date = LocalDate.now(); JapaneseDate jpDate = JapaneseDate.from(date); |
Since both of them inherit the ChronoLocalDate class, you can operate the date through the ChronoLocalDate class without knowing the specific calendar:
1 2 |
Chronology jpChronology = Chronology.ofLocale(Locale.JAPANESE); ChronoLocalDate jpChronoLocalDate = jpChronology.dateNow(); |
We should try to avoid using ChronoLocalDate in the development process, and try to operate the time in a way that is not related to the calendar, because different calendars calculate the date in different ways. For example, the developer will make some assumptions in the program, assuming that there are 12 months in a year. If the Chinese lunar calendar contains leap months, the year may be 13 months, but the developer thinks it is 12 months, and the extra month belongs to the next year. For another example, suppose that the years are cumulative. After one year, one will be added to the original year. However, the emperor of Japan needs to re record the year after the replacement, so the year after one year may be calculated from 1.
It is recommended to use LocalDate in the actual development process, including storage, operation and interpretation of business rules; Unless you need to localize the input or output of the program, you can use the ChronoLocalDate class.