angular / angular/components

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

Aperta
#31,803 2 commenti 0 reazioni 0 assegnatari Vedi su GitHub
area: material/timepicker P3
Lingua principale
TypeScript
Stelle
25k
Fork
6.8k
Merge medio
1g 8h
PR unite (30g)
91

Descrizione

### 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

Guida per i contributori

Apri la guida per i contributori

Direzione di ricerca

Inizia dalla logica _assignUserSelection del timepicker e riproduci il caso 2025-10-26 Europe/Vienna usando lo StackBlitz collegato. Traccia il modo in cui vengono combinati l’ultima data valida e l’orario estratto, quindi verifica che la selezione delle 02:00 in DST e nell’ora solare rimanga distinta e che il comportamento previsto sia mantenuto con l’adattatore Luxon personalizzato.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Valutazione

Stack tecnologico
angular, typescript
Ambito
frontend
Tipo di issue
Bug
Difficoltà
4/5
Tempo stimato
3-5 giorni
Stato di attività
Tranquilla
Chiarezza
Abbastanza chiara
Idoneità per principianti
48/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.