ENH: Format `datetime.datetime` and `pd.Timestamp` objects in `pd.to_datetime`
State: closed

- [X] Closes #49298
- [x] [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
- [x] All [code checks passed](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#pre-commit).
- [X] Added [type annotations](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#type-hints) to new arguments/methods/functions.
- [x] Added an entry in the latest `doc/source/whatsnew/vX.X.X.rst` file if fixing a bug or adding a new feature.

---

**Description of changes**

When passing `datetime.datetime` or `pd.Timestamp` objects to `pd.to_datetime`, while using the `format` argument, a `ValueError` is raised because those object does not match the expected format.

The implemented solution looks for these non-string objects in the `array_strptime` method, and then each object is processed to maintain its contents (which might differ from the expected `format` for strings), and then continuing processing as usual.

Labels:
name: Datetime
description: Datetime data dtype

Comments:
> In this case, they're both `tz-aware`, so is it right that the error message refers to `tz-naive`?

To be clear, should this case raise a ValueError or not? Is the message the problem? What's the expected behavior in this case?

Relatedly, what should happen if there are no string to format? That argument would not have any effect, is a warning necessary/appropriate?


If they all have the same offset, then something like:
```python
In [3]: to_datetime(['01-01-2000 00:00:00 +01:00', '01-02-2000 00:00:00 +01:00'], format='%d-%m-%Y %H:%M:%S %z')
Out[3]: DatetimeIndex(['2000-01-01 00:00:00+01:00', '2000-02-01 00:00:00+01:00'], dtype='datetime64[ns, pytz.FixedOffset(60)]', freq=None)
```

If they have different offsets:
```
In [4]: to_datetime(['01-01-2000 00:00:00 +01:00', '01-02-2000 00:00:00 +02:00'], format='%d-%m-%Y %H:%M:%S %z')
Out[4]: Index([2000-01-01 00:00:00+01:00, 2000-02-01 00:00:00+02:00], dtype='object')
```

> Relatedly, what should happen if there are no string to format? That argument would not have any effect, is a warning necessary/appropriate?

🤔 that's a good one - tempted to say that we should just let the argument not have any effect, wouldn't bother with a warning

EDIT: I wasn't quite correct here. If they have different offsets, then it should be allowed if `utc=True`, and should raise otherwise. It should be consistent with when `format` is an ISO8601 format, which already allows for pydatetime objects
I'd suggest looking at

https://github.com/pandas-dev/pandas/blob/f3c46cd0899d5e11e0602798d9390c90e51e9ba7/pandas/_libs/tslib.pyx#L442

and perhaps making a function to factor out from there, because in the ISO8601 case then it seems that datetime / Timestamp objects are handled fine:

```python
In [10]: to_datetime(['2000-01-01', ts], format='%Y-%m-%d')
Out[10]: DatetimeIndex(['2000-01-01', '1950-01-01'], dtype='datetime64[ns]', freq=None)

In [11]: to_datetime(['2000-01-01', ts], format='%Y-%d-%m')
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In [11], line 1
----> 1 to_datetime(['2000-01-01', ts], format='%Y-%d-%m')

File ~/tmp/.venv/lib/python3.10/site-packages/pandas/core/tools/datetimes.py:1100, in to_datetime(arg, errors, dayfirst, yearfirst, utc, format, exact, unit, infer_datetime_format, origin, cache)
   1098         result = _convert_and_box_cache(argc, cache_array)
   1099     else:
-> 1100         result = convert_listlike(argc, format)
   1101 else:
   1102     result = convert_listlike(np.array([arg]), format)[0]

File ~/tmp/.venv/lib/python3.10/site-packages/pandas/core/tools/datetimes.py:430, in _convert_listlike_datetimes(arg, format, name, tz, unit, errors, infer_datetime_format, dayfirst, yearfirst, exact)
    427         format = None
    429 if format is not None:
--> 430     res = _to_datetime_with_format(
    431         arg, orig_arg, name, tz, format, exact, errors, infer_datetime_format
    432     )
    433     if res is not None:
    434         return res

File ~/tmp/.venv/lib/python3.10/site-packages/pandas/core/tools/datetimes.py:538, in _to_datetime_with_format(arg, orig_arg, name, tz, fmt, exact, errors, infer_datetime_format)
    535         return _box_as_indexlike(result, utc=utc, name=name)
    537 # fallback
--> 538 res = _array_strptime_with_fallback(
    539     arg, name, tz, fmt, exact, errors, infer_datetime_format
    540 )
    541 return res

File ~/tmp/.venv/lib/python3.10/site-packages/pandas/core/tools/datetimes.py:473, in _array_strptime_with_fallback(arg, name, tz, fmt, exact, errors, infer_datetime_format)
    470 utc = tz == "utc"
    472 try:
--> 473     result, timezones = array_strptime(arg, fmt, exact=exact, errors=errors)
    474 except OutOfBoundsDatetime:
    475     if errors == "raise":

File ~/tmp/.venv/lib/python3.10/site-packages/pandas/_libs/tslibs/strptime.pyx:156, in pandas._libs.tslibs.strptime.array_strptime()

ValueError: unconverted data remains:  00:00:00
```
Closing in favor of #49893