How does chrono::TimeZone::with_ymd_and_hms handle ambiguous local times during DST transitions?

with_ymd_and_hms returns a LocalResult<T> that explicitly represents the three possible outcomes when constructing a datetime from local time: unique times return Single(T), ambiguous times during DST "fall back" transitions return Ambiguous(T, T) (two possible UTC times), and non-existent times during DST "spring forward" transitions return None. This design forces developers to acknowledge and handle the edge cases that occur when local time cannot be uniquely mapped to UTC.

Understanding the Problem: DST Transitions

use chrono::{TimeZone, Local, NaiveDateTime, Duration};
 
fn dst_transitions_explained() {
    // Daylight Saving Time creates two types of problematic transitions:
    
    // 1. "Spring forward" - Clocks jump forward (e.g., 2:00 AM β†’ 3:00 AM)
    //    The hour 2:00-3:00 AM never exists
    //    Times in this range are NON-EXISTENT
    
    // 2. "Fall back" - Clocks fall backward (e.g., 2:00 AM β†’ 1:00 AM)  
    //    The hour 1:00-2:00 AM occurs twice
    //    Times in this range are AMBIGUOUS (two possible UTC times)
    
    // Example: US Eastern Time, November 5, 2023 at 1:30 AM
    // This time occurs twice:
    // - Once in EDT (Eastern Daylight Time) UTC-4
    // - Once in EST (Eastern Standard Time) UTC-5
    // 
    // When someone says "1:30 AM on Nov 5", which one do they mean?
    
    // chrono's solution: Return all possibilities and let you decide
}

DST transitions create times that are either ambiguous or non-existent.

The LocalResult Return Type

use chrono::{TimeZone, Local, NaiveDate, NaiveTime, LocalResult};
 
fn local_result_explained() {
    // with_ymd_and_hms returns LocalResult<NaiveDateTime> when given components
    // or LocalResult<DateTime<Tz>> when called on a timezone
    
    // LocalResult represents three outcomes:
    let unique: LocalResult<i32> = LocalResult::Single(42);
    let ambiguous: LocalResult<i32> = LocalResult::Ambiguous(1, 2);
    let nonexistent: LocalResult<i32> = LocalResult::None;
    
    // This type forces you to handle all three cases
    match unique {
        LocalResult::Single(value) => println!("Unique time: {}", value),
        LocalResult::Ambiguous(earliest, latest) => {
            println!("Ambiguous: could be {} or {}", earliest, latest);
        }
        LocalResult::None => println!("Non-existent time"),
    }
    
    // The type system ensures you can't ignore ambiguity
}
 
fn basic_usage() {
    // Normal case: unambiguous time
    let result = Local.with_ymd_and_hms(2023, 6, 15, 14, 30, 0);
    
    // Result is LocalResult<DateTime<Local>>
    // For unambiguous times, it's LocalResult::Single
    assert!(result.is_single());
    
    // Single can be extracted with unwrap(), which panics on ambiguous/none
    let dt = result.single().expect("Should be unambiguous");
    println!("DateTime: {}", dt);
    
    // Or use earliest/latest which handle all cases:
    // - Single(v) -> Some(v) for both earliest and latest
    // - Ambiguous(e, l) -> Some(e) for earliest, Some(l) for latest
    // - None -> None for both
}

LocalResult is an enum that explicitly represents the three possible outcomes.

Handling Non-Existent Times (Spring Forward)

use chrono::{TimeZone, Local, FixedOffset, NaiveDate};
 
fn nonexistent_time_example() {
    // US Eastern Time spring forward: March 12, 2023
    // At 2:00 AM, clocks jump to 3:00 AM
    // Times from 2:00 AM to 2:59 AM do not exist
    
    // Use FixedOffset to simulate a specific timezone
    // US Eastern Daylight Time is UTC-4
    // US Eastern Standard Time is UTC-5
    // The transition happens at 2:00 AM local time
    
    // For demonstration, let's use a simpler example
    // Consider a timezone that transitions at midnight
    
    // Create a datetime that would be in a "gap"
    // In reality, you'd use the actual timezone transitions
    
    // Simulate the problem with a fixed offset
    let eastern = FixedOffset::west_opt(5 * 3600).unwrap(); // UTC-5
    
    // Normal time works fine
    let valid_time = eastern.with_ymd_and_hms(2023, 3, 12, 1, 30, 0);
    assert!(valid_time.is_single());
    
    // But what about times in the "gap"?
    // The specific times depend on the timezone's transition rules
    // When a time doesn't exist, we get LocalResult::None
    
    // For actual DST handling, you need real timezone data:
    use chrono_tz::US::Eastern;
    
    // During spring forward, 2:00-2:59 AM doesn't exist
    let nonexistent = Eastern.with_ymd_and_hms(2023, 3, 12, 2, 30, 0);
    
    // This returns LocalResult::None
    assert!(nonexistent.is_none());
    
    match nonexistent {
        LocalResult::None => println!("This time doesn't exist!"),
        LocalResult::Single(dt) => println!("Time: {}", dt),
        LocalResult::Ambiguous(early, late) => {
            println!("Ambiguous: {} or {}", early, late);
        }
    }
}
 
