Múi giờ (timezone) là một yếu tố quan trọng trong việc xử lý thời gian trong các ứng dụng phần mềm. Trong Java, thư viện chuẩn cung cấp nhiều công cụ và lớp để làm việc với múi giờ. Trong bài viết này, chúng ta sẽ tìm hiểu về cách sử dụng và xử lý timezone trong Java.

1. Làm sao để hiểu được timezone trong Java

Timezone trong Java là một khái niệm quan trọng để đại diện cho các khu vực địa lý trên trái đất có cùng thời gian chuẩn. Mỗi múi giờ có một vùng thời gian cụ thể được xác định bằng số giờ chênh lệch với thời gian tiêu chuẩn quốc tế(UTC).

1.1. Một số thuật ngữ chúng ta cần biết để dễ theo dõi bài viết này:

moment: thời gian tuyệt đối

rtime (relative/represent time): thời gian tương đối, hoặc cũng có thể gọi là thời gian chỉ để hiển thị

offset: độ lệch

zone: múi giờ

1.2. Khái niệm về thời gian

Để hiểu được vấn đề về timezone ta cần nắm được 2 khái niệm sau: thời gian tuyệt đối và thời gian tương đối

  • Thời gian tuyệt đối(moment)

Là một khoảnh khắc trong lịch sử. Một khoảnh khắc sẽ có hai thành phần: ngày giờ + ngữ cảnh.

Thời gian ở Việt Nam sẽ khác thời gian ở Đài Bắc. Tại thời điểm 23h tôi đã đáp máy bay từ Đài Bắc về sân bay Nội Bài thì tại thời đó giờ Đài Bắc mới là 22h mà thôi.

Ở đây, ngữ cảnh chính là múi giờ(zone). Các múi giờ là đặc trưng cho độ lệch thời gian(offset) so với giờ phối hợp quốc tế UTC. Độ lệch được biểu diễn dưới dạng ±hh:mm

Việt Nam thuộc múi giờ Đông Dương (Indochina Time - ICT) có độ lệch UTC+07:00 , nghĩa là đồng hồ ở Việt Nam chạy nhanh hơn 7 tiếng so với đồng hồ của UTC.

Với máy tính, moment được biểu diễn bằng số giây đã trôi qua tính từ thời 0:00:00 ngày 1 tháng 1 năm 1970 giờ UTC

  • Thời gian tương đối(rtime)

Là thời gian để hiển thị tại một địa điểm cụ thể nào đó(ví dụ: giờ Việt Nam, giờ Mỹ) không bao gồm ngữ cảnh.

Người dân toàn cầu sẽ đón giao thừa vào thời điểm 00:00 ngày 01/01

Tại cùng một thời điểm, thời gian ở các vị trí khác nhau có thể là không giống nhau. Do đó, để có thể so sánh thời gian giữa các địa điểm với nhau chúng ta cần đưa nó về thời gian tuyệt đối rồi mới so sánh.

moment = rtime + (zone or offset)

Giờ Việt Nam là ICT UTC+7:00

Xem thêm bài viết về ngôn ngữ lập trình Java:

- LinkedList trong java và ví dụ cụ thể

- Khai báo và sử dụng mảng các đối tượng trong Java

- Tìm hiểu về ràng buộc (Constraint) trong SQL

- Phương thức main trong Java và những điều bạn cần biết

- Lập trình đa luồng là gì? Hướng dẫn lập trình đa luồng bằng ngôn ngữ Java

- Phân biệt ArrayList, Set và Vector trong Java
 

2. Xử lý timezone trong Java

Để xử lý thời gian trong Java, một số cách xử ký thông dụng đó là sử dụng các lớp Date, Calendar. Kể từ phiên bản 1.8, Java đã cung cấp cho chúng ta thêm nhiều API mạnh mẽ khác để xử lý ngày và giờ

Offset/ZoneThời gian tương đối(rtime)Thời gian tuyệt đối(moment)
ZoneOffset: chứa thông tin độ lệch theo format ±hh:mmLocalDate: lưu thông tin một ngày (ngày, tháng, năm)Instant: biển diễn một epoch time với độ chính xác nanosecond.
ZoneId: chứa tên của múi giờ theo tiêu chuẩn tz databaseLocalTime: lưu thông tin một giờ (giờ, phút, giây) với độ chính xác nanosecondsOffsetDateTime: lưu ngày giờ và một offset (không kèm múi giờ)
 LocalDateTime: lưu cả ngày lẫn giờZonedDateTime: lưu ngày giờ kèm theo múi giờ (zone ID)

Ta có thể thấy được các lớp xử lý thời gian tương đối được mô tả bằng từ Local tức là "địa phương". Do đó các lớp bắt đầu bằng từ Local sẽ biểu diễn rtime - thời gian hiển thị của địa phương. Bản thân các lớp Local không chứa thông tin về múi giờ hoặc độ lệch

Date là một moment, nó thể hiện ngày giờ với TimeZone mặc định của server. Sau này, rất nhiều phương thức của nó đã bị deprecated và thay thế bởi lớp Instant.

3. Thực thi việc chuyển đổi timezone như thế nào?

3.1. Nguyên tắc xử lý:

Khi cần tạo ra một thời điểm, chúng ta sẽ tạo ra LocalDateTime(rtime), xác định ZoneId(zone + offset) sau đó kết hợp thành một đối tượng ZonedDateTime(moment)

