Date & Time

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"]
TipeTimezoneKapan digunakan
NaiveDateTidakTanggal kalender, ulang tahun, deadline
NaiveTimeTidakWaktu tanpa konteks timezone
NaiveDateTimeTidakTimestamp di database, log lokal
DateTime<Utc>UTCTimestamp server, API response
DateTime<Local>Lokal sistemTampilan ke pengguna
DateTime<FixedOffset>Offset tetapParse 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:

SimbolMaknaContoh output
%YTahun 4 digit2024
%mBulan 2 digit08
%dHari 2 digit24
%HJam (00–23)14
%MMenit30
%SDetik45
%3fMilidetik123
%ANama hari lengkapSaturday
%BNama bulan lengkapAugust
%aNama hari pendekSat
%bNama bulan pendekAug
%ZNama timezoneUTC
%zOffset 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 seperti Asia/Jakarta, America/New_York, atau Europe/London, tambahkan crate chrono-tz ke Cargo.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::Instant untuk mengukur durasi — monotonic, tidak pernah mundur, tepat untuk benchmarking dan timeout. Bukan untuk tanggal atau format.
  • std::time::SystemTime untuk Unix timestamp — bisa dikonversi ke detik sejak epoch via duration_since(UNIX_EPOCH). Perlu penanganan error karena bisa gagal akibat NTP.
  • chrono untuk semua kebutuhan datetime lengkap — tanggal, waktu, timezone, format, dan parsing.
  • Naive vs timezone-awareNaiveDate/NaiveDateTime untuk 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 _opt untuk validasiNaiveDate::from_ymd_opt(2024, 2, 30) mengembalikan None (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-tz untuk timezone database lengkap — crate terpisah yang menyertakan semua nama IANA timezone (Asia/Jakarta, dll.) secara compile-time.
  • Duration dari chrono berbeda dari std::time::Duration — chrono Duration bisa negatif (untuk selisih), std::time::Duration selalu non-negatif.

← Sebelumnya: Map   Berikutnya: Regex →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact