CyStack logo
  • Sản phẩm & Dịch vụ
  • Giải pháp
  • Bảng giá
  • Công ty
  • Tài liệu
Vi

vi

Trang chủHướng dẫnDate Time API trong Java 8 – LocalDate, LocalDateTime, Instant
Java

Date Time API trong Java 8 – LocalDate, LocalDateTime, Instant

CyStack blog 8 phút để đọc
CyStack blog23/07/2025
Locker Avatar

Bao Tran

Web Developer

Locker logo social
Reading Time: 8 minutes

Date Time API trong Java 8 là một trong những tính năng nổi bật nhất. Từ trước đến nay, Java thiếu một cách tiếp cận nhất quán cho việc xử lý Ngày và Giờ, vì vậy Java 8 Date Time API là một bổ sung rất đáng hoan nghênh vào bộ API lõi của Java.

Date Time API trong Java 8

Tại sao cần có Java Date Time API mới?

Trước khi tìm hiểu về Java 8 Date Time API, hãy cùng xem tại sao ta lại cần một API mới cho vấn đề này. Các lớp xử lý ngày giờ trước đây trong Java gặp khá nhiều vấn đề, tiêu biểu như:

  • Các lớp ngày giờ trong Java không được định nghĩa một cách nhất quán, ví dụ như lớp Date tồn tại trong cả java.util và java.sql. Trong khi đó, các lớp định dạng và phân tích cú pháp (formatting và parsing) lại nằm trong gói java.text.
  • java.util.Date chứa cả giá trị ngày lẫn giờ, trong khi java.sql.Date chỉ chứa ngày. Việc đặt java.sql.Date trong gói java.sql là không hợp lý. Thêm nữa, cả hai lớp này trùng tên, là một thiết kế rất tệ.
  • Không có lớp nào được định nghĩa rõ ràng cho các thành phần như giờ, timestamp, định dạng hay phân tích. Chúng ta phải dùng lớp trừu tượng java.text.DateFormat cho nhu cầu phân tích và định dạng. Thông thường, chúng ta dùng SimpleDateFormat cho mục đích này.
  • Tất cả các lớp Date đều có thể thay đổi (mutable), nên không an toàn trong môi trường đa luồng. Đây là một trong những vấn đề lớn nhất với các lớp Date và Calendar.
  • Lớp Date không hỗ trợ quốc tế hóa (internationalization), cũng như không có hỗ trợ múi giờ (timezone). Vì vậy, Java đã thêm các lớp java.util.Calendar và java.util.TimeZone, nhưng chúng vẫn mắc các vấn đề đã nêu ở trên.

Ngoài ra còn có những vấn đề khác với các phương thức trong Date và Calendar, và những vấn đề trên cho thấy rõ rằng Java cần một Date Time API mạnh mẽ và hoàn thiện hơn. Đó là lý do vì sao Joda Time đã đóng vai trò quan trọng như một giải pháp thay thế chất lượng cho các yêu cầu về Java Date Time.

Nguyên lý thiết kế của Java 8 Date Time

Java 8 Date Time API là một phần của chuẩn JSR-310. Nó được thiết kế để khắc phục toàn bộ những nhược điểm của các API xử lý ngày giờ cũ. Một số nguyên lý thiết kế chính của Date Time API mới bao gồm:

  1. Tính bất biến ( immutable): Tất cả các lớp trong API mới đều là immutable, rất phù hợp với môi trường đa luồng.
  2. Tách biệt các mối quan tâm (Separation of Concerns): API mới tách rõ giữa ngày giờ có thể đọc được bởi con người và thời gian dùng cho máy móc (Unix timestamp). Nó định nghĩa các lớp riêng biệt cho Date, Time, DateTime, Timestamp, Timezone, v.v.
  3. Tính rõ ràng: Các phương thức được định nghĩa rõ ràng và thực hiện hành vi giống nhau ở mọi lớp. Ví dụ, để lấy thời điểm hiện tại, ta đều dùng phương thức now(). Các phương thức format() và parse() đều được định nghĩa trong từng lớp thay vì gom vào một lớp riêng.

Các lớp sử dụng Factory Pattern và Strategy Pattern để xử lý linh hoạt hơn. Một khi đã quen với một lớp, việc sử dụng các lớp còn lại sẽ không còn khó nữa.

  1. Tiện ích mở rộng: Tất cả các lớp đều có sẵn các phương thức tiện ích để thực hiện các tác vụ phổ biến như cộng/trừ ngày, định dạng, phân tích chuỗi, tách riêng từng phần của ngày/giờ, v.v.
  2. Mở rộng được: Date Time API **mới hoạt động dựa trên hệ thống lịch ISO-8601, nhưng chúng ta cũng có thể sử dụng nó với các hệ lịch không thuộc ISO khác.

