Repository: mui/mui-x (2026-04-13T09:59:32Z)

Issue:
[pickers] Time Picker Changes 12 PM to 11:00 PM When Switching PM to AM on March 8 (DST)
### Steps to reproduce

Steps: Live demo: https://stackblitz.com/edit/tjixwd9z?file=src%2FDemo.jsx

1. Open the time picker with the date set to March 8.
2. Set the time to 12:00 PM.
3. Click on the PM field.
4. Type “A” to change PM to AM.
5. Observe the updated time.


### Current behavior

- The time changes to 11:00 PM.
- The hour is unexpectedly decremented.
- This suggests an incorrect hour adjustment during AM/PM toggle, possibly related to DST offset calculations.

### Expected behavior

- The time should change from 12:00 PM to 12:00 AM.
- Only the meridiem should toggle.
- The hour should remain 12.

### Context

- Likely issue with 12-hour to 24-hour conversion logic combined with DST adjustment.
- This has only been observed on March 8.

### Your environment

<details>
  <summary><code>npx @mui/envinfo</code></summary>

```
  Don't forget to mention which browser you used.
  Output from `npx @mui/envinfo` goes here.
```
</details>



**Search keywords**: Time picker, DST, time

**Order ID**: 80452


Discussion:
<!-- linear-linkback -->
<p><a href="https://linear.app/mui/issue/X-4652/time-picker-changes-12-pm-to-1100-pm-when-switching-pm-to-am-on-march">X-4652</a></p>
Hey @naimezeqiri ... I can confirm this bug.
It is using `adapter.addHours`, which is affected by DST transitions resulting in an incorrect hour after transition.

