Repository: pandas-dev/pandas (python)

Issue:
DOC: `pd.to_datetime` can also return `TypeError`
### Pandas version checks

- [X] I have checked that the issue still exists on the latest versions of the docs on `main` [here](https://pandas.pydata.org/docs/dev/)


### Location of the documentation

https://pandas.pydata.org/docs/reference/api/pandas.to_datetime.html

### Documentation problem

`to_datetime` can also return a `TypeError` when parsing fails. Example:

```
$ docker run -it --rm --entrypoint bash python:3.12
Unable to find image 'python:3.12' locally
3.12: Pulling from library/python
...
Digest: sha256:89f4c413ac0f36072211bced42ff7e8870cf5347c3cde4b84a67b5f87911b9a3
Status: Downloaded newer image for python:3.12
root@5fc68ee7d209:/# python3 -m pip install pandas
...
Successfully installed numpy-1.26.2 pandas-2.1.3 python-dateutil-2.8.2 pytz-2023.3.post1 six-1.16.0 tzdata-2023.3
root@5fc68ee7d209:/# python3
Python 3.12.0 (main, Nov  1 2023, 12:56:53) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import pandas as pd
>>> pd.to_datetime([True, False])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python3.12/site-packages/pandas/core/tools/datetimes.py", line 1144, in to_datetime
    result = convert_listlike(argc, format)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/pandas/core/tools/datetimes.py", line 490, in _convert_listlike_datetimes
    result, tz_parsed = objects_to_datetime64ns(
                        ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/pandas/core/arrays/datetimes.py", line 2346, in objects_to_datetime64ns
    result, tz_parsed = tslib.array_to_datetime(
                        ^^^^^^^^^^^^^^^^^^^^^^^^
  File "tslib.pyx", line 403, in pandas._libs.tslib.array_to_datetime
  File "tslib.pyx", line 552, in pandas._libs.tslib.array_to_datetime
  File "tslib.pyx", line 541, in pandas._libs.tslib.array_to_datetime
TypeError: <class 'bool'> is not convertible to datetime, at position 0
>>>
```

### Suggested fix for documentation

add

```
TypeError
  When a type cannot be converted to datetime
```

to the list of possible exceptions.

Pull request description:
feat: add exception
- [X] closes #56087 (Replace xxxx with the GitHub issue number)
- [ ] [Tests added and passed](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#writing-tests) if fixing a bug or adding a new feature
- [ ] All [code checks passed](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#pre-commit).
- [ ] Added [type annotations](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#type-hints) to new arguments/methods/functions.
- [ ] Added an entry in the latest `doc/source/whatsnew/vX.X.X.rst` file if fixing a bug or adding a new feature.


Patch:
diff --git a/doc/source/whatsnew/v2.2.0.rst b/doc/source/whatsnew/v2.2.0.rst
index cf54af7a86..9be54e95bf 100644
--- a/doc/source/whatsnew/v2.2.0.rst
+++ b/doc/source/whatsnew/v2.2.0.rst
@@ -381,7 +381,7 @@ Timezones
 ^^^^^^^^^
 - Bug in :class:`AbstractHolidayCalendar` where timezone data was not propagated when computing holiday observances (:issue:`54580`)
 - Bug in :class:`Timestamp` construction with an ambiguous value and a ``pytz`` timezone failing to raise ``pytz.AmbiguousTimeError`` (:issue:`55657`)
--
+- Bug in :meth:`Timestamp.tz_localize` with ``nonexistent="shift_forward`` around UTC+0 during DST (:issue:`51501`)
 
 Numeric
 ^^^^^^^
diff --git a/pandas/_libs/tslibs/tzconversion.pyx b/pandas/_libs/tslibs/tzconversion.pyx
index e77a385113..2c4f0cd14d 100644
--- a/pandas/_libs/tslibs/tzconversion.pyx
+++ b/pandas/_libs/tslibs/tzconversion.pyx
@@ -416,8 +416,13 @@ timedelta-like}
 
                 else:
                     delta_idx = bisect_right_i8(info.tdata, new_local, info.ntrans)
-
-                    delta_idx = delta_idx - delta_idx_offset
+                    # Logic similar to the precompute section. But check the current
+                    # delta in case we are moving between UTC+0 and non-zero timezone
+                    if (shift_forward or shift_delta > 0) and \
+                       info.deltas[delta_idx - 1] >= 0:
+                        delta_idx = delta_idx - 1
+                    else:
+                        delta_idx = delta_idx - delta_idx_offset
                     result[i] = new_local - info.deltas[delta_idx]
             elif fill_nonexist:
                 result[i] = NPY_NAT
diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py
index 34a6e11873..b76ad5268e 100644
--- a/pandas/core/arrays/datetimes.py
+++ b/pandas/core/arrays/datetimes.py
@@ -2379,6 +2379,7 @@ def objects_to_datetime64(
     Raises
     ------
     ValueError : if data cannot be converted to datetimes
+    TypeError  : When a type cannot be converted to datetime
     """
     assert errors in ["raise", "ignore", "coerce"]
 


Test patch:
diff --git a/pandas/tests/scalar/timestamp/methods/test_tz_localize.py b/pandas/tests/scalar/timestamp/methods/test_tz_localize.py
index 247a583bc3..9df0a02373 100644
--- a/pandas/tests/scalar/timestamp/methods/test_tz_localize.py
+++ b/pandas/tests/scalar/timestamp/methods/test_tz_localize.py
@@ -123,6 +123,44 @@ class TestTimestampTZLocalize:
             ts.tz_localize(tz, nonexistent="raise")
         assert ts.tz_localize(tz, nonexistent="NaT") is NaT
 
+    @pytest.mark.parametrize(
+        "stamp, tz, forward_expected, backward_expected",
+        [
+            (
+                "2015-03-29 02:00:00",
+                "Europe/Warsaw",
+                "2015-03-29 03:00:00",
+                "2015-03-29 01:59:59",
+            ),  # utc+1 -> utc+2
+            (
+                "2023-03-12 02:00:00",
+                "America/Los_Angeles",
+                "2023-03-12 03:00:00",
+                "2023-03-12 01:59:59",
+            ),  # utc-8 -> utc-7
+            (
+                "2023-03-26 01:00:00",
+                "Europe/London",
+                "2023-03-26 02:00:00",
+                "2023-03-26 00:59:59",
+            ),  # utc+0 -> utc+1
+            (
+                "2023-03-26 00:00:00",
+                "Atlantic/Azores",
+                "2023-03-26 01:00:00",
+                "2023-03-25 23:59:59",
+            ),  # utc-1 -> utc+0
+        ],
+    )
+    def test_tz_localize_nonexistent_shift(
+        self, stamp, tz, forward_expected, backward_expected
+    ):
+        ts = Timestamp(stamp)
+        forward_ts = ts.tz_localize(tz, nonexistent="shift_forward")
+        assert forward_ts == Timestamp(forward_expected, tz=tz)
+        backward_ts = ts.tz_localize(tz, nonexistent="shift_backward")
+        assert backward_ts == Timestamp(backward_expected, tz=tz)
+
     def test_tz_localize_ambiguous_raise(self):
         # GH#13057
         ts = Timestamp("2015-11-1 01:00")