Các gói trong Date Time API

Java 8 Date Time API bao gồm các gói sau.

  1. java.time: Đây là gói nền tảng của Java Date Time API mới. Các lớp chính nằm ở đây như LocalDate, LocalTime, LocalDateTime, Instant, Period, Duration, v.v. Tất cả đều immutable và an toàn trong môi trường đa luồng (thread-safe). Gói này thường đủ dùng cho hầu hết nhu cầu xử lý chung.
  2. java.time.chrono: Cung cấp API cho các hệ lịch không phải ISO. Có thể mở rộng lớp AbstractChronology để tạo ra hệ lịch riêng.
  3. java.time.format: Chứa các lớp định dạng và phân tích ngày giờ. Tuy nhiên, thường ta không cần dùng trực tiếp vì các lớp trong java.time đã tích hợp sẵn các phương thức format() và parse().
  4. java.time.temporal: Chứa các đối tượng thời gian tạm thời, dùng để truy xuất các thông tin cụ thể từ đối tượng ngày giờ. Ví dụ, chúng ta có thể dùng để tìm ngày đầu tiên hoặc cuối cùng trong tháng. Các phương thức này thường có dạng “withXXX”.
  5. java.time.zone: Cung cấp các lớp hỗ trợ múi giờ và quy tắc liên quan.

Ví dụ các lớp trong Java 8 Date Time API

Chúng ta đã tìm hiểu phần lớn các điểm chính của Java Date Time API. Giờ là lúc xem qua các lớp quan trọng trong Date Time API cùng với các ví dụ.

1. LocalDate

LocalDate là một lớp immutable, đại diện cho một ngày với định dạng mặc định là yyyy-MM-dd. Chúng ta có thể dùng now() để lấy ngày hiện tại, hoặc truyền vào các tham số year, month, date để tạo đối tượng LocalDate.

Lớp này còn có phiên bản now() nhận ZoneId để lấy ngày theo múi giờ cụ thể. Nó có chức năng tương tự java.sql.Date.

package com.journaldev.java8.time;

import java.time.LocalDate;
import java.time.Month;
import java.time.ZoneId;

/**
 * LocalDate Examples
 * @author pankaj
 *
 */
public class LocalDateExample {

	public static void main(String[] args) {
		
		//Current Date
		LocalDate today = LocalDate.now();
		System.out.println("Current Date="+today);
		
		//Creating LocalDate by providing input arguments
		LocalDate firstDay_2014 = LocalDate.of(2014, Month.JANUARY, 1);
		System.out.println("Specific Date="+firstDay_2014);
		
		
		//Try creating date by providing invalid inputs
		//LocalDate feb29_2014 = LocalDate.of(2014, Month.FEBRUARY, 29);
		//Exception in thread "main" java.time.DateTimeException: 
		//Invalid date 'February 29' as '2014' is not a leap year
		
		//Current date in "Asia/Kolkata", you can get it from ZoneId javadoc
		LocalDate todayKolkata = LocalDate.now(ZoneId.of("Asia/Kolkata"));
		System.out.println("Current Date in IST="+todayKolkata);

		//java.time.zone.ZoneRulesException: Unknown time-zone ID: IST
		//LocalDate todayIST = LocalDate.now(ZoneId.of("IST"));
		
		//Getting date from the base date i.e 01/01/1970
		LocalDate dateFromBase = LocalDate.ofEpochDay(365);
		System.out.println("365th day from base date= "+dateFromBase);
		
		LocalDate hundredDay2014 = LocalDate.ofYearDay(2014, 100);
		System.out.println("100th day of 2014="+hundredDay2014);
	}

}

Chú thích các phương thức LocalDate được trình bày trong phần comment. Khi chạy chương trình, kết quả như sau.

Current Date=2014-04-28
Specific Date=2014-01-01
Current Date in IST=2014-04-29
365th day from base date= 1971-01-01
100th day of 2014=2014-04-10

2. LocalTime

LocalTime là lớp immutable, đại diện cho thời gian ở định dạng dễ đọc cho con người. Định dạng mặc định là hh:mm:ss.zzz. Tương tự LocalDate, lớp này hỗ trợ múi giờ và có thể tạo đối tượng bằng cách truyền giờ, phút, giây.

package com.journaldev.java8.time;

import java.time.LocalTime;
import java.time.ZoneId;