fn handle_nonexistent_time() {
    use chrono_tz::US::Eastern;
    use chrono::{TimeZone, Duration};
    
    let result = Eastern.with_ymd_and_hms(2023, 3, 12, 2, 30, 0);
    
    // Strategy 1: Return an error
    let dt = match result.single() {
        Some(dt) => dt,
        None => {
            eprintln!("Invalid time: non-existent during DST transition");
            return;
        }
    };
    
    // Strategy 2: Use the earliest valid time (start of gap + offset)
    // If time doesn't exist, use the first valid time after
    let dt = result.single().or_else(|| {
        // Try the next hour
        result.latest()
    });
    
    // Strategy 3: Shift forward to next valid time
    let dt = result.single().or_else(|| {
        // During spring forward, add an hour to skip the gap
        Eastern.with_ymd_and_hms(2023, 3, 12, 3, 30, 0).single()
    });
    
    // Strategy 4: Return to caller and let them handle it
    // (Best for libraries - don't make policy decisions for users)
}

Non-existent times occur during spring-forward when clocks skip an hour.

Handling Ambiguous Times (Fall Back)

use chrono::{TimeZone, LocalResult};
use chrono_tz::US::Eastern;
 
fn ambiguous_time_example() {
    // US Eastern Time fall back: November 5, 2023
    // At 2:00 AM, clocks fall back to 1:00 AM
    // Times from 1:00 AM to 1:59 AM occur twice
    
    // 1:30 AM on Nov 5, 2023 is ambiguous
    let ambiguous = Eastern.with_ymd_and_hms(2023, 11, 5, 1, 30, 0);
    
    // This returns LocalResult::Ambiguous
    match ambiguous {
        LocalResult::Ambiguous(earliest, latest) => {
            println!("Ambiguous time detected!");
            println!("  Earliest (in DST): {}", earliest);
            println!("  Latest (in standard): {}", latest);
            
            // Both represent the same wall clock time
            // But different UTC offsets
            // earliest is in EDT (UTC-4)
            // latest is in EST (UTC-5)
        }
        LocalResult::Single(dt) => println!("Unambiguous: {}", dt),
        LocalResult::None => println!("Non-existent"),
    }
}
 
fn handle_ambiguous_time() {
    use chrono_tz::US::Eastern;
    
    let result = Eastern.with_ymd_and_hms(2023, 11, 5, 1, 30, 0);
    
    // Strategy 1: Always use earliest (DST time)
    let earliest = result.earliest();
    // Returns Some(dt) for Ambiguous case, taking the first occurrence
    
    // Strategy 2: Always use latest (Standard time)
    let latest = result.latest();
    // Returns Some(dt) for Ambiguous case, taking the second occurrence
    
    // Strategy 3: Ask user to specify
    // Many applications show "1:30 AM EDT" vs "1:30 AM EST"
    
    // Strategy 4: Use a heuristic based on context
    // E.g., prefer standard time if it's a recurring scheduled event
    
    // The method names make intent clear:
    match result {
        LocalResult::Ambiguous(early, late) => {
            // early is in DST (earlier UTC time)
            // late is in standard time (later UTC time)
            println!("Ambiguous: {} or {}", early, late);
            
            // For scheduling, you might prefer the later one
            // to avoid running the task twice
            let chosen = late;
            println!("Using later time: {}", chosen);
        }
        LocalResult::Single(dt) => println!("Unique: {}", dt),
        LocalResult::None => println!("Non-existent time"),
    }
}

Ambiguous times occur during fall-back when an hour repeats.

Complete Decision Flow

use chrono::{TimeZone, LocalResult};
use chrono_tz::US::Eastern;
 