Will open a PR shortly!
**This issue has been closed.** If you have a similar problem but not exactly the same, please open a [new issue](https://github.com/mui/mui-x/issues/new/choose).
Now, if you have additional information related to this issue or things that could help future readers, feel free to leave a comment.

---

@naimezeqiri Thanks again for creating this issue! If you have a moment, we would love to hear your thoughts on how we handled it with this short [feedback form](https://tally.mui.com/support-satisfaction-survey?issue=21687&productId=x).
@michelengelen

Will this fix be back-ported to v8? It's my understanding it's present there as well
I just opened a cherry pick PR @GNajeraAdv 👍🏼 
> [@michelengelen](https://github.com/michelengelen)
> 
> Will this fix be back-ported to v8? It's my understanding it's present there as well

I just merged the cherry-pick PR, so it should be introduced in the next release!



Patch:
diff --git a/packages/x-date-pickers/src/internals/hooks/useField/useField.utils.ts b/packages/x-date-pickers/src/internals/hooks/useField/useField.utils.ts
index 28b512b333317..de8b18b752d8e 100644
--- a/packages/x-date-pickers/src/internals/hooks/useField/useField.utils.ts
+++ b/packages/x-date-pickers/src/internals/hooks/useField/useField.utils.ts
@@ -17,6 +17,7 @@ import {
   InferFieldSection,
 } from '../../../models';
 import { getMonthsInYear } from '../../utils/date-utils';
+import { convertToMeridiem } from '../../utils/time-utils';
 import { PickerValidValue } from '../../models';
 
 export const getDateSectionConfigFromFormatToken = (
@@ -515,18 +516,8 @@ const transferDateSectionValue = (
     }
 
     case 'meridiem': {
-      const isAM = adapter.getHours(dateToTransferFrom) < 12;
-      const mergedDateHours = adapter.getHours(dateToTransferTo);
-
-      if (isAM && mergedDateHours >= 12) {
-        return adapter.addHours(dateToTransferTo, -12);
-      }
-
-      if (!isAM && mergedDateHours < 12) {
-        return adapter.addHours(dateToTransferTo, 12);
-      }
-
-      return dateToTransferTo;
+      const meridiem = adapter.getHours(dateToTransferFrom) < 12 ? 'am' : 'pm';
+      return convertToMeridiem(dateToTransferTo, meridiem, true, adapter);
     }
 
     case 'hours': {


Test patch:
diff --git a/packages/x-date-pickers/src/TimeField/tests/timezone.TimeField.test.tsx b/packages/x-date-pickers/src/TimeField/tests/timezone.TimeField.test.tsx
new file mode 100644
index 0000000000000..1986ef4fe30c3
--- /dev/null
+++ b/packages/x-date-pickers/src/TimeField/tests/timezone.TimeField.test.tsx
@@ -0,0 +1,68 @@
+import { spy } from 'sinon';
+import { TimeField } from '@mui/x-date-pickers/TimeField';
+import { expectFieldValue, describeAdapters } from 'test/utils/pickers';
+
+describe('<TimeField /> - Timezone', () => {
+  describeAdapters('DST meridiem toggling', TimeField, ({ adapter, renderWithProps }) => {
+    describe.skipIf(!adapter.isTimezoneCompatible)('timezoneCompatible', () => {
+      const timezone = 'America/Los_Angeles';
+      // 2024-03-10 is the DST spring-forward day in America/Los_Angeles.
+      // On this day, clocks move forward at 2:00 AM (PST, UTC-8) to 3:00 AM (PDT, UTC-7).
+      // Toggling meridiem with the old `addHours(±12)` implementation was broken
+      // because absolute-time arithmetic crosses the DST gap.
+      // Regression tests for https://github.com/mui/mui-x/issues/21687
+
+      it('should correctly toggle meridiem from PM to AM on a DST spring-forward date', async () => {
+        const onChange = spy();
+        // noon PDT (UTC-7) = 2024-03-10T19:00:00Z
+        const initialValue = adapter.date('2024-03-10T12:00:00', timezone);
+
+        const view = renderWithProps({
+          defaultValue: initialValue,
+          format: adapter.formats.fullTime12h,
+          timezone,
+          onChange,
+        });
+
+        await view.selectSectionAsync('meridiem');
+        // Toggle PM → AM
+        await view.user.keyboard('{ArrowDown}');
+
+        // Old code (addHours(-12)) would have produced "11:00 PM" (wrong).
+        // New code (setHours) correctly produces "12:00 AM" (midnight).
+        expectFieldValue(view.getSectionsContainer(), '12:00 AM');
+
+        // Verify the onChange value is midnight on March 10 (2024-03-10T08:00:00Z),
+        // not 11:00 PM on March 9 (2024-03-09T07:00:00Z).
+        const expectedDate = adapter.setHours(initialValue, 0);
+        expect(onChange.lastCall.firstArg).toEqualDateTime(expectedDate);
+      });
+
+      it('should correctly toggle meridiem from AM to PM on a DST spring-forward date', async () => {
+        const onChange = spy();
+        // midnight PST (UTC-8) = 2024-03-10T08:00:00Z
+        const initialValue = adapter.date('2024-03-10T00:00:00', timezone);
+
+        const view = renderWithProps({
+          defaultValue: initialValue,
+          format: adapter.formats.fullTime12h,
+          timezone,
+          onChange,
+        });
+
+        await view.selectSectionAsync('meridiem');
+        // Toggle AM → PM
+        await view.user.keyboard('{ArrowUp}');
+
+        // Old code (addHours(+12)) would have produced "01:00 PM" (wrong).
+        // New code (setHours) correctly produces "12:00 PM" (noon).
+        expectFieldValue(view.getSectionsContainer(), '12:00 PM');
+
+        // Verify the onChange value is noon on March 10 (2024-03-10T19:00:00Z),
+        // not 1:00 PM (2024-03-10T20:00:00Z).
+        const expectedDate = adapter.setHours(initialValue, 12);
+        expect(onChange.lastCall.firstArg).toEqualDateTime(expectedDate);
+      });
+    });
+  });
+});