Repository: astropy/astropy

Issue:
documentation on jyear and decimalyear
### What is the problem this feature will solve?

The current documentation on the "jyear" and "decimalyear" data types is unclear/nonexistent.
What are the differences between them, and when should one be used rather than the other?

I would argue that the name "jyear" itself is confusing, because it suggests something like the Julian Day Number expressed in years.

### Describe the desired outcome

_No response_

### Additional context

_No response_

Discussion:

Patch:
diff --git a/astropy/time/formats.py b/astropy/time/formats.py
index 885ef89db5..6cd1a4e651 100644
--- a/astropy/time/formats.py
+++ b/astropy/time/formats.py
@@ -646,14 +646,26 @@ def _check_val_type_not_quantity(format_name, val1, val2):
 
 class TimeDecimalYear(TimeNumeric):
     """
-    Time as a decimal year, with integer values corresponding to midnight
-    of the first day of each year.
+    Time as a decimal year, with integer values corresponding to midnight of the first
+    day of each year.
 
-    For example 2000.5 corresponds to the ISO time '2000-07-02 00:00:00'.
+    The fractional part represents the exact fraction of the year, considering the
+    precise number of days in the year (365 or 366). The following example shows
+    essentially how the decimal year is computed::
 
-    Since for this format the length of the year varies between 365 and
-    366 days, it is not possible to use Quantity input, in which a year
-    is always 365.25 days.
+      >>> from astropy.time import Time
+      >>> tm = Time("2024-04-05T12:34:00")
+      >>> tm0 = Time("2024-01-01T00:00:00")
+      >>> tm1 = Time("2025-01-01T00:00:00")
+      >>> print(2024 + (tm.jd - tm0.jd) / (tm1.jd - tm0.jd))  # doctest: +FLOAT_CMP
+      2024.2609934729812
+      >>> print(tm.decimalyear)  # doctest: +FLOAT_CMP
+      2024.2609934729812
+
+    Since for this format the length of the year varies between 365 and 366 days, it is
+    not possible to use Quantity input, in which a year is always 365.25 days.
+
+    This format is convenient for low-precision applications or for plotting data.
     """
 
     name = "decimalyear"
@@ -2041,11 +2053,23 @@ class TimeEpochDate(TimeNumeric):
 
 
 class TimeBesselianEpoch(TimeEpochDate):
-    """Besselian Epoch year as value(s) like 1950.0.
+    """Besselian Epoch year as decimal value(s) like 1950.0.
+
+    For information about this epoch format, see:
+    `<https://en.wikipedia.org/wiki/Epoch_(astronomy)#Besselian_years>`_.
+
+    The astropy Time class uses the ERFA functions ``epb2jd`` and ``epb`` to convert
+    between Besselian epoch years and Julian dates. This is roughly equivalent to the
+    following formula (see the wikipedia page for the reference)::
+
+      B = 1900.0 + (Julian date - 2415020.31352) / 365.242198781
+
+    Since for this format the length of the year varies, input needs to be floating
+    point; it is not possible to use Quantity input, for which a year always equals
+    365.25 days.
 
-    Since for this format the length of the year varies, input needs to
-    be floating point; it is not possible to use Quantity input, for
-    which a year always equals 365.25 days.
+    The Besselian epoch year is used for expressing the epoch or equinox in older source
+    catalogs, but it has been largely replaced by the Julian epoch year.
     """
 
     name = "byear"
@@ -2059,7 +2083,32 @@ class TimeBesselianEpoch(TimeEpochDate):
 
 
 class TimeJulianEpoch(TimeEpochDate):
-    """Julian Epoch year as value(s) like 2000.0."""
+    """Julian epoch year as decimal value(s) like 2000.0.
+
+    This format is based the Julian year which is exactly 365.25 days/year and a day is
+    exactly 86400 SI seconds.
+
+    The Julian epoch year is defined so that 2000.0 is 12:00 TT on January 1, 2000.
+    Using astropy this is expressed as::
+
+      >>> from astropy.time import Time
+      >>> import astropy.units as u
+      >>> j2000_epoch = Time("2000-01-01T12:00:00", scale="tt")
+      >>> print(j2000_epoch.jyear)  # doctest: +FLOAT_CMP
+      2000.0
+      >>> print((j2000_epoch + 365.25 * u.day).jyear)  # doctest: +FLOAT_CMP
+      2001.0
+
+    The Julian year is commonly used in astronomy for expressing the epoch of a source
+    catalog or the time of an observation. The Julian epoch year is sometimes written as
+    a string like "J2001.5" with a preceding "J". You can initialize a ``Time`` object with
+    such a string::
+
+      >>> print(Time("J2001.5").jyear)  # doctest: +FLOAT_CMP
+      2001.5
+
+    See also: `<https://en.wikipedia.org/wiki/Julian_year_(astronomy)>`_.
+    """
 
     name = "jyear"
     unit = erfa.DJY  # 365.25, the Julian year, for conversion to quantities
diff --git a/astropy/units/core.py b/astropy/units/core.py
index a6fd66f1ff..ee40591599 100644
--- a/astropy/units/core.py
+++ b/astropy/units/core.py
@@ -18,10 +18,9 @@ import numpy as np
 
 from astropy.utils.compat import COPY_IF_NEEDED
 from astropy.utils.decorators import deprecated, lazyproperty
-from astropy.utils.exceptions import AstropyWarning
+from astropy.utils.exceptions import AstropyDeprecationWarning, AstropyWarning
 from astropy.utils.misc import isiterable
 
-from . import format as unit_format
 from .errors import UnitConversionError, UnitParserWarning, UnitsError, UnitsWarning
 from .utils import (
     is_effectively_unity,
@@ -36,6 +35,7 @@ if TYPE_CHECKING:
     from types import TracebackType
     from typing import Any, Final, Literal, Self
 
+    from .format import Base
     from .physical import PhysicalType
     from .quantity import Quantity
     from .typing import Complex, Real, UnitPower, UnitScale
@@ -652,18 +652,20 @@ class UnitBase:
         -------
         Latex string
         """
-        return unit_format.Latex.to_string(self)
+        from .format import Latex
+
+        return Latex.to_string(self)
 
     def __bytes__(self) -> bytes:
-        return unit_format.Generic.to_string(self).encode("unicode_escape")
+        return str(self).encode("unicode_escape")
 
     def __str__(self) -> str:
-        return unit_format.Generic.to_string(self)
+        from .format import Generic
 
-    def __repr__(self) -> str:
-        string = unit_format.Generic.to_string(self)
+        return Generic.to_string(self)
 
-        return f'Unit("{string}")'
+    def __repr__(self) -> str:
+        return f'Unit("{self}")'
 
     @cached_property
     def _physical_type_id(self) -> tuple[tuple[str, UnitPower], ...]:
@@ -676,27 +678,6 @@ class UnitBase:
         unit = self.decompose()
         return tuple(zip((base.name for base in unit.bases), unit.powers))
 
-    @property
-    def names(self) -> list[str]:
-        """All the names associated with the unit."""
-        raise AttributeError(
-            "Can not get names from unnamed units. Perhaps you meant to_string()?"
-        )
-
-    @property
-    def name(self) -> str:
-        """The canonical (short) name associated with the unit."""
-        raise AttributeError(
-            "Can not get names from unnamed units. Perhaps you meant to_string()?"
-        )
-
-    @property
-    def aliases(self) -> list[str]:
-        """The aliases (long) names for the unit."""
-        raise AttributeError(
-            "Can not get aliases from unnamed units. Perhaps you meant to_string()?"
-        )
-
     @property
     def scale(self) -> UnitScale:
         """The scale of the unit."""
@@ -712,11 +693,7 @@ class UnitBase:
         """The powers of the bases of the unit."""
         return [1]
 
-    def to_string(
-        self,
-        format: type[unit_format.Base] | str | None = unit_format.Generic,
-        **kwargs,
-    ) -> str:
+    def to_string(self, format: type[Base] | str | None = None, **kwargs) -> str:
         r"""Output the unit in the given format as a string.
 
         Parameters
@@ -759,8 +736,9 @@ class UnitBase:
         ──
         s
         """
-        f = unit_format.get_format(format)
-        return f.to_string(self, **kwargs)
+        from .format import get_format
+
+        return get_format(format).to_string(self, **kwargs)
 
     def __format__(self, format_spec: str) -> str:
         try:
@@ -808,15 +786,26 @@ class UnitBase:
                 ) from None
             return CompositeUnit(1, [self], [sanitize_power(p)], _error_check=False)
 
-    def __truediv__(self, m):
-        if isinstance(m, (bytes, str)):
-            m = Unit(m)
+    @staticmethod
+    def _warn_about_operation_with_deprecated_type(op: str, other: bytes | str) -> None:
+        warnings.warn(
+            AstropyDeprecationWarning(
+                f"{op} involving a unit and a '{type(other).__name__}' instance are "
+                f"deprecated since v7.1. Convert {other!r} to a unit explicitly."
+            ),
+            stacklevel=3,
+        )
 
+    def __truediv__(self, m):
         if isinstance(m, UnitBase):
             if m.is_unity():
                 return self
             return CompositeUnit(1, [self, m], [1, -1], _error_check=False)
 
+        if isinstance(m, (bytes, str)):
+            self._warn_about_operation_with_deprecated_type("divisions", m)
+            return self / Unit(m)
+
         try:
             # Cannot handle this as Unit, re-try as Quantity
             from .quantity import Quantity
@@ -827,6 +816,7 @@ class UnitBase:
 
     def __rtruediv__(self, m):
         if isinstance(m, (bytes, str)):
+            self._warn_about_operation_with_deprecated_type("divisions", m)
             return Unit(m) / self
 
         try:
@@ -847,9 +837,6 @@ class UnitBase:
             return NotImplemented
 
     def __mul__(self, m):
-        if isinstance(m, (bytes, str)):
-            m = Unit(m)
-
         if isinstance(m, UnitBase):
             if m.is_unity():
                 return self
@@ -857,6 +844,10 @@ class UnitBase:
                 return m
             return CompositeUnit(1, [self, m], [1, 1], _error_check=False)
 
+        if isinstance(m, (bytes, str)):
+            self._warn_about_operation_with_deprecated_type("products", m)
+            return self * Unit(m)
+
         # Cannot handle this as Unit, re-try as Quantity.
         try:
             from .quantity import Quantity
@@ -867,6 +858,7 @@ class UnitBase:
 
     def __rmul__(self, m):
         if isinstance(m, (bytes, str)):
+            self._warn_about_operation_with_deprecated_type("products", m)
             return Unit(m) * self
 
         # Cannot handle this as Unit.  Here, m cannot be a Quantity,
@@ -2021,7 +2013,9 @@ class _UnitMetaClass(type):
                 # Return the NULL unit
                 return dimensionless_unscaled
 
-            f = unit_format.get_format(format)
+            from .format import Generic, get_format
+
+            f = get_format(format)
             if isinstance(s, bytes):
                 s = s.decode("ascii")
 
@@ -2060,7 +2054,7 @@ class _UnitMetaClass(type):
                 if parse_strict != "silent":
                     # Deliberately not issubclass here. Subclasses
                     # should use their name.
-                    format_clause = "" if f is unit_format.Generic else f.name + " "
+                    format_clause = "" if f is Generic else f.name + " "
                     msg = (
                         f"'{s}' did not parse as {format_clause}unit: {str(e)} "
                         "If this is meant to be a custom unit, "
diff --git a/astropy/units/format/__init__.py b/astropy/units/format/__init__.py
index 9023840e72..e0092eabe6 100644
--- a/astropy/units/format/__init__.py
+++ b/astropy/units/format/__init__.py
@@ -8,16 +8,10 @@ in the :meth:`~astropy.units.UnitBase.to_string` method, i.e.,
 these classes rarely if ever need to be imported directly.
 """
 
-import sys
 import warnings
 
 from astropy.utils.exceptions import AstropyDeprecationWarning
 
-# This is pretty atrocious, but it will prevent a circular import for those
-# formatters that need access to the units.core module An entry for it should
-# exist in sys.modules since astropy.units.core imports this module
-core = sys.modules["astropy.units.core"]
-
 from .base import Base
 from .cds import CDS
 from .console import Console
diff --git a/astropy/units/format/base.py b/astropy/units/format/base.py
index 17c89c75bd..b68b7226f7 100644
--- a/astropy/units/format/base.py
+++ b/astropy/units/format/base.py
@@ -5,12 +5,11 @@ from __future__ import annotations
 import warnings
 from typing import TYPE_CHECKING
 
+from astropy.units.core import CompositeUnit, NamedUnit, Unit, get_current_unit_registry
 from astropy.units.errors import UnitsWarning
 from astropy.units.utils import maybe_simple_fraction
 from astropy.utils.misc import did_you_mean
 
-from . import core
-
 if TYPE_CHECKING:
     from collections.abc import Iterable
     from typing import ClassVar, Literal
@@ -18,7 +17,7 @@ if TYPE_CHECKING:
     import numpy as np
 
     from astropy.extern.ply.lex import LexToken
-    from astropy.units import NamedUnit, UnitBase
+    from astropy.units import UnitBase
     from astropy.units.typing import UnitPower, UnitScale
 
 
@@ -227,7 +226,7 @@ class _ParsingFormatMixin:
         try:
             return cls._validate_unit(t.value)
         except ValueError as e:
-            registry = core.get_current_unit_registry()
+            registry = get_current_unit_registry()
             if t.value in registry.aliases:
                 return registry.aliases[t.value]
 
@@ -272,25 +271,23 @@ class _ParsingFormatMixin:
         )
 
     @classmethod
-    def _decompose_to_known_units(
-        cls, unit: core.CompositeUnit | core.NamedUnit
-    ) -> UnitBase:
+    def _decompose_to_known_units(cls, unit: CompositeUnit | NamedUnit) -> UnitBase:
         """
         Partially decomposes a unit so it is only composed of units that
         are "known" to a given format.
         """
-        if isinstance(unit, core.CompositeUnit):
-            return core.CompositeUnit(
+        if isinstance(unit, CompositeUnit):
+            return CompositeUnit(
                 unit.scale,
                 [cls._decompose_to_known_units(base) for base in unit.bases],
                 unit.powers,
                 _error_check=False,
             )
-        if isinstance(unit, core.NamedUnit):
+        if isinstance(unit, NamedUnit):
             try:
                 return cls._validate_unit(unit._get_format_name(cls.name))
             except ValueError:
-                if isinstance(unit, core.Unit):
+                if isinstance(unit, Unit):
                     return cls._decompose_to_known_units(unit._represents)
                 raise
         raise TypeError(
diff --git a/astropy/units/format/cds.py b/astropy/units/format/cds.py
index cbc9a5108b..34c655128b 100644
--- a/astropy/units/format/cds.py
+++ b/astropy/units/format/cds.py
@@ -17,6 +17,7 @@ from __future__ import annotations
 import re
 from typing import TYPE_CHECKING
 
+from astropy.units.core import CompositeUnit, Unit
 from astropy.units.utils import is_effectively_unity
 from astropy.utils import classproperty, parsing
 
@@ -147,7 +148,6 @@ class CDS(Base, _ParsingFormatMixin):
                  | factor
             """
             from astropy.units import dex
-            from astropy.units.core import CompositeUnit, Unit
 
             if len(p) == 3:
                 p[0] = CompositeUnit(p[1] * p[2].scale, p[2].bases, p[2].powers)
diff --git a/astropy/units/format/fits.py b/astropy/units/format/fits.py
index 23e7a72500..5aa1c16b20 100644
--- a/astropy/units/format/fits.py
+++ b/astropy/units/format/fits.py
@@ -10,10 +10,11 @@ from typing import TYPE_CHECKING
 
 import numpy as np
 
+from astropy.units.core import CompositeUnit
 from astropy.units.errors import UnitScaleError
 from astropy.utils import classproperty
 
-from . import Base, core, utils
+from . import Base, utils
 from .generic import _GenericParserMixin
 
 if TYPE_CHECKING:
@@ -85,7 +86,7 @@ class FITS(Base, _GenericParserMixin):
             # all values in FITS are set that way.  So, instead do it
             # here, and use a unity-scale unit for the rest.
             parts.append(f"10**{int(base)}")
-            unit = core.CompositeUnit(1, unit.bases, unit.powers)
+            unit = CompositeUnit(1, unit.bases, unit.powers)
 
         if unit.bases:
             parts.append(super().to_string(unit, fraction=fraction))
diff --git a/astropy/units/format/generic.py b/astropy/units/format/generic.py
index 7b83f792f2..17db84d324 100644
--- a/astropy/units/format/generic.py
+++ b/astropy/units/format/generic.py
@@ -22,11 +22,11 @@ import warnings
 from fractions import Fraction
 from typing import TYPE_CHECKING
 
+from astropy.units.core import CompositeUnit, Unit, get_current_unit_registry
 from astropy.units.errors import UnitsWarning
 from astropy.utils import classproperty, parsing
 from astropy.utils.misc import did_you_mean
 
-from . import core
 from .base import Base, _ParsingFormatMixin
 
 if TYPE_CHECKING:
@@ -204,8 +204,6 @@ class _GenericParserMixin(_ParsingFormatMixin):
                  | factor product inverse_unit
                  | factor
             """
-            from astropy.units.core import CompositeUnit, Unit
-
             if len(p) == 2:
                 p[0] = Unit(p[1])
             elif len(p) == 3:
@@ -218,8 +216,6 @@ class _GenericParserMixin(_ParsingFormatMixin):
             division_product_of_units : division_product_of_units division product_of_units
                                       | product_of_units
             """
-            from astropy.units.core import Unit
-
             if len(p) == 4:
                 p[0] = Unit(p[1] / p[3])
             else:
@@ -445,7 +441,7 @@ class Generic(Base, _GenericParserMixin):
 
     @classmethod
     def _validate_unit(cls, s: str, detailed_exception: bool = True) -> UnitBase:
-        registry = core.get_current_unit_registry().registry
+        registry = get_current_unit_registry().registry
         if s in cls._unit_symbols:
             s = cls._unit_symbols[s]
 
diff --git a/astropy/units/format/ogip.py b/astropy/units/format/ogip.py
index be3a87fef9..234526bf8f 100644
--- a/astropy/units/format/ogip.py
+++ b/astropy/units/format/ogip.py
@@ -23,10 +23,11 @@ import warnings
 from fractions import Fraction
 from typing import TYPE_CHECKING
 
+from astropy.units.core import CompositeUnit
 from astropy.units.errors import UnitParserWarning, UnitsWarning
 from astropy.utils import classproperty, parsing
 
-from . import core, utils
+from . import utils
 from .base import Base, _ParsingFormatMixin
 
 if TYPE_CHECKING:
@@ -173,9 +174,7 @@ class OGIP(Base, _ParsingFormatMixin):
             """
             match p[1:]:
                 case (factor, unit) | (factor, _, unit):
-                    p[0] = core.CompositeUnit(
-                        factor * unit.scale, unit.bases, unit.powers
-                    )
+                    p[0] = CompositeUnit(factor * unit.scale, unit.bases, unit.powers)
                 case _:
                     p[0] = p[1]
 
@@ -350,7 +349,7 @@ class OGIP(Base, _ParsingFormatMixin):
         # Remove units that aren't known to the format
         unit = cls._decompose_to_known_units(unit)
 
-        if isinstance(unit, core.CompositeUnit):
+        if isinstance(unit, CompositeUnit):
             # Can't use np.log10 here, because p[0] may be a Python long.
             if math.log10(unit.scale) % 1.0 != 0.0:
                 warnings.warn(
diff --git a/astropy/units/format/vounit.py b/astropy/units/format/vounit.py
index 2ccdd28a8e..9c704bc5ce 100644
--- a/astropy/units/format/vounit.py
+++ b/astropy/units/format/vounit.py
@@ -9,6 +9,14 @@ import re
 import warnings
 from typing import TYPE_CHECKING
 
+from astropy.units.core import (
+    CompositeUnit,
+    NamedUnit,
+    PrefixUnit,
+    def_unit,
+    dimensionless_unscaled,
+    si_prefixes,
+)
 from astropy.units.errors import (
     UnitParserWarning,
     UnitScaleError,
@@ -17,7 +25,7 @@ from astropy.units.errors import (
 )
 from astropy.utils import classproperty
 
-from . import Base, core, utils
+from . import Base, utils
 from .generic import _GenericParserMixin
 
 if TYPE_CHECKING:
@@ -100,7 +108,7 @@ class VOUnit(Base, _GenericParserMixin):
         if s in ("unknown", "UNKNOWN"):
             return None
         if s == "":
-            return core.dimensionless_unscaled
+            return dimensionless_unscaled
         # Check for excess solidi, but exclude fractional exponents (allowed)
         if s.count("/") > 1 and s.count("/") - len(re.findall(r"\(\d+/\d+\)", s)) > 1:
             raise UnitsError(
@@ -129,15 +137,13 @@ class VOUnit(Base, _GenericParserMixin):
             raise
 
     @classmethod
-    def _decompose_to_known_units(
-        cls, unit: core.CompositeUnit | core.NamedUnit
-    ) -> UnitBase:
+    def _decompose_to_known_units(cls, unit: CompositeUnit | NamedUnit) -> UnitBase:
         # The da- and d- prefixes are discouraged.  This has the
         # effect of adding a scale to value in the result.
-        if isinstance(unit, core.PrefixUnit) and unit._represents.scale in (0.1, 10.0):
+        if isinstance(unit, PrefixUnit) and unit._represents.scale in (0.1, 10.0):
             return cls._decompose_to_known_units(unit._represents)
         if (
-            isinstance(unit, core.NamedUnit)
+            isinstance(unit, NamedUnit)
             and unit._get_format_name(cls.name) in cls._custom_units
         ):
             return unit
@@ -150,27 +156,25 @@ class VOUnit(Base, _GenericParserMixin):
                 return cls._custom_units[name]
 
             if name.startswith("'"):
-                return core.def_unit(
+                return def_unit(
                     [name[1:-1], name],
                     format={"vounit": name},
                     namespace=cls._custom_units,
                 )
             else:
-                return core.def_unit(name, namespace=cls._custom_units)
+                return def_unit(name, namespace=cls._custom_units)
 
         if unit in cls._custom_units:
             return cls._custom_units[unit]
 
-        for short, full, factor in core.si_prefixes:
+        for short, full, factor in si_prefixes:
             for prefix in short:
                 if unit.startswith(prefix):
                     base_name = unit[len(prefix) :]
                     base_unit = def_base(base_name)
-                    return core.PrefixUnit(
+                    return PrefixUnit(
                         [prefix + x for x in base_unit.names],
-                        core.CompositeUnit(
-                            factor, [base_unit], [1], _error_check=False
-                        ),
+                        CompositeUnit(factor, [base_unit], [1], _error_check=False),
                         format={"vounit": prefix + base_unit.names[-1]},
                         namespace=cls._custom_units,
                     )
diff --git a/astropy/utils/data.py b/astropy/utils/data.py
index d3879708fe..a33ca9b64d 100644
--- a/astropy/utils/data.py
+++ b/astropy/utils/data.py
@@ -13,9 +13,6 @@ import io
 import os
 import re
 import shutil
-
-# import ssl moved inside functions using ssl to avoid import failure
-# when running in pyodide/Emscripten
 import sys
 import urllib.error
 import urllib.parse
@@ -23,6 +20,7 @@ import urllib.request
 import zipfile
 from importlib import import_module
 from tempfile import NamedTemporaryFile, TemporaryDirectory, gettempdir
+from types import MappingProxyType
 from warnings import warn
 
 import astropy_iers_data
@@ -1915,12 +1913,7 @@ def _url_to_dirname(url):
     return hashlib.md5(url_c.encode("utf-8"), usedforsecurity=False).hexdigest()
 
 
-class ReadOnlyDict(dict):
-    def __setitem__(self, key, value):
-        raise TypeError("This object is read-only.")
-
-
-_NOTHING = ReadOnlyDict({})
+_NOTHING = MappingProxyType({})
 
 
 class CacheDamaged(ValueError):
@@ -2185,7 +2178,7 @@ def cache_contents(pkgname="astropy"):
                     os.path.join(dldir, entry.name, "url"), encoding="utf-8"
                 )
                 r[url] = os.path.abspath(os.path.join(dldir, entry.name, "contents"))
-    return ReadOnlyDict(r)
+    return MappingProxyType(r)
 
 
 def export_download_cache(
diff --git a/docs/changes/units/17586.api.rst b/docs/changes/units/17586.api.rst
new file mode 100644
index 0000000000..0cc97e9f2b
--- /dev/null
+++ b/docs/changes/units/17586.api.rst
@@ -0,0 +1,2 @@
+Automatic conversion of a ``str`` or ``bytes`` instance to a unit when it is
+multiplied or divided with an existing unit or quantity is deprecated.


Test patch:
diff --git a/astropy/units/tests/test_quantity.py b/astropy/units/tests/test_quantity.py
index 398bce6a9e..fcc6648593 100644
--- a/astropy/units/tests/test_quantity.py
+++ b/astropy/units/tests/test_quantity.py
@@ -16,7 +16,7 @@ from astropy import units as u
 from astropy.units.quantity import _UNIT_NOT_INITIALISED
 from astropy.utils import isiterable
 from astropy.utils.compat import COPY_IF_NEEDED
-from astropy.utils.exceptions import AstropyWarning
+from astropy.utils.exceptions import AstropyDeprecationWarning, AstropyWarning
 from astropy.utils.masked import Masked
 
 """ The Quantity class will represent a number + unit + uncertainty """
@@ -1608,16 +1608,33 @@ def test_quantity_initialized_with_quantity():
 
 
 def test_quantity_string_unit():
-    q1 = 1.0 * u.m / "s"
+    with pytest.warns(
+        AstropyDeprecationWarning,
+        match=(
+            "^divisions involving a unit and a 'str' instance are deprecated since "
+            r"v7\.1\. Convert 's' to a unit explicitly\.$"
+        ),
+    ):
+        q1 = 1.0 * u.m / "s"
     assert q1.value == 1
     assert q1.unit == (u.m / u.s)
 
-    q2 = q1 * "m"
+    with pytest.warns(
+        AstropyDeprecationWarning,
+        match=(
+            "^products involving a unit and a 'str' instance are deprecated since "
+            r"v7\.1\. Convert 'm' to a unit explicitly\.$"
+        ),
+    ):
+        q2 = q1 * "m"
     assert q2.unit == ((u.m * u.m) / u.s)
 
 
 def test_quantity_invalid_unit_string():
-    with pytest.raises(ValueError):
+    with (
+        pytest.raises(ValueError),
+        pytest.warns(AstropyDeprecationWarning, match="^products involving .* a 'str'"),
+    ):
         "foo" * u.m
 
 
diff --git a/astropy/units/tests/test_units.py b/astropy/units/tests/test_units.py
index ec3b93b702..da1adfb835 100644
--- a/astropy/units/tests/test_units.py
+++ b/astropy/units/tests/test_units.py
@@ -731,9 +731,23 @@ def test_repr_latex():
 
 
 def test_operations_with_strings():
-    assert u.m / "5s" == (u.m / (5.0 * u.s))
+    with pytest.warns(
+        AstropyDeprecationWarning,
+        match=(
+            "^divisions involving a unit and a 'str' instance are deprecated since "
+            r"v7\.1\. Convert '5s' to a unit explicitly\.$"
+        ),
+    ):
+        assert u.m / "5s" == (u.m / (5.0 * u.s))
 
-    assert u.m * "5s" == (5.0 * u.m * u.s)
+    with pytest.warns(
+        AstropyDeprecationWarning,
+        match=(
+            "^products involving a unit and a 'str' instance are deprecated since "
+            r"v7\.1\. Convert '5s' to a unit explicitly\.$"
+        ),
+    ):
+        assert u.m * "5s" == (5.0 * u.m * u.s)
 
 
 def test_comparison():
@@ -756,19 +770,29 @@ def test_compose_into_arbitrary_units():
 
 
 def test_unit_multiplication_with_string():
-    """Check that multiplication with strings produces the correct unit."""
-    u1 = u.cm
-    us = "kg"
-    assert us * u1 == u.Unit(us) * u1
-    assert u1 * us == u1 * u.Unit(us)
+    with pytest.warns(
+        AstropyDeprecationWarning,
+        match=(
+            "^products involving a unit and a 'str' instance are deprecated since "
+            r"v7\.1\. Convert 'kg' to a unit explicitly\.$"
+        ),
+    ):
+        assert "kg" * u.cm == u.kg * u.cm
+    with pytest.warns(AstropyDeprecationWarning, match="^products involving .* 'str'"):
+        assert u.cm * "kg" == u.cm * u.kg
 
 
 def test_unit_division_by_string():
-    """Check that multiplication with strings produces the correct unit."""
-    u1 = u.cm
-    us = "kg"
-    assert us / u1 == u.Unit(us) / u1
-    assert u1 / us == u1 / u.Unit(us)
+    with pytest.warns(
+        AstropyDeprecationWarning,
+        match=(
+            "^divisions involving a unit and a 'str' instance are deprecated since "
+            r"v7\.1\. Convert 'kg' to a unit explicitly\.$"
+        ),
+    ):
+        assert "kg" / u.cm == u.kg / u.cm
+    with pytest.warns(AstropyDeprecationWarning, match="^divisions involving .* 'str'"):
+        assert u.cm / "kg" == u.cm / u.kg
 
 
 def test_sorted_bases():