Loading pageā¦
Rust walkthroughs
Loading pageā¦
chrono::DateTime::parse_from_rfc3339 ensure standards-compliant datetime parsing?chrono::DateTime::parse_from_rfc3339 parses datetime strings according to the RFC 3339 standard, which specifies a strict subset of ISO 8601 for representing timestamps in internet protocols. The parser enforces the exact format YYYY-MM-DDTHH:MM:SS±HH:MM (or Z for UTC), rejecting ambiguous or malformed inputs that might pass more lenient parsers. This strictness ensures interoperability between systems, prevents subtle bugs from misinterpreted timestamps, and guarantees that parsed datetimes can be correctly serialized back to RFC 3339 format without information loss.
use chrono::{DateTime, Utc, FixedOffset};
fn main() {
// Parse a valid RFC 3339 datetime
let dt: DateTime<Utc> = "2024-03-15T14:30:00Z".parse().unwrap();
println!("UTC: {}", dt);
// Alternative: use parse_from_rfc3339 directly
let dt2 = DateTime::parse_from_rfc3339("2024-03-15T14:30:00Z").unwrap();
println!("FixedOffset: {}", dt2);
// With timezone offset
let dt3 = DateTime::parse_from_rfc3339("2024-03-15T14:30:00+05:30").unwrap();
println!("With offset: {}", dt3);
}parse_from_rfc3339 returns a DateTime<FixedOffset> preserving the original timezone offset.
use chrono::{DateTime, FixedOffset};
fn main() {
// Valid RFC 3339 formats:
let valid_examples = [
"2024-03-15T14:30:00Z", // UTC with Z
"2024-03-15T14:30:00+00:00", // UTC with offset
"2024-03-15T14:30:00-05:00", // Eastern time
"2024-03-15T14:30:00.123Z", // With fractional seconds
"2024-03-15T14:30:00.123456789Z", // Up to 9 fractional digits
];
for example in valid_examples {
match DateTime::parse_from_rfc3339(example) {
Ok(dt) => println!("Valid: {} -> {}", example, dt),
Err(e) => println!("Invalid: {} -> {}", example, e),
}
}
}RFC 3339 requires: date, 'T' separator, time, and timezone (Z or ±HH:MM).
use chrono::{DateTime, FixedOffset};
fn main() {
// These will all FAIL parsing:
let invalid_examples = [
"2024-03-15 14:30:00Z", // Space instead of T
"2024/03/15T14:30:00Z", // Slash instead of hyphen
"2024-3-15T14:30:00Z", // Single-digit month
"2024-03-15T14:30:00", // Missing timezone
"2024-03-15T14:30Z", // Missing seconds
"2024-03-15T14:30:00+0500", // Offset without colon
"2024-03-15T14:30:00+05", // Incomplete offset
];
for example in invalid_examples {
match DateTime::parse_from_rfc3339(example) {
Ok(dt) => println!("Unexpected success: {} -> {}", example, dt),
Err(_) => println!("Correctly rejected: {}", example),
}
}
}Strict parsing catches common mistakes that other formats might accept.
use chrono::{DateTime, FixedOffset};
fn main() {
// RFC 3339 validates date components
let invalid_dates = [
"2024-13-15T14:30:00Z", // Invalid month (13)
"2024-00-15T14:30:00Z", // Invalid month (0)
"2024-03-32T14:30:00Z", // Invalid day (32)
"2024-02-30T14:30:00Z", // Invalid day (Feb 30)
"2024-04-31T14:30:00Z", // Invalid day (Apr 31)
];
for date_str in invalid_dates {
match DateTime::parse_from_rfc3339(date_str) {
Ok(_) => println!("Unexpected: {} parsed", date_str),
Err(_) => println!("Correctly rejected: {}", date_str),
}
}
// Leap years are handled correctly
let leap_ok = DateTime::parse_from_rfc3339("2024-02-29T14:30:00Z");
println!("Leap day 2024: {:?}", leap_ok.is_ok()); // true (2024 is leap)
let leap_fail = DateTime::parse_from_rfc3339("2023-02-29T14:30:00Z");
println!("Feb 29 2023: {:?}", leap_fail.is_ok()); // false (not leap)
}Date validation ensures impossible dates are rejected.
use chrono::{DateTime, FixedOffset};
fn main() {
// Valid time formats
let valid_times = [
"2024-03-15T00:00:00Z", // Midnight
"2024-03-15T23:59:59Z", // End of day
"2024-03-15T14:30:00.1Z", // 1 fractional digit
"2024-03-15T14:30:00.12Z", // 2 fractional digits
"2024-03-15T14:30:00.123Z", // 3 fractional digits (milliseconds)
"2024-03-15T14:30:00.123456789Z", // 9 fractional digits (nanoseconds)
];
for time_str in valid_times {
let dt = DateTime::parse_from_rfc3339(time_str).unwrap();
println!("Parsed: {} -> {:?}", time_str, dt);
}
// Invalid times
let invalid_times = [
"2024-03-15T24:00:00Z", // Hour > 23
"2024-03-15T14:60:00Z", // Minute > 59
"2024-03-15T14:30:60Z", // Second > 59 (without leap)
];
for time_str in invalid_times {
println!("Invalid {}: {:?}", time_str,
DateTime::parse_from_rfc3339(time_str).is_err());
}
}Time validation catches impossible times except valid leap seconds.
use chrono::{DateTime, FixedOffset, Utc, TimeZone};
fn main() {
// Parse with various offsets
let dt_utc = DateTime::parse_from_rfc3339("2024-03-15T14:30:00Z").unwrap();
let dt_est = DateTime::parse_from_rfc3339("2024-03-15T14:30:00-05:00").unwrap();
let dt_ist = DateTime::parse_from_rfc3339("2024-03-15T14:30:00+05:30").unwrap();
// All represent the same instant in time, just displayed differently
println!("UTC: {}", dt_utc);
println!("EST: {}", dt_est);
println!("IST: {}", dt_ist);
// Convert to UTC
let utc_from_est = dt_est.with_timezone(&Utc);
println!("EST as UTC: {}", utc_from_est);
// Offset limits: -23:59 to +23:59
let max_offset = DateTime::parse_from_rfc3339("2024-03-15T00:00:00+23:59");
let min_offset = DateTime::parse_from_rfc3339("2024-03-15T00:00:00-23:59");
println!("Max offset valid: {}", max_offset.is_ok());
println!("Min offset valid: {}", min_offset.is_ok());
}Offsets are preserved and can be converted between timezones.
use chrono::{DateTime, FixedOffset};
fn main() {
// RFC 3339 allows fractional seconds with arbitrary precision
let dt1 = DateTime::parse_from_rfc3339("2024-03-15T14:30:00.123Z").unwrap();
let dt2 = DateTime::parse_from_rfc3339("2024-03-15T14:30:00.123456Z").unwrap();
let dt3 = DateTime::parse_from_rfc3339("2024-03-15T14:30:00.123456789Z").unwrap();
println!("Milliseconds: {}", dt1.timestamp_millis());
println!("Microseconds: {}", dt2.timestamp_micros());
println!("Nanoseconds: {}", dt3.timestamp_nanos_opt().unwrap());
// Fractional seconds are preserved in the parsed result
println!("Subsecond 1: {:?}", dt1.timestamp_subsec_nanos());
println!("Subsecond 2: {:?}", dt2.timestamp_subsec_nanos());
println!("Subsecond 3: {:?}", dt3.timestamp_subsec_nanos());
// Chrono preserves up to nanosecond precision
}Fractional seconds are preserved with nanosecond precision.
use chrono::{DateTime, FixedOffset, Utc};
fn main() {
let input = "2024-03-15T14:30:00Z";
// RFC 3339 parsing (strict)
let rfc_result = DateTime::parse_from_rfc3339(input);
// DateTime::parse_from_str (flexible format)
// Can accept more variations but requires format string
let flexible_result = DateTime::parse_from_str(
input,
"%Y-%m-%dT%H:%M:%S%#z"
);
// Converting to UTC
let utc_result: Result<DateTime<Utc>, _> = input.parse();
println!("RFC 3339: {:?}", rfc_result);
println!("Flexible: {:?}", flexible_result);
println!("UTC parse: {:?}", utc_result);
// RFC 3339 is best for:
// - API input validation
// - Interoperability
// - Configuration files
// - Network protocols
}RFC 3339 parsing is stricter than general datetime parsing.
use chrono::{DateTime, FixedOffset};
use chrono::ParseError;
fn parse_datetime(input: &str) -> Result<DateTime<FixedOffset>, String> {
DateTime::parse_from_rfc3339(input)
.map_err(|e| format!("Invalid RFC 3339 datetime '{}': {}", input, e))
}
fn main() {
let test_cases = [
"2024-03-15T14:30:00Z",
"2024-03-15 14:30:00Z", // Space instead of T
"not a datetime",
];
for input in test_cases {
match parse_datetime(input) {
Ok(dt) => println!("Parsed: {}", dt),
Err(e) => println!("Error: {}", e),
}
}
}ParseError provides information about why parsing failed.
use chrono::{DateTime, FixedOffset};
fn main() {
let original = "2024-03-15T14:30:00.123456+05:30";
// Parse RFC 3339 string
let dt = DateTime::parse_from_rfc3339(original).unwrap();
println!("Parsed: {}", dt);
// Serialize back to RFC 3339
let serialized = dt.to_rfc3339();
println!("Serialized: {}", serialized);
// Parse again - should succeed
let dt2 = DateTime::parse_from_rfc3339(&serialized).unwrap();
println!("Round-trip: {}", dt2);
// RFC 3339 guarantees lossless round-trip
assert_eq!(dt, dt2);
}RFC 3339 ensures parsing and serialization are reversible without data loss.
use chrono::{DateTime, FixedOffset};
use serde::{Deserialize, Serialize};
// For API input, strict RFC 3339 ensures consistency
#[derive(Debug, Deserialize)]
struct Event {
#[serde(deserialize_with = "deserialize_rfc3339")]
timestamp: DateTime<FixedOffset>,
name: String,
}
fn deserialize_rfc3339<'de, D>(deserializer: D) -> Result<DateTime<FixedOffset>, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
DateTime::parse_from_rfc3339(&s)
.map_err(|e| serde::de::Error::custom(format!("Invalid RFC 3339: {}", e)))
}
fn main() {
let json = r#"{"timestamp":"2024-03-15T14:30:00Z","name":"Event"}"#;
let event: Event = serde_json::from_str(json).unwrap();
println!("Event: {} at {}", event.name, event.timestamp);
// Invalid RFC 3339 would fail deserialization
let bad_json = r#"{"timestamp":"2024-03-15 14:30:00Z","name":"Event"}"#;
match serde_json::from_str::<Event>(bad_json) {
Ok(_) => println!("Unexpected success"),
Err(e) => println!("Correctly rejected: {}", e),
}
}Using RFC 3339 for API input prevents ambiguous timestamp interpretation.
use chrono::{DateTime, FixedOffset, Utc, Local, TimeZone};
fn main() {
// Parse RFC 3339
let dt = DateTime::parse_from_rfc3339("2024-03-15T14:30:00-05:00").unwrap();
// Convert to UTC
let utc: DateTime<Utc> = dt.with_timezone(&Utc);
println!("UTC: {}", utc);
// Convert to local timezone
let local: DateTime<Local> = dt.with_timezone(&Local);
println!("Local: {}", local);
// Format back to RFC 3339
println!("RFC 3339: {}", utc.to_rfc3339());
// Other formats (not RFC 3339)
println!("RFC 2822: {}", utc.to_rfc2822());
println!("Custom: {}", utc.format("%Y/%m/%d %H:%M:%S"));
}Parsed datetimes can be converted between timezones and reformatted.
use chrono::{DateTime, FixedOffset};
fn main() {
// Leap seconds (June 30 or December 31)
// RFC 3339 allows second = 60 for leap seconds
// chrono accepts this in parsing
// Note: chrono's leap second support is limited
let leap_second = "2016-12-31T23:59:60Z";
match DateTime::parse_from_rfc3339(leap_second) {
Ok(dt) => println!("Leap second parsed: {}", dt),
Err(e) => println!("Leap second rejected: {}", e),
}
// Midnight variations
let midnight1 = DateTime::parse_from_rfc3339("2024-03-15T00:00:00Z");
let midnight2 = DateTime::parse_from_rfc3339("2024-03-15T00:00:00+00:00");
println!("Midnight Z: {:?}", midnight1.is_ok());
println!("Midnight +00:00: {:?}", midnight2.is_ok());
}Leap second handling depends on chrono's capabilities.
use chrono::{DateTime, FixedOffset, NaiveDateTime, Utc};
fn main() {
// Use RFC 3339 when you need strict validation:
// - API boundaries
// - Database storage
// - Inter-service communication
// - Configuration files
// - Log timestamps
// Use lenient parsing when you need to accept user input:
// - User-provided timestamps
// - Legacy data formats
// - Multiple date formats
// Strict: RFC 3339 only
fn strict_parse(input: &str) -> Result<DateTime<FixedOffset>, &'static str> {
DateTime::parse_from_rfc3339(input)
.map_err(|_| "Invalid RFC 3339 format")
}
// Lenient: Try multiple formats
fn lenient_parse(input: &str) -> Result<DateTime<Utc>, String> {
// Try RFC 3339 first
if let Ok(dt) = DateTime::parse_from_rfc3339(input) {
return Ok(dt.with_timezone(&Utc));
}
// Try other formats...
// NaiveDateTime::parse_from_str(...)
Err("Could not parse datetime".to_string())
}
println!("Strict: {:?}", strict_parse("2024-03-15T14:30:00Z"));
println!("Lenient: {:?}", lenient_parse("2024-03-15T14:30:00Z"));
}Choose strictness based on the data source and interoperability requirements.
use chrono::{DateTime, FixedOffset};
fn main() {
// RFC 3339 is a subset of ISO 8601
// ISO 8601 allows more variations:
// These are ISO 8601 but NOT RFC 3339:
let not_rfc3339 = [
"2024-03-15", // Date only
"20240315T143000Z", // Compact form
"2024-W11-5", // Week format
"2024-075", // Ordinal date
"14:30:00Z", // Time only
"2024-03-15T14:30:00+05", // Offset without minutes
"2024-03-15T14:30:00+0500", // Compact offset
];
for iso_date in not_rfc3339 {
let result = DateTime::parse_from_rfc3339(iso_date);
println!("{}: RFC 3339 = {}", iso_date, result.is_ok());
}
// RFC 3339 specifically requires:
// - Full date and time
// - T separator (not space)
// - Timezone (Z or ±HH:MM)
// - No compact forms
}RFC 3339 is stricter than ISO 8601, which allows many variations.
use chrono::{DateTime, FixedOffset};
fn main() {
// Common precision levels
let dt_seconds = DateTime::parse_from_rfc3339("2024-03-15T14:30:00Z").unwrap();
let dt_millis = DateTime::parse_from_rfc3339("2024-03-15T14:30:00.123Z").unwrap();
let dt_micros = DateTime::parse_from_rfc3339("2024-03-15T14:30:00.123456Z").unwrap();
let dt_nanos = DateTime::parse_from_rfc3339("2024-03-15T14:30:00.123456789Z").unwrap();
println!("Seconds: {}", dt_seconds);
println!("Milliseconds: {}", dt_millis);
println!("Microseconds: {}", dt_micros);
println!("Nanoseconds: {}", dt_nanos);
// All represent the same instant
assert_eq!(dt_seconds.timestamp(), dt_millis.timestamp());
// But differ in subsecond precision
println!("Subsec nanos (seconds): {}", dt_seconds.timestamp_subsec_nanos());
println!("Subsec nanos (millis): {}", dt_millis.timestamp_subsec_nanos());
println!("Subsec nanos (micros): {}", dt_micros.timestamp_subsec_nanos());
}Precision is preserved from the input string.
use chrono::{DateTime, FixedOffset, Utc};
// Wrapper that accepts common variations
fn parse_timestamp_flexible(input: &str) -> Result<DateTime<Utc>, String> {
// Normalize common variations
let normalized = input
.replace(' ', "T") // Space to T
.replace("+00:00", "Z") // Normalize UTC
.replace("-00:00", "Z"); // Normalize UTC
// Try RFC 3339 first
if let Ok(dt) = DateTime::parse_from_rfc3339(&normalized) {
return Ok(dt.with_timezone(&Utc));
}
// Try without timezone (assume UTC)
if let Ok(dt) = NaiveDateTime::parse_from_str(&normalized, "%Y-%m-%dT%H:%M:%S") {
return Ok(dt.and_utc());
}
Err(format!("Could not parse timestamp: {}", input))
}
use chrono::NaiveDateTime;
fn main() {
// Strict RFC 3339
let strict = DateTime::parse_from_rfc3339("2024-03-15T14:30:00Z");
println!("Strict: {:?}", strict);
// Flexible parsing
let flex1 = parse_timestamp_flexible("2024-03-15T14:30:00Z");
let flex2 = parse_timestamp_flexible("2024-03-15 14:30:00+00:00");
println!("Flex1: {:?}", flex1);
println!("Flex2: {:?}", flex2);
}For user input, consider accepting common variations while normalizing internally.
RFC 3339 format requirements:
| Component | Format | Valid Examples | |-----------|--------|----------------| | Date | YYYY-MM-DD | 2024-03-15 | | Separator | T | T (not space) | | Time | HH:MM:SS | 14:30:00 | | Fractional | .sss | .123, .123456 | | Timezone | Z or ±HH:MM | Z, +05:30, -08:00 |
Strict parsing guarantees:
| Requirement | RFC 3339 Behavior | |-------------|-------------------| | Invalid dates | Rejected | | Invalid times | Rejected | | Missing timezone | Rejected | | Wrong separators | Rejected | | Compact forms | Rejected | | Leap days | Correctly handled |
Return type:
| Method | Return Type | Timezone |
|--------|-------------|----------|
| parse_from_rfc3339 | DateTime<FixedOffset> | Preserved from input |
| .parse() | DateTime<Utc> | Converted to UTC |
| .to_rfc3339() | String | RFC 3339 formatted |
Common rejection reasons:
| Input | Rejection Reason |
|-------|-----------------|
| 2024-03-15 14:30:00Z | Space instead of T |
| 2024-03-15T14:30:00 | Missing timezone |
| 2024-03-15T14:30:00+0500 | Offset missing colon |
| 2024-13-15T14:30:00Z | Invalid month |
| 2024-03-15T14:30:00+05 | Incomplete offset |
Key insight: DateTime::parse_from_rfc3339 enforces strict RFC 3339 compliance by requiring exactly the format YYYY-MM-DDTHH:MM:SS[.sss][Z|±HH:MM], rejecting any deviation from this specification. This strictness ensures interoperabilityāevery valid RFC 3339 string will be parsed identically by any compliant implementation across languages and systems. The parser validates date components (rejecting February 30, month 13, etc.), time components (rejecting hour 24, minute 60 for non-leap cases), and timezone offsets (requiring the ±HH:MM format with colon). The return type DateTime<FixedOffset> preserves the original timezone offset information, unlike parsing directly to DateTime<Utc> which converts to UTC. For APIs, configuration files, and network protocols, this strict parsing prevents the subtle bugs that arise when different systems interpret timestamps differentlyāensuring that 2024-03-15T14:30:00-05:00 is always interpreted as the same instant regardless of implementation.