fn complete_dst_handling() {
    // DateTime construction flow:
    // 1. Parse or receive local time components
    // 2. Call with_ymd_and_hms
    // 3. Match on LocalResult
    // 4. Apply appropriate policy for ambiguous/nonexistent
    
    fn parse_local_time(year: i32, month: u32, day: u32, hour: u32, min: u32, sec: u32) 
        -> Result<chrono::DateTime<chrono_tz::Tz>, String> 
    {
        let result = Eastern.with_ymd_and_hms(year, month, day, hour, min, sec);
        
        match result {
            LocalResult::Single(dt) => {
                // Normal case: unambiguous time
                Ok(dt)
            }
            LocalResult::Ambiguous(earliest, latest) => {
                // DST fall-back: this hour happened twice
                // Default policy: use later time (standard time)
                // Adjust based on your application's needs
                Ok(latest)
            }
            LocalResult::None => {
                // DST spring-forward: this time never happened
                // Default policy: return error
                Err(format!(
                    "Time {:04}-{:02}-{:02} {:02}:{:02}:{:02} doesn't exist (DST transition)",
                    year, month, day, hour, min, sec
                ))
            }
        }
    }
    
    // Usage
    match parse_local_time(2023, 11, 5, 1, 30, 0) {
        Ok(dt) => println!("Parsed: {}", dt),
        Err(e) => println!("Error: {}", e),
    }
}

A complete handling strategy must account for all three cases.

Practical Example: Scheduling System

use chrono::{TimeZone, LocalResult, DateTime, Duration};
use chrono_tz::{Tz, US::Eastern};
 
// Real-world scenario: scheduling recurring events
struct ScheduledEvent {
    name: String,
    // Store as UTC internally to avoid ambiguity
    utc_time: DateTime<Tz>,
}
 
impl ScheduledEvent {
    // Create from local time input (e.g., user form input)
    fn from_local(
        name: String,
        year: i32, month: u32, day: u32,
        hour: u32, min: u32, sec: u32,
        timezone: Tz,
    ) -> Result<Self, String> {
        let result = timezone.with_ymd_and_hms(year, month, day, hour, min, sec);
        
        match result {
            LocalResult::Single(dt) => Ok(Self {
                name,
                utc_time: dt,
            }),
            LocalResult::Ambiguous(earliest, latest) => {
                // For scheduling, we need to ask the user
                // In a web app, this would return to the UI
                // For this example, we'll default to standard time (later)
                println!(
                    "Warning: Ambiguous time. Using {} (standard time) instead of {} (daylight time)",
                    latest, earliest
                );
                Ok(Self {
                    name,
                    utc_time: latest,
                })
            }
            LocalResult::None => {
                Err(format!(
                    "Time {}-{}-{} {}:{}:{} doesn't exist in {} (DST spring-forward)",
                    year, month, day, hour, min, sec, timezone.name()
                ))
            }
        }
    }
    
    // Display in local time
    fn local_display(&self, timezone: Tz) -> String {
        self.utc_time.with_timezone(&timezone).to_rfc3339()
    }
}
 
fn scheduling_example() {
    // Normal case
    let event1 = ScheduledEvent::from_local(
        "Meeting".to_string(),
        2023, 6, 15, 14, 30, 0,
        Eastern,
    ).unwrap();
    
    // Ambiguous case (fall back)
    let event2 = ScheduledEvent::from_local(
        "Recurring Task".to_string(),
        2023, 11, 5, 1, 30, 0,
        Eastern,
    );
    // Prints warning about ambiguity
    
    // Non-existent case (spring forward)
    let event3 = ScheduledEvent::from_local(
        "Early Meeting".to_string(),
        2023, 3, 12, 2, 30, 0,
        Eastern,
    );
    // Returns error: time doesn't exist
}

Storing UTC internally and handling local time conversion at boundaries is the recommended pattern.

Extracting Values Safely

use chrono::{TimeZone, LocalResult};
use chrono_tz::US::Eastern;
 
fn extraction_methods() {
    let result = Eastern.with_ymd_and_hms(2023, 11, 5, 1, 30, 0);
    
    // Method 1: single() - Returns Some for Single, None for Ambiguous/None
    let single = result.single();
    // Returns None for ambiguous times
    // Use when you only want unambiguous times
    
    // Method 2: earliest() - Returns earliest valid interpretation
    let earliest = result.earliest();
    // For Ambiguous: returns first occurrence (DST)
    // For Single: returns the time
    // For None: returns None
    
    // Method 3: latest() - Returns latest valid interpretation  
    let latest = result.latest();
    // For Ambiguous: returns second occurrence (standard)
    // For Single: returns the time
    // For None: returns None
    
    // Method 4: unwrap() - Panics on Ambiguous/None
    // Only use when you're certain the time is valid and unambiguous
    // let dt = result.unwrap(); // DANGEROUS
    
    // Safe unwrap with explanation:
    let dt = result.single().expect(
        "Time should be valid and unambiguous; \
         check DST transitions if this fails"
    );
}
 
fn safe_conversions() {
    use chrono::{NaiveDate, NaiveTime, NaiveDateTime};
    
    // Sometimes you have NaiveDateTime and want to convert
    let naive = NaiveDate::from_ymd_opt(2023, 11, 5).unwrap()
        .and_hms_opt(1, 30, 0).unwrap();
    
    // Use from_local_datetime for NaiveDateTime
    let result = Eastern.from_local_datetime(&naive);
    
    // Same LocalResult handling
    match result {
        LocalResult::Single(dt) => println!("Unique: {}", dt),
        LocalResult::Ambiguous(e, l) => println!("Ambiguous: {} or {}", e, l),
        LocalResult::None => println!("Non-existent"),
    }
}

Choose extraction methods based on how you want to handle edge cases.

Working with Timezones

use chrono::{TimeZone, LocalResult};
use chrono_tz::{Tz, US::Eastern, Europe::London, Asia::Tokyo};
 
fn timezone_comparison() {
    // Different timezones have different DST rules
    // US DST: Second Sunday in March to first Sunday in November
    // Europe DST: Last Sunday in March to last Sunday in October
    // Japan: No DST
    
    let year = 2023;
    let month = 3;
    let day = 26; // Around DST transition in both US and Europe
    
    // London DST transition (last Sunday in March)
    let london_time = London.with_ymd_and_hms(year, month, day, 1, 30, 0);
    // Around 1:00 AM, clocks jump to 2:00 AM
    
    // US Eastern DST transition (second Sunday in March - earlier)
    // March 12, 2023: 2:00 AM -> 3:00 AM
    // March 26 is well after the transition
    let eastern_time = Eastern.with_ymd_and_hms(year, month, day, 1, 30, 0);
    // This is unambiguous - DST is already in effect
    
    // Tokyo (no DST)
    let tokyo_time = Tokyo.with_ymd_and_hms(year, month, day, 1, 30, 0);
    // Always unambiguous - no DST transitions
    assert!(tokyo_time.is_single());
    
    // Lesson: DST transitions happen at different times in different zones
    // A time ambiguous in one zone might be fine in another
}
 
fn timezone_aware_applications() {
    // For applications dealing with multiple timezones:
    
    // 1. Store all times in UTC
    // 2. Convert to local only for display
    // 3. When accepting local time input, use with_ymd_and_hms
    // 4. Handle LocalResult appropriately for the timezone
    
    fn convert_local_to_utc(
        local_time: chrono::NaiveDateTime,
        tz: Tz,
    ) -> Result<chrono::DateTime<Tz>, String> {
        match tz.from_local_datetime(&local_time) {
            LocalResult::Single(dt) => Ok(dt),
            LocalResult::Ambiguous(earliest, latest) => {
                // Policy: use later time (standard)
                // Alternative: return both and let user choose
                Ok(latest)
            }
            LocalResult::None => {
                Err("Time doesn't exist due to DST transition".to_string())
            }
        }
    }
}

Different timezones have different DST schedules; handle each appropriately.

Best Practices

use chrono::{TimeZone, LocalResult, DateTime};
use chrono_tz::Tz;
 
fn best_practices() {
    // 1. Never use .single().unwrap() without checking
    //    The time might be ambiguous or non-existent
    
    // Bad:
    // let dt = tz.with_ymd_and_hms(2023, 11, 5, 1, 30, 0).single().unwrap();
    // Panics on DST transitions!
    
    // Good:
    // let dt = tz.with_ymd_and_hms(2023, 11, 5, 1, 30, 0).single()
    //     .ok_or("Invalid time")?;
    
    // 2. Use earliest/latest with intent
    //    .earliest() -> first occurrence during fall-back (in DST)
    //    .latest() -> second occurrence during fall-back (in standard)
    
    // 3. Store UTC, display local
    //    Database: UTC timestamp
    //    User display: convert to user's timezone
    
    // 4. Document your DST policy
    //    What happens when user schedules during DST transition?
    //    Does your app reject, adjust, or ask for clarification?
    
    // 5. Test edge cases
    //    Test code around DST transition dates
    //    Use timezone-aware tests with known transition dates
}
 