/**
 * LocalTime Examples
 * @author pankaj
 *
 */
public class LocalTimeExample {

	public static void main(String[] args) {
		
		//Current Time
		LocalTime time = LocalTime.now();
		System.out.println("Current Time="+time);
		
		//Creating LocalTime by providing input arguments
		LocalTime specificTime = LocalTime.of(12,20,25,40);
		System.out.println("Specific Time of Day="+specificTime);
		
		
		//Try creating time by providing invalid inputs
		//LocalTime invalidTime = LocalTime.of(25,20);
		//Exception in thread "main" java.time.DateTimeException: 
		//Invalid value for HourOfDay (valid values 0 - 23): 25
		
		//Current date in "Asia/Kolkata", you can get it from ZoneId javadoc
		LocalTime timeKolkata = LocalTime.now(ZoneId.of("Asia/Kolkata"));
		System.out.println("Current Time in IST="+timeKolkata);

		//java.time.zone.ZoneRulesException: Unknown time-zone ID: IST
		//LocalTime todayIST = LocalTime.now(ZoneId.of("IST"));
		
		//Getting date from the base date i.e 01/01/1970
		LocalTime specificSecondTime = LocalTime.ofSecondOfDay(10000);
		System.out.println("10000th second time= "+specificSecondTime);

	}

}

Kết quả:

Current Time=15:51:45.240
Specific Time of Day=12:20:25.000000040
Current Time in IST=04:21:45.276
10000th second time= 02:46:40

3. LocalDateTime

LocalDateTime là một đối tượng immutable đại diện cho cả ngày và giờ với định dạng mặc định là yyyy-MM-dd-HH-mm-ss.zzz. Có phương thức factory nhận đối số LocalDate và LocalTime để tạo đối tượng LocalDateTime.

package com.journaldev.java8.time;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Month;
import java.time.ZoneId;
import java.time.ZoneOffset;

public class LocalDateTimeExample {

	public static void main(String[] args) {
		
		//Current Date
		LocalDateTime today = LocalDateTime.now();
		System.out.println("Current DateTime="+today);
		
		//Current Date using LocalDate and LocalTime
		today = LocalDateTime.of(LocalDate.now(), LocalTime.now());
		System.out.println("Current DateTime="+today);
		
		//Creating LocalDateTime by providing input arguments
		LocalDateTime specificDate = LocalDateTime.of(2014, Month.JANUARY, 1, 10, 10, 30);
		System.out.println("Specific Date="+specificDate);
		
		
		//Try creating date by providing invalid inputs
		//LocalDateTime feb29_2014 = LocalDateTime.of(2014, Month.FEBRUARY, 28, 25,1,1);
		//Exception in thread "main" java.time.DateTimeException: 
		//Invalid value for HourOfDay (valid values 0 - 23): 25

		
		//Current date in "Asia/Kolkata", you can get it from ZoneId javadoc
		LocalDateTime todayKolkata = LocalDateTime.now(ZoneId.of("Asia/Kolkata"));
		System.out.println("Current Date in IST="+todayKolkata);

		//java.time.zone.ZoneRulesException: Unknown time-zone ID: IST
		//LocalDateTime todayIST = LocalDateTime.now(ZoneId.of("IST"));
		
		//Getting date from the base date i.e 01/01/1970
		LocalDateTime dateFromBase = LocalDateTime.ofEpochSecond(10000, 0, ZoneOffset.UTC);
		System.out.println("10000th second time from 01/01/1970= "+dateFromBase);

	}

}

Trong cả ba ví dụ trên, chúng ta đã thấy rằng nếu truyền vào giá trị không hợp lệ khi tạo ngày/giờ, sẽ xảy ra java.time.DateTimeException. Đây là một RuntimeException nên ta không cần bắt lỗi nó một cách tường minh.

Chúng ta cũng có thể truyền ZoneId để lấy thông tin ngày/giờ theo múi giờ. Danh sách ZoneId được liệt kê trong JavaDoc. Khi chạy chương trình, kết quả như sau.

Current DateTime=2014-04-28T16:00:49.455
Current DateTime=2014-04-28T16:00:49.493
Specific Date=2014-01-01T10:10:30
Current Date in IST=2014-04-29T04:30:49.493
10000th second time from 01/01/1970= 1970-01-01T02:46:40

4. Instant

Lớp Instant được dùng để làm việc với thời gian dạng máy móc. Instant lưu ngày giờ dưới dạng unix timestamp.

package com.journaldev.java8.time;

import java.time.Duration;
import java.time.Instant;

