Time & Duration #
Hampir setiap program yang cukup kompleks perlu berurusan dengan waktu — mengukur berapa lama suatu operasi berjalan, menentukan apakah suatu deadline sudah terlewat, menerapkan timeout pada network request, atau mencatat kapan suatu kejadian terjadi. Rust menyediakan dua konsep waktu yang sengaja dipisahkan di standard library: Instant untuk pengukuran durasi yang monotonic (selalu maju, tidak terpengaruh perubahan jam sistem), dan SystemTime untuk waktu kalender yang bisa dibandingkan dengan waktu dunia nyata. Perbedaan ini bukan kebetulan — mencampurnya adalah sumber bug yang umum di bahasa lain. Artikel ini membahas keduanya beserta Duration sebagai representasi interval waktu, pola idiomatik penggunaannya, dan cara menangani waktu kalender yang lebih lengkap dengan crate chrono.
Duration — Merepresentasikan Interval Waktu #
Duration adalah tipe yang merepresentasikan interval waktu yang tidak negatif. Ia menyimpan data sebagai jumlah detik dan nanodetik, memberikan presisi hingga satu nanodetik.
use std::time::Duration;
fn main() {
// Membuat Duration dari berbagai unit
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 milidetik
let dari_float = Duration::from_secs_f64(1.5); // 1.5 detik
// Konstanta yang berguna
let nol = Duration::ZERO;
let maks = Duration::MAX;
println!("Satu detik: {:?}", satu_detik); // 1s
println!("Setengah detik: {:?}", setengah_detik); // 500ms
// Mengakses komponen
let durasi = Duration::from_millis(1_500); // 1.5 detik
println!("Detik: {}", durasi.as_secs()); // 1
println!("Subsecond nanos: {}", durasi.subsec_nanos()); // 500_000_000
println!("Total milidetik: {}", durasi.as_millis()); // 1500
println!("Total nanodetik: {}", durasi.as_nanos()); // 1_500_000_000
println!("Sebagai f64 detik: {}", durasi.as_secs_f64()); // 1.5
// Aritmatika Duration
let d1 = Duration::from_secs(10);
let d2 = Duration::from_secs(3);
let penjumlahan = d1 + d2; // 13 detik
let pengurangan = d1 - d2; // 7 detik
let perkalian = d1 * 3; // 30 detik
let pembagian = d1 / 2; // 5 detik
println!("{:?}", penjumlahan); // 13s
println!("{:?}", pembagian); // 5s
// Checked arithmetic — mengembalikan None jika overflow atau underflow
let hasil = d2.checked_sub(d1); // None — hasil negatif tidak valid
println!("{:?}", hasil); // None
let hasil = d1.checked_sub(d2); // Some(7s)
println!("{:?}", hasil); // Some(7s)
// Saturating arithmetic — hasil dibatasi antara ZERO dan MAX
let hasil = d2.saturating_sub(d1); // Duration::ZERO, bukan underflow
println!("{:?}", hasil); // 0ns
// Perbandingan
println!("{}", d1 > d2); // true
println!("{}", d1 == Duration::from_millis(10_000)); // true
}
Instant — Pengukuran Waktu Monotonic #
Instant merepresentasikan titik waktu yang dijamin monotonic — nilainya selalu bertambah, tidak pernah mundur. Ini yang harus digunakan untuk mengukur durasi operasi, bukan SystemTime yang bisa mundur saat jam sistem disetel ulang.
use std::time::Instant;
use std::thread;
use std::time::Duration;
fn main() {
// Ambil titik waktu sekarang
let mulai = Instant::now();
// Lakukan operasi yang ingin diukur
operasi_yang_ingin_diukur();
// Hitung elapsed time
let durasi = mulai.elapsed();
println!("Operasi memakan waktu: {:?}", durasi);
println!("Dalam milidetik: {}", durasi.as_millis());
// elapsed() ekuivalen dengan Instant::now() - mulai
let sekarang = Instant::now();
let manual = sekarang - mulai;
// Instant bisa digunakan untuk deadline
let batas_waktu = Instant::now() + Duration::from_secs(5);
while Instant::now() < batas_waktu {
// kerjakan sesuatu sampai waktu habis
thread::sleep(Duration::from_millis(100));
if kondisi_selesai() {
break;
}
}
// Mengukur banyak operasi
let operasi = vec!["kecil", "sedang", "besar"];
for nama in &operasi {
let t = Instant::now();
simulasi_operasi(nama);
println!("{}: {:?}", nama, t.elapsed());
}
}
fn operasi_yang_ingin_diukur() {
thread::sleep(Duration::from_millis(50));
}
fn kondisi_selesai() -> bool { false }
fn simulasi_operasi(nama: &str) {
let durasi = match nama {
&"kecil" => Duration::from_millis(10),
&"sedang" => Duration::from_millis(50),
_ => Duration::from_millis(100),
};
thread::sleep(durasi);
}
Mengapa Instant, Bukan SystemTime, untuk Pengukuran #
use std::time::{Instant, SystemTime};
fn main() {
// ANTI-PATTERN: menggunakan SystemTime untuk mengukur durasi
let mulai = SystemTime::now();
operasi_lambat();
let durasi = SystemTime::now().duration_since(mulai);
// duration_since mengembalikan Result karena SystemTime bisa mundur!
// Jika jam sistem disetel ke masa lalu selama operasi, hasilnya Err
// BENAR: Instant selalu monotonic, tidak pernah mundur
let mulai = Instant::now();
operasi_lambat();
let durasi = mulai.elapsed(); // selalu berhasil, tidak perlu unwrap
println!("{:?}", durasi);
}
fn operasi_lambat() {
std::thread::sleep(std::time::Duration::from_millis(10));
}
flowchart TD
A{Kebutuhan waktu?} --> B{Perlu waktu kalender?}
B -- Ya --> C["SystemTime\nWaktu sejak UNIX epoch\nBisa dibandingkan dengan tanggal"]
B -- Tidak --> D{Mengukur durasi operasi?}
D -- Ya --> E["Instant::now()\nMonotonic, tidak bisa mundur\nGunakan .elapsed()"]
D -- Tidak --> F{Interval tetap?}
F -- Ya --> G["Duration::from_secs(n)\nInterval waktu yang fix"]
F -- Tidak --> H["Kombinasi Instant + Duration\nDeadline dan timeout"]
style C fill:#fff3e0
style E fill:#e8f5e9
style G fill:#e3f2fd
style H fill:#e8f5e9SystemTime — Waktu Kalender #
SystemTime merepresentasikan titik waktu berdasarkan jam sistem. Gunakan saat kamu perlu waktu yang bisa dikomunikasikan ke dunia luar — timestamp file, expiry token, logging dengan waktu.
use std::time::{SystemTime, UNIX_EPOCH, Duration};
fn main() {
// Waktu sekarang
let sekarang = SystemTime::now();
// Konversi ke UNIX timestamp (detik sejak 1 Januari 1970 UTC)
let unix_timestamp = sekarang
.duration_since(UNIX_EPOCH)
.expect("Waktu sebelum UNIX epoch");
println!("UNIX timestamp: {}", unix_timestamp.as_secs());
println!("Dengan milidetik: {}", unix_timestamp.as_millis());
// Membuat SystemTime dari UNIX timestamp
let timestamp: u64 = 1_700_000_000; // contoh timestamp
let waktu = UNIX_EPOCH + Duration::from_secs(timestamp);
println!("{:?}", waktu);
// Aritmatika SystemTime
let satu_jam_lalu = SystemTime::now() - Duration::from_secs(3600);
let satu_jam_lagi = SystemTime::now() + Duration::from_secs(3600);
// duration_since mengembalikan Result — bisa gagal jika urutan waktu salah
match satu_jam_lagi.duration_since(SystemTime::now()) {
Ok(durasi) => println!("Dalam {} detik lagi", durasi.as_secs()),
Err(e) => println!("Waktu sudah lewat {} detik yang lalu", e.duration().as_secs()),
}
// Pengecekan apakah suatu waktu sudah lewat
fn sudah_kadaluarsa(expiry: SystemTime) -> bool {
SystemTime::now() > expiry
}
let token_expiry = SystemTime::now() + Duration::from_secs(3600);
println!("Token kadaluarsa: {}", sudah_kadaluarsa(token_expiry)); // false
// Metadata file — waktu modifikasi
use std::fs;
if let Ok(metadata) = fs::metadata("Cargo.toml") {
if let Ok(waktu_modif) = metadata.modified() {
let umur = SystemTime::now()
.duration_since(waktu_modif)
.unwrap_or(Duration::ZERO);
println!("File terakhir dimodifikasi {} detik lalu", umur.as_secs());
}
}
}
Timeout pada Operasi Konkuren #
Timeout adalah salah satu penggunaan Duration dan Instant yang paling sering di kode nyata — terutama saat bekerja dengan channel, lock, atau network.
use std::sync::mpsc;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use std::thread;
fn main() {
// Timeout pada channel recv
let (tx, rx) = mpsc::channel::<String>();
thread::spawn(move || {
thread::sleep(Duration::from_millis(200));
tx.send(String::from("pesan terlambat")).unwrap();
});
match rx.recv_timeout(Duration::from_millis(100)) {
Ok(pesan) => println!("Diterima: {}", pesan),
Err(mpsc::RecvTimeoutError::Timeout) => println!("Timeout — pesan tidak datang tepat waktu"),
Err(mpsc::RecvTimeoutError::Disconnected) => println!("Sender sudah ter-drop"),
}
// Timeout pada Mutex lock
let mutex = Arc::new(Mutex::new(0i32));
let mutex_clone = Arc::clone(&mutex);
// Simulasi thread yang memegang lock lama
let _guard_holder = thread::spawn(move || {
let _lock = mutex_clone.lock().unwrap();
thread::sleep(Duration::from_millis(500));
// lock dilepas saat thread selesai
});
thread::sleep(Duration::from_millis(10)); // beri waktu thread lain ambil lock
// try_lock dengan retry loop dan deadline
let deadline = Instant::now() + Duration::from_millis(100);
let berhasil = loop {
match mutex.try_lock() {
Ok(mut nilai) => {
*nilai += 1;
break true;
}
Err(_) => {
if Instant::now() >= deadline {
break false;
}
thread::sleep(Duration::from_millis(10));
}
}
};
println!("Lock berhasil diperoleh: {}", berhasil);
// Retry dengan exponential backoff
fn operasi_dengan_retry<F, T, E>(
operasi: F,
maks_percobaan: u32,
backoff_awal: Duration,
) -> Result<T, E>
where
F: Fn() -> Result<T, E>,
{
let mut backoff = backoff_awal;
for percobaan in 0..maks_percobaan {
match operasi() {
Ok(hasil) => return Ok(hasil),
Err(e) if percobaan + 1 == maks_percobaan => return Err(e),
Err(_) => {
println!("Percobaan {} gagal, menunggu {:?}", percobaan + 1, backoff);
thread::sleep(backoff);
backoff = backoff.saturating_mul(2); // double setiap retry
}
}
}
unreachable!()
}
let mut percobaan = 0;
let hasil = operasi_dengan_retry(
|| -> Result<i32, &str> {
percobaan += 1;
if percobaan < 3 { Err("gagal") } else { Ok(42) }
},
5,
Duration::from_millis(10),
);
println!("Hasil setelah retry: {:?}", hasil);
}
Benchmarking Sederhana #
Instant adalah fondasi untuk mengukur performa kode. Meskipun untuk benchmarking serius kamu perlu crate seperti criterion, pengukuran cepat dengan Instant sudah berguna untuk validasi awal.
use std::time::{Instant, Duration};
// Helper sederhana untuk benchmarking
fn ukur_waktu<F: Fn()>(nama: &str, iterasi: u32, fungsi: F) {
// Warm-up — biarkan CPU cache dan branch predictor beradaptasi
for _ in 0..10 {
fungsi();
}
let mulai = Instant::now();
for _ in 0..iterasi {
fungsi();
}
let total = mulai.elapsed();
let per_iterasi = total / iterasi;
println!("{}: total {:?}, per iterasi {:?}", nama, total, per_iterasi);
}
fn main() {
let data: Vec<i32> = (0..10_000).collect();
// Bandingkan dua pendekatan
let data_clone = data.clone();
ukur_waktu("iter sum", 1000, || {
let _: i32 = data_clone.iter().sum();
});
let data_clone = data.clone();
ukur_waktu("fold manual", 1000, || {
let _: i32 = data_clone.iter().fold(0, |acc, &x| acc + x);
});
let data_clone = data.clone();
ukur_waktu("loop manual", 1000, || {
let mut total = 0i32;
for &x in &data_clone {
total += x;
}
let _ = total;
});
// Mengukur operasi yang tidak bisa di-loop trivially
struct Timer {
nama: String,
mulai: Instant,
}
impl Timer {
fn baru(nama: &str) -> Self {
Timer {
nama: nama.to_string(),
mulai: Instant::now(),
}
}
}
impl Drop for Timer {
fn drop(&mut self) {
println!("[{}] selesai dalam {:?}", self.nama, self.mulai.elapsed());
}
}
// Timer otomatis diprint saat keluar scope
{
let _t = Timer::baru("inisialisasi database");
// simulasi kerja
std::thread::sleep(Duration::from_millis(50));
} // "inisialisasi database: selesai dalam 50ms" tercetak di sini
{
let _t = Timer::baru("load konfigurasi");
std::thread::sleep(Duration::from_millis(20));
}
}
Formatting dan Parsing Waktu dengan Chrono #
Standard library Rust sengaja tidak menyertakan formatting tanggal dan parsing timezone karena kompleksitasnya. Untuk kebutuhan tersebut, crate chrono adalah pilihan standar di ekosistem Rust.
# Cargo.toml
[dependencies]
chrono = { version = "0.4", features = ["serde"] }
use chrono::{DateTime, Local, Utc, NaiveDate, NaiveDateTime, TimeZone, Duration};
fn main() {
// Waktu sekarang
let sekarang_utc: DateTime<Utc> = Utc::now();
let sekarang_lokal: DateTime<Local> = Local::now();
println!("UTC: {}", sekarang_utc);
println!("Lokal: {}", sekarang_lokal);
// Formatting — menggunakan format string seperti strftime
println!("{}", sekarang_utc.format("%Y-%m-%d %H:%M:%S")); // 2024-01-15 10:30:00
println!("{}", sekarang_lokal.format("%d/%m/%Y")); // 15/01/2024
println!("{}", sekarang_utc.format("%A, %B %d, %Y")); // Monday, January 15, 2024
// Format RFC 3339 (ISO 8601) — untuk API dan serialisasi
println!("{}", sekarang_utc.to_rfc3339()); // 2024-01-15T10:30:00+00:00
// Parsing dari string
let dari_string: DateTime<Utc> = "2024-01-15T10:30:00Z"
.parse::<DateTime<Utc>>()
.expect("Format tidak valid");
let dari_format = DateTime::parse_from_str(
"15/01/2024 10:30:00 +0700",
"%d/%m/%Y %H:%M:%S %z"
).expect("Format tidak valid");
// NaiveDate — tanggal tanpa timezone
let tanggal = NaiveDate::from_ymd_opt(2024, 1, 15).unwrap();
println!("{}", tanggal); // 2024-01-15
println!("Hari ke-{} dalam tahun ini", tanggal.ordinal());
println!("Hari dalam seminggu: {}", tanggal.format("%A"));
// Aritmatika tanggal dengan chrono::Duration
let besok = tanggal + Duration::days(1);
let minggu_depan = tanggal + Duration::weeks(1);
let bulan_depan = tanggal + Duration::days(30); // chrono tidak punya Duration::months
println!("Besok: {}", besok);
println!("Minggu depan: {}", minggu_depan);
// Komponen tanggal
use chrono::Datelike;
println!("Tahun: {}", tanggal.year());
println!("Bulan: {}", tanggal.month());
println!("Hari: {}", tanggal.day());
// Komponen waktu
use chrono::Timelike;
let waktu = sekarang_utc;
println!("Jam: {}", waktu.hour());
println!("Menit: {}", waktu.minute());
println!("Detik: {}", waktu.second());
}
Konversi Antara std::time dan Chrono #
use std::time::{SystemTime, UNIX_EPOCH};
use chrono::{DateTime, Utc};
fn main() {
// SystemTime → chrono DateTime
let sys_time = SystemTime::now();
let datetime: DateTime<Utc> = sys_time.into();
println!("{}", datetime.to_rfc3339());
// chrono DateTime → SystemTime
let chrono_time: DateTime<Utc> = Utc::now();
let sys_time: SystemTime = chrono_time.into();
// UNIX timestamp → chrono
let timestamp: i64 = 1_700_000_000;
let datetime = DateTime::<Utc>::from_timestamp(timestamp, 0)
.expect("Timestamp tidak valid");
println!("{}", datetime.format("%Y-%m-%d %H:%M:%S UTC"));
// chrono → UNIX timestamp
let ts = Utc::now().timestamp(); // detik
let ts_ms = Utc::now().timestamp_millis(); // milidetik
let ts_ns = Utc::now().timestamp_nanos_opt().unwrap(); // nanodetik
println!("Timestamp: {}", ts);
}
Pola Idiomatik dengan Waktu #
Beberapa pola yang sering muncul saat bekerja dengan waktu di kode Rust produksi.
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
use std::thread;
fn main() {
// 1. Rate limiting sederhana
struct RateLimiter {
interval: Duration,
terakhir: Instant,
}
impl RateLimiter {
fn baru(per_detik: u32) -> Self {
RateLimiter {
interval: Duration::from_secs(1) / per_detik,
terakhir: Instant::now() - Duration::from_secs(1),
}
}
fn izinkan(&mut self) -> bool {
let sekarang = Instant::now();
if sekarang.duration_since(self.terakhir) >= self.interval {
self.terakhir = sekarang;
true
} else {
false
}
}
fn tunggu_dan_izinkan(&mut self) {
let sekarang = Instant::now();
let lewat = sekarang.duration_since(self.terakhir);
if lewat < self.interval {
thread::sleep(self.interval - lewat);
}
self.terakhir = Instant::now();
}
}
let mut limiter = RateLimiter::baru(10); // 10 request per detik
for i in 0..5 {
limiter.tunggu_dan_izinkan();
println!("Request {} diizinkan", i);
}
// 2. Cache dengan TTL (Time To Live)
struct EntriCache<T> {
nilai: T,
dibuat: Instant,
ttl: Duration,
}
impl<T> EntriCache<T> {
fn baru(nilai: T, ttl: Duration) -> Self {
EntriCache {
nilai,
dibuat: Instant::now(),
ttl,
}
}
fn masih_valid(&self) -> bool {
self.dibuat.elapsed() < self.ttl
}
fn nilai(&self) -> Option<&T> {
if self.masih_valid() { Some(&self.nilai) } else { None }
}
}
let cache = EntriCache::baru("data dari database", Duration::from_secs(300));
match cache.nilai() {
Some(data) => println!("Cache hit: {}", data),
None => println!("Cache expired, perlu refresh"),
}
// 3. Mengukur performa bagian kode tertentu
struct Stopwatch {
checkpoints: Vec<(String, Duration)>,
mulai: Instant,
terakhir: Instant,
}
impl Stopwatch {
fn mulai() -> Self {
let sekarang = Instant::now();
Stopwatch {
checkpoints: Vec::new(),
mulai: sekarang,
terakhir: sekarang,
}
}
fn lap(&mut self, nama: &str) {
let sekarang = Instant::now();
self.checkpoints.push((nama.to_string(), sekarang - self.terakhir));
self.terakhir = sekarang;
}
fn cetak_laporan(&self) {
println!("=== Laporan Performa ===");
for (nama, durasi) in &self.checkpoints {
println!(" {}: {:?}", nama, durasi);
}
println!(" Total: {:?}", self.mulai.elapsed());
}
}
let mut sw = Stopwatch::mulai();
// Simulasi tahap-tahap operasi
thread::sleep(Duration::from_millis(20));
sw.lap("koneksi database");
thread::sleep(Duration::from_millis(50));
sw.lap("query data");
thread::sleep(Duration::from_millis(10));
sw.lap("proses hasil");
sw.cetak_laporan();
// === Laporan Performa ===
// koneksi database: ~20ms
// query data: ~50ms
// proses hasil: ~10ms
// Total: ~80ms
// 4. Timestamp untuk logging
fn log(level: &str, pesan: &str) {
let ts = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis();
println!("[{}] [{}] {}", ts, level, pesan);
}
log("INFO", "Aplikasi dimulai");
log("WARN", "Koneksi lambat");
log("ERROR", "Gagal terhubung ke database");
}
sleep dan Presisi Waktu #
thread::sleep adalah cara paling sederhana untuk menunggu, tapi ada beberapa hal yang perlu dipahami tentang presisinya.
use std::thread;
use std::time::{Duration, Instant};
fn main() {
// sleep tidak dijamin presisi — OS bisa menunda lebih lama
let diminta = Duration::from_millis(10);
let mulai = Instant::now();
thread::sleep(diminta);
let aktual = mulai.elapsed();
println!("Diminta: {:?}, Aktual: {:?}", diminta, aktual);
// Aktual bisa lebih dari diminta, tergantung scheduler OS
// Spin-wait untuk presisi lebih tinggi (tapi menghabiskan CPU)
fn sleep_presisi(durasi: Duration) {
let deadline = Instant::now() + durasi;
// Tidur sebagian besar waktu dengan sleep biasa
let batas_spin = duration_from_millis_sat(1);
if durasi > batas_spin {
thread::sleep(durasi - batas_spin);
}
// Spin untuk sisa waktu
while Instant::now() < deadline {
std::hint::spin_loop(); // hint ke CPU bahwa ini busy-wait loop
}
}
let mulai = Instant::now();
sleep_presisi(Duration::from_millis(10));
println!("Spin-sleep aktual: {:?}", mulai.elapsed());
// yield_now — relinquish time slice ke thread lain tanpa tidur
for _ in 0..1000 {
thread::yield_now(); // beri kesempatan thread lain berjalan
}
// Interval loop yang tidak drift
fn jalankan_pada_interval(interval: Duration, iterasi: u32) {
let mut deadline = Instant::now();
for i in 0..iterasi {
deadline += interval;
println!("Iterasi {} pada {:?}", i, Instant::now().elapsed());
let sekarang = Instant::now();
if deadline > sekarang {
thread::sleep(deadline - sekarang);
}
// Jika sudah terlambat, langsung lanjut tanpa tidur
}
}
jalankan_pada_interval(Duration::from_millis(50), 5);
}
fn duration_from_millis_sat(ms: u64) -> Duration {
Duration::from_millis(ms)
}
Ringkasan #
Durationuntuk interval,Instantuntuk pengukuran,SystemTimeuntuk kalender — ketiganya punya peran berbeda. Mencampurnya adalah sumber bug yang umum.- Gunakan
Instant::now()untuk mengukur performa — ia monotonic dan tidak terpengaruh perubahan jam sistem.elapsed()selalu berhasil tanpaunwrap.SystemTimebisa mundur —duration_sincemengembalikanResultbukanDurationkarena waktu sistem bisa disetel ulang. Selalu tangani error-nya.Duration::checked_subdansaturating_sub— hindari panic saat operasi aritmatika Duration bisa menghasilkan nilai negatif.saturating_submengembalikanZEROalih-alih panic.recv_timeoutdantry_lockuntuk timeout — gunakanDurationsebagai parameter timeout pada operasi blocking seperti channel recv dan Mutex lock.thread::sleeptidak presisi — OS bisa menunda lebih lama dari yang diminta. Untuk aplikasi real-time, gunakan async runtime dengan timer yang lebih presisi, atau spin-wait untuk granularitas microsecond.- Gunakan
chronountuk tanggal kalender — formatting, parsing, timezone, dan aritmatika bulan/tahun yang benar. Standard library sengaja tidak menyertakannya karena kompleksitas timezone.- Interval loop tanpa drift — gunakan pola
deadline += intervalbukansleep(interval). Yang kedua akan drift karena waktu eksekusi kode tidak nol.