fn recommended_pattern() {
    use chrono_tz::US::Eastern;
    
    // Recommended pattern for handling local time input:
    
    // Step 1: Define your policy
    #[derive(Debug)]
    enum DstPolicy {
        Reject,         // Fail on ambiguous/non-existent
        PreferEarliest, // Use first occurrence for ambiguous
        PreferLatest,   // Use second occurrence for ambiguous
        Adjust,         // Shift non-existent times forward
    }
    
    // Step 2: Apply policy consistently
    fn resolve_local_time(
        year: i32, month: u32, day: u32,
        hour: u32, min: u32, sec: u32,
        policy: DstPolicy,
    ) -> Result<DateTime<Tz>, String> {
        let result = Eastern.with_ymd_and_hms(year, month, day, hour, min, sec);
        
        match (&result, &policy) {
            (LocalResult::Single(dt), _) => Ok(*dt),
            
            (LocalResult::Ambiguous(earliest, _latest), DstPolicy::PreferEarliest) => {
                Ok(*earliest)
            }
            
            (LocalResult::Ambiguous(_earliest, latest), DstPolicy::PreferLatest) => {
                Ok(*latest)
            }
            
            (LocalResult::Ambiguous(_, _), DstPolicy::Reject) => {
                Err("Ambiguous time during DST transition".to_string())
            }
            
            (LocalResult::None, DstPolicy::Reject) => {
                Err("Non-existent time during DST transition".to_string())
            }
            
            (LocalResult::None, DstPolicy::Adjust) => {
                // Try adding an hour for spring-forward gap
                Eastern.with_ymd_and_hms(year, month, day, hour + 1, min, sec)
                    .single()
                    .ok_or("Could not adjust time".to_string())
            }
            
            _ => Err("Unhandled DST case".to_string()),
        }
    }
}

Define and document your DST handling policy; apply it consistently.

Synthesis

use chrono::{TimeZone, LocalResult};
use chrono_tz::Tz;
 
fn complete_guide_summary() {
    // β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    // β”‚ LocalResult Variant β”‚ Meaning           β”‚ Example (US Eastern)         β”‚
    // β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
    // β”‚ Single(dt)          β”‚ Unambiguous time  β”‚ 2023-06-15 14:30             β”‚
    // β”‚ Ambiguous(e, l)     β”‚ DST fall-back     β”‚ 2023-11-05 01:30 (twice)     β”‚
    // β”‚ None                β”‚ DST spring-forwardβ”‚ 2023-03-12 02:30 (never)      β”‚
    // β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    
    // β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    // β”‚ Method      β”‚ Single           β”‚ Ambiguous            β”‚ None           β”‚
    // β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
    // β”‚ .single()   β”‚ Some(dt)         β”‚ None                 β”‚ None           β”‚
    // β”‚ .earliest() β”‚ Some(dt)         β”‚ Some(first time)     β”‚ None           β”‚
    // β”‚ .latest()   β”‚ Some(dt)         β”‚ Some(second time)    β”‚ None           β”‚
    // β”‚ .unwrap()   β”‚ dt               β”‚ PANIC                β”‚ PANIC          β”‚
    // β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    
    // Key insight:
    // with_ymd_and_hms doesn't make assumptions about ambiguous times.
    // It returns all possibilities and forces you to choose.
    // This is correct behavior: there's no universally right answer.
    
    // For ambiguous times:
    // - earliest() gives DST time (earlier UTC, same wall clock)
    // - latest() gives standard time (later UTC, same wall clock)
    
    // For non-existent times:
    // - You must decide: reject, adjust forward, or ask user
    
    // Best practice: store UTC, convert to local for display,
    // and when accepting local input, handle LocalResult explicitly.
}
 
// Key insight:
// with_ymd_and_hms returns LocalResult because local time is
// fundamentally ambiguous during DST transitions. The type system
// forces you to acknowledge and handle this. There's no "right"
// defaultβ€”different applications need different policies. Scheduling
// systems might reject ambiguous times; logging systems might use
// earliest(); display systems might ask the user. The key is that
// you must choose, and the LocalResult type ensures you can't forget.

Key insight: with_ymd_and_hms returns LocalResult because local time is fundamentally ambiguous during DST transitions. The three variantsβ€”Single, Ambiguous, and Noneβ€”correspond to the three real-world possibilities: unique times, times that occurred twice (fall-back), and times that never occurred (spring-forward). The Ambiguous(e, l) variant gives you both possible UTC times: the earliest is in DST (earlier UTC offset), the latest is in standard time (later UTC offset). By forcing explicit handling through LocalResult, chrono ensures developers can't accidentally ignore DST edge cases. The recommended pattern is to define your DST policy upfront (reject, prefer earliest, prefer latest, or adjust), apply it consistently through a helper function, and store UTC internally to avoid ambiguity in your data model.