public class InstantExample {

	public static void main(String[] args) {
		//Current timestamp
		Instant timestamp = Instant.now();
		System.out.println("Current Timestamp = "+timestamp);
		
		//Instant from timestamp
		Instant specificTime = Instant.ofEpochMilli(timestamp.toEpochMilli());
		System.out.println("Specific Time = "+specificTime);
		
		//Duration example
		Duration thirtyDay = Duration.ofDays(30);
		System.out.println(thirtyDay);
	}

}

Kết quả:

Current Timestamp = 2014-04-28T23:20:08.489Z
Specific Time = 2014-04-28T23:20:08.489Z
PT720H

Java 8 Date API – Các tiện ích đi kèm

Hầu hết các lớp trong API đều có nhiều phương thức tiện ích như cộng/trừ ngày, tuần, tháng, v.v. Ngoài ra còn có các phương thức dùng TemporalAdjuster để điều chỉnh ngày hoặc tính khoảng thời gian giữa hai mốc thời gian.

package com.journaldev.java8.time;

import java.time.LocalDate;
import java.time.LocalTime;
import java.time.Period;
import java.time.temporal.TemporalAdjusters;

public class DateAPIUtilities {

	public static void main(String[] args) {
		
		LocalDate today = LocalDate.now();
		
		//Get the Year, check if it's leap year
		System.out.println("Year "+today.getYear()+" is Leap Year? "+today.isLeapYear());
		
		//Compare two LocalDate for before and after
		System.out.println("Today is before 01/01/2015? "+today.isBefore(LocalDate.of(2015,1,1)));
		
		//Create LocalDateTime from LocalDate
		System.out.println("Current Time="+today.atTime(LocalTime.now()));
		
		//plus and minus operations
		System.out.println("10 days after today will be "+today.plusDays(10));
		System.out.println("3 weeks after today will be "+today.plusWeeks(3));
		System.out.println("20 months after today will be "+today.plusMonths(20));

		System.out.println("10 days before today will be "+today.minusDays(10));
		System.out.println("3 weeks before today will be "+today.minusWeeks(3));
		System.out.println("20 months before today will be "+today.minusMonths(20));
		
		//Temporal adjusters for adjusting the dates
		System.out.println("First date of this month= "+today.with(TemporalAdjusters.firstDayOfMonth()));
		LocalDate lastDayOfYear = today.with(TemporalAdjusters.lastDayOfYear());
		System.out.println("Last date of this year= "+lastDayOfYear);
		
		Period period = today.until(lastDayOfYear);
		System.out.println("Period Format= "+period);
		System.out.println("Months remaining in the year= "+period.getMonths());		
	}
}

Kết quả:

Year 2014 is Leap Year? false
Today is before 01/01/2015? true
Current Time=2014-04-28T16:23:53.154
10 days after today will be 2014-05-08
3 weeks after today will be 2014-05-19
20 months after today will be 2015-12-28
10 days before today will be 2014-04-18
3 weeks before today will be 2014-04-07
20 months before today will be 2012-08-28
First date of this month= 2014-04-01
Last date of this year= 2014-12-31
Period Format= P8M3D
Months remaining in the year= 8

Java 8 Date – Phân tích và định dạng ngày giờ

Việc định dạng ngày thành nhiều kiểu khác nhau rồi phân tích chuỗi thành đối tượng ngày/giờ là rất phổ biến.

package com.journaldev.java8.time;

import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class DateParseFormatExample {

	public static void main(String[] args) {
		
		//Format examples
		LocalDate date = LocalDate.now();
		//default format
		System.out.println("Default format of LocalDate="+date);
		//specific format
		System.out.println(date.format(DateTimeFormatter.ofPattern("d::MMM::uuuu")));
		System.out.println(date.format(DateTimeFormatter.BASIC_ISO_DATE));
		
		
		LocalDateTime dateTime = LocalDateTime.now();
		//default format
		System.out.println("Default format of LocalDateTime="+dateTime);
		//specific format
		System.out.println(dateTime.format(DateTimeFormatter.ofPattern("d::MMM::uuuu HH::mm::ss")));
		System.out.println(dateTime.format(DateTimeFormatter.BASIC_ISO_DATE));
		
		Instant timestamp = Instant.now();
		//default format
		System.out.println("Default format of Instant="+timestamp);
		
		//Parse examples
		LocalDateTime dt = LocalDateTime.parse("27::Apr::2014 21::39::48",
				DateTimeFormatter.ofPattern("d::MMM::uuuu HH::mm::ss"));
		System.out.println("Default format after parsing = "+dt);
	}

}