Toàn bộ thao tác so sánh thời gian nên quy đổi về Instant

Các API mới được cung cấp từ jdk 1.8(ngoài trừ Date, Calendar) đều là immutable object

3.2. So sánh thời gian giữa các timezone

Chúng ta sẽ cùng phân tích ví dụ hiển thị ra các giá trị có thời gian tạo nằm trong khoảng từ tháng 4 đến tháng 6 năm 2023:

public void printReport() {

    ZoneId zoneId = ZoneId.of("Asia/Ho_Chi_Minh");

    // Create one rtime at the beginning of April with offset
    Instant beginOfApril = LocalDateTime.parse("2023-04-01T00:00:00").atZone(zoneId).toInstant();

    // Do the same with the end of May
    Instant endOfMay = LocalDateTime.parse("2023-05-31T23:59:59").atZone(zoneId).toInstant();

    // Filter list report created in this period of time
    List<Report> thisYearReports = allReports
            .stream()
            .filter(r -> isBetween(r.createdAt, beginOfApril, endOfMay))
            .collect(Collectors.toList());

    // Just print it
    allReports.foreach(System.out::println);

}


// Compare to check if date is between start and end time
boolean isBetween(Date d, Instant start, Instant end) {

    Instant moment = d.toInstant();
    return (start.equals(moment) || start.isBefore(moment)) &&
           (moment.equals(end) || moment.isBefore(end));
}

Việc thực hiện kiểm tra so sánh rất đơn giản khi chúng ta đã phân biệt được momentrtime. Trong ví dụ trên, tôi đã thực hiện lấy ra đúng timezone của vị trí địa lý mà chúng ta cần kiểm tra.

Sau khi lấy được timezone, chúng ta sẽ cần tìm hai mốc thời gian bắt đầu và kết thúc để so sánh. Dĩ nhiên, nó phải là thời gian tương đối(rtime), vì chúng ta sẽ so sánh các báo cáo được tạo ra tại múi giờ này mà :D. Ở đây, rtime trong Java được thể hiện bởi ZonedDateTime và là kết quả trả về của phương thức LocalDateTime.atZone(ZoneId).

Để có thể so sánh, chúng ta sẽ chuyển toàn bộ các giá trị rtime về moment bằng phương thức toInstant(). Đến đây thì vấn đề chúng ta cần xử lý đã xong, việc còn lại cần làm là so sánh moment của báo cáo với moment của hai đầu thời gian.

3.3. Chuyển đổi thời gian giữa các múi giờ

Với Date Time API 1.8, việc chuyển đổi qua lại giữa các múi giờ cũng linh hoạt dễ dàng. Hãy xem xét ví dụ sau:

// Get a timezone
ZoneId florida = ZoneId.of("America/New_York");

// Get current rtime
ZonedDateTime now = ZonedDateTime.now();
now.toLocalDateTime();  //2023-07-23, 20:24

// Current time at florida timezone
ZonedDateTime nowAtFlorida = now.withZoneSameInstant(florida);
nowAtFlorida.toLocalDateTime(); // 2021-07-23, 09:24

Nhìn vào tên method .withZoneSameInstant(ZoneId) có thể đoán được ngay chức năng của nó là dịch chuyển từ timezone này sang timezone khác mà vẫn giữ nguyên giá trị moment ("sameInstant").

Một cách xử lý khác cũng khá hay, chúng ta có thể rút gắn source code thành

ZonedDateTime nowAtFlorida = ZonedDateTime.ofInstant(now, florida);

Bằng cách này, chúng ta cũng thu được kết quả tương tự

Một ví dụ khác về cách chuyển đổi thời gian giữa các múi giờ


ZoneId florida = ZoneId.of("America/New_York");

// Local time
ZonedDateTime now = ZonedDateTime.now;

// Rtime in Florida
ZonedDateTime nowAtFlorida = now.withZoneSameLocal(florida);

Trái ngược với ví dụ trước, ở đây .withZoneSameLocal(ZoneId) sẽ giữa nguyên rtime ("sameLocal") và gắn vào đó một timezone khác, do đó giá trị tuyệt đối (moment) của nownowAtFlorida hoàn toàn khác nhau.

Kết bài

Qua bài viết này, Stringee đã giới thiệu tới các bạn các khái niệm quan trọng trong việc xử lý múi giờ. Một khi đã "đắc đạo", Stringee tin rằng việc xử lý timezone trong Java sẽ không còn là nỗi sợ hãi của các developers Java nữa. Tuy nhiên, việc xử lý thời gian luôn luôn cần sự tỉ mỉ, vẫn sẽ có những sai lầm tạo ra bug nếu chúng ta không chú ý, hẹn gặp lại các bạn ở các bài viết tiếp theo.

Stringee API cung cấp các tính năng như gọi thoại, gọi video, tin nhắn chat, SMS hay tổng đài chăm sóc khách hàng (CSKH) có thể được nhúng trực tiếp vào các ứng dụng/website của doanh nghiệp nhanh chóng. Điều này giúp tiết kiệm đến 80% thời gian và chi phí cho doanh nghiệp, trong khi nếu tự phát triển các tính năng này có thể mất từ 1 - 3 năm.

Quý doanh nghiệp quan tâm xin mời đăng ký nhận tư vấn tại đây: