Convert Unix Timestamp in Rust
Rust offers two main approaches for handling Unix timestamps: the standard library's std::time module for basic operations, and the chrono crate for comprehensive datetime handling with timezones and formatting.
Using Standard Library (std::time)
use std::time::{Duration, SystemTime, UNIX_EPOCH};
fn main() {
// Current Unix timestamp in seconds
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time went backwards")
.as_secs();
println!("Current timestamp: {}", now); // e.g., 1704067200
// Current timestamp in milliseconds
let now_ms = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time went backwards")
.as_millis();
println!("Current timestamp (ms): {}", now_ms);
// Unix timestamp to SystemTime
let timestamp: u64 = 1704067200;
let datetime = UNIX_EPOCH + Duration::from_secs(timestamp);
println!("{:?}", datetime); // SystemTime { ... }
}Using chrono Crate (Recommended)
Add to your Cargo.toml:
[dependencies] chrono = "0.4"
use chrono::{DateTime, TimeZone, Utc};
fn main() {
// Unix timestamp to DateTime<Utc>
let timestamp: i64 = 1704067200;
let datetime = Utc.timestamp_opt(timestamp, 0)
.single()
.expect("Invalid timestamp");
println!("{}", datetime); // 2024-01-01 00:00:00 UTC
// From milliseconds
let timestamp_ms: i64 = 1704067200000;
let datetime_ms = Utc.timestamp_millis_opt(timestamp_ms)
.single()
.expect("Invalid timestamp");
println!("{}", datetime_ms); // 2024-01-01 00:00:00 UTC
// Current time to Unix timestamp
let now = Utc::now();
println!("Seconds: {}", now.timestamp()); // e.g., 1704067200
println!("Millis: {}", now.timestamp_millis()); // e.g., 1704067200000
println!("Nanos: {}", now.timestamp_nanos_opt().unwrap());
}Timezone Handling with chrono-tz
Add chrono-tz for IANA timezone support:
[dependencies] chrono = "0.4" chrono-tz = "0.9"
use chrono::{TimeZone, Utc};
use chrono_tz::America::New_York;
use chrono_tz::Asia::Tokyo;
fn main() {
let timestamp: i64 = 1704067200;
let utc_time = Utc.timestamp_opt(timestamp, 0).single().unwrap();
// Convert to different timezones
let ny_time = utc_time.with_timezone(&New_York);
let tokyo_time = utc_time.with_timezone(&Tokyo);
println!("UTC: {}", utc_time); // 2024-01-01 00:00:00 UTC
println!("New York: {}", ny_time); // 2023-12-31 19:00:00 EST
println!("Tokyo: {}", tokyo_time); // 2024-01-01 09:00:00 JST
// Create time in specific timezone
let ny_midnight = New_York
.with_ymd_and_hms(2024, 1, 1, 0, 0, 0)
.single()
.expect("Invalid datetime");
println!("NY midnight as UTC: {}", ny_midnight.with_timezone(&Utc));
println!("NY midnight timestamp: {}", ny_midnight.timestamp()); // 1704085200
}Formatting and Parsing
use chrono::{DateTime, TimeZone, Utc, NaiveDateTime};
fn main() {
let timestamp: i64 = 1704067200;
let datetime = Utc.timestamp_opt(timestamp, 0).single().unwrap();
// Formatting with strftime-style patterns
println!("{}", datetime.format("%Y-%m-%d")); // 2024-01-01
println!("{}", datetime.format("%Y-%m-%d %H:%M:%S")); // 2024-01-01 00:00:00
println!("{}", datetime.format("%B %d, %Y")); // January 01, 2024
println!("{}", datetime.to_rfc3339()); // 2024-01-01T00:00:00+00:00
println!("{}", datetime.to_rfc2822()); // Mon, 1 Jan 2024 00:00:00 +0000
// Parsing from string
let parsed = DateTime::parse_from_rfc3339("2024-01-01T00:00:00Z")
.expect("Invalid RFC3339");
println!("Parsed timestamp: {}", parsed.timestamp()); // 1704067200
// Parsing with custom format
let naive = NaiveDateTime::parse_from_str(
"2024-01-01 00:00:00",
"%Y-%m-%d %H:%M:%S"
).expect("Invalid format");
let datetime_utc = Utc.from_utc_datetime(&naive);
println!("From custom format: {}", datetime_utc.timestamp());
}Common Pitfalls
Handling timestamp_opt() Results
chrono 0.4.23+ deprecated timestamp() in favor of timestamp_opt()which returns a LocalResult. Always handle the result properly.
use chrono::{TimeZone, Utc, LocalResult};
// ❌ Old way (deprecated)
// let dt = Utc.timestamp(1704067200, 0);
// ✅ New way - handle the result
let dt = match Utc.timestamp_opt(1704067200, 0) {
LocalResult::Single(dt) => dt,
LocalResult::Ambiguous(dt1, _) => dt1, // DST transition
LocalResult::None => panic!("Invalid timestamp"),
};
// ✅ Or use single() for simple cases
let dt = Utc.timestamp_opt(1704067200, 0)
.single()
.expect("Invalid timestamp");Signed vs Unsigned Timestamps
chrono uses i64 for timestamps (supporting pre-1970 dates), while std::time uses u64 Duration. Be careful when converting.
// ❌ This will panic for negative timestamps
let timestamp: i64 = -86400; // 1969-12-31
let duration = Duration::from_secs(timestamp as u64); // Panic!
// ✅ Use chrono for pre-1970 dates
use chrono::{TimeZone, Utc};
let dt = Utc.timestamp_opt(-86400, 0).single().unwrap();
println!("{}", dt); // 1969-12-31 00:00:00 UTCSeconds vs Milliseconds
Check the magnitude of your timestamp. 10 digits = seconds, 13 digits = milliseconds.
// ❌ Wrong - treating milliseconds as seconds
let ts = 1704067200000_i64;
let dt = Utc.timestamp_opt(ts, 0).single(); // Year 55963!
// ✅ Correct - use timestamp_millis_opt
let dt = Utc.timestamp_millis_opt(ts).single().unwrap();
println!("{}", dt); // 2024-01-01 00:00:00 UTCEdge Cases
use chrono::{TimeZone, Utc, DateTime, NaiveDateTime};
fn main() {
// Negative timestamps (before 1970)
let before_1970 = Utc.timestamp_opt(-86400, 0).single().unwrap();
println!("{}", before_1970); // 1969-12-31 00:00:00 UTC
// Maximum DateTime (year 262143)
let max_ts: i64 = 8210298412799;
let max_dt = Utc.timestamp_opt(max_ts, 0).single().unwrap();
println!("Max: {}", max_dt);
// Minimum DateTime
let min_ts: i64 = -8334632851200;
let min_dt = Utc.timestamp_opt(min_ts, 0).single().unwrap();
println!("Min: {}", min_dt);
// Nanosecond precision
let precise = Utc.timestamp_opt(1704067200, 123_456_789)
.single()
.unwrap();
println!("Nanoseconds: {}", precise.timestamp_subsec_nanos()); // 123456789
// Check for valid timestamp
let invalid = Utc.timestamp_opt(i64::MAX, 0);
if invalid.single().is_none() {
println!("Invalid timestamp!");
}
}Duration Calculations
use chrono::{Duration, TimeZone, Utc};
fn main() {
let start = Utc.timestamp_opt(1704067200, 0).single().unwrap();
let end = Utc.timestamp_opt(1704153600, 0).single().unwrap();
// Calculate difference
let diff = end.signed_duration_since(start);
println!("Seconds: {}", diff.num_seconds()); // 86400
println!("Minutes: {}", diff.num_minutes()); // 1440
println!("Hours: {}", diff.num_hours()); // 24
println!("Days: {}", diff.num_days()); // 1
// Add/subtract duration
let tomorrow = start + Duration::days(1);
let yesterday = start - Duration::days(1);
println!("Tomorrow: {}", tomorrow.timestamp());
println!("Yesterday: {}", yesterday.timestamp());
// Add specific time units
let later = start + Duration::hours(2) + Duration::minutes(30);
println!("2h 30m later: {}", later);
}Frequently Asked Questions
What crate should I use for Unix timestamps in Rust?
For basic timestamps, use std::time::SystemTime and UNIX_EPOCH. For full datetime operations, timezones, and formatting, use the chrono crate—it's the de facto standard for datetime handling in Rust.
Does Rust use seconds or milliseconds for Unix timestamps?
std::time::Duration stores time with nanosecond precision. When converting to/from Unix timestamps, you typically work with seconds via as_secs() or with subsecond precision via as_millis(), as_micros(), or as_nanos().
How do I handle timezones in Rust?
Use the chrono crate with chrono-tz for timezone support. chrono provides Utc and Local types, while chrono-tz provides all IANA timezone definitions like America/New_York.
What is the difference between SystemTime and chrono::DateTime?
SystemTime is from the standard library and represents a point in time with nanosecond precision, but has limited formatting/parsing. chrono::DateTime provides rich datetime operations, formatting, parsing, and timezone support.