Kết quả:

Default format of LocalDate=2014-04-28
28::Apr::2014
20140428
Default format of LocalDateTime=2014-04-28T16:25:49.341
28::Apr::2014 16::25::49
20140428
Default format of Instant=2014-04-28T23:25:49.342Z
Default format after parsing = 2014-04-27T21:39:48

Java Date API – Hỗ trợ các lớp cũ

Các lớp Date/Time cũ vẫn được sử dụng rất nhiều trong các ứng dụng, vì vậy khả năng tương thích ngược là điều bắt buộc. Java 8 cung cấp nhiều phương thức để chuyển đổi qua lại giữa các lớp cũ và lớp mới.

package com.journaldev.java8.time;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;

public class DateAPILegacySupport {

	public static void main(String[] args) {
		
		//Date to Instant
		Instant timestamp = new Date().toInstant();
		//Now we can convert Instant to LocalDateTime or other similar classes
		LocalDateTime date = LocalDateTime.ofInstant(timestamp, 
						ZoneId.of(ZoneId.SHORT_IDS.get("PST")));
		System.out.println("Date = "+date);
		
		//Calendar to Instant
		Instant time = Calendar.getInstance().toInstant();
		System.out.println(time);
		//TimeZone to ZoneId
		ZoneId defaultZone = TimeZone.getDefault().toZoneId();
		System.out.println(defaultZone);
		
		//ZonedDateTime from specific Calendar
		ZonedDateTime gregorianCalendarDateTime = new GregorianCalendar().toZonedDateTime();
		System.out.println(gregorianCalendarDateTime);
		
		//Date API to Legacy classes
		Date dt = Date.from(Instant.now());
		System.out.println(dt);
		
		TimeZone tz = TimeZone.getTimeZone(defaultZone);
		System.out.println(tz);
		
		GregorianCalendar gc = GregorianCalendar.from(gregorianCalendarDateTime);
		System.out.println(gc);
		
	}

}

Kết quả:

Date = 2014-04-28T16:28:54.340
2014-04-28T23:28:54.395Z
America/Los_Angeles
2014-04-28T16:28:54.404-07:00[America/Los_Angeles]
Mon Apr 28 16:28:54 PDT 2014
sun.util.calendar.ZoneInfo[id="America/Los_Angeles",offset=-28800000,dstSavings=3600000,useDaylight=true,transitions=185,lastRule=java.util.SimpleTimeZone[id=America/Los_Angeles,offset=-28800000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]]
java.util.GregorianCalendar[time=1398727734404,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="America/Los_Angeles",offset=-28800000,dstSavings=3600000,useDaylight=true,transitions=185,lastRule=java.util.SimpleTimeZone[id=America/Los_Angeles,offset=-28800000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]],firstDayOfWeek=2,minimalDaysInFirstWeek=4,ERA=1,YEAR=2014,MONTH=3,WEEK_OF_YEAR=18,WEEK_OF_MONTH=5,DAY_OF_MONTH=28,DAY_OF_YEAR=118,DAY_OF_WEEK=2,DAY_OF_WEEK_IN_MONTH=4,AM_PM=1,HOUR=4,HOUR_OF_DAY=16,MINUTE=28,SECOND=54,MILLISECOND=404,ZONE_OFFSET=-28800000,DST_OFFSET=3600000]

Có thể thấy rằng phương thức toString() của các lớp cũ như TimeZoneGregorianCalendar thường dài dòng và khó đọc.

Kết luận

Mình rất thích Date Time API mới này. Một số lớp được dùng thường xuyên nhất có thể kể đến là LocalDate và LocalDateTime. Làm việc với các lớp mới rất dễ dàng. Ngoài ra, việc các phương thức có tên giống nhau cho cùng một nhiệm vụ giúp dễ nhớ và dễ dùng. Việc chuyển từ các lớp cũ sang lớp mới sẽ mất thời gian, nhưng chắc chắn rất xứng đáng với công sức bỏ ra.

0 Bình luận

Đăng nhập để thảo luận

Chuyên mục Hướng dẫn

Tổng hợp các bài viết hướng dẫn, nghiên cứu và phân tích chi tiết về kỹ thuật, các xu hướng công nghệ mới nhất dành cho lập trình viên.

Đăng ký nhận bản tin của chúng tôi

Hãy trở thành người nhận được các nội dung hữu ích của CyStack sớm nhất

Xem chính sách của chúng tôi Chính sách bảo mật.

Đăng ký nhận Newsletter

Nhận các nội dung hữu ích mới nhất