Loading pageā¦
Rust walkthroughs
Loading pageā¦
chrono::DateTime::parse_from_rfc3339 handle timezone information in ISO 8601 strings?chrono::DateTime::parse_from_rfc3339 parses RFC3339-formatted datetime strings and extracts the timezone offset directly from the string, returning a DateTime<FixedOffset> that captures the exact UTC offset specified in the input. RFC3339 requires timezone informationāeither an explicit offset like +05:30 or Z for UTCāso every valid RFC3339 string includes timezone data that becomes part of the parsed DateTime. The parsed offset is fixed and cannot change: a string ending in +02:00 produces a DateTime<FixedOffset> with that offset embedded, distinct from a DateTime<Utc> or DateTime<Tz> where timezone conversions might apply.
use chrono::{DateTime, FixedOffset};
fn basic_parse() {
// RFC3339 format: YYYY-MM-DDTHH:MM:SS[.microseconds]+HH:MM or Z
let dt: DateTime<FixedOffset> = DateTime::parse_from_rfc3339("2024-03-15T14:30:00+02:00")
.expect("valid RFC3339");
println!("DateTime: {}", dt);
println!("Offset: {}", dt.offset());
// Output:
// DateTime: 2024-03-15 14:30:00 +02:00
// Offset: +02:00
}parse_from_rfc3339 returns a DateTime<FixedOffset> containing the offset from the string.
use chrono::{DateTime, FixedOffset, Utc};
fn utc_parsing() {
// 'Z' suffix indicates UTC
let dt: DateTime<FixedOffset> = DateTime::parse_from_rfc3339("2024-03-15T14:30:00Z")
.expect("valid RFC3339");
assert_eq!(dt.offset().local_minus_utc(), 0);
// Can convert to DateTime<Utc>
let utc_dt: DateTime<Utc> = dt.with_timezone(&Utc);
println!("UTC: {}", utc_dt);
}The Z suffix is parsed as a zero offset (UTC), stored as FixedOffset.
use chrono::{DateTime, FixedOffset};
fn offset_parsing() {
// Positive offset: +HH:MM
let eastern = DateTime::parse_from_rfc3339("2024-03-15T14:30:00+05:30")
.expect("valid RFC3339");
println!("IST: {}", eastern);
// Negative offset: -HH:MM
let western = DateTime::parse_from_rfc3339("2024-03-15T14:30:00-08:00")
.expect("valid RFC3339");
println!("PST: {}", western);
// Both are DateTime<FixedOffset>
// The offset is extracted from the string
}Positive and negative offsets are parsed directly from the string format.
use chrono::{DateTime, FixedOffset};
fn fractional_seconds() {
// With microseconds
let dt = DateTime::parse_from_rfc3339("2024-03-15T14:30:00.123456Z")
.expect("valid RFC3339");
println!("With microseconds: {}", dt);
// With nanoseconds (if precision available)
let dt2 = DateTime::parse_from_rfc3339("2024-03-15T14:30:00.123456789Z")
.expect("valid RFC3339");
println!("With nanoseconds: {}", dt2);
// Without fractional seconds
let dt3 = DateTime::parse_from_rfc3339("2024-03-15T14:30:00Z")
.expect("valid RFC3339");
println!("No fraction: {}", dt3);
}RFC3339 allows optional fractional seconds with variable precision.
use chrono::{DateTime, FixedOffset, TimeZone};
fn offset_info() {
let dt = DateTime::parse_from_rfc3339("2024-03-15T14:30:00+05:30")
.expect("valid RFC3339");
// Get the FixedOffset
let offset = dt.offset();
println!("Offset: {}", offset); // +05:30
// Get offset in seconds
let offset_seconds = offset.local_minus_utc();
println!("Offset seconds: {}", offset_seconds); // 19800 (5.5 * 3600)
// Convert to UTC
let utc = dt.with_timezone(&chrono::Utc);
println!("UTC equivalent: {}", utc);
}The FixedOffset stores the offset and can be used for timezone conversions.
use chrono::{DateTime, FixedOffset, Utc, TimeZone};
fn timezone_conversion() {
// Parse with offset
let dt = DateTime::parse_from_rfc3339("2024-03-15T14:30:00+02:00")
.expect("valid RFC3339");
// Convert to UTC
let utc: DateTime<Utc> = dt.with_timezone(&Utc);
println!("UTC: {}", utc); // 2024-03-15 12:30:00 UTC
// Convert to different FixedOffset
let tokyo = chrono::FixedOffset::east_opt(9 * 3600).unwrap();
let tokyo_time: DateTime<FixedOffset> = dt.with_timezone(&tokyo);
println!("Tokyo: {}", tokyo_time); // 2024-03-15 21:30:00 +09:00
// Convert to timezone with DST (chrono-tz crate)
// let eastern: DateTime<chrono_tz::Tz> = dt.with_timezone(&chrono_tz::America::New_York);
}with_timezone converts the DateTime to any timezone implementing TimeZone.
use chrono::{DateTime, FixedOffset, TimeZone};
fn fixed_offset_characteristics() {
// FixedOffset is a fixed UTC offset (no DST transitions)
let offset_plus_5 = FixedOffset::east_opt(5 * 3600).unwrap();
let offset_minus_8 = FixedOffset::west_opt(8 * 3600).unwrap();
// Parse strings with different offsets
let dt1 = DateTime::parse_from_rfc3339("2024-01-15T10:00:00+05:00").unwrap();
let dt2 = DateTime::parse_from_rfc3339("2024-01-15T10:00:00-08:00").unwrap();
// Both have FixedOffset, but different values
assert_ne!(dt1.offset(), dt2.offset());
// FixedOffset does NOT track DST
// It's always the same offset regardless of date
let summer = DateTime::parse_from_rfc3339("2024-07-15T10:00:00+02:00").unwrap();
let winter = DateTime::parse_from_rfc3339("2024-01-15T10:00:00+02:00").unwrap();
// Both have same offset (+02:00)
assert_eq!(summer.offset(), winter.offset());
}FixedOffset is a constant offsetāit doesn't handle DST or timezone rules.
use chrono::{DateTime, FixedOffset};
fn parsing_errors() {
// Invalid format
let result = DateTime::parse_from_rfc3339("2024-03-15 14:30:00");
assert!(result.is_err());
// Missing timezone (RFC3339 requires it)
let result = DateTime::parse_from_rfc3339("2024-03-15T14:30:00");
assert!(result.is_err());
// Invalid date
let result = DateTime::parse_from_rfc3339("2024-13-15T14:30:00Z");
assert!(result.is_err());
// Invalid time
let result = DateTime::parse_from_rfc3339("2024-03-15T25:30:00Z");
assert!(result.is_err());
// Invalid offset
let result = DateTime::parse_from_rfc3339("2024-03-15T14:30:00+25:00");
assert!(result.is_err());
}Invalid format, missing timezone, or invalid values return errors.
use chrono::{DateTime, FixedOffset};
fn valid_formats() {
// All valid RFC3339 formats:
// UTC with Z
let dt = DateTime::parse_from_rfc3339("2024-03-15T14:30:00Z").unwrap();
// Positive offset
let dt = DateTime::parse_from_rfc3339("2024-03-15T14:30:00+05:30").unwrap();
// Negative offset
let dt = DateTime::parse_from_rfc3339("2024-03-15T14:30:00-08:00").unwrap();
// With fractional seconds
let dt = DateTime::parse_from_rfc3339("2024-03-15T14:30:00.123Z").unwrap();
let dt = DateTime::parse_from_rfc3339("2024-03-15T14:30:00.123456Z").unwrap();
let dt = DateTime::parse_from_rfc3339("2024-03-15T14:30:00.123456789Z").unwrap();
// Offset without colon (NOT valid RFC3339)
let result = DateTime::parse_from_rfc3339("2024-03-15T14:30:00+0500");
assert!(result.is_err());
}RFC3339 requires Z or offset in +HH:MM / -HH:MM format with colon.
use chrono::{DateTime, FixedOffset, Utc, TimeZone};
fn parsing_comparison() {
let rfc3339_string = "2024-03-15T14:30:00+02:00";
// parse_from_rfc3339: RFC3339 only, returns DateTime<FixedOffset>
let dt1: DateTime<FixedOffset> = DateTime::parse_from_rfc3339(rfc3339_string)
.expect("RFC3339 format");
// parse_from_str: Custom format, returns DateTime<FixedOffset>
let dt2: DateTime<FixedOffset> = DateTime::parse_from_str(
rfc3339_string,
"%Y-%m-%dT%H:%M:%S%:z"
).expect("custom format");
// Result is equivalent
assert_eq!(dt1, dt2);
// DateTime::parse_from_rfc2822: RFC2822 (email) format
let rfc2822_string = "Fri, 15 Mar 2024 14:30:00 +0200";
let dt3: DateTime<FixedOffset> = DateTime::parse_from_rfc2822(rfc2822_string)
.expect("RFC2822 format");
// Utc::datetime_from_str: Parse into DateTime<Utc>
let dt4: DateTime<Utc> = Utc.datetime_from_str(
"2024-03-15T14:30:00Z",
"%Y-%m-%dT%H:%M:%SZ"
).expect("UTC format");
}parse_from_rfc3339 is specialized for RFC3339; parse_from_str allows custom formats.
use chrono::{DateTime, FixedOffset};
fn rfc3339_vs_iso8601() {
// RFC3339 is a subset of ISO 8601
// Valid RFC3339 (also valid ISO 8601)
let valid_rfc3339 = [
"2024-03-15T14:30:00Z",
"2024-03-15T14:30:00+02:00",
"2024-03-15T14:30:00.123Z",
];
for s in valid_rfc3339 {
assert!(DateTime::parse_from_rfc3339(s).is_ok());
}
// Valid ISO 8601 but NOT valid RFC3339
let iso_only = [
"2024-03-15", // Missing time
"2024-03-15T14:30:00", // Missing timezone
"2024-03-15T14:30:00+0200", // Offset without colon
"2024-0315T14:30:00Z", // Different date format
"2024-W11T14:30:00Z", // Week number format
];
for s in iso_only {
assert!(DateTime::parse_from_rfc3339(s).is_err());
}
}RFC3339 is stricter than ISO 8601āit requires time and timezone.
use chrono::{DateTime, FixedOffset, Datelike, Timelike, TimeZone};
fn working_with_parsed() {
let dt = DateTime::parse_from_rfc3339("2024-03-15T14:30:45.123456+02:00")
.expect("valid RFC3339");
// Date components
println!("Year: {}", dt.year());
println!("Month: {}", dt.month());
println!("Day: {}", dt.day());
// Time components
println!("Hour: {}", dt.hour());
println!("Minute: {}", dt.minute());
println!("Second: {}", dt.second());
println!("Microsecond: {}", dt.microsecond());
// Day of week
println!("Weekday: {}", dt.weekday());
// Timestamp
println!("Unix timestamp: {}", dt.timestamp());
// Convert to other timezones
let utc = dt.with_timezone(&chrono::Utc);
println!("UTC: {}", utc);
}The parsed DateTime<FixedOffset> provides access to all datetime components.
use chrono::{DateTime, FixedOffset};
fn storing_and_displaying() {
let dt = DateTime::parse_from_rfc3339("2024-03-15T14:30:00+02:00")
.expect("valid RFC3339");
// Display in RFC3339 format
println!("RFC3339: {}", dt.to_rfc3339());
// Output: 2024-03-15T14:30:00+02:00
// Parse back to same value
let parsed_back = DateTime::parse_from_rfc3339(&dt.to_rfc3339()).unwrap();
assert_eq!(dt, parsed_back);
// Custom format
println!("Custom: {}", dt.format("%Y-%m-%d %H:%M:%S %:z"));
// Output: 2024-03-15 14:30:00 +02:00
// ISO 8601 format
println!("ISO 8601: {}", dt.to_iso8601(chrono::SecondsFormat::Secs, true));
}to_rfc3339() converts back to RFC3339 string; format() allows custom formats.
use chrono::{DateTime, FixedOffset};
fn offset_limits() {
// Valid offsets: -23:59 to +23:59
let valid_offsets = [
"+00:00", "+01:00", "+05:30", "+12:00", "+23:59",
"-00:00", "-01:00", "-08:00", "-12:00", "-23:59",
];
for offset_suffix in valid_offsets {
let s = format!("2024-03-15T14:30:00{}", offset_suffix);
assert!(DateTime::parse_from_rfc3339(&s).is_ok());
}
// Invalid offsets
let invalid_offsets = [
"+24:00", // Out of range
"-24:00", // Out of range
"+99:00", // Out of range
];
for offset_suffix in invalid_offsets {
let s = format!("2024-03-15T14:30:00{}", offset_suffix);
assert!(DateTime::parse_from_rfc3339(&s).is_err());
}
}Offsets must be within -23:59 to +23:59 per RFC3339 specification.
use chrono::{DateTime, FixedOffset, Utc};
use serde::{Deserialize, Serialize};
// Many APIs return RFC3339 timestamps
#[derive(Deserialize)]
struct ApiResponse {
created_at: String,
updated_at: String,
expires_at: Option<String>,
}
#[derive(Debug)]
struct ParsedTimestamps {
created: DateTime<FixedOffset>,
updated: DateTime<FixedOffset>,
expires: Option<DateTime<FixedOffset>>,
}
fn parse_api_response(response: ApiResponse) -> Result<ParsedTimestamps, String> {
let created = DateTime::parse_from_rfc3339(&response.created_at)
.map_err(|e| format!("Invalid created_at: {}", e))?;
let updated = DateTime::parse_from_rfc3339(&response.updated_at)
.map_err(|e| format!("Invalid updated_at: {}", e))?;
let expires = response.expires_at
.map(|s| DateTime::parse_from_rfc3339(&s))
.transpose()
.map_err(|e| format!("Invalid expires_at: {}", e))?;
Ok(ParsedTimestamps { created, updated, expires })
}APIs commonly use RFC3339 for timestamp fields.
use chrono::{DateTime, FixedOffset, Utc};
fn store_timestamp() {
// Parse user input
let user_time = DateTime::parse_from_rfc3339("2024-03-15T14:30:00+05:30")
.expect("valid input");
// Store as UTC in database
let db_timestamp: DateTime<Utc> = user_time.with_timezone(&Utc);
println!("Stored in DB (UTC): {}", db_timestamp);
println!("Unix timestamp: {}", db_timestamp.timestamp());
// When retrieving, convert back to user's timezone
let user_offset = chrono::FixedOffset::east_opt(5 * 3600 + 30 * 60).unwrap();
let display_time: DateTime<FixedOffset> = db_timestamp.with_timezone(&user_offset);
println!("Display to user: {}", display_time);
}Store timestamps in UTC; convert to local timezones for display.
use chrono::{DateTime, FixedOffset, Utc, TimeZone};
fn compare_across_timezones() {
// Same moment, different representations
let dt1 = DateTime::parse_from_rfc3339("2024-03-15T08:30:00Z").unwrap();
let dt2 = DateTime::parse_from_rfc3339("2024-03-15T10:30:00+02:00").unwrap();
let dt3 = DateTime::parse_from_rfc3339("2024-03-15T03:30:00-05:00").unwrap();
// All represent the same moment
assert_eq!(dt1.timestamp(), dt2.timestamp());
assert_eq!(dt2.timestamp(), dt3.timestamp());
// Convert to UTC for comparison
let utc1: DateTime<Utc> = dt1.with_timezone(&Utc);
let utc2: DateTime<Utc> = dt2.with_timezone(&Utc);
let utc3: DateTime<Utc> = dt3.with_timezone(&Utc);
assert_eq!(utc1, utc2);
assert_eq!(utc2, utc3);
// Compare directly (chrono handles timezone conversion)
assert_eq!(dt1, dt2);
assert_eq!(dt2, dt3);
}DateTime comparison normalizes timezonesāthey compare equal if representing the same moment.
use chrono::{DateTime, FixedOffset, Utc, TimeZone, Duration};
struct Event {
name: String,
start_time: DateTime<FixedOffset>,
end_time: DateTime<FixedOffset>,
}
impl Event {
fn parse_from_rfc3339(name: &str, start: &str, end: &str) -> Result<Self, String> {
let start_time = DateTime::parse_from_rfc3339(start)
.map_err(|e| format!("Invalid start time: {}", e))?;
let end_time = DateTime::parse_from_rfc3339(end)
.map_err(|e| format!("Invalid end time: {}", e))?;
if end_time <= start_time {
return Err("End time must be after start time".to_string());
}
Ok(Self {
name: name.to_string(),
start_time,
end_time,
})
}
fn duration(&self) -> Duration {
self.end_time.signed_duration_since(self.start_time)
}
fn format_for_timezone(&self, offset_seconds: i32) -> String {
let tz = FixedOffset::east_opt(offset_seconds).unwrap();
let start = self.start_time.with_timezone(&tz);
let end = self.end_time.with_timezone(&tz);
format!("{} - {}", start.format("%H:%M"), end.format("%H:%M"))
}
}
fn event_example() {
let event = Event::parse_from_rfc3339(
"Team Meeting",
"2024-03-15T14:00:00+02:00",
"2024-03-15T15:30:00+02:00",
).expect("valid event");
println!("Duration: {} minutes", event.duration().num_minutes());
println!("NY time: {}", event.format_for_timezone(-5 * 3600));
}Events parsed from RFC3339 can be displayed in any timezone.
RFC3339 timezone handling:
| Input Format | Offset | Result Type |
|--------------|--------|-------------|
| 2024-03-15T14:30:00Z | UTC (+00:00) | DateTime<FixedOffset> |
| 2024-03-15T14:30:00+02:00 | +02:00 | DateTime<FixedOffset> |
| 2024-03-15T14:30:00-08:00 | -08:00 | DateTime<FixedOffset> |
Timezone type comparison:
| Type | Stores | DST Support | Use Case |
|------|--------|-------------|----------|
| DateTime<Utc> | UTC | N/A | Server timestamps |
| DateTime<Local> | System TZ | No (fixed) | Local display |
| DateTime<FixedOffset> | UTC offset | No | Parsed offsets |
| DateTime<Tz> (chrono-tz) | Timezone | Yes | DST-aware |
Method comparison:
| Method | Format | Return Type | Use Case |
|--------|--------|--------------|----------|
| parse_from_rfc3339 | RFC3339 only | DateTime<FixedOffset> | API timestamps |
| parse_from_rfc2822 | RFC2822 (email) | DateTime<FixedOffset> | Email dates |
| parse_from_str | Custom | DateTime<FixedOffset> | Any format |
Key insight: DateTime::parse_from_rfc3339 extracts the timezone offset directly from the input string, returning a DateTime<FixedOffset> that preserves the exact offset specified. This differs from parsing into DateTime<Utc> (which assumes UTC) or DateTime<Tz> (which applies timezone rules including DST). The FixedOffset captures a moment in time with its offset from UTC, enabling correct conversion to any other timezone. RFC3339 requires explicit timezone information (either Z or +HH:MM/-HH:MM), making every parsed datetime unambiguous about its moment in timeāa key difference from ISO 8601 which allows timezone-optional formats. For applications storing or comparing timestamps across timezones, parsing RFC3339 into DateTime<FixedOffset> then converting to UTC for storage ensures consistent handling.