Time zone Vietnam la gì
Từ lâu việc xử lý thời gian đã là một chủ đề mang lại nhiều cơn đau đầu cho các developers, đặc biệt nếu phải xử lý thời gian theo nhiều múi giờ khác nhau. Bài viết này sẽ giúp bạn "đả thông kinh mạch" để thoát khỏi nỗi sợ này. Show Một số thuật ngữ mình sẽ sử dụng trong bài viết:
Để hiểu được bản chất vấn đề, chúng ta cần nắm rõ 2 khái niệm sau: 1. Thời gian tuyệt đốiLà một khoảng khắc cụ thể (moment) trong dòng chảy lịch sử.
Khi nói về thời gian tuyệt đối, cần có đủ hai thành phần: ngày giờ + ngữ cảnh nơi chốn.
Ngữ cảnh ở đây chính là múi giờ (zone). Các múi giờ được đặc trưng bởi một độ 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.
Trong máy tính, moment được biểu diễn dưới dạng Epoch Seconds - số giây trôi qua kể từ 00:00:00 ngày 1 tháng 1 năm 1970 theo giờ UTC. 2. Thời gian tương đốiLà thời gian chỉ dùng để hiển thị (relative/represent time - gọi ngắn gọn là rtime), không bao gồm ngữ cảnh múi giờ.
Trong sinh hoạt thường ngày, khi muốn đối chiếu thời gian, chúng ta không thể dùng rtime, mà phải thêm vào một múi giờ hoặc một độ lệch để rtime trở nên tuyệt đối (moment) rồi mới đem đi so sánh. moment = rtime + (zone or offset)
Và một vài quy ướcSau khi đã phân biệt được rtime và moment, chúng ta sẽ tìm hiểu cách để biểu diễn chúng. 1. Tiêu chuẩn ISO-8601Được công bố vào năm 1988, ISO-8601 là một tiêu chuẩn quốc tế mô tả một quy tắc chung để viết ngày giờ, tiện cho việc liên lạc & trao đổi thông tin liên quan đến thời gian. Dưới đây là một moment được viết theo tiêu chuẩn ISO-8601, bao gồm ngày, giờ và offset Như vậy, để biểu diễn rtime, chúng ta chỉ cần bỏ đi phần offset. Và ngược lại, khi gắn offset vào rtime (ngày giờ), chúng ta có moment. moment = rtime + offset Để ý chúng ta thấy, moment trong tiêu chuẩn ISO-8601 chỉ sử dụng offset mà không đề cập đến tên của múi giờ. 2. IANA Time Zone DatabaseHay còn gọi là tz database, là một bộ database tổng hợp thông tin của toàn bộ múi giờ trên thế giới, được quản lý bởi tổ chức ICANN. Trong tz database, một múi giờ sẽ có tên gọi dựa trên vị trí địa lý của nó, theo dạng Area/Location, trong đó area là tên của lục địa hoặc đại dương, location là tên của thành phố hoặc hòn đảo.
Lúc này có thể bạn sẽ hỏi
Đúng là để biểu diễn thời gian tuyệt đối, chỉ cần thời gian tương đối (ngày giờ) và offset là đủ. Cuộc sống sẽ cứ êm đềm như vậy, nếu không xuất hiện khái niệm Daylight Saving Time. 3. Daylight Saving Time (DST) là cái gì?Nếu bạn chưa biết, một thành phố có thể sử dụng 2 múi giờ luân phiên trong năm.
Tại sao lại có hiện tượng lạ này?
Điều này dẫn chúng ta đến 1 sự thật "kinh hoàng": tồn tại những múi giờ mang 2 giá trị offset luân phiên nhau trong năm 😱😱😱 Đây chính là lý do bộ dữ liệu IANA tz database ra đời:
Ok, lý thuyết như vậy đủ rồi. Bây giờ chúng ta sẽ tìm hiểu code thực tế như thế nào. Java Date Time APIKhi xử lý thời gian, trước đây chúng ta thường dùng Date, SimpleDateFormat và Calendar. Kể từ phiên bản 1.8 trở đi, Java cung cấp những API mạnh mẽ để xử lý ngày - giờ.
Để ý ta thấy, đội ngũ Java sử dụng chữ "Local" để ám chỉ giờ địa phương. Do đó các class bắt đầu bằng chữ Local sẽ biểu diễn rtime - thời gian hiển thị của địa phương. Bản thân các class Local không chứa thông tin về múi giờ hoặc độ lệch. Ngoài ra, (có thể bạn đã biết), Date là một moment, thể hiện ngày giờ với TimeZone mặc định của server. Tuy nhiên do thiếu sót trong thiết kế ban đầu nên phần lớn method đã bị deprecated và thay thế bởi Instant 1. DiagramDưới đây là diagram tóm tắt cách chuyển đổi qua lại giữa các class 2. Áp dụng vào codeNguyên tắc chung:
Hãy cùng xem qua một vài thao tác xử lý, giả sử chúng ta muốn xuất báo cáo được tạo trong năm 2020. class Report { Date createdAt; } [...] public void processReportIn2020() { ZoneId zoneId = ZoneId.of("Asia/Ho_Chi_Minh"); // Để lấy ra moment đầu năm 2020 tại HCM, chúng ta sẽ tạo một rtime // tại thời điểm 0 giờ ngày 1/1, và ghép múi giờ vào Instant beginOfYear = LocalDateTime.parse("2020-01-01T00:00:00").atZone(zoneId).toInstant(); // Tương tự với cuối năm Instant endOfYear = LocalDateTime.parse("2020-12-31T23:59:59").atZone(zoneId).toInstant(); // Lọc ra báo cáo trong năm 2020 List<Report> thisYearReports = allReports .stream() .filter(r -> isBetween(r.createdAt, beginOfYear, endOfYear)) .collect(Collectors.toList()); // xử lý báo cáo [...] } // Kiểm tra xem thời gian của báo cáo có nằm giữa 2 khoảng instant hay không boolean isBetween(Date d, Instant start, Instant end) { Instant timePoint = d.toInstant(); return (start.equals(timePoint) || start.isBefore(timePoint)) && (timePoint.equals(end) || timePoint.isBefore(end)); }Như bạn thấy, nếu đã phân biệt được rõ ràng đâu là moment, đâu là rtime, việc xử lý thời gian trở nên vô cùng đơn giản. 3. Một vài thao tác dịch chuyển 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: // Để biết được múi giờ ở Florida có tên trong tz database là gì, // chúng ta tra cứu trên trang web https://time.is ZoneId florida = ZoneId.of("America/New_York"); // Thời gian hiện tại ở HCM (+07:00) ZonedDateTime now = ZonedDateTime.now(); now.toLocalDateTime(); // 2021-08-22, 20:24 // Cũng là hiện tại nhưng ở Florida (-04:00) ZonedDateTime nowAtFlorida = now.withZoneSameInstant(florida); nowAtFlorida.toLocalDateTime(); // 2021-08-22, 09:24Nhì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"). 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 newYearEveHCM và newYearEveFlorida hoàn toàn khác nhau. 4. Vậy còn Daylight Saving Time (DST)?Thật may mắn là chúng ta không cần làm gì hết! JRE và JDK sẽ handle toàn bộ việc thay đổi liên quan đến DST nếu chúng ta sử dụng ZonedDateTime và cung cấp ZoneId theo tiêu chuẩn của tz database. Chúng ta sẽ tái hiện lại thời điểm bắt đầu DST tại Victoria ở ví dụ đã nói ở trên, lúc 2h sáng chủ nhật đầu tiên của tháng 10 (là ngày 04/10/2020) ZoneId zId = ZoneId.of("Australia/Melbourne"); // https://time.is/Victoria // Vì 2h sẽ xảy ra DST, chúng ta sẽ lấy thời gian sớm hơn 5 phút ZonedDateTime before = LocalDateTime.parse("2020-10-04T01:55:00") .atZone(zId);
Sau đó cộng thêm 10 phút để qua thời điểm DST. ZonedDateTime after = before.plusMinutes(10);
Chúng ta thấy thời gian đã tự động cộng thêm 1 tiếng, và độ dời offset cũng đã tự thay đổi từ +10:00 thành +11:00 Trong tương lai, nếu có bất kì thay đổi nào liên quan đến DST tại một địa phương, dữ liệu sẽ được cập nhật tại website của tz database và được chỉnh sửa trong lần phát hành tiếp theo của JRE. Tóm lạiChúng ta đã hiểu được khái niệm moment (tgian tuyệt đối) và rtime (tgian tương đối).
Ứng dụng vào trong code:
KếtVậy là mình đã giới thiệu xong những khái niệm quan trọng. Một khi đã nắm rõ chúng, mình tin rằng việc xử lý ngày giờ không còn là nỗi sợ hãi quá lớn của các developers Java 😊 Tuy nhiên, 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 ở phần tiếp theo. (Phần tiếp theo: Xử lý DateTime như thế nào cho chuẩn?)
Liên kết ngoài
|