Jiff, thư viện ngày·giờ cho Rust
(github.com/BurntSushi)- Jiff là một thư viện ngày·giờ cho Rust, hướng tới các kiểu dữ liệu ngày·giờ cơ bản ở mức cao khó bị dùng sai và hiệu năng hợp lý
- Các tính năng cốt lõi gồm tích hợp tự động với Time Zone Database, phép tính và làm tròn có nhận biết DST, định dạng·phân tích không mất dữ liệu cho ngày·giờ có múi giờ, cùng hỗ trợ Serde tùy chọn
- Thiết kế API chịu ảnh hưởng lớn từ Temporal, đề xuất của TC39 nhằm cải thiện cách xử lý ngày·giờ trong JavaScript
- Bản phát hành Jiff 1.0 ban đầu nhắm tới mùa hè 2025, nhưng đến tháng 4/2026 lịch trình đã bị lùi và hiện chưa có mốc thời gian phát hành 1.0
- Hiệu năng là mục tiêu quan trọng, nhưng khả năng dễ hiểu của API và độ chính xác được ưu tiên hơn; trên Unix, dự án theo đuổi chính sách phụ thuộc thận trọng với chủ đích giữ số lượng phụ thuộc ở mức 0
Mô hình ngày·giờ mà Jiff cung cấp
- Jiff là thư viện để xử lý ngày·giờ trong Rust, tập trung vào việc cung cấp các kiểu dữ liệu cơ bản ở mức cao khó bị sử dụng sai
- Các khả năng hỗ trợ chính
- Tích hợp tự động và liền mạch với Time Zone Database
- Phép tính và làm tròn có nhận biết DST
- Định dạng·phân tích không mất dữ liệu cho ngày·giờ có múi giờ
- Hỗ trợ
serdetùy chọn
- Thiết kế API chịu ảnh hưởng lớn từ Temporal, đề xuất của TC39 nhằm cải thiện việc xử lý ngày·giờ trong JavaScript
- Giấy phép là giấy phép kép MIT hoặc UNLICENSE
Tài liệu và ví dụ sử dụng
- Tài liệu và tài nguyên liên quan
- Tài liệu API trên docs.rs
- Tài liệu so sánh với
chrono,time,hifitime,icu - Tài liệu về cơ sở thiết kế API của Jiff
- Tài liệu hỗ trợ nền tảng
- Nhật ký thay đổi
- Ví dụ cơ bản phân tích một chuỗi thời điểm theo RFC 3339, chuyển nó thành ngày·giờ có múi giờ, cộng thêm một khoảng thời gian rồi xuất ra chuỗi không mất dữ liệu
- Phân tích
"2024-07-11T01:14:00Z"thànhTimestamp - Chuyển sang múi giờ
America/New_York - Cộng thêm
1.month().hours(2) - Chuỗi kết quả là
"2024-08-10T23:14:00-04:00[America/New_York]" - Thời điểm UTC theo định dạng RFC 3339 là
"2024-08-11T03:14:00Z"
- Phân tích
Cài đặt và luồng chạy cơ bản
- Jiff có trên crates.io và có thể dùng bằng cách thêm
jiffvào phần phụ thuộc trongCargo.tomlcủa dự án Rust - Lệnh thêm nhanh là
cargo add jiff - Luồng ví dụ đầy đủ trong README
- Tạo dự án mới bằng
cargo new jiff-example - Thêm phụ thuộc bằng
cargo add jiff - Trong
src/main.rs, dùngZoned::now().round(Unit::Second)?để làm tròn thời gian hiện tại đến giây - Chạy bằng
cargo run
- Tạo dự án mới bằng
- Kết quả ví dụ hiển thị dạng có kèm múi giờ như
2024-07-10T19:54:20-04:00[America/New_York] - Ở lần chạy đầu tiên sẽ có nhiều đầu ra biên dịch hơn, nhưng các lần chạy sau sẽ không cần biên dịch lại các phụ thuộc
Feature flag và kế hoạch 1.0
- Jiff cung cấp nhiều crate feature để điều chỉnh việc hỗ trợ thư viện chuẩn Rust, hỗ trợ
serde, và có nhúng Time Zone Database vào binary hay không - Danh sách đầy đủ các feature flag được hỗ trợ nằm trong mục “crate features” của tài liệu
- Kế hoạch phát hành ban đầu của Jiff 1.0 là vào mùa hè 2025, nhưng tính đến tháng 4/2026 lịch trình đã bị lùi và hiện chưa có mốc phát hành nào
- Sau khi phát hành 1.0, dự án có kế hoạch duy trì API vô thời hạn, với mục tiêu để người dùng có thể dùng Jiff như một nền tảng API công khai ổn định
- Ngay cả sau khi Jiff 1.0 phát hành, Jiff 0.2 vẫn dự kiến nhận các bản cập nhật sửa lỗi quan trọng trong 1 năm
- Không có phát triển tính năng mới đang hoạt động
- Do đã có nhiều người dùng Jiff 0.2, dự án muốn cung cấp một khoảng thời gian chuyển đổi
Hiệu năng và lối thoát cho đường dẫn định dạng nâng cao
- Mục tiêu thiết kế quan trọng nhất là trở thành một thư viện ngày·giờ mức cao khó bị dùng sai
- Hiệu năng là mục tiêu thứ hai; dự án hướng tới hiệu năng hợp lý, nhưng có thể vẫn còn những khu vực cần cải thiện
- Benchmark nằm trong thư mục
bench - Một số phần đã được tối ưu, nhưng vẫn còn nhiều phần chưa được tối ưu
- Cải thiện hiệu năng là mục tiêu, nhưng nhìn chung khả năng dễ hiểu của API và độ chính xác được ưu tiên hơn hiệu năng
- Các mô-đun cấp thấp hơn của Jiff, đặc biệt là
jiff::fmt, đóng vai trò lối thoát cho các API phức tạp khi cần nhiều quyền kiểm soát hơn- Triển khai
std::fmt::DisplaycủaZonedcó thể khiến việc ghi vào bộ đệm tái sử dụng trở nên khó khăn jiff::fmt::temporal::DateTimePrinter::print_zonedcho phép ghiZonedvàoStringhoặcVec<u8>có sẵn
- Triển khai
Xử lý múi giờ theo nền tảng
- Vấn đề hỗ trợ nền tảng của thư viện ngày·giờ chủ yếu liên quan đến hỗ trợ múi giờ
- Các tương tác nền tảng mà Jiff xử lý
- Cách tìm dữ liệu chuyển đổi cho định danh múi giờ IANA như
Antarctica/Troll - Cách tìm múi giờ mặc định hiện tại của hệ thống
- Cách tìm dữ liệu chuyển đổi cho định danh múi giờ IANA như
- Dữ liệu chuyển đổi múi giờ phụ thuộc vào IANA Time Zone Database
- Trên Unix, thông thường sử dụng
/usr/share/zoneinfovà cũng tuân theo thiết lập biến môi trườngTZDIR - Trên Windows, thư viện sẽ tự động nhúng một bản sao của Time Zone Database vào thư viện đã biên dịch
- Cách phát hiện múi giờ hệ thống
- Trên Unix, đọc
/etc/localtime - Trên Windows, đọc định danh múi giờ riêng của Windows bằng
GetDynamicTimeZoneInformation, rồi ánh xạ sang định danh múi giờ IANA thông qua dữ liệu XML CLDR của Unicode
- Trên Unix, đọc
- Hỗ trợ cho các nền tảng khác được kỳ vọng sẽ tăng dần theo thời gian, nhưng với những nền tảng hiếm khó kiểm thử, dự án nhiều khả năng sẽ phụ thuộc vào pull request từ cộng tác viên
Chính sách phụ thuộc và MSRV
- Việc Jiff có 0 phụ thuộc trên Unix là một thiết kế có chủ đích
- Tiêu chí thêm phụ thuộc mới vào Jiff, một crate thuộc hệ sinh thái, là rất thận trọng
- Khi thực sự cần thiết để tương tác với nền tảng
- Khi cần cho khả năng tương tác, ví dụ như
serde
- Ngay cả các phụ thuộc phục vụ khả năng tương tác cũng được xem xét thận trọng; nhìn chung dự án chỉ muốn phụ thuộc vào những thành phần có ít bản phát hành breaking change hơn Jiff
- Nếu chia Jiff thành nhiều crate thì có thể sẽ phát sinh phụ thuộc mới, nhưng hiện chưa có kế hoạch như vậy
- Dự án muốn giữ số lượng crate ở mức nhỏ; kinh nghiệm cho thấy chi phí bảo trì khi quản lý nhiều ranh giới API semver là khá lớn
- Phiên bản trình biên dịch Rust tối thiểu được hỗ trợ của Jiff là MSRV 1.70.0
- Bản thân Jiff và các phụ thuộc của Jiff trong kho mã không dùng phiên bản Rust đòi hỏi trình biên dịch Rust được phát hành trong vòng 1 năm gần nhất
- Các crate trong kho mã phụ thuộc vào Jiff như
jiff-icucó thể dùng MSRV mới hơn, nhưng sẽ không yêu cầu MSRV mới hơn crate mà chúng tích hợp cùng - MSRV của các phụ thuộc bên ngoài như
windows-sys,portable-atomickhông nằm trong khả năng kiểm soát của Jiff - Theo thông lệ của hệ sinh thái Rust, Jiff không xem việc tăng MSRV là thay đổi không tương thích semver, nhưng sẽ giới hạn việc tăng MSRV trong các bản cập nhật minor version
1 bình luận
Ý kiến trên Hacker News
Nhìn chung có vẻ tốt, nhưng hơi vướng ở cú pháp ToSpan
let span = 5.days().hours(8).minutes(1);Cảm giác hơi lạ vì chỉ con số đầu tiên đứng phía trước, còn các phần còn lại lại đi vào làm đối số hàm
Nếu không thích thì có thể viết như
let span = Span::new().days(5).hours(8).minutes(1);; chỉ dài thêm vài ký tự nên cũng không tệuse jiff::{Timestamp, ToSpan};,"2024-07-11T01:14:00Z".parse()?được parse thành TimestampMình nhớ Rust dùng thứ gì đó giống interface thay vì class, nên thấy thắc mắc vì sao khi import thư viện thì chuỗi đột nhiên có thêm method
parse(), và vì sao method có cái tên chung chung đó lại tạo ra một đối tượngTimestampCũng tò mò liệu kiểu ở vế trái có quyết định ý nghĩa của
str.parse()hay không, và nếu một thư viện ngày tháng cùng một thư viện biểu thức Lisp đều gắnparse()vào chuỗi thì chuyện gì xảy raRồi lại sinh thêm nhiều câu hỏi, cả chuyện vì sao không dùng cú pháp kiểu
Timestamp.parse(str)let span = 5.days() + 8.hours() + 1.minutes();hơnCú pháp ban đầu nhìn giống kiểu Ruby hay làm
Khi đọc code, nếu thấy trước là đang xử lý span thì sẽ tốt hơn
let span = Span::days(5).hours(8).minutes(1);trông còn ổn hơnMình đã thấy nhiều trường hợp đánh giá thấp độ phức tạp của thư viện ngày/giờ
Những câu như “biểu diễn nội bộ cứ dùng UTC/Unix time là được”, “duration cứ biểu diễn bằng nanosecond là được”, “thay vì múi giờ chỉ cần dùng offset là được” cứ lặp đi lặp lại
Nếu bạn nghĩ như vậy thì nên đọc tài liệu thiết kế của Jiff: https://github.com/BurntSushi/jiff/blob/master/DESIGN.md
Đúng chất các sản phẩm của BurntSushi: xuất sắc và chi tiết
Phần so sánh với chrono, thư viện ngày/giờ gần như là chuẩn de facto của Rust, cũng đáng đọc: https://docs.rs/jiff/latest/jiff/_documentation/comparison/i...
Những yếu tố như tính toán DST vẫn đúng sau khi serialize/deserialize, duration có thể làm tròn, phép tính lịch có nhận biết múi giờ, phát hiện xung đột múi giờ trong quá khứ khiến thư viện trở nên chính xác, hữu ích và dễ dùng
chrono là một thư viện bao quát và “chính xác”, nhưng khá cứng nhắc và không dễ dùng
Mình đã dùng ripgrep từ trước khi Debian cung cấp sẵn
rg, và cũng từng dùng JodaTime vào thời vẫn còn những người nghĩ Eclipse tốt hơn IntelliJ IDEANhưng trong tài liệu đó không có chỗ nào phản bác câu “duration cứ biểu diễn bằng nanosecond là được”
Người dùng cần thấy thời điểm đúng theo múi giờ và DST, nhưng chương trình thì thường không cần như vậy
Nếu không phải là công việc trực tiếp xử lý nhiều múi giờ, biểu diễn duration bằng millisecond/nanosecond nhìn chung là rất an toàn
Việc con người tạo ra múi giờ và DST không làm thay đổi hiện tượng vật lý rằng đồng hồ bên trong CPU tích tắc hàng tỷ lần mỗi giây
Chỉ cần nhìn các kernel hệ điều hành không bị nổ tung trên nửa Trái Đất vài ngày trước cũng thấy có rất nhiều nơi timeout trong code được biểu diễn theo millisecond
Có thể bị hiểu lầm thành kiểu khi triển khai “cooldown 30 giây” thì phải cộng 30 giây vào giờ hiện tại theo múi giờ hiện tại, lưu thành chuỗi bao gồm cả múi giờ, DST, định dạng 12/24 giờ, rồi còn phải kiểm tra cả giây nhuận
Chỉ cần dùng timeout/cooldown 30 giây biểu diễn bằng millisecond là được; dù theo người dùng nó xảy ra sau 29 giây thì cũng không cần bận tâm đến giây nhuận
Trông như một thư viện rất hay
Tò mò vì sao BurntSushi lại tạo một thư viện mới
Tôi chưa xử lý thời gian nhiều trong Rust, nên muốn biết liệu các thư viện hiện có có vấn đề về hiệu năng, API hiện tại khó dùng, hay chỉ làm cho vui, hoặc có lý do nào khác không
Phần triển khai trong thư viện chuẩn Java không tốt, và JodaTime ra đời để giải quyết vấn đề đó
Sau đó Java 8 đưa vào DateTime API mới chịu ảnh hưởng lớn từ JodaTime, và vì nó nằm trong thư viện chuẩn nên các tác giả thư viện có thể áp dụng rộng rãi hơn
https://www.joda.org/joda-time/
https://www.baeldung.com/java-8-date-time-intro
https://github.com/BurntSushi/jiff/blob/master/DESIGN.md
https://github.com/BurntSushi/jiff/blob/master/COMPARE.md
Tóm lại, chủ yếu là vì API và hỗ trợ múi giờ IANA
Tình trạng thư viện lịch của Rust không thật sự lý tưởng
Trong Pandas có
.tz_convert()và.tz_localize(), và việc chuyển đổi múi giờ về cơ bản chỉ cần thế là xongTác vụ tôi lấy làm chuẩn là “lấy giờ đầu tiên của một ngày CET/CEST theo UTC cho một ngày cho trước”, và trong Pandas thì cực kỳ đơn giản
Với Chrono, phải tạo
NaiveDate, đổi sangDateTime, rồi lại đổi sangDateTime, và tôi không tìm được một mẫu nhất quán nào cho những phép chuyển đổi nàyLúc thì là hàm thành viên, lúc thì là phương thức tĩnh của đối tượng múi giờ
Hy vọng một ngày nào đó sẽ có người sửa lại. Jiff trông như một bước đi đúng hướng, nhưng cú pháp đôi khi hơi kỳ, nên tôi muốn một dạng dễ dự đoán hơn
date(y, m, d).intz("Europe/Rome")?Nếu muốn lấy UTC từ đó thì chỉ cần thêm
with_time_zone(TimeZone::UTC)Tôi muốn bạn giải thích thêm phần cú pháp kỳ ở đâu, và Jiff có thể cải thiện như thế nào
“Em yêu dậy đi, BurntSushi mới ra rồi”[0]
Nghiêm túc mà nói, tôi tò mò liệu trong số người dùng Rust ở đây có ai biết vì sao crate này không dùng những thứ như
tracingkhôngCó thể là vì nó quá nặng. Tính theo crate thì kích thước gần như bằng một nửa
logtất nhiên cũng ổn, và tôi cũng không biết việc trace đến từng lời gọi thao tác múi giờ có phải use case phổ biến không, nhưng tôi tò mò liệu có lý do đặc biệt nào không[0] https://knowyourmeme.com/memes/wake-up-babe
loggần như là mẫu số chung nhỏ nhấtNó cũng tích hợp tốt với
tracing, nên có lẽ đó là lý do Jiff dùng nóNó cũng là thứ đã được dùng liên tục từ những ngày đầu crates.io xuất hiện
Jiff không có yêu cầu gì ngoài việc phát ra thông điệp ở một mức log nhất định
Tất nhiên tôi nghĩ
tracingcũng sẽ hoạt động tốtBurntSushi cũng là người đã tạo ra toàn bộ hệ sinh thái biểu thức chính quy của Rust
Điều đáng tiếc nhất ở các thư viện thời gian hiện có, dù trong Rust hay hệ sinh thái khác, là thiếu hỗ trợ giây nhuận
Phần lớn là vì bên trong chúng dùng UNIX timestamp thay vì TAI, và tiếc là thư viện này cũng không khác
Có vẻ có cách hỗ trợ phần nào bằng file tzif, nhưng không phải hỗ trợ hạng nhất
Issue liên quan của Jiff có chi tiết tại: https://github.com/BurntSushi/jiff/issues/7
UNIX timestamp không dùng định nghĩa giây SI mà dùng 1/86000 của ngày hiện tại, nên không phải mọi giây UNIX đều có độ dài như nhau, làm hỏng phép tính khoảng thời gian chính xác
Tôi hiểu đó là sự đánh đổi để kế thừa khả năng tương thích với cách theo dõi thời gian cũ và để định dạng lịch nhanh hơn, nhưng tôi không nghĩ đó là sự đánh đổi đúng
UNIX timestamp trộn lẫn mối quan tâm về biểu diễn với dữ liệu
Tôi nghĩ giây nhuận nên được xử lý đúng theo cùng cách như ngày 29 tháng 2 hay múi giờ
hifitimelại không phù hợpMỗi khi nói đến giây nhuận, câu chuyện thường có vẻ trôi sang hướng trừu tượng
Rất hiếm khi nó gắn với use case trong thế giới thực
Tôi hiểu các use case khoa học, và tôi nghĩ các thư viện chuyên biệt xử lý tốt những mục đích đó
Ngoài ra thì tôi không rõ còn cần gì thêm không
Jiffđược phát âm với G cứng, tức là “Giff”gohay G tronggin“Jiff được phát âm là
gifvới g mềm như tronggem.”Tuyệt đối không được!
Tôi sẽ ở lại với chrono †
† Tất nhiên phát âm như
chtrongchase, tức là “trono”Vậy rốt cuộc phát âm là jiff, hay là gif?
“Jiff được phát âm là
gifvới âm g mềm, như tronggem.”À, hiểu trò đùa là gì rồi…
Theo người tạo ra GIF thì nó luôn được phát âm là “jif”, nên để tránh nhầm lẫn, mong tác giả của công cụ mới này phát âm Jiff là “gif”