Date & Time #
Penanganan tanggal dan waktu di Rust terbagi dua level: standard library menyediakan std::time untuk kebutuhan sederhana seperti mengukur durasi dan mengambil timestamp Unix, tapi tidak mengenal konsep “tanggal” atau “zona waktu”. Untuk kebutuhan yang lebih lengkap — parsing tanggal, format lokal, konversi timezone, kalkulasi “30 hari dari sekarang” — kamu membutuhkan crate chrono. Artikel ini membahas keduanya: std::time untuk penggunaan tanpa dependensi eksternal, dan chrono untuk semua kebutuhan datetime yang nyata di aplikasi produksi.
std::time — DateTime Tanpa Dependensi
#
Standard library menyediakan dua tipe utama untuk waktu:
flowchart LR
ST["std::time"]
ST --> I["Instant\nWaktu monotonic\nUntuk mengukur durasi\nTidak bisa dibandingkan\ndengan waktu nyata"]
ST --> S["SystemTime\nWaktu sistem (wall clock)\nBisa dikonversi ke Unix timestamp\nBisa maju atau mundur (NTP)"]Instant — Mengukur Durasi
#
Instant adalah waktu monotonic — ia hanya bisa maju, tidak pernah mundur (berbeda dari system clock yang bisa diubah oleh NTP atau admin). Gunakan ini untuk mengukur berapa lama sesuatu berjalan:
use std::time::{Instant, Duration};
use std::thread;
fn hitung_fibonacci(n: u64) -> u64 {
match n {
0 => 0,
1 => 1,
n => hitung_fibonacci(n - 1) + hitung_fibonacci(n - 2),
}
}
fn main() {
// Catat waktu mulai
let mulai = Instant::now();
let hasil = hitung_fibonacci(35);
// Hitung elapsed time
let durasi = mulai.elapsed();
println!("fib(35) = {}", hasil);
println!("Waktu komputasi: {:?}", durasi);
println!("Dalam milidetik: {} ms", durasi.as_millis());
println!("Dalam mikrodetik: {} µs", durasi.as_micros());
// Simulasi operasi dengan sleep
let t = Instant::now();
thread::sleep(Duration::from_millis(100));
println!("Tidur 100ms, aktual: {:?}", t.elapsed());
// Bandingkan dua Instant
let awal = Instant::now();
thread::sleep(Duration::from_millis(10));
let akhir = Instant::now();
println!("akhir > awal: {}", akhir > awal);
println!("Selisih: {:?}", akhir.duration_since(awal));
}
SystemTime dan Unix Timestamp
#
SystemTime merepresentasikan waktu sistem — bisa dikonversi ke Unix timestamp (detik sejak 1 Januari 1970 UTC):
use std::time::{SystemTime, UNIX_EPOCH, Duration};
fn main() {
// Waktu sekarang
let sekarang = SystemTime::now();
// Konversi ke Unix timestamp
match sekarang.duration_since(UNIX_EPOCH) {
Ok(durasi) => {
println!("Unix timestamp: {} detik", durasi.as_secs());
println!("Unix timestamp: {} ms", durasi.as_millis());
}
Err(e) => println!("Error: {}", e),
}
// Buat SystemTime dari Unix timestamp
let timestamp = UNIX_EPOCH + Duration::from_secs(1_700_000_000);
println!("Dari timestamp 1700000000: {:?}", timestamp);
// Selisih dua SystemTime
let t1 = SystemTime::now();
std::thread::sleep(Duration::from_millis(50));
let t2 = SystemTime::now();
match t2.duration_since(t1) {
Ok(d) => println!("Selisih: {:?}", d),
Err(_) => println!("t2 lebih awal dari t1"), // bisa terjadi akibat NTP adjustment
}
}
Duration — Merepresentasikan Selang Waktu
#
Duration dari standard library merepresentasikan selang waktu non-negatif:
use std::time::Duration;
fn main() {
// Membuat Duration
let satu_detik = Duration::from_secs(1);
let setengah_detik = Duration::from_millis(500);
let satu_menit = Duration::from_secs(60);
let satu_jam = Duration::from_secs(3600);
let presisi_tinggi = Duration::from_nanos(1_500_000); // 1.5 ms
// Operasi aritmetika
let dua_detik = satu_detik + satu_detik;
let nol_koma_lima = satu_detik / 2;
println!("2 detik: {:?}", dua_detik);
println!("0.5 detik: {:?}", nol_koma_lima);
// Konversi satuan
let durasi = Duration::from_secs(3723); // 1 jam 2 menit 3 detik
println!("Detik: {}", durasi.as_secs());
println!("Milidetik: {}", durasi.as_millis());
println!("Jam (bulat): {}", durasi.as_secs() / 3600);
println!("Menit sisa: {}", (durasi.as_secs() % 3600) / 60);
// Membandingkan Duration
println!("1 menit > 30 detik: {}", satu_menit > setengah_detik);
println!("Terbesar: {:?}", satu_jam.max(satu_menit));
}
Crate chrono — Tanggal dan Waktu Lengkap
#
Untuk kebutuhan yang lebih dari sekadar timestamp dan durasi — parsing tanggal dari string, format ke ISO 8601, kalkulasi “berapa hari sampai deadline”, konversi timezone — gunakan crate chrono.
Tambahkan ke Cargo.toml:
[dependencies]
chrono = "0.4"
Peta Tipe di chrono
#
flowchart TD
C["Tipe chrono"]
C --> N["Naive (tanpa timezone)"]
C --> TZ["Timezone-aware"]
N --> ND["NaiveDate\nHanya tanggal\n2024-08-24"]
N --> NT["NaiveTime\nHanya waktu\n14:30:00"]
N --> NDT["NaiveDateTime\nTanggal + waktu\ntanpa timezone"]
TZ --> DU["DateTime<Utc>\nWaktu UTC"]
TZ --> DL["DateTime<Local>\nWaktu lokal sistem"]
TZ --> DF["DateTime<FixedOffset>\nOffset tetap\n+07:00, -05:00"]| Tipe | Timezone | Kapan digunakan |
|---|---|---|
NaiveDate | Tidak | Tanggal kalender, ulang tahun, deadline |
NaiveTime | Tidak | Waktu tanpa konteks timezone |
NaiveDateTime | Tidak | Timestamp di database, log lokal |
DateTime<Utc> | UTC | Timestamp server, API response |
DateTime<Local> | Lokal sistem | Tampilan ke pengguna |
DateTime<FixedOffset> | Offset tetap | Parse dari string dengan offset |
Mendapatkan Waktu Sekarang #
use chrono::prelude::*;
fn main() {
// Waktu UTC sekarang
let utc_now: DateTime<Utc> = Utc::now();
println!("UTC: {}", utc_now);
println!("UTC RFC3339: {}", utc_now.to_rfc3339());
// Waktu lokal sekarang (timezone sistem)
let lokal_now: DateTime<Local> = Local::now();
println!("Lokal: {}", lokal_now);
// Hanya tanggal hari ini
let hari_ini: NaiveDate = Local::now().date_naive();
println!("Hari ini: {}", hari_ini);
// Hanya waktu sekarang
let waktu_kini: NaiveTime = Local::now().time();
println!("Jam sekarang: {}", waktu_kini.format("%H:%M:%S"));
// Unix timestamp dari chrono
let ts = utc_now.timestamp();
let ts_ms = utc_now.timestamp_millis();
println!("Unix timestamp: {}", ts);
println!("Unix timestamp ms: {}", ts_ms);
}
Membuat DateTime dari Nilai Tertentu #
Artikel asli menggunakan API lama yang sudah deprecated. Berikut cara yang benar untuk chrono 0.4 terbaru:
use chrono::prelude::*;
fn main() {
// NaiveDate — hanya tanggal
let tanggal = NaiveDate::from_ymd_opt(2024, 8, 24)
.expect("Tanggal tidak valid");
println!("Tanggal: {}", tanggal);
// NaiveTime — hanya waktu
let waktu = NaiveTime::from_hms_opt(14, 30, 45)
.expect("Waktu tidak valid");
println!("Waktu: {}", waktu);
// NaiveDateTime — gabungan tanggal dan waktu
let naive_dt = NaiveDateTime::new(tanggal, waktu);
println!("NaiveDateTime: {}", naive_dt);
// DateTime<Utc> dari NaiveDateTime
let utc_dt = naive_dt.and_utc();
println!("UTC: {}", utc_dt);
// DateTime<FixedOffset> — dengan offset WIB (+07:00)
let wib = FixedOffset::east_opt(7 * 3600).unwrap();
let wib_dt = wib.from_local_datetime(&naive_dt)
.single()
.expect("Waktu ambigu");
println!("WIB: {}", wib_dt);
// Dari Unix timestamp
let dari_ts = DateTime::from_timestamp(1_700_000_000, 0)
.expect("Timestamp tidak valid");
println!("Dari timestamp: {}", dari_ts);
// Validasi tanggal — from_ymd_opt mengembalikan None untuk tanggal tidak valid
let tidak_valid = NaiveDate::from_ymd_opt(2024, 2, 30);
println!("30 Feb 2024: {:?}", tidak_valid); // None
let valid = NaiveDate::from_ymd_opt(2024, 2, 29); // 2024 tahun kabisat
println!("29 Feb 2024: {:?}", valid); // Some(2024-02-29)
}
Aritmetika Tanggal dan Waktu #
use chrono::prelude::*;
use chrono::Duration;
fn main() {
let sekarang = Utc::now();
// Tambah dan kurangi Duration
let besok = sekarang + Duration::days(1);
let seminggu_lalu = sekarang - Duration::weeks(1);
let dua_jam_lagi = sekarang + Duration::hours(2);
let tiga_puluh_menit_lalu = sekarang - Duration::minutes(30);
println!("Sekarang: {}", sekarang.format("%Y-%m-%d %H:%M:%S"));
println!("Besok: {}", besok.format("%Y-%m-%d %H:%M:%S"));
println!("Seminggu lalu: {}", seminggu_lalu.format("%Y-%m-%d"));
println!("2 jam lagi: {}", dua_jam_lagi.format("%H:%M:%S"));
// Operasi pada NaiveDate — tambah bulan dan tahun
let hari_ini = NaiveDate::from_ymd_opt(2024, 8, 24).unwrap();
// checked_add_months — aman untuk akhir bulan
use chrono::Months;
let bulan_depan = hari_ini.checked_add_months(Months::new(1))
.expect("Overflow");
println!("Bulan depan: {}", bulan_depan);
let tahun_depan = hari_ini.checked_add_months(Months::new(12))
.expect("Overflow");
println!("Tahun depan: {}", tahun_depan);
// Selisih dua DateTime — chrono::Duration
let deadline = NaiveDate::from_ymd_opt(2024, 12, 31).unwrap();
let sisa_hari = deadline.signed_duration_since(hari_ini).num_days();
println!("Sisa hari hingga akhir tahun: {} hari", sisa_hari);
// Selisih dua DateTime<Utc>
let t1 = Utc::now();
std::thread::sleep(std::time::Duration::from_millis(50));
let t2 = Utc::now();
let selisih = t2.signed_duration_since(t1);
println!("Selisih: {} ms", selisih.num_milliseconds());
// Perbandingan DateTime
let d1 = NaiveDate::from_ymd_opt(2024, 1, 1).unwrap();
let d2 = NaiveDate::from_ymd_opt(2024, 6, 15).unwrap();
println!("d1 < d2: {}", d1 < d2);
println!("d2 > d1: {}", d2 > d1);
println!("Terbaru: {}", d1.max(d2));
}
Format dan Parsing #
Format ke String #
chrono menggunakan format string mirip strftime di C. Berikut simbol-simbol yang paling sering digunakan:
| Simbol | Makna | Contoh output |
|---|---|---|
%Y | Tahun 4 digit | 2024 |
%m | Bulan 2 digit | 08 |
%d | Hari 2 digit | 24 |
%H | Jam (00–23) | 14 |
%M | Menit | 30 |
%S | Detik | 45 |
%3f | Milidetik | 123 |
%A | Nama hari lengkap | Saturday |
%B | Nama bulan lengkap | August |
%a | Nama hari pendek | Sat |
%b | Nama bulan pendek | Aug |
%Z | Nama timezone | UTC |
%z | Offset timezone | +0700 |
use chrono::prelude::*;
fn main() {
let dt = Utc::now();
// Format umum
println!("{}", dt.format("%Y-%m-%d")); // 2024-08-24
println!("{}", dt.format("%d/%m/%Y")); // 24/08/2024
println!("{}", dt.format("%Y-%m-%d %H:%M:%S")); // 2024-08-24 14:30:45
println!("{}", dt.format("%H:%M")); // 14:30
println!("{}", dt.format("%A, %d %B %Y")); // Saturday, 24 August 2024
// Format standar internasional
println!("{}", dt.to_rfc3339()); // 2024-08-24T14:30:45.123456789Z
println!("{}", dt.to_rfc2822()); // Sat, 24 Aug 2024 14:30:45 +0000
// Ke string
let sebagai_string = dt.format("%Y-%m-%d").to_string();
println!("Tipe String: {}", sebagai_string);
}
Parsing dari String #
use chrono::prelude::*;
fn main() {
// Parse NaiveDateTime (tanpa timezone)
let s = "2024-08-24 14:30:45";
let dt = NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S")
.expect("Format tidak cocok");
println!("Parsed: {}", dt);
// Parse NaiveDate saja
let tgl_str = "24/08/2024";
let tgl = NaiveDate::parse_from_str(tgl_str, "%d/%m/%Y")
.expect("Format tanggal tidak cocok");
println!("Tanggal: {}", tgl);
// Parse DateTime dengan timezone (RFC3339 / ISO 8601)
let rfc_str = "2024-08-24T14:30:45+07:00";
let dt_tz = DateTime::parse_from_rfc3339(rfc_str)
.expect("Bukan format RFC3339");
println!("RFC3339: {}", dt_tz);
// Parse dengan error handling yang tepat
let input = "bukan-tanggal";
match NaiveDate::parse_from_str(input, "%Y-%m-%d") {
Ok(d) => println!("Berhasil: {}", d),
Err(e) => println!("Gagal parse '{}': {}", input, e),
}
// Parse dan konversi ke UTC
let lokal_str = "2024-08-24T14:30:45+07:00";
let dt_wib = DateTime::parse_from_rfc3339(lokal_str).unwrap();
let dt_utc: DateTime<Utc> = dt_wib.into();
println!("WIB: {}", dt_wib);
println!("UTC: {}", dt_utc);
}
Konversi Zona Waktu #
use chrono::prelude::*;
fn main() {
let utc_now: DateTime<Utc> = Utc::now();
// Konversi ke offset tetap
let wib = FixedOffset::east_opt(7 * 3600).unwrap(); // UTC+7
let wita = FixedOffset::east_opt(8 * 3600).unwrap(); // UTC+8
let wit = FixedOffset::east_opt(9 * 3600).unwrap(); // UTC+9
let est = FixedOffset::west_opt(5 * 3600).unwrap(); // UTC-5
let wib_now = utc_now.with_timezone(&wib);
let wita_now = utc_now.with_timezone(&wita);
let wit_now = utc_now.with_timezone(&wit);
let est_now = utc_now.with_timezone(&est);
println!("UTC : {}", utc_now.format("%Y-%m-%d %H:%M:%S %Z"));
println!("WIB : {}", wib_now.format("%Y-%m-%d %H:%M:%S %z"));
println!("WITA : {}", wita_now.format("%Y-%m-%d %H:%M:%S %z"));
println!("WIT : {}", wit_now.format("%Y-%m-%d %H:%M:%S %z"));
println!("EST : {}", est_now.format("%Y-%m-%d %H:%M:%S %z"));
// Untuk timezone database lengkap (Asia/Jakarta, America/New_York, dll.)
// gunakan crate chrono-tz:
// [dependencies]
// chrono = "0.4"
// chrono-tz = "0.9"
//
// use chrono_tz::Asia::Jakarta;
// let jakarta_now = utc_now.with_timezone(&Jakarta);
// Simpan waktu sebagai UTC, tampilkan dalam lokal
let simpan_di_db: DateTime<Utc> = Utc::now();
let tampil_ke_user = simpan_di_db.with_timezone(&wib);
println!("\nDisimpan (UTC): {}", simpan_di_db.to_rfc3339());
println!("Ditampilkan (WIB): {}", tampil_ke_user.format("%d/%m/%Y %H:%M"));
}
Untuk konversi timezone dengan nama IANA penuh sepertiAsia/Jakarta,America/New_York, atauEurope/London, tambahkan cratechrono-tzkeCargo.toml. Crate ini menyertakan database timezone lengkap secara compile-time tanpa dependensi sistem.
Kasus Penggunaan Nyata #
Menghitung Usia dari Tanggal Lahir #
use chrono::prelude::*;
fn hitung_usia(tanggal_lahir: NaiveDate) -> u32 {
let hari_ini = Local::now().date_naive();
let tahun_penuh = hari_ini.year() - tanggal_lahir.year();
// Cek apakah ulang tahun tahun ini sudah lewat
let sudah_ulang_tahun = (hari_ini.month(), hari_ini.day())
>= (tanggal_lahir.month(), tanggal_lahir.day());
if sudah_ulang_tahun {
tahun_penuh as u32
} else {
(tahun_penuh - 1) as u32
}
}
fn main() {
let lahir = NaiveDate::from_ymd_opt(1995, 8, 24).unwrap();
println!("Usia: {} tahun", hitung_usia(lahir));
}
Timestamp untuk Log dan Audit Trail #
use chrono::prelude::*;
struct CatatanAudit {
aksi: String,
pengguna: String,
waktu: DateTime<Utc>,
}
impl CatatanAudit {
fn baru(aksi: &str, pengguna: &str) -> Self {
CatatanAudit {
aksi: aksi.to_string(),
pengguna: pengguna.to_string(),
waktu: Utc::now(),
}
}
fn tampilkan(&self) {
println!(
"[{}] {} oleh {}",
self.waktu.format("%Y-%m-%dT%H:%M:%S%.3fZ"),
self.aksi,
self.pengguna
);
}
}
fn main() {
let log = vec![
CatatanAudit::baru("LOGIN", "[email protected]"),
CatatanAudit::baru("BUAT_DOKUMEN", "[email protected]"),
CatatanAudit::baru("HAPUS_DATA", "[email protected]"),
];
for catatan in &log {
catatan.tampilkan();
}
}
Ringkasan #
std::time::Instantuntuk mengukur durasi — monotonic, tidak pernah mundur, tepat untuk benchmarking dan timeout. Bukan untuk tanggal atau format.std::time::SystemTimeuntuk Unix timestamp — bisa dikonversi ke detik sejak epoch viaduration_since(UNIX_EPOCH). Perlu penanganan error karena bisa gagal akibat NTP.chronountuk semua kebutuhan datetime lengkap — tanggal, waktu, timezone, format, dan parsing.- Naive vs timezone-aware —
NaiveDate/NaiveDateTimeuntuk data tanpa konteks timezone (kalender, database lokal);DateTime<Utc>untuk timestamp server;DateTime<Local>untuk tampilan ke pengguna.- Selalu simpan sebagai UTC, tampilkan dalam lokal — ini konvensi standar untuk menghindari bug timezone di aplikasi multi-wilayah.
- Gunakan API
_optuntuk validasi —NaiveDate::from_ymd_opt(2024, 2, 30)mengembalikanNone(tanggal tidak valid), bukan panic seperti API lama yang deprecated.to_rfc3339()untuk serialisasi — format ISO 8601 dengan timezone adalah standar untuk API dan database.chrono-tzuntuk timezone database lengkap — crate terpisah yang menyertakan semua nama IANA timezone (Asia/Jakarta, dll.) secara compile-time.Durationdari chrono berbeda daristd::time::Duration— chronoDurationbisa negatif (untuk selisih),std::time::Durationselalu non-negatif.