Repository: pandas-dev/pandas (python)

Issue:
BUG: explicit tz=None ignored in Timestamp constructor
```python
In [14]: Timestamp(datetime(2022, 1, 1, tzinfo=timezone.utc), tz=None)
Out[14]: Timestamp('2022-01-01 00:00:00+0000', tz='UTC')
```

If a user explicitly passes tz=None and a tzaware string or datetime object, we should raise.  This was fixed for DatetimeIndex in GH#48659


Pull request:
BUG: Timestamp ignoring explicit tz=None
- [x] closes #48688(Replace xxxx with the GitHub issue number)
- [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.


Patch:
diff --git a/ci/code_checks.sh b/ci/code_checks.sh
index b76ef69efa..01baadab67 100755
--- a/ci/code_checks.sh
+++ b/ci/code_checks.sh
@@ -153,9 +153,6 @@ if [[ -z "$CHECK" || "$CHECK" == "docstrings" ]]; then
         -i "pandas.DatetimeTZDtype SA01" \
         -i "pandas.DatetimeTZDtype.tz SA01" \
         -i "pandas.DatetimeTZDtype.unit SA01" \
-        -i "pandas.ExcelFile PR01,SA01" \
-        -i "pandas.ExcelFile.parse PR01,SA01" \
-        -i "pandas.ExcelWriter SA01" \
         -i "pandas.Float32Dtype SA01" \
         -i "pandas.Float64Dtype SA01" \
         -i "pandas.Grouper PR02,SA01" \
diff --git a/doc/source/getting_started/install.rst b/doc/source/getting_started/install.rst
index 3cd9e030d6..cf5f15ceb8 100644
--- a/doc/source/getting_started/install.rst
+++ b/doc/source/getting_started/install.rst
@@ -269,6 +269,8 @@ SciPy                     1.10.0             computation     Miscellaneous stati
 xarray                    2022.12.0          computation     pandas-like API for N-dimensional data
 ========================= ================== =============== =============================================================
 
+.. _install.excel_dependencies:
+
 Excel files
 ^^^^^^^^^^^
 
diff --git a/doc/source/getting_started/intro_tutorials/02_read_write.rst b/doc/source/getting_started/intro_tutorials/02_read_write.rst
index ae658ec6ab..aa032b186a 100644
--- a/doc/source/getting_started/intro_tutorials/02_read_write.rst
+++ b/doc/source/getting_started/intro_tutorials/02_read_write.rst
@@ -111,6 +111,12 @@ strings (``object``).
 
 My colleague requested the Titanic data as a spreadsheet.
 
+.. note::
+    If you want to use :func:`~pandas.to_excel` and :func:`~pandas.read_excel`,
+    you need to install an Excel reader as outlined in the
+    :ref:`Excel files <install.excel_dependencies>` section of the
+    installation documentation.
+
 .. ipython:: python
 
     titanic.to_excel("titanic.xlsx", sheet_name="passengers", index=False)
diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst
index e05cc87d1a..c19c59afd0 100644
--- a/doc/source/whatsnew/v3.0.0.rst
+++ b/doc/source/whatsnew/v3.0.0.rst
@@ -208,6 +208,8 @@ Removal of prior version deprecations/changes
 - :meth:`SeriesGroupBy.agg` no longer pins the name of the group to the input passed to the provided ``func`` (:issue:`51703`)
 - All arguments except ``name`` in :meth:`Index.rename` are now keyword only (:issue:`56493`)
 - All arguments except the first ``path``-like argument in IO writers are now keyword only (:issue:`54229`)
+- Disallow allowing logical operations (``||``, ``&``, ``^``) between pandas objects and dtype-less sequences (e.g. ``list``, ``tuple``); wrap the objects in :class:`Series`, :class:`Index`, or ``np.array`` first instead (:issue:`52264`)
+- Disallow automatic casting to object in :class:`Series` logical operations (``&``, ``^``, ``||``) between series with mismatched indexes and dtypes other than ``object`` or ``bool`` (:issue:`52538`)
 - Disallow calling :meth:`Series.replace` or :meth:`DataFrame.replace` without a ``value`` and with non-dict-like ``to_replace`` (:issue:`33302`)
 - Disallow constructing a :class:`arrays.SparseArray` with scalar data (:issue:`53039`)
 - Disallow non-standard (``np.ndarray``, :class:`Index`, :class:`ExtensionArray`, or :class:`Series`) to :func:`isin`, :func:`unique`, :func:`factorize` (:issue:`52986`)
@@ -357,6 +359,7 @@ Categorical
 
 Datetimelike
 ^^^^^^^^^^^^
+- Bug in :class:`Timestamp` constructor failing to raise when ``tz=None`` is explicitly specified in conjunction with timezone-aware ``tzinfo`` or data (:issue:`48688`)
 - Bug in :func:`date_range` where the last valid timestamp would sometimes not be produced (:issue:`56134`)
 - Bug in :func:`date_range` where using a negative frequency value would not include all points between the start and end values (:issue:`56382`)
 -
diff --git a/pandas/_libs/hashtable_class_helper.pxi.in b/pandas/_libs/hashtable_class_helper.pxi.in
index e3a9102fec..5c6254c6a1 100644
--- a/pandas/_libs/hashtable_class_helper.pxi.in
+++ b/pandas/_libs/hashtable_class_helper.pxi.in
@@ -3,7 +3,7 @@ Template for each `dtype` helper function for hashtable
 
 WARNING: DO NOT edit .pxi FILE directly, .pxi is generated from .pxi.in
 """
-
+from cpython.unicode cimport PyUnicode_AsUTF8
 
 {{py:
 
@@ -98,7 +98,6 @@ from pandas._libs.khash cimport (
 # VectorData
 # ----------------------------------------------------------------------
 
-from pandas._libs.tslibs.util cimport get_c_string
 from pandas._libs.missing cimport C_NA
 
 
@@ -998,7 +997,7 @@ cdef class StringHashTable(HashTable):
         cdef:
             khiter_t k
             const char *v
-        v = get_c_string(val)
+        v = PyUnicode_AsUTF8(val)
 
         k = kh_get_str(self.table, v)
         if k != self.table.n_buckets:
@@ -1012,7 +1011,7 @@ cdef class StringHashTable(HashTable):
             int ret = 0
             const char *v
 
-        v = get_c_string(key)
+        v = PyUnicode_AsUTF8(key)
 
         k = kh_put_str(self.table, v, &ret)
         if kh_exist_str(self.table, k):
@@ -1037,7 +1036,7 @@ cdef class StringHashTable(HashTable):
             raise MemoryError()
         for i in range(n):
             val = values[i]
-            v = get_c_string(val)
+            v = PyUnicode_AsUTF8(val)
             vecs[i] = v
 
         with nogil:
@@ -1071,11 +1070,11 @@ cdef class StringHashTable(HashTable):
             val = values[i]
 
             if isinstance(val, str):
-                # GH#31499 if we have a np.str_ get_c_string won't recognize
+                # GH#31499 if we have a np.str_ PyUnicode_AsUTF8 won't recognize
                 #  it as a str, even though isinstance does.
-                v = get_c_string(<str>val)
+                v = PyUnicode_AsUTF8(<str>val)
             else:
-                v = get_c_string(self.na_string_sentinel)
+                v = PyUnicode_AsUTF8(self.na_string_sentinel)
             vecs[i] = v
 
         with nogil:
@@ -1109,11 +1108,11 @@ cdef class StringHashTable(HashTable):
             val = values[i]
 
             if isinstance(val, str):
-                # GH#31499 if we have a np.str_ get_c_string won't recognize
+                # GH#31499 if we have a np.str_ PyUnicode_AsUTF8 won't recognize
                 #  it as a str, even though isinstance does.
-                v = get_c_string(<str>val)
+                v = PyUnicode_AsUTF8(<str>val)
             else:
-                v = get_c_string(self.na_string_sentinel)
+                v = PyUnicode_AsUTF8(self.na_string_sentinel)
             vecs[i] = v
 
         with nogil:
@@ -1195,9 +1194,9 @@ cdef class StringHashTable(HashTable):
             else:
                 # if ignore_na is False, we also stringify NaN/None/etc.
                 try:
-                    v = get_c_string(<str>val)
+                    v = PyUnicode_AsUTF8(<str>val)
                 except UnicodeEncodeError:
-                    v = get_c_string(<str>repr(val))
+                    v = PyUnicode_AsUTF8(<str>repr(val))
                 vecs[i] = v
 
         # compute
diff --git a/pandas/_libs/tslibs/np_datetime.pyx b/pandas/_libs/tslibs/np_datetime.pyx
index aa01a05d0d..61095b3f03 100644
--- a/pandas/_libs/tslibs/np_datetime.pyx
+++ b/pandas/_libs/tslibs/np_datetime.pyx
@@ -18,6 +18,7 @@ from cpython.object cimport (
     Py_LT,
     Py_NE,
 )
+from cpython.unicode cimport PyUnicode_AsUTF8AndSize
 from libc.stdint cimport INT64_MAX
 
 import_datetime()
@@ -44,7 +45,6 @@ from pandas._libs.tslibs.dtypes cimport (
     npy_unit_to_abbrev,
     npy_unit_to_attrname,
 )
-from pandas._libs.tslibs.util cimport get_c_string_buf_and_size
 
 
 cdef extern from "pandas/datetime/pd_datetime.h":
@@ -341,13 +341,13 @@ cdef int string_to_dts(
         const char* format_buf
         FormatRequirement format_requirement
 
-    buf = get_c_string_buf_and_size(val, &length)
+    buf = PyUnicode_AsUTF8AndSize(val, &length)
     if format is None:
         format_buf = b""
         format_length = 0
         format_requirement = INFER_FORMAT
     else:
-        format_buf = get_c_string_buf_and_size(format, &format_length)
+        format_buf = PyUnicode_AsUTF8AndSize(format, &format_length)
         format_requirement = <FormatRequirement>exact
     return parse_iso_8601_datetime(buf, length, want_exc,
                                    dts, out_bestunit, out_local, out_tzoffset,
diff --git a/pandas/_libs/tslibs/parsing.pyx b/pandas/_libs/tslibs/parsing.pyx
index 384df1cac9..85ef3fd93f 100644
--- a/pandas/_libs/tslibs/parsing.pyx
+++ b/pandas/_libs/tslibs/parsing.pyx
@@ -19,6 +19,7 @@ from cpython.datetime cimport (
 from datetime import timezone
 
 from cpython.object cimport PyObject_Str
+from cpython.unicode cimport PyUnicode_AsUTF8AndSize
 from cython cimport Py_ssize_t
 from libc.string cimport strchr
 
@@ -74,10 +75,7 @@ import_pandas_datetime()
 
 from pandas._libs.tslibs.strptime import array_strptime
 
-from pandas._libs.tslibs.util cimport (
-    get_c_string_buf_and_size,
-    is_array,
-)
+from pandas._libs.tslibs.util cimport is_array
 
 
 cdef extern from "pandas/portable.h":
@@ -175,7 +173,7 @@ cdef datetime _parse_delimited_date(
         int day = 1, month = 1, year
         bint can_swap = 0
 
-    buf = get_c_string_buf_and_size(date_string, &length)
+    buf = PyUnicode_AsUTF8AndSize(date_string, &length)
     if length == 10 and _is_delimiter(buf[2]) and _is_delimiter(buf[5]):
         # parsing MM?DD?YYYY and DD?MM?YYYY dates
         month = _parse_2digit(buf)
@@ -251,7 +249,7 @@ cdef bint _does_string_look_like_time(str parse_string):
         Py_ssize_t length
         int hour = -1, minute = -1
 
-    buf = get_c_string_buf_and_size(parse_string, &length)
+    buf = PyUnicode_AsUTF8AndSize(parse_string, &length)
     if length >= 4:
         if buf[1] == b":":
             # h:MM format
@@ -467,7 +465,7 @@ cpdef bint _does_string_look_like_datetime(str py_string):
         char first
         int error = 0
 
-    buf = get_c_string_buf_and_size(py_string, &length)
+    buf = PyUnicode_AsUTF8AndSize(py_string, &length)
     if length >= 1:
         first = buf[0]
         if first == b"0":
@@ -521,7 +519,7 @@ cdef datetime _parse_dateabbr_string(str date_string, datetime default,
             pass
 
     if 4 <= date_len <= 7:
-        buf = get_c_string_buf_and_size(date_string, &date_len)
+        buf = PyUnicode_AsUTF8AndSize(date_string, &date_len)
         try:
             i = date_string.index("Q", 1, 6)
             if i == 1:
diff --git a/pandas/_libs/tslibs/timestamps.pyx b/pandas/_libs/tslibs/timestamps.pyx
index d4cd90613c..82daa6d942 100644
--- a/pandas/_libs/tslibs/timestamps.pyx
+++ b/pandas/_libs/tslibs/timestamps.pyx
@@ -1751,7 +1751,7 @@ class Timestamp(_Timestamp):
         tzinfo_type tzinfo=None,
         *,
         nanosecond=None,
-        tz=None,
+        tz=_no_input,
         unit=None,
         fold=None,
     ):
@@ -1783,6 +1783,10 @@ class Timestamp(_Timestamp):
         _date_attributes = [year, month, day, hour, minute, second,
                             microsecond, nanosecond]
 
+        explicit_tz_none = tz is None
+        if tz is _no_input:
+            tz = None
+
         if tzinfo is not None:
             # GH#17690 tzinfo must be a datetime.tzinfo object, ensured
             #  by the cython annotation.
@@ -1883,6 +1887,11 @@ class Timestamp(_Timestamp):
         if ts.value == NPY_NAT:
             return NaT
 
+        if ts.tzinfo is not None and explicit_tz_none:
+            raise ValueError(
+                "Passed data is timezone-aware, incompatible with 'tz=None'."
+            )
+
         return create_timestamp_from_ts(ts.value, ts.dts, ts.tzinfo, ts.fold, ts.creso)
 
     def _round(self, freq, mode, ambiguous="raise", nonexistent="raise"):
diff --git a/pandas/_libs/tslibs/util.pxd b/pandas/_libs/tslibs/util.pxd
index a5822e57d3..f144275e0e 100644
--- a/pandas/_libs/tslibs/util.pxd
+++ b/pandas/_libs/tslibs/util.pxd
@@ -1,6 +1,5 @@
 
 from cpython.object cimport PyTypeObject
-from cpython.unicode cimport PyUnicode_AsUTF8AndSize
 
 
 cdef extern from "Python.h":
@@ -155,36 +154,6 @@ cdef inline bint is_nan(object val):
     return is_complex_object(val) and val != val
 
 
-cdef inline const char* get_c_string_buf_and_size(str py_string,
-                                                  Py_ssize_t *length) except NULL:
-    """
-    Extract internal char* buffer of unicode or bytes object `py_string` with
-    getting length of this internal buffer saved in `length`.
-
-    Notes
-    -----
-    Python object owns memory, thus returned char* must not be freed.
-    `length` can be NULL if getting buffer length is not needed.
-
-    Parameters
-    ----------
-    py_string : str
-    length : Py_ssize_t*
-
-    Returns
-    -------
-    buf : const char*
-    """
-    # Note PyUnicode_AsUTF8AndSize() can
-    #  potentially allocate memory inside in unlikely case of when underlying
-    #  unicode object was stored as non-utf8 and utf8 wasn't requested before.
-    return PyUnicode_AsUTF8AndSize(py_string, length)
-
-
-cdef inline const char* get_c_string(str py_string) except NULL:
-    return get_c_string_buf_and_size(py_string, NULL)
-
-
 cdef inline bytes string_encode_locale(str py_string):
     """As opposed to PyUnicode_Encode, use current system locale to encode."""
     return PyUnicode_EncodeLocale(py_string, NULL)
diff --git a/pandas/core/dtypes/dtypes.py b/pandas/core/dtypes/dtypes.py
index 2d8e490f02..98e6895287 100644
--- a/pandas/core/dtypes/dtypes.py
+++ b/pandas/core/dtypes/dtypes.py
@@ -698,8 +698,8 @@ class DatetimeTZDtype(PandasExtensionDtype):
     Parameters
     ----------
     unit : str, default "ns"
-        The precision of the datetime data. Currently limited
-        to ``"ns"``.
+        The precision of the datetime data. Valid options are
+        ``"s"``, ``"ms"``, ``"us"``, ``"ns"``.
     tz : str, int, or datetime.tzinfo
         The timezone.
 
diff --git a/pandas/core/ops/array_ops.py b/pandas/core/ops/array_ops.py
index 810e30d369..983a3df57e 100644
--- a/pandas/core/ops/array_ops.py
+++ b/pandas/core/ops/array_ops.py
@@ -12,7 +12,6 @@ from typing import (
     TYPE_CHECKING,
     Any,
 )
-import warnings
 
 import numpy as np
 
@@ -29,7 +28,6 @@ from pandas._libs.tslibs import (
     is_supported_dtype,
     is_unitless,
 )
-from pandas.util._exceptions import find_stack_level
 
 from pandas.core.dtypes.cast import (
     construct_1d_object_array_from_listlike,
@@ -424,15 +422,13 @@ def logical_op(left: ArrayLike, right: Any, op) -> ArrayLike:
     right = lib.item_from_zerodim(right)
     if is_list_like(right) and not hasattr(right, "dtype"):
         # e.g. list, tuple
-        warnings.warn(
+        raise TypeError(
+            # GH#52264
             "Logical ops (and, or, xor) between Pandas objects and dtype-less "
-            "sequences (e.g. list, tuple) are deprecated and will raise in a "
-            "future version. Wrap the object in a Series, Index, or np.array "
+            "sequences (e.g. list, tuple) are no longer supported. "
+            "Wrap the object in a Series, Index, or np.array "
             "before operating instead.",
-            FutureWarning,
-            stacklevel=find_stack_level(),
         )
-        right = construct_1d_object_array_from_listlike(right)
 
     # NB: We assume extract_array has already been called on left and right
     lvalues = ensure_wrapped_if_datetimelike(left)
diff --git a/pandas/core/series.py b/pandas/core/series.py
index 0f796964eb..ab24b224b0 100644
--- a/pandas/core/series.py
+++ b/pandas/core/series.py
@@ -5859,17 +5859,12 @@ class Series(base.IndexOpsMixin, NDFrame):  # type: ignore[misc]
                         object,
                         np.bool_,
                     ):
-                        warnings.warn(
-                            "Operation between non boolean Series with different "
-                            "indexes will no longer return a boolean result in "
-                            "a future version. Cast both Series to object type "
-                            "to maintain the prior behavior.",
-                            FutureWarning,
-                            stacklevel=find_stack_level(),
-                        )
-                    # to keep original value's dtype for bool ops
-                    left = left.astype(object)
-                    right = right.astype(object)
+                        pass
+                        # GH#52538 no longer cast in these cases
+                    else:
+                        # to keep original value's dtype for bool ops
+                        left = left.astype(object)
+                        right = right.astype(object)
 
                 left, right = left.align(right)
 
diff --git a/pandas/io/excel/_base.py b/pandas/io/excel/_base.py
index a9da95054b..2b35cfa044 100644
--- a/pandas/io/excel/_base.py
+++ b/pandas/io/excel/_base.py
@@ -979,6 +979,12 @@ class ExcelWriter(Generic[_WorkbookT]):
 
         .. versionadded:: 1.3.0
 
+    See Also
+    --------
+    read_excel : Read an Excel sheet values (xlsx) file into DataFrame.
+    read_csv : Read a comma-separated values (csv) file into DataFrame.
+    read_fwf : Read a table of fixed-width formatted lines into DataFrame.
+
     Notes
     -----
     For compatibility with CSV writers, ExcelWriter serializes lists
@@ -1434,6 +1440,7 @@ def inspect_excel_format(
         return "zip"
 
 
+@doc(storage_options=_shared_docs["storage_options"])
 class ExcelFile:
     """
     Class for parsing tabular Excel sheets into DataFrame objects.
@@ -1472,19 +1479,27 @@ class ExcelFile:
            - Otherwise if ``path_or_buffer`` is in xlsb format,
              `pyxlsb <https://pypi.org/project/pyxlsb/>`_ will be used.
 
-           .. versionadded:: 1.3.0
+        .. versionadded:: 1.3.0
 
            - Otherwise if `openpyxl <https://pypi.org/project/openpyxl/>`_ is installed,
              then ``openpyxl`` will be used.
            - Otherwise if ``xlrd >= 2.0`` is installed, a ``ValueError`` will be raised.
 
-           .. warning::
+        .. warning::
 
-            Please do not report issues when using ``xlrd`` to read ``.xlsx`` files.
-            This is not supported, switch to using ``openpyxl`` instead.
+           Please do not report issues when using ``xlrd`` to read ``.xlsx`` files.
+           This is not supported, switch to using ``openpyxl`` instead.
+    {storage_options}
     engine_kwargs : dict, optional
         Arbitrary keyword arguments passed to excel engine.
 
+    See Also
+    --------
+    DataFrame.to_excel : Write DataFrame to an Excel file.
+    DataFrame.to_csv : Write DataFrame to a comma-separated values (csv) file.
+    read_csv : Read a comma-separated values (csv) file into DataFrame.
+    read_fwf : Read a table of fixed-width formatted lines into DataFrame.
+
     Examples
     --------
     >>> file = pd.ExcelFile("myfile.xlsx")  # doctest: +SKIP
@@ -1595,11 +1610,134 @@ class ExcelFile:
         Equivalent to read_excel(ExcelFile, ...)  See the read_excel
         docstring for more info on accepted parameters.
 
+        Parameters
+        ----------
+        sheet_name : str, int, list, or None, default 0
+            Strings are used for sheet names. Integers are used in zero-indexed
+            sheet positions (chart sheets do not count as a sheet position).
+            Lists of strings/integers are used to request multiple sheets.
+            Specify ``None`` to get all worksheets.
+        header : int, list of int, default 0
+            Row (0-indexed) to use for the column labels of the parsed
+            DataFrame. If a list of integers is passed those row positions will
+            be combined into a ``MultiIndex``. Use None if there is no header.
+        names : array-like, default None
+            List of column names to use. If file contains no header row,
+            then you should explicitly pass header=None.
+        index_col : int, str, list of int, default None
+            Column (0-indexed) to use as the row labels of the DataFrame.
+            Pass None if there is no such column.  If a list is passed,
+            those columns will be combined into a ``MultiIndex``.  If a
+            subset of data is selected with ``usecols``, index_col
+            is based on the subset.
+
+            Missing values will be forward filled to allow roundtripping with
+            ``to_excel`` for ``merged_cells=True``. To avoid forward filling the
+            missing values use ``set_index`` after reading the data instead of
+            ``index_col``.
+        usecols : str, list-like, or callable, default None
+            * If None, then parse all columns.
+            * If str, then indicates comma separated list of Excel column letters
+              and column ranges (e.g. "A:E" or "A,C,E:F"). Ranges are inclusive of
+              both sides.
+            * If list of int, then indicates list of column numbers to be parsed
+              (0-indexed).
+            * If list of string, then indicates list of column names to be parsed.
+            * If callable, then evaluate each column name against it and parse the
+              column if the callable returns ``True``.
+
+            Returns a subset of the columns according to behavior above.
+        converters : dict, default None
+            Dict of functions for converting values in certain columns. Keys can
+            either be integers or column labels, values are functions that take one
+            input argument, the Excel cell content, and return the transformed
+            content.
+        true_values : list, default None
+            Values to consider as True.
+        false_values : list, default None
+            Values to consider as False.
+        skiprows : list-like, int, or callable, optional
+            Line numbers to skip (0-indexed) or number of lines to skip (int) at the
+            start of the file. If callable, the callable function will be evaluated
+            against the row indices, returning True if the row should be skipped and
+            False otherwise. An example of a valid callable argument would be ``lambda
+            x: x in [0, 2]``.
+        nrows : int, default None
+            Number of rows to parse.
+        na_values : scalar, str, list-like, or dict, default None
+            Additional strings to recognize as NA/NaN. If dict passed, specific
+            per-column NA values.
+        parse_dates : bool, list-like, or dict, default False
+            The behavior is as follows:
+
+            * ``bool``. If True -> try parsing the index.
+            * ``list`` of int or names. e.g. If [1, 2, 3] -> try parsing columns 1, 2, 3
+              each as a separate date column.
+            * ``list`` of lists. e.g.  If [[1, 3]] -> combine columns 1 and 3 and
+              parse as a single date column.
+            * ``dict``, e.g. {{'foo' : [1, 3]}} -> parse columns 1, 3 as date and call
+              result 'foo'
+
+            If a column or index contains an unparsable date, the entire column or
+            index will be returned unaltered as an object data type. If you
+            don`t want to parse some cells as date just change their type
+            in Excel to "Text".For non-standard datetime parsing, use
+            ``pd.to_datetime`` after ``pd.read_excel``.
+
+            Note: A fast-path exists for iso8601-formatted dates.
+        date_parser : function, optional
+            Function to use for converting a sequence of string columns to an array of
+            datetime instances. The default uses ``dateutil.parser.parser`` to do the
+            conversion. Pandas will try to call `date_parser` in three different ways,
+            advancing to the next if an exception occurs: 1) Pass one or more arrays
+            (as defined by `parse_dates`) as arguments; 2) concatenate (row-wise) the
+            string values from the columns defined by `parse_dates` into a single array
+            and pass that; and 3) call `date_parser` once for each row using one or
+            more strings (corresponding to the columns defined by `parse_dates`) as
+            arguments.
+
+            .. deprecated:: 2.0.0
+               Use ``date_format`` instead, or read in as ``object`` and then apply
+               :func:`to_datetime` as-needed.
+        date_format : str or dict of column -> format, default ``None``
+           If used in conjunction with ``parse_dates``, will parse dates
+           according to this format. For anything more complex,
+           please read in as ``object`` and then apply :func:`to_datetime` as-needed.
+        thousands : str, default None
+            Thousands separator for parsing string columns to numeric.  Note that
+            this parameter is only necessary for columns stored as TEXT in Excel,
+            any numeric columns will automatically be parsed, regardless of display
+            format.
+        comment : str, default None
+            Comments out remainder of line. Pass a character or characters to this
+            argument to indicate comments in the input file. Any data between the
+            comment string and the end of the current line is ignored.
+        skipfooter : int, default 0
+            Rows at the end to skip (0-indexed).
+        dtype_backend : {{'numpy_nullable', 'pyarrow'}}, default 'numpy_nullable'
+            Back-end data type applied to the resultant :class:`DataFrame`
+            (still experimental). Behaviour is as follows:
+
+            * ``"numpy_nullable"``: returns nullable-dtype-backed :class:`DataFrame`
+              (default).
+            * ``"pyarrow"``: returns pyarrow-backed nullable :class:`ArrowDtype`
+              DataFrame.
+
+            .. versionadded:: 2.0
+        **kwds : dict, optional
+            Arbitrary keyword arguments passed to excel engine.
+
         Returns
         -------
         DataFrame or dict of DataFrames
             DataFrame from the passed in Excel file.
 
+        See Also
+        --------
+        read_excel : Read an Excel sheet values (xlsx) file into DataFrame.
+        read_csv : Read a comma-separated values (csv) file into DataFrame.
+        read_fwf : Read a table of fixed-width formatted lines into DataFrame.
+
         Examples
         --------
         >>> df = pd.DataFrame([[1, 2, 3], [4, 5, 6]], columns=["A", "B", "C"])


Test patch:
diff --git a/pandas/_testing/_warnings.py b/pandas/_testing/_warnings.py
index d951607778..cd2e2b4141 100644
--- a/pandas/_testing/_warnings.py
+++ b/pandas/_testing/_warnings.py
@@ -11,6 +11,7 @@ import sys
 from typing import (
     TYPE_CHECKING,
     Literal,
+    Union,
     cast,
 )
 import warnings
@@ -32,7 +33,8 @@ def assert_produces_warning(
     ] = "always",
     check_stacklevel: bool = True,
     raise_on_extra_warnings: bool = True,
-    match: str | None = None,
+    match: str | tuple[str | None, ...] | None = None,
+    must_find_all_warnings: bool = True,
 ) -> Generator[list[warnings.WarningMessage], None, None]:
     """
     Context manager for running code expected to either raise a specific warning,
@@ -68,8 +70,15 @@ def assert_produces_warning(
     raise_on_extra_warnings : bool, default True
         Whether extra warnings not of the type `expected_warning` should
         cause the test to fail.
-    match : str, optional
-        Match warning message.
+    match : {str, tuple[str, ...]}, optional
+        Match warning message. If it's a tuple, it has to be the size of
+        `expected_warning`. If additionally `must_find_all_warnings` is
+        True, each expected warning's message gets matched with a respective
+        match. Otherwise, multiple values get treated as an alternative.
+    must_find_all_warnings : bool, default True
+        If True and `expected_warning` is a tuple, each expected warning
+        type must get encountered. Otherwise, even one expected warning
+        results in success.
 
     Examples
     --------
@@ -97,13 +106,35 @@ def assert_produces_warning(
             yield w
         finally:
             if expected_warning:
-                expected_warning = cast(type[Warning], expected_warning)
-                _assert_caught_expected_warning(
-                    caught_warnings=w,
-                    expected_warning=expected_warning,
-                    match=match,
-                    check_stacklevel=check_stacklevel,
-                )
+                if isinstance(expected_warning, tuple) and must_find_all_warnings:
+                    match = (
+                        match
+                        if isinstance(match, tuple)
+                        else (match,) * len(expected_warning)
+                    )
+                    for warning_type, warning_match in zip(expected_warning, match):
+                        _assert_caught_expected_warnings(
+                            caught_warnings=w,
+                            expected_warning=warning_type,
+                            match=warning_match,
+                            check_stacklevel=check_stacklevel,
+                        )
+                else:
+                    expected_warning = cast(
+                        Union[type[Warning], tuple[type[Warning], ...]],
+                        expected_warning,
+                    )
+                    match = (
+                        "|".join(m for m in match if m)
+                        if isinstance(match, tuple)
+                        else match
+                    )
+                    _assert_caught_expected_warnings(
+                        caught_warnings=w,
+                        expected_warning=expected_warning,
+                        match=match,
+                        check_stacklevel=check_stacklevel,
+                    )
             if raise_on_extra_warnings:
                 _assert_caught_no_extra_warnings(
                     caught_warnings=w,
@@ -123,10 +154,10 @@ def maybe_produces_warning(
         return nullcontext()
 
 
-def _assert_caught_expected_warning(
+def _assert_caught_expected_warnings(
     *,
     caught_warnings: Sequence[warnings.WarningMessage],
-    expected_warning: type[Warning],
+    expected_warning: type[Warning] | tuple[type[Warning], ...],
     match: str | None,
     check_stacklevel: bool,
 ) -> None:
@@ -134,6 +165,11 @@ def _assert_caught_expected_warning(
     saw_warning = False
     matched_message = False
     unmatched_messages = []
+    warning_name = (
+        tuple(x.__name__ for x in expected_warning)
+        if isinstance(expected_warning, tuple)
+        else expected_warning.__name__
+    )
 
     for actual_warning in caught_warnings:
         if issubclass(actual_warning.category, expected_warning):
@@ -149,13 +185,11 @@ def _assert_caught_expected_warning(
                     unmatched_messages.append(actual_warning.message)
 
     if not saw_warning:
-        raise AssertionError(
-            f"Did not see expected warning of class {expected_warning.__name__!r}"
-        )
+        raise AssertionError(f"Did not see expected warning of class {warning_name!r}")
 
     if match and not matched_message:
         raise AssertionError(
-            f"Did not see warning {expected_warning.__name__!r} "
+            f"Did not see warning {warning_name!r} "
             f"matching '{match}'. The emitted warning messages are "
             f"{unmatched_messages}"
         )
diff --git a/pandas/_testing/contexts.py b/pandas/_testing/contexts.py
index b986e03e25..7ebed8857f 100644
--- a/pandas/_testing/contexts.py
+++ b/pandas/_testing/contexts.py
@@ -173,7 +173,7 @@ def raises_chained_assignment_error(warn=True, extra_warnings=(), extra_match=()
     elif PYPY and extra_warnings:
         return assert_produces_warning(
             extra_warnings,
-            match="|".join(extra_match),
+            match=extra_match,
         )
     else:
         if using_copy_on_write():
@@ -190,5 +190,5 @@ def raises_chained_assignment_error(warn=True, extra_warnings=(), extra_match=()
             warning = (warning, *extra_warnings)  # type: ignore[assignment]
         return assert_produces_warning(
             warning,
-            match="|".join((match, *extra_match)),
+            match=(match, *extra_match),
         )
diff --git a/pandas/tests/indexing/test_at.py b/pandas/tests/indexing/test_at.py
index d786940187..217ca74bd7 100644
--- a/pandas/tests/indexing/test_at.py
+++ b/pandas/tests/indexing/test_at.py
@@ -136,7 +136,11 @@ class TestAtSetItem:
 class TestAtSetItemWithExpansion:
     def test_at_setitem_expansion_series_dt64tz_value(self, tz_naive_fixture):
         # GH#25506
-        ts = Timestamp("2017-08-05 00:00:00+0100", tz=tz_naive_fixture)
+        ts = (
+            Timestamp("2017-08-05 00:00:00+0100", tz=tz_naive_fixture)
+            if tz_naive_fixture is not None
+            else Timestamp("2017-08-05 00:00:00+0100")
+        )
         result = Series(ts)
         result.at[1] = ts
         expected = Series([ts, ts])
diff --git a/pandas/tests/indexing/test_chaining_and_caching.py b/pandas/tests/indexing/test_chaining_and_caching.py
index 2a2772d1b3..b28c3cba7d 100644
--- a/pandas/tests/indexing/test_chaining_and_caching.py
+++ b/pandas/tests/indexing/test_chaining_and_caching.py
@@ -284,7 +284,9 @@ class TestChaining:
         with tm.raises_chained_assignment_error():
             df.loc[2]["C"] = "foo"
         tm.assert_frame_equal(df, df_original)
-        with tm.raises_chained_assignment_error(extra_warnings=(FutureWarning,)):
+        with tm.raises_chained_assignment_error(
+            extra_warnings=(FutureWarning,), extra_match=(None,)
+        ):
             df["C"][2] = "foo"
         tm.assert_frame_equal(df, df_original)
 
diff --git a/pandas/tests/io/parser/common/test_read_errors.py b/pandas/tests/io/parser/common/test_read_errors.py
index 0827f64dcc..bd47e04541 100644
--- a/pandas/tests/io/parser/common/test_read_errors.py
+++ b/pandas/tests/io/parser/common/test_read_errors.py
@@ -196,7 +196,6 @@ def test_warn_bad_lines(all_parsers):
     expected_warning = ParserWarning
     if parser.engine == "pyarrow":
         match_msg = "Expected 1 columns, but found 3: 1,2,3"
-        expected_warning = (ParserWarning, DeprecationWarning)
 
     with tm.assert_produces_warning(
         expected_warning, match=match_msg, check_stacklevel=False
@@ -315,7 +314,6 @@ a,b
     expected_warning = ParserWarning
     if parser.engine == "pyarrow":
         match_msg = "Expected 2 columns, but found 3: a,b,c"
-        expected_warning = (ParserWarning, DeprecationWarning)
 
     with tm.assert_produces_warning(
         expected_warning, match=match_msg, check_stacklevel=False
diff --git a/pandas/tests/io/parser/test_parse_dates.py b/pandas/tests/io/parser/test_parse_dates.py
index 0bc0c3e744..8968948df5 100644
--- a/pandas/tests/io/parser/test_parse_dates.py
+++ b/pandas/tests/io/parser/test_parse_dates.py
@@ -343,7 +343,7 @@ KORD,19990127, 23:00:00, 22:56:00, -0.5900, 1.7100, 4.6000, 0.0000, 280.0000
         "names": ["X0", "X1", "X2", "X3", "X4", "X5", "X6", "X7", "X8"],
     }
     with tm.assert_produces_warning(
-        (DeprecationWarning, FutureWarning), match=depr_msg, check_stacklevel=False
+        FutureWarning, match=depr_msg, check_stacklevel=False
     ):
         result = parser.read_csv(StringIO(data), **kwds)
 
@@ -724,7 +724,7 @@ def test_multiple_date_col_name_collision(all_parsers, data, parse_dates, msg):
     )
     with pytest.raises(ValueError, match=msg):
         with tm.assert_produces_warning(
-            (FutureWarning, DeprecationWarning), match=depr_msg, check_stacklevel=False
+            FutureWarning, match=depr_msg, check_stacklevel=False
         ):
             parser.read_csv(StringIO(data), parse_dates=parse_dates)
 
@@ -1248,14 +1248,14 @@ KORD,19990127, 23:00:00, 22:56:00, -0.5900, 1.7100, 4.6000, 0.0000, 280.0000
         "Support for nested sequences for 'parse_dates' in pd.read_csv is deprecated"
     )
     with tm.assert_produces_warning(
-        (FutureWarning, DeprecationWarning), match=depr_msg, check_stacklevel=False
+        FutureWarning, match=depr_msg, check_stacklevel=False
     ):
         with_indices = parser.read_csv(
             StringIO(data), parse_dates={"nominal": [1, 2]}, index_col="nominal"
         )
 
     with tm.assert_produces_warning(
-        (FutureWarning, DeprecationWarning), match=depr_msg, check_stacklevel=False
+        FutureWarning, match=depr_msg, check_stacklevel=False
     ):
         with_names = parser.read_csv(
             StringIO(data),
@@ -1280,13 +1280,13 @@ KORD,19990127, 23:00:00, 22:56:00, -0.5900, 1.7100, 4.6000, 0.0000, 280.0000
         "Support for nested sequences for 'parse_dates' in pd.read_csv is deprecated"
     )
     with tm.assert_produces_warning(
-        (FutureWarning, DeprecationWarning), match=depr_msg, check_stacklevel=False
+        FutureWarning, match=depr_msg, check_stacklevel=False
     ):
         result = parser.read_csv(
             StringIO(data), index_col=["nominal", "ID"], parse_dates={"nominal": [1, 2]}
         )
     with tm.assert_produces_warning(
-        (FutureWarning, DeprecationWarning), match=depr_msg, check_stacklevel=False
+        FutureWarning, match=depr_msg, check_stacklevel=False
     ):
         expected = parser.read_csv(StringIO(data), parse_dates={"nominal": [1, 2]})
 
@@ -2267,7 +2267,7 @@ def test_parse_dates_dict_format_two_columns(all_parsers, key, parse_dates):
         "Support for nested sequences for 'parse_dates' in pd.read_csv is deprecated"
     )
     with tm.assert_produces_warning(
-        (FutureWarning, DeprecationWarning), match=depr_msg, check_stacklevel=False
+        FutureWarning, match=depr_msg, check_stacklevel=False
     ):
         result = parser.read_csv(
             StringIO(data), date_format={key: "%d- %m-%Y"}, parse_dates=parse_dates
diff --git a/pandas/tests/io/parser/usecols/test_parse_dates.py b/pandas/tests/io/parser/usecols/test_parse_dates.py
index 75efe87c40..ab98857e0c 100644
--- a/pandas/tests/io/parser/usecols/test_parse_dates.py
+++ b/pandas/tests/io/parser/usecols/test_parse_dates.py
@@ -146,7 +146,7 @@ def test_usecols_with_parse_dates4(all_parsers):
         "Support for nested sequences for 'parse_dates' in pd.read_csv is deprecated"
     )
     with tm.assert_produces_warning(
-        (FutureWarning, DeprecationWarning), match=depr_msg, check_stacklevel=False
+        FutureWarning, match=depr_msg, check_stacklevel=False
     ):
         result = parser.read_csv(
             StringIO(data),
@@ -187,7 +187,7 @@ def test_usecols_with_parse_dates_and_names(all_parsers, usecols, names, request
         "Support for nested sequences for 'parse_dates' in pd.read_csv is deprecated"
     )
     with tm.assert_produces_warning(
-        (FutureWarning, DeprecationWarning), match=depr_msg, check_stacklevel=False
+        FutureWarning, match=depr_msg, check_stacklevel=False
     ):
         result = parser.read_csv(
             StringIO(s), names=names, parse_dates=parse_dates, usecols=usecols
diff --git a/pandas/tests/scalar/timestamp/test_constructors.py b/pandas/tests/scalar/timestamp/test_constructors.py
index bbda9d3ee7..4ebdea3733 100644
--- a/pandas/tests/scalar/timestamp/test_constructors.py
+++ b/pandas/tests/scalar/timestamp/test_constructors.py
@@ -621,7 +621,6 @@ class TestTimestampConstructors:
         ]
 
         timezones = [
-            (None, 0),
             ("UTC", 0),
             (pytz.utc, 0),
             ("Asia/Tokyo", 9),
@@ -1013,6 +1012,18 @@ class TestTimestampConstructors:
         assert result.hour == expected.hour
         assert result == expected
 
+    def test_explicit_tz_none(self):
+        # GH#48688
+        msg = "Passed data is timezone-aware, incompatible with 'tz=None'"
+        with pytest.raises(ValueError, match=msg):
+            Timestamp(datetime(2022, 1, 1, tzinfo=timezone.utc), tz=None)
+
+        with pytest.raises(ValueError, match=msg):
+            Timestamp("2022-01-01 00:00:00", tzinfo=timezone.utc, tz=None)
+
+        with pytest.raises(ValueError, match=msg):
+            Timestamp("2022-01-01 00:00:00-0400", tz=None)
+
 
 def test_constructor_ambiguous_dst():
     # GH 24329
diff --git a/pandas/tests/scalar/timestamp/test_formats.py b/pandas/tests/scalar/timestamp/test_formats.py
index b4493088ac..e1299c272e 100644
--- a/pandas/tests/scalar/timestamp/test_formats.py
+++ b/pandas/tests/scalar/timestamp/test_formats.py
@@ -118,7 +118,7 @@ class TestTimestampRendering:
     def test_repr_utcoffset(self):
         # This can cause the tz field to be populated, but it's redundant to
         # include this information in the date-string.
-        date_with_utc_offset = Timestamp("2014-03-13 00:00:00-0400", tz=None)
+        date_with_utc_offset = Timestamp("2014-03-13 00:00:00-0400")
         assert "2014-03-13 00:00:00-0400" in repr(date_with_utc_offset)
         assert "tzoffset" not in repr(date_with_utc_offset)
         assert "UTC-04:00" in repr(date_with_utc_offset)
diff --git a/pandas/tests/series/test_arithmetic.py b/pandas/tests/series/test_arithmetic.py
index 00f48bf3b1..44bf3475b8 100644
--- a/pandas/tests/series/test_arithmetic.py
+++ b/pandas/tests/series/test_arithmetic.py
@@ -807,9 +807,6 @@ class TestNamePreservation:
             r"Logical ops \(and, or, xor\) between Pandas objects and "
             "dtype-less sequences"
         )
-        warn = None
-        if box in [list, tuple] and is_logical:
-            warn = FutureWarning
 
         right = box(right)
         if flex:
@@ -818,9 +815,12 @@ class TestNamePreservation:
                 return
             result = getattr(left, name)(right)
         else:
-            # GH#37374 logical ops behaving as set ops deprecated
-            with tm.assert_produces_warning(warn, match=msg):
-                result = op(left, right)
+            if is_logical and box in [list, tuple]:
+                with pytest.raises(TypeError, match=msg):
+                    # GH#52264 logical ops with dtype-less sequences deprecated
+                    op(left, right)
+                return
+            result = op(left, right)
 
         assert isinstance(result, Series)
         if box in [Index, Series]:
diff --git a/pandas/tests/series/test_logical_ops.py b/pandas/tests/series/test_logical_ops.py
index b76b69289b..f59eacea3f 100644
--- a/pandas/tests/series/test_logical_ops.py
+++ b/pandas/tests/series/test_logical_ops.py
@@ -86,7 +86,7 @@ class TestSeriesLogicalOps:
         # GH#9016: support bitwise op for integer types
         s_0123 = Series(range(4), dtype="int64")
 
-        warn_msg = (
+        err_msg = (
             r"Logical ops \(and, or, xor\) between Pandas objects and "
             "dtype-less sequences"
         )
@@ -97,9 +97,8 @@ class TestSeriesLogicalOps:
         with pytest.raises(TypeError, match=msg):
             s_0123 & 3.14
         msg = "unsupported operand type.+for &:"
-        with pytest.raises(TypeError, match=msg):
-            with tm.assert_produces_warning(FutureWarning, match=warn_msg):
-                s_0123 & [0.1, 4, 3.14, 2]
+        with pytest.raises(TypeError, match=err_msg):
+            s_0123 & [0.1, 4, 3.14, 2]
         with pytest.raises(TypeError, match=msg):
             s_0123 & np.array([0.1, 4, 3.14, 2])
         with pytest.raises(TypeError, match=msg):
@@ -108,7 +107,7 @@ class TestSeriesLogicalOps:
     def test_logical_operators_int_dtype_with_str(self):
         s_1111 = Series([1] * 4, dtype="int8")
 
-        warn_msg = (
+        err_msg = (
             r"Logical ops \(and, or, xor\) between Pandas objects and "
             "dtype-less sequences"
         )
@@ -116,9 +115,8 @@ class TestSeriesLogicalOps:
         msg = "Cannot perform 'and_' with a dtyped.+array and scalar of type"
         with pytest.raises(TypeError, match=msg):
             s_1111 & "a"
-        with pytest.raises(TypeError, match="unsupported operand.+for &"):
-            with tm.assert_produces_warning(FutureWarning, match=warn_msg):
-                s_1111 & ["a", "b", "c", "d"]
+        with pytest.raises(TypeError, match=err_msg):
+            s_1111 & ["a", "b", "c", "d"]
 
     def test_logical_operators_int_dtype_with_bool(self):
         # GH#9016: support bitwise op for integer types
@@ -129,17 +127,15 @@ class TestSeriesLogicalOps:
         result = s_0123 & False
         tm.assert_series_equal(result, expected)
 
-        warn_msg = (
+        msg = (
             r"Logical ops \(and, or, xor\) between Pandas objects and "
             "dtype-less sequences"
         )
-        with tm.assert_produces_warning(FutureWarning, match=warn_msg):
-            result = s_0123 & [False]
-        tm.assert_series_equal(result, expected)
+        with pytest.raises(TypeError, match=msg):
+            s_0123 & [False]
 
-        with tm.assert_produces_warning(FutureWarning, match=warn_msg):
-            result = s_0123 & (False,)
-        tm.assert_series_equal(result, expected)
+        with pytest.raises(TypeError, match=msg):
+            s_0123 & (False,)
 
         result = s_0123 ^ False
         expected = Series([False, True, True, True])
@@ -188,9 +184,8 @@ class TestSeriesLogicalOps:
         )
 
         expected = Series([True, False, False, False, False])
-        with tm.assert_produces_warning(FutureWarning, match=msg):
-            result = left & right
-        tm.assert_series_equal(result, expected)
+        with pytest.raises(TypeError, match=msg):
+            left & right
         result = left & np.array(right)
         tm.assert_series_equal(result, expected)
         result = left & Index(right)
@@ -199,9 +194,8 @@ class TestSeriesLogicalOps:
         tm.assert_series_equal(result, expected)
 
         expected = Series([True, True, True, True, True])
-        with tm.assert_produces_warning(FutureWarning, match=msg):
-            result = left | right
-        tm.assert_series_equal(result, expected)
+        with pytest.raises(TypeError, match=msg):
+            left | right
         result = left | np.array(right)
         tm.assert_series_equal(result, expected)
         result = left | Index(right)
@@ -210,9 +204,8 @@ class TestSeriesLogicalOps:
         tm.assert_series_equal(result, expected)
 
         expected = Series([False, True, True, True, True])
-        with tm.assert_produces_warning(FutureWarning, match=msg):
-            result = left ^ right
-        tm.assert_series_equal(result, expected)
+        with pytest.raises(TypeError, match=msg):
+            left ^ right
         result = left ^ np.array(right)
         tm.assert_series_equal(result, expected)
         result = left ^ Index(right)
@@ -233,26 +226,22 @@ class TestSeriesLogicalOps:
 
         # s_0123 will be all false now because of reindexing like s_tft
         expected = Series([False] * 7, index=[0, 1, 2, 3, "a", "b", "c"])
-        with tm.assert_produces_warning(FutureWarning):
-            result = s_tft & s_0123
+        result = s_tft & s_0123
         tm.assert_series_equal(result, expected)
 
-        # GH 52538: Deprecate casting to object type when reindex is needed;
+        # GH#52538: no longer to object type when reindex is needed;
         # matches DataFrame behavior
-        expected = Series([False] * 7, index=[0, 1, 2, 3, "a", "b", "c"])
-        with tm.assert_produces_warning(FutureWarning):
-            result = s_0123 & s_tft
-        tm.assert_series_equal(result, expected)
+        msg = r"unsupported operand type\(s\) for &: 'float' and 'bool'"
+        with pytest.raises(TypeError, match=msg):
+            s_0123 & s_tft
 
         s_a0b1c0 = Series([1], list("b"))
 
-        with tm.assert_produces_warning(FutureWarning):
-            res = s_tft & s_a0b1c0
+        res = s_tft & s_a0b1c0
         expected = s_tff.reindex(list("abc"))
         tm.assert_series_equal(res, expected)
 
-        with tm.assert_produces_warning(FutureWarning):
-            res = s_tft | s_a0b1c0
+        res = s_tft | s_a0b1c0
         expected = s_tft.reindex(list("abc"))
         tm.assert_series_equal(res, expected)
 
@@ -273,9 +262,8 @@ class TestSeriesLogicalOps:
             r"Logical ops \(and, or, xor\) between Pandas objects and "
             "dtype-less sequences"
         )
-        with tm.assert_produces_warning(FutureWarning, match=msg):
-            result = s & list(s)
-        tm.assert_series_equal(result, expected)
+        with pytest.raises(TypeError, match=msg):
+            s & list(s)
 
     def test_scalar_na_logical_ops_corners_aligns(self):
         s = Series([2, 3, 4, 5, 6, 7, 8, 9, datetime(2005, 1, 1)])
@@ -405,27 +393,24 @@ class TestSeriesLogicalOps:
         tm.assert_series_equal(result, expected)
 
         # vs non-matching
-        with tm.assert_produces_warning(FutureWarning):
-            result = a & Series([1], ["z"])
+        result = a & Series([1], ["z"])
         expected = Series([False, False, False, False], list("abcz"))
         tm.assert_series_equal(result, expected)
 
-        with tm.assert_produces_warning(FutureWarning):
-            result = a | Series([1], ["z"])
+        result = a | Series([1], ["z"])
         expected = Series([True, True, False, False], list("abcz"))
         tm.assert_series_equal(result, expected)
 
         # identity
         # we would like s[s|e] == s to hold for any e, whether empty or not
-        with tm.assert_produces_warning(FutureWarning):
-            for e in [
-                empty.copy(),
-                Series([1], ["z"]),
-                Series(np.nan, b.index),
-                Series(np.nan, a.index),
-            ]:
-                result = a[a | e]
-                tm.assert_series_equal(result, a[a])
+        for e in [
+            empty.copy(),
+            Series([1], ["z"]),
+            Series(np.nan, b.index),
+            Series(np.nan, a.index),
+        ]:
+            result = a[a | e]
+            tm.assert_series_equal(result, a[a])
 
         for e in [Series(["z"])]:
             warn = FutureWarning if using_infer_string else None
@@ -519,7 +504,6 @@ class TestSeriesLogicalOps:
         tm.assert_frame_equal(s3.to_frame() | s4.to_frame(), exp_or1.to_frame())
         tm.assert_frame_equal(s4.to_frame() | s3.to_frame(), exp_or.to_frame())
 
-    @pytest.mark.xfail(reason="Will pass once #52839 deprecation is enforced")
     def test_int_dtype_different_index_not_bool(self):
         # GH 52500
         ser1 = Series([1, 2, 3], index=[10, 11, 23], name="a")
diff --git a/pandas/tests/util/test_assert_produces_warning.py b/pandas/tests/util/test_assert_produces_warning.py
index 80e3264690..5b917dbbe7 100644
--- a/pandas/tests/util/test_assert_produces_warning.py
+++ b/pandas/tests/util/test_assert_produces_warning.py
@@ -42,7 +42,6 @@ def f():
     warnings.warn("f2", RuntimeWarning)
 
 
-@pytest.mark.filterwarnings("ignore:f1:FutureWarning")
 def test_assert_produces_warning_honors_filter():
     # Raise by default.
     msg = r"Caused unexpected warning\(s\)"
@@ -180,6 +179,44 @@ def test_match_multiple_warnings():
         warnings.warn("Match this too", UserWarning)
 
 
+def test_must_match_multiple_warnings():
+    # https://github.com/pandas-dev/pandas/issues/56555
+    category = (FutureWarning, UserWarning)
+    msg = "Did not see expected warning of class 'UserWarning'"
+    with pytest.raises(AssertionError, match=msg):
+        with tm.assert_produces_warning(category, match=r"^Match this"):
+            warnings.warn("Match this", FutureWarning)
+
+
+def test_must_match_multiple_warnings_messages():
+    # https://github.com/pandas-dev/pandas/issues/56555
+    category = (FutureWarning, UserWarning)
+    msg = r"The emitted warning messages are \[UserWarning\('Not this'\)\]"
+    with pytest.raises(AssertionError, match=msg):
+        with tm.assert_produces_warning(category, match=r"^Match this"):
+            warnings.warn("Match this", FutureWarning)
+            warnings.warn("Not this", UserWarning)
+
+
+def test_allow_partial_match_for_multiple_warnings():
+    # https://github.com/pandas-dev/pandas/issues/56555
+    category = (FutureWarning, UserWarning)
+    with tm.assert_produces_warning(
+        category, match=r"^Match this", must_find_all_warnings=False
+    ):
+        warnings.warn("Match this", FutureWarning)
+
+
+def test_allow_partial_match_for_multiple_warnings_messages():
+    # https://github.com/pandas-dev/pandas/issues/56555
+    category = (FutureWarning, UserWarning)
+    with tm.assert_produces_warning(
+        category, match=r"^Match this", must_find_all_warnings=False
+    ):
+        warnings.warn("Match this", FutureWarning)
+        warnings.warn("Not this", UserWarning)
+
+
 def test_right_category_wrong_match_raises(pair_different_warnings):
     target_category, other_category = pair_different_warnings
     with pytest.raises(AssertionError, match="Did not see warning.*matching"):