angular / angular/components

bug(timepicker): Timepicker DST edge case: incorrect hour mapping with fixed timezone + custom Luxon adapter

Offen
#31,803 2 Kommentare 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen
area: material/timepicker P3
Vorherrschende Sprache
TypeScript
Sterne
25k
Forks
6.8k
Ø Merge
1 T. 8 Std.
Gemergte PRs (30 T.)
91

Beschreibung

### Is this a regression?

- [ ] Yes, this behavior used to work in the previous version

### The previous version in which this bug was not present was

_No response_

### Description

When using Angular’s timepicker with a shared FormControl, Luxon as the date adapter (with a fixed timezone and locale), there is an edge case when switching from **DST → standard time**.

The problem arises because `_assignUserSelection` uses `getValidDateOrNull` and then applies extracted hours/minutes/seconds onto a **target date** that may carry the wrong DST context. This causes the two distinct `02:00` times (DST vs standard) on the fallback day to collapse into one — selecting the non-DST `02:00` ends up mapped to the DST `02:00`.

### Custom DateAdapter:
To ensure consistent Luxon `DateTime` handling, I replaced the default `LuxonDateAdapter` with a custom one.

```
@Injectable()
export class CustomDateAdapter extends LuxonDateAdapter {
protected override locale = DateTimeService.LOCALE;

constructor() {
super(DATE_FORMATS);
}

/** Ensure datepicker provides Luxon DateTimes in the default zone */
public override createDate(year: number, month: number, date: number): DateTime {
return super.createDate(year, month, date)
.setZone(DateTimeService.DEFAULT_ZONE)
.setLocale(DateTimeService.LOCALE);
}

/**
* Needed so the timepicker does not treat DST and non-DST versions of the same wall time as equal.
* Without this, both values are considered the same and the DST one always “wins”.
*/
public override sameTime(first: DateTime | null, second: DateTime | null): boolean {
if (!isDefined(first) || !isDefined(second)) {
return false;
}
return +first === +second;
}

public override clone(date: DateTime): DateTime {
let cloned = super.clone(date);
cloned = cloned.setZone(DateTimeService.DEFAULT_ZONE);
cloned = cloned.setLocale(DateTimeService.LOCALE);
return cloned;
}
}
´´´

Despite these adjustments, the underlying `_assignUserSelection` logic in the timepicker still collapses the two 02:00 times into one, because of how it uses the last valid date + setTime.

### Reproduction

StackBlitz link: https://stackblitz.com/edit/stackblitz-starters-fyaws5vk
Steps to reproduce:
* Use the custom CustomDateAdapter (above) instead of the default LuxonDateAdapter.
On the DST end day ( 2025-10-26), try selecting both 02:00 (DST) and 02:00 (STD).
* Observe that selecting the 02:00 (STD) still results in the 02:00 (DST) value being applied (or both depending on Custom vs LuxonDateAdapter).

### Expected Behavior

* The timepicker should treat 02:00 DST and 02:00 STD as distinct values when using a fixed timezone.
* Selecting the standard-time 02:00 should not be coerced into the DST-time 02:00.

### Actual Behavior

* Because `_assignUserSelection` rebuilds the target date and applies extracted hours, the ambiguous hour is resolved to the earlier offset (DST).
* Even with a custom adapter that distinguishes times (`sameTime` override), the timepicker ends up normalizing both selections to the DST instance.

### Environment

- Angular: 20.2
- CDK/Material: 20.2
- Browser(s): Chrome, ...
- Operating System (e.g. Windows, macOS, Ubuntu): macOS

- Custom DateAdapter: subclass of LuxonDateAdapter (see above)
- Timezone: Europe/Vienna

Beitragsleitfaden

Beitragsleitfaden öffnen

Rechercherichtung

Beginnen Sie bei der _assignUserSelection-Logik des timepicker und reproduzieren Sie den Fall 2025-10-26 Europe/Vienna mit dem verlinkten StackBlitz. Verfolgen Sie, wie das letzte gültige Datum und die extrahierte Zeit kombiniert werden, und überprüfen Sie anschließend, dass die Auswahl von 02:00 in der DST und in der Standardzeit unterscheidbar bleibt und dass das erwartete Verhalten mit dem benutzerdefinierten Luxon-Adapter erhalten bleibt.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Bewertung

Tech-Stack
angular, typescript
Bereich
frontend
Issue-Typ
Bug
Schwierigkeit
4/5
Geschätzter Aufwand
3-5 Tage
Aktivitätsstatus
Ruhig
Klarheit
Größtenteils klar
Anfängerfreundlichkeit
48/100

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.