2 điểm bởi GN⁺ 2024-07-23 | 1 bình luận | Chia sẻ qua WhatsApp
  • 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ợ 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 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ành Timestamp
    • 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"

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 jiff vào phần phụ thuộc trong Cargo.toml củ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ùng Zoned::now().round(Unit::Second)? để làm tròn thời gian hiện tại đến giây
    • Chạy bằng cargo run
  • 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

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
  • 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/zoneinfo và cũng tuân theo thiết lập biến môi trường TZDIR
  • 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
  • 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-icu có 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-atomic khô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

 
GN⁺ 2024-07-23
Ý 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ệ

    • Mình bị vướng ở chỗ sau use jiff::{Timestamp, ToSpan};, "2024-07-11T01:14:00Z".parse()? được parse thành Timestamp
      Mì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ượng Timestamp
      Cũ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ắn parse() vào chuỗi thì chuyện gì xảy ra
      Rồ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)
    • Đồng ý. Cá nhân mình thích kiểu let span = 5.days() + 8.hours() + 1.minutes(); hơn
    • Mình thích phiên bản này vì trông nhất quán
      Cú pháp ban đầu nhìn giống kiểu Ruby hay làm
    • Đồng ý. Theo mạch suy nghĩ thì mình muốn chỉ định trước mình đang làm gì, rồi sau đó mới đưa chi tiết vào, nhưng cú pháp này lại ngược lại
      Khi đọc code, nếu thấy trước là đang xử lý span thì sẽ tốt hơn
    • Đi xa hơn nữa, let span = Span::days(5).hours(8).minutes(1); trông còn ổn hơn
  • Mì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 thích ripgrep của BurntSushi, và vẫn dùng thường xuyên bằng cách gọi trực tiếp từ Emacs
      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 IDEA
      Như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
    • Đọc đến cụm “vẫn hoạt động sau ser/de” mình mới nhận ra vì sao crate Serde lại có tên như vậy
    • Cảm ơn vì đã chỉ tài liệu thiết kế. Lúc mới lướt qua repository mình đã bỏ sót, nhưng đó là tài liệu được viết rất tốt và đã giải đáp nhiều điều mình thắc mắc
    • Nếu muốn xem sự điên rồ của ngày/giờ theo cách thú vị và dễ tiếp cận, bài nói của Kip Cole tại ElixirConf 2022 rất đáng xem: https://www.youtube.com/watch?v=4VfPvCI901c
    • Những ai nghĩ ngày/giờ là dễ thì không nên để gần scheduling hoặc code xử lý ngày tháng
  • 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

  • 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().tz_localize(), và việc chuyển đổi múi giờ về cơ bản chỉ cần thế là xong
    Tá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 sang DateTime, rồi lại đổi sang DateTime, 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ày
    Lú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

    • Có thể làm việc đó bằng 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ư tracing không
    Có 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
    log tấ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

    • Tôi xem log gần như là mẫu số chung nhỏ nhất
      Nó 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ĩ tracing cũng sẽ hoạt động tốt
  • BurntSushi 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

    • Tôi nhớ có lần tranh luận với ai đó trên Reddit về một chuyện gì đó, rồi nhận ra đối phương là ai và nghĩ “thôi được rồi”
  • Đ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ờ

    • Tôi tò mò vì sao lại cần. Use case thực tế là gì, và vì sao các thư viện khoa học chuyên biệt như hifitime lại không phù hợp
      Mỗ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”

    • Tôi không phải người bản ngữ, hard G chính xác nghĩa là gì? Tôi thắc mắc đó là G trong go hay G trong gin
  • “Jiff được phát âm là gif với g mềm như trong gem.”
    Tuyệt đối không được!

    • Đã có một quãng thời gian vui vẻ khi lên hạng nhất Hacker News trong chốc lát, nhưng giờ có vẻ một khiếm khuyết chí mạng đã lộ ra
      Tôi sẽ ở lại với chrono †
      † Tất nhiên phát âm như ch trong chase, tức là “trono”
    • https://x.com/drgonzo123/status/527520297512345600
    • Lạ thật, tôi cứ tưởng Jiff được phát âm với “J” cứng
    • Không phải GIF mà là GIF à?!
  • Vậy rốt cuộc phát âm là jiff, hay là gif?

    • https://github.com/BurntSushi/jiff/blob/master/DESIGN.md#why...
      “Jiff được phát âm là gif với âm g mềm, như trong gem.”
      À, hiểu trò đùa là gì rồi…
    • Tôi nghĩ cách phát âm là do người tạo ra quyết định
      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”
    • Vì đây là thư viện xử lý thời gian nên tôi sẽ cứ gọi nó là biff