Tanggal dan Waktu di Rust dengan chrono
Program selalu berurusan dengan waktu. “Berapa lama proses ini berjalan?” “Kapan tagihan ini jatuh tempo?” “Tinggal berapa hari lagi sebelum liburan?” Rust membagi pertanyaan seperti ini menjadi dua jenis.
Modul std::time dari standard library adalah sebuah stopwatch: ia mengukur berapa banyak waktu yang berlalu. Ia tidak tahu apa-apa soal kalender, bulan, atau nama hari. Untuk urusan kalender, hampir semua proyek Rust memakai crate (library Rust) chrono. Halaman ini dimulai dari stopwatch, lalu lanjut ke kalender.
Mengukur waktu yang berlalu dengan Instant
Section titled “Mengukur waktu yang berlalu dengan Instant”Instant::now() mencatat waktu saat ini dari jam komputer. Nanti, .elapsed() memberi tahu berapa lama waktu yang sudah berlalu sejak catatan itu, dalam bentuk Duration:
use std::time::{Duration, Instant};
fn main() { let start = Instant::now();
let mut total: u64 = 0; for i in 0..1_000_000 { total += i; }
let elapsed = start.elapsed(); println!("total = {total}"); println!("butuh {:?}", elapsed); println!("butuh {} ms", elapsed.as_millis()); println!("kurang dari 5 detik? {}", elapsed < Duration::from_secs(5));}Output-nya sengaja tidak kami tampilkan: waktunya berbeda di setiap komputer dan setiap kali dijalankan. Kamu akan melihat sesuatu seperti butuh 6.862793ms. Coba klik tombolnya dua kali dan bandingkan hasilnya.
Instant hanya berguna untuk mengukur. Kamu tidak bisa mencetaknya sebagai tanggal, dan nilainya hanya bergerak maju. Justru itu yang kamu butuhkan untuk mengukur kecepatan kode, karena Instant tidak terpengaruh kalau seseorang mengubah jam sistem.
Durasi
Section titled “Durasi”Duration adalah sebuah rentang waktu, misalnya “90 detik”. Kamu bisa membuatnya dari detik atau milidetik, menjumlahkan beberapa durasi, dan membandingkannya:
use std::time::Duration;
fn main() { let short = Duration::from_millis(1500); let long = Duration::from_secs(90);
println!("{:?}", short); println!("{:?}", long); println!("{} ms", short.as_millis()); println!("{:.1} s", short.as_secs_f64());
let total = short + long; println!("total: {:?}", total); println!("{} menit {} detik", long.as_secs() / 60, long.as_secs() % 60); println!("{}", short < long);}Output:
1.5s90s1500 ms1.5 stotal: 91.5s1 menit 30 detiktrueKamu akan sering bertemu Duration: std::thread::sleep(Duration::from_millis(100)) menghentikan sebuah thread sejenak, dan batas waktu (timeout) di library jaringan juga memakai Duration.
Waktu jam dinding dengan SystemTime
Section titled “Waktu jam dinding dengan SystemTime”SystemTime adalah waktu di jam komputer, yang kamu lihat di pojok layar. Biasanya kita memakainya untuk bertanya sudah berapa lama sejak Unix epoch, yaitu 1 Januari 1970 (UTC). Angka itu disebut Unix timestamp:
use std::time::{SystemTime, UNIX_EPOCH};
fn main() { let now = SystemTime::now(); let since_1970 = now .duration_since(UNIX_EPOCH) .expect("jam diatur ke sebelum 1970"); println!("Detik sejak 1 Jan 1970: {}", since_1970.as_secs());}Angkanya tergantung kapan kamu menjalankannya, jadi sekali lagi tidak ada output yang ditampilkan. Kenapa duration_since mengembalikan Result? Karena jam sistem bisa saja salah atau dimundurkan. Itu juga alasan kenapa kamu sebaiknya memakai Instant, bukan SystemTime, untuk mengukur kecepatan kode.
Kurang lebih hanya itu yang bisa dilakukan std::time. Ia sama sekali tidak tahu arti “Maret” atau “Jumat”. Untuk itu, kamu butuh chrono.
Menambahkan chrono
Section titled “Menambahkan chrono”Tambahkan crate-nya ke proyekmu (lihat Cargo dan crate kalau ini masih baru buatmu):
cargo add chrono[dependencies]chrono = "0.4"Rust Playground juga sudah menyertakan chrono, jadi semua tombol di bawah bisa dipakai. chrono punya dua keluarga tipe:
- Tipe naive (
NaiveDate,NaiveTime,NaiveDateTime) adalah tanggal dan jam tanpa zona waktu. Seperti “14 Maret 2025” atau “09:30” yang ditulis di kalender. Sebagian besar kode sehari-hari memakai tipe ini. DateTime<Tz>adalah satu titik waktu dengan zona waktu, sepertiDateTime<Utc>atauDateTime<Local>.
Membuat tanggal dari nilai tetap
Section titled “Membuat tanggal dari nilai tetap”NaiveDate::from_ymd_opt(tahun, bulan, tanggal) membuat sebuah tanggal. Hasilnya berupa Option, karena tidak semua kombinasi angka adalah tanggal yang benar-benar ada:
use chrono::{Datelike, NaiveDate, NaiveTime};
fn main() { let date = NaiveDate::from_ymd_opt(2025, 3, 14).unwrap(); println!("{date}"); println!("tahun {}, bulan {}, tanggal {}", date.year(), date.month(), date.day());
let time = NaiveTime::from_hms_opt(9, 30, 0).unwrap(); let meeting = date.and_time(time); println!("{meeting}");
let other = date.and_hms_opt(17, 45, 10).unwrap(); println!("{other}");
let impossible = NaiveDate::from_ymd_opt(2025, 2, 30); println!("{:?}", impossible);}Output:
2025-03-14tahun 2025, bulan 3, tanggal 142025-03-14 09:30:002025-03-14 17:45:10NoneTanggal 30 Februari tidak ada, jadi kamu mendapat None, bukan tanggal yang rusak. Untuk nilai tetap yang kamu ketik sendiri, .unwrap() tidak masalah. Untuk nilai dari pengguna, tangani kasus None-nya.
Baris use chrono::Datelike membawa method .year(), .month(), dan .day(). (Timelike melakukan hal yang sama untuk .hour() dan .minute().)
Tanggal dan jam saat ini
Section titled “Tanggal dan jam saat ini”Utc::now() memberi waktu saat ini dalam UTC. Local::now() memberinya dalam zona waktu komputer itu sendiri:
use chrono::{Local, Utc};
fn main() { let utc_now = Utc::now(); let local_now = Local::now();
println!("UTC: {}", utc_now); println!("Lokal: {}", local_now); println!("Hari ini: {}", local_now.format("%Y-%m-%d"));
let today = local_now.date_naive(); println!("Sebagai NaiveDate: {today}");}Output-nya adalah tanggal hari ini, jadi berubah setiap hari. Server Playground memakai UTC, jadi di sana kedua baris pertama menunjukkan jam yang sama. .date_naive() membuang zona waktu dan jamnya, sehingga yang tersisa hanya NaiveDate biasa. Kebiasaan yang baik: ambil “sekarang” satu kali saja di bagian luar program, lalu kirim tanggalnya ke fungsi-fungsimu. Dengan begitu fungsimu tetap mudah dites, seperti yang akan kamu lihat di contoh tenggat waktu nanti.
Memformat tanggal
Section titled “Memformat tanggal”.format() menerima sebuah pola. Setiap kode % diganti dengan bagian dari tanggal. %Y adalah tahun, %m nomor bulan, %d tanggal:
use chrono::NaiveDate;
fn main() { let date = NaiveDate::from_ymd_opt(2025, 3, 14).unwrap(); let dt = date.and_hms_opt(9, 5, 0).unwrap();
println!("{}", date.format("%Y-%m-%d")); println!("{}", date.format("%d/%m/%Y")); println!("{}", date.format("%A, %B %-d, %Y")); println!("{}", date.format("%a %d %b")); println!("{}", dt.format("%H:%M")); println!("{}", dt.format("%-I:%M %p"));
let file_name = date.format("laporan-%Y%m%d.txt").to_string(); println!("{file_name}");}Output:
2025-03-1414/03/2025Friday, March 14, 2025Fri 14 Mar09:059:05 AMlaporan-20250314.txtNama hari dan bulan (Friday, March) keluar dalam bahasa Inggris, karena itulah bawaan chrono. Kode yang paling sering kamu pakai: %Y tahun, %m bulan (01–12), %d tanggal (01–31), %H jam (00–23), %M menit, %S detik, %A nama hari, %B nama bulan. Tanda - setelah % menghapus nol di depan, jadi %-d mencetak 5, bukan 05. .format() mengembalikan sesuatu yang bisa langsung dicetak. Panggil .to_string() kalau kamu butuh String.
Mengubah teks menjadi tanggal (parsing)
Section titled “Mengubah teks menjadi tanggal (parsing)”NaiveDate::parse_from_str melakukan kebalikannya: ia membaca teks dengan pola % yang sama. Hasilnya berupa Result, karena bisa saja teksnya tidak cocok dengan pola:
use chrono::{NaiveDate, NaiveDateTime};
fn main() { let a = NaiveDate::parse_from_str("2025-03-14", "%Y-%m-%d"); println!("{:?}", a);
let b = NaiveDate::parse_from_str("14/03/2025", "%d/%m/%Y"); println!("{:?}", b);
let c: Result<NaiveDate, _> = "2025-03-14".parse(); println!("{:?}", c);
let d = NaiveDateTime::parse_from_str("2025-03-14 09:30", "%Y-%m-%d %H:%M"); println!("{:?}", d);
for text in ["2025-13-01", "14 Maret", "2025-03"] { match NaiveDate::parse_from_str(text, "%Y-%m-%d") { Ok(date) => println!("Berhasil: {date}"), Err(e) => println!("Gagal mem-parse {text:?}: {e}"), } }}Output:
Ok(2025-03-14)Ok(2025-03-14)Ok(2025-03-14)Ok(2025-03-14T09:30:00)Gagal mem-parse "2025-13-01": input is out of rangeGagal mem-parse "14 Maret": input contains invalid charactersGagal mem-parse "2025-03": premature end of inputUntuk bentuk standar YYYY-MM-DD, kamu juga bisa langsung memanggil .parse(). Bulan ke-13 tidak ada, jadi contoh gagal pertama menghasilkan “out of range” (di luar jangkauan). Pesan error-nya dari chrono, dalam bahasa Inggris: singkat, tapi cukup jelas. “invalid characters” berarti ada karakter yang tidak cocok dengan pola, dan “premature end of input” berarti teksnya habis sebelum polanya selesai.
Aritmetika tanggal
Section titled “Aritmetika tanggal”Menambahkan hari ke sebuah tanggal secara manual ternyata cukup sulit: panjang setiap bulan berbeda, dan ada tahun kabisat. chrono mengurus semuanya untukmu. Pakai checked_add_days dengan Days, checked_add_months dengan Months, atau tambahkan sebuah TimeDelta:
use chrono::{Days, Months, NaiveDate, TimeDelta};
fn main() { let start = NaiveDate::from_ymd_opt(2025, 1, 31).unwrap();
let plus_30 = start.checked_add_days(Days::new(30)).unwrap(); println!("30 hari kemudian: {plus_30}");
let minus_7 = start.checked_sub_days(Days::new(7)).unwrap(); println!("7 hari sebelumnya: {minus_7}");
let next_month = start.checked_add_months(Months::new(1)).unwrap(); println!("1 bulan kemudian: {next_month}");
let week_later = start + TimeDelta::weeks(1); println!("1 minggu kemudian: {week_later}");
let dt = start.and_hms_opt(23, 30, 0).unwrap(); let later = dt + TimeDelta::hours(2); println!("2 jam kemudian: {later}");}Output:
30 hari kemudian: 2025-03-027 hari sebelumnya: 2025-01-241 bulan kemudian: 2025-02-281 minggu kemudian: 2025-02-072 jam kemudian: 2025-02-01 01:30:00Perhatikan baris “1 bulan kemudian”: 31 Januari ditambah satu bulan menjadi 28 Februari, karena tanggal 31 Februari tidak ada. chrono memilih hari terakhir yang valid. Method checked_ mengembalikan Option, yang bernilai None hanya kalau hasilnya keluar dari rentang tahun chrono (yang sangat besar). Menambahkan 2 jam ke pukul 23:30 juga dengan benar berpindah ke hari berikutnya.
Nama hari dan selisih dua tanggal
Section titled “Nama hari dan selisih dua tanggal”.weekday() (dari Datelike) memberi tahu hari apa sebuah tanggal. Mengurangkan satu tanggal dari tanggal lain menghasilkan TimeDelta, dan .num_days() mengubahnya menjadi angka:
use chrono::{Datelike, NaiveDate, Weekday};
fn main() { let date = NaiveDate::from_ymd_opt(2025, 3, 15).unwrap(); let day = date.weekday(); println!("{date} adalah hari {day} ({})", date.format("%A"));
let weekend = matches!(day, Weekday::Sat | Weekday::Sun); println!("akhir pekan? {weekend}");
let start = NaiveDate::from_ymd_opt(2025, 1, 1).unwrap(); let end = NaiveDate::from_ymd_opt(2025, 12, 25).unwrap(); let gap = end - start; println!("{} hari, atau {} minggu penuh", gap.num_days(), gap.num_weeks()); println!("dibalik: {} hari", (start - end).num_days());
let check_in = start.and_hms_opt(14, 0, 0).unwrap(); let check_out = NaiveDate::from_ymd_opt(2025, 1, 4).unwrap().and_hms_opt(11, 0, 0).unwrap(); println!("menginap di hotel: {} jam", (check_out - check_in).num_hours());}Output:
2025-03-15 adalah hari Sat (Saturday)akhir pekan? true358 hari, atau 51 minggu penuhdibalik: -358 harimenginap di hotel: 69 jamWeekday adalah sebuah enum dengan varian Mon sampai Sun, jadi kamu bisa memakai match padanya. Perhatikan bahwa selisihnya bisa negatif kalau tanggal kedua lebih belakang. Ini berguna: sisa hari yang negatif berarti “sudah lewat tenggat”.
Contoh: berapa hari lagi sampai tenggat
Section titled “Contoh: berapa hari lagi sampai tenggat”Mari kita gabungkan semuanya. Fungsi di bawah menerima “hari ini” sebagai parameter, alih-alih memanggil Local::now() di dalamnya. Dengan begitu output di bawah selalu sama, dan fungsinya mudah dites. Di program sungguhan, kamu tinggal mengirim Local::now().date_naive().
use chrono::NaiveDate;
fn days_left(today: NaiveDate, deadline: NaiveDate) -> String { let days = (deadline - today).num_days(); match days { 0 => String::from("jatuh tempo hari ini"), 1 => String::from("jatuh tempo besok"), d if d > 1 => format!("{d} hari lagi"), -1 => String::from("terlambat 1 hari"), d => format!("terlambat {} hari", -d), }}
fn main() { let today = NaiveDate::from_ymd_opt(2025, 6, 10).unwrap();
let tasks = [ ("Bayar sewa", "2025-06-10"), ("Kirim laporan", "2025-06-11"), ("Perpanjang paspor", "2025-07-01"), ("Kembalikan buku", "2025-06-09"), ("Tagihan dokter gigi", "2025-06-03"), ];
println!("Hari ini {}", today.format("%A, %-d %B %Y")); for (name, date_text) in tasks { let deadline = NaiveDate::parse_from_str(date_text, "%Y-%m-%d").unwrap(); println!("{:<20} {} {}", name, deadline.format("%d %b"), days_left(today, deadline)); }}Output:
Hari ini Tuesday, 10 June 2025Bayar sewa 10 Jun jatuh tempo hari iniKirim laporan 11 Jun jatuh tempo besokPerpanjang paspor 01 Jul 21 hari lagiKembalikan buku 09 Jun terlambat 1 hariTagihan dokter gigi 03 Jun terlambat 7 hariCoba ganti today dengan tanggal lain lalu jalankan lagi. {:<20} menambahkan spasi pada nama sampai 20 karakter, sehingga kolomnya rapi (lihat Output).
chrono bukan satu-satunya pilihan. Crate time adalah alternatif populer yang lebih kecil, dan crate jiff yang lebih baru sangat memperhatikan zona waktu dan daylight saving time. Halaman ini memakai chrono karena paling banyak dipakai dan tersedia di Playground.
Kesalahan umum
Section titled “Kesalahan umum”Lupa menulis use chrono::Datelike. Tanpa baris itu, .year(), .month(), dan .weekday() tidak bisa dipakai. Pesan error-nya bilang method itu “private”, yang membingungkan, tapi baris help memberi tahu solusi sebenarnya: trait Datelike belum di-import.
error[E0624]: method `year` is private --> src/main.rs:5:25 |5 | println!("{}", date.year()); | ^^^^ private method | = help: items from traits can only be used if the trait is in scopehelp: trait `Datelike` which provides `year` is implemented but not in scope; perhaps you want to import it |1 + use chrono::Datelike; |Tertukar antara %m dan %M. %m adalah bulan, %M adalah menit. Kalau ada tanggal dan jam, pilihan yang salah tidak menghasilkan error. Programmu diam-diam mencetak menitnya:
use chrono::NaiveDate;
fn main() { let meeting = NaiveDate::from_ymd_opt(2025, 3, 14).unwrap().and_hms_opt(9, 5, 0).unwrap(); println!("{}", meeting.format("%Y-%M-%d")); // salah: menit println!("{}", meeting.format("%Y-%m-%d")); // benar: bulan}Output:
2025-05-142025-03-14Mengukur kecepatan kode dengan SystemTime. Jam sistem bisa melompat (misalnya saat menyinkronkan diri dengan internet). Pakai Instant untuk mengukur, dan pakai SystemTime atau chrono hanya kalau kamu butuh tanggal yang sebenarnya.
Memanggil Local::now() jauh di dalam logika program. Akibatnya hasil berubah setiap hari dan test jadi sulit ditulis. Kirim tanggalnya sebagai parameter, seperti yang dilakukan days_left.
Ringkasan
Section titled “Ringkasan”std::time::Instantmengukur waktu yang berlalu;Durationadalah rentang waktu;SystemTimeadalah jam dinding (cocok untuk Unix timestamp).- Untuk kalender, tambahkan
chrono. PakaiNaiveDate/NaiveDateTimeuntuk tanggal tanpa zona waktu, danUtc::now()/Local::now()untuk waktu saat ini. from_ymd_optmengembalikanOption, danparse_from_strmengembalikanResult, karena tidak semua input adalah tanggal yang benar-benar ada..format("%Y-%m-%d")mengubah tanggal menjadi teks dengan kode%. Ingat:%mitu bulan,%Mitu menit.- Tambahkan waktu dengan
checked_add_days,checked_add_months, atau+ TimeDelta. Kurangkan dua tanggal untuk mendapatTimeDelta, lalu panggil.num_days(). - Kirim “hari ini” ke dalam fungsimu sebagai parameter, jangan membaca jam di dalam fungsi.
Kuis singkat
Section titled “Kuis singkat”Kuis singkat
1. Tipe mana yang sebaiknya kamu pakai untuk mengukur berapa lama sebuah kode berjalan?
2. Apa hasil dari NaiveDate::from_ymd_opt(2025, 2, 30)?
3. Di string format chrono, apa arti %M?
4. Kamu mengurangkan dua NaiveDate: deadline - today. Bagaimana cara mendapatkan jumlah harinya?
5. Berapa hasil 31 Januari 2025 ditambah Months::new(1) di chrono?
Skor: 0 / 5
Bacaan lebih lanjut
Section titled “Bacaan lebih lanjut”- std::time — standard library Rust (bahasa Inggris):
Instant,Duration, danSystemTime, dengan lebih banyak contoh. - Dokumentasi chrono — docs.rs (bahasa Inggris): panduan lengkap untuk setiap tipe dan method chrono.
- Kode format chrono — docs.rs (bahasa Inggris): daftar lengkap kode
%untukformatdanparse_from_str. - Date and Time — Rust Cookbook (bahasa Inggris): resep singkat untuk tugas tanggal dan waktu yang umum.
- Dokumentasi jiff — docs.rs (bahasa Inggris): crate tanggal dan waktu yang lebih baru dengan dukungan zona waktu yang kuat, kalau kamu ingin membandingkan.