Repository: pydicom/pydicom

Issue:
Pickling/unpickling timezone in DT does not work
**Describe the bug**

The following tests fail because the timezone is not set in the unpickled `DT`:
```py
    def test_pickling_with_timezone():
        dt = pydicom.valuerep.DT("19111213212123-0630")
        loaded_dt = pickle.loads(pickle.dumps(dt))
        assert dt == loaded_dt

    def test_pickling_dt_from_datetime_with_timezone():
        tz_info = timezone(timedelta(seconds=-23400), '-0630')
        dt_object = datetime(2022, 12, 31, 23, 59, 59, 42, tzinfo=tz_info)
        dt = pydicom.valuerep.DT(dt_object)
        loaded_dt = pickle.loads(pickle.dumps(dt))
        assert dt == loaded_dt
```

This is a spin-off of PR #1365, see [this comment](https://github.com/pydicom/pydicom/pull/1365#issuecomment-829544827).

Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/pydicom/pydicom/pull/1375?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pydicom) Report
> Merging [#1375](https://codecov.io/gh/pydicom/pydicom/pull/1375?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pydicom) (44a4cab) into [master](https://codecov.io/gh/pydicom/pydicom/commit/389b1ad97c1b3616851b37749e4bd2520755f0d1?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pydicom) (389b1ad) will **increase** coverage by `0.02%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/pydicom/pydicom/pull/1375/graphs/tree.svg?width=650&height=150&src=pr&token=YciLV15Lwr&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pydicom)](https://codecov.io/gh/pydicom/pydicom/pull/1375?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pydicom)

```diff
@@            Coverage Diff             @@
##           master    #1375      +/-   ##
==========================================
+ Coverage   95.24%   95.26%   +0.02%     
==========================================
  Files          60       60              
  Lines        9720     9720              
==========================================
+ Hits         9258     9260       +2     
+ Misses        462      460       -2     
```


| [Impacted Files](https://codecov.io/gh/pydicom/pydicom/pull/1375?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pydicom) | Coverage Δ | |
|---|---|---|
| [pydicom/pixel\_data\_handlers/pylibjpeg\_handler.py](https://codecov.io/gh/pydicom/pydicom/pull/1375/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pydicom#diff-cHlkaWNvbS9waXhlbF9kYXRhX2hhbmRsZXJzL3B5bGlianBlZ19oYW5kbGVyLnB5) | `97.22% <ø> (-0.06%)` | :arrow_down: |
| [pydicom/valuerep.py](https://codecov.io/gh/pydicom/pydicom/pull/1375/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pydicom#diff-cHlkaWNvbS92YWx1ZXJlcC5weQ==) | `98.74% <100.00%> (+0.42%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/pydicom/pydicom/pull/1375?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pydicom).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pydicom)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/pydicom/pydicom/pull/1375?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pydicom). Last update [c14bf96...44a4cab](https://codecov.io/gh/pydicom/pydicom/pull/1375?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pydicom). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pydicom).

scaramallion: So much simpler

Patch:
diff --git a/.github/workflows/master-push.yml b/.github/workflows/master-push.yml
index f10284e05..b11b41ab0 100644
--- a/.github/workflows/master-push.yml
+++ b/.github/workflows/master-push.yml
@@ -33,6 +33,8 @@ jobs:
 
     steps:
     - uses: actions/checkout@v2
+      with:
+        fetch-depth: 2
     - name: Set up Python ${{ matrix.python-version }}
       uses: actions/setup-python@v2
       with:
@@ -108,7 +110,7 @@ jobs:
       if: ${{ matrix.pylibjpeg-dep == 'pylibjpeg' }}
       run: |
         python -m pip install pylibjpeg
-        python -m pip uninstall -y pylibjpeg-openjpeg
+        python -m pip uninstall -y pylibjpeg-openjpeg pylibjpeg-rle
         pytest ${{ matrix.pytest-args }} pydicom/tests/test_pylibjpeg.py
         python -m pip install pylibjpeg-openjpeg pylibjpeg-libjpeg pylibjpeg-rle
         pytest ${{ matrix.pytest-args }} pydicom/tests/test_pylibjpeg.py
@@ -121,11 +123,7 @@ jobs:
 
     - name: Send coverage results
       if: ${{ success() && matrix.coverage == 'coverage' }}
-      run: >
-        bash <(curl --connect-timeout 10 --retry 10 --retry-max-time
-        0 https://codecov.io/bash) || (sleep 30 && bash <(curl
-        --connect-timeout 10 --retry 10 --retry-max-time
-        0 https://codecov.io/bash))
+      uses: codecov/codecov-action@v1
 
     - name: Install pymedphys
       if: ${{ matrix.pymedphys-dep == 'pymedphys' }}
diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml
index dfb063793..ea6ebd0c7 100644
--- a/.github/workflows/pythonpackage.yml
+++ b/.github/workflows/pythonpackage.yml
@@ -98,6 +98,8 @@ jobs:
 
     steps:
     - uses: actions/checkout@v2
+      with:
+        fetch-depth: 2
     - name: Set up Python ${{ matrix.python-version }}
       uses: actions/setup-python@v2
       with:
@@ -185,8 +187,4 @@ jobs:
 
     - name: Send coverage results
       if: ${{ success() && matrix.coverage == 'coverage' }}
-      run: >
-        bash <(curl --connect-timeout 10 --retry 10 --retry-max-time
-        0 https://codecov.io/bash) || (sleep 30 && bash <(curl
-        --connect-timeout 10 --retry 10 --retry-max-time
-        0 https://codecov.io/bash))
+      uses: codecov/codecov-action@v1
diff --git a/pydicom/valuerep.py b/pydicom/valuerep.py
index a4acdb114..41975162e 100644
--- a/pydicom/valuerep.py
+++ b/pydicom/valuerep.py
@@ -2,23 +2,19 @@
 """Special classes for DICOM value representations (VR)"""
 
 import datetime
-from decimal import Decimal
-from math import floor, isfinite, log10
-import platform
 import re
 import sys
+import warnings
+from decimal import Decimal
+from math import floor, isfinite, log10
+from typing import Sequence as SequenceType
 from typing import (
-    TypeVar, Type, Tuple, Optional, List, Dict, Union, Any, Generator, AnyStr,
-    Callable, Iterator, overload
+    TypeVar, Type, Tuple, Optional, List, Dict, Union, Any, Generator, Callable
 )
-from typing import Sequence as SequenceType
-import warnings
 
 # don't import datetime_conversion directly
 from pydicom import config
 from pydicom.multival import MultiValue
-from pydicom.uid import UID
-
 
 # Types
 _T = TypeVar('_T')
@@ -84,23 +80,23 @@ class DA(_DateTimeBase, datetime.date):
 
     Note that the :class:`datetime.date` base class is immutable.
     """
-    def __new__(
-        cls: Type[_DA], val: Union[None, str, _DA, datetime.date]
-    ) -> Optional[_DA]:
+    def __new__(cls: Type[_DA], *args, **kwargs) -> Optional[_DA]:
         """Create an instance of DA object.
 
         Raise an exception if the string cannot be parsed or the argument
         is otherwise incompatible.
 
-        Parameters
-        ----------
-        val : str
-            A string conformant to the DA definition in the DICOM Standard,
-            Part 5, :dcm:`Table 6.2-1<part05/sect_6.2.html#table_6.2-1>`.
+        The arguments (``*args`` and ``**kwargs``) are either the ones
+        inherited from :class:`datetime.date`, or the first argument is
+        a string conformant to the DA definition in the DICOM Standard,
+        Part 5, :dcm:`Table 6.2-1<part05/sect_6.2.html#table_6.2-1>`,
+        or it is a :class:`datetime.date` object, or an object of type
+        :class:`~pydicom.valuerep.DA`.
         """
-        if val is None:
+        if not args or args[0] is None:
             return None
 
+        val = args[0]
         if isinstance(val, str):
             if val.strip() == '':
                 return None  # empty date
@@ -123,14 +119,15 @@ class DA(_DateTimeBase, datetime.date):
             return super().__new__(cls, val.year, val.month, val.day)
 
         try:
-            return super().__new__(cls, val)
+            return super().__new__(cls, *args, **kwargs)
         except Exception as exc:
             raise ValueError(
                 f"Unable to convert '{val}' to 'DA' object"
             ) from exc
 
-    def __init__(self, val: Union[str, _DA, datetime.date]) -> None:
+    def __init__(self, *args, **kwargs) -> None:
         """Create a new **DA** element value."""
+        val = args[0]
         if isinstance(val, str):
             self.original_string = val
         elif isinstance(val, DA) and hasattr(val, 'original_string'):
@@ -171,23 +168,23 @@ class DT(_DateTimeBase, datetime.datetime):
             name=value
         )
 
-    def __new__(
-        cls: Type[_DT], val: Union[None, str, _DT, datetime.datetime]
-    ) -> Optional[_DT]:
+    def __new__(cls: Type[_DT], *args, **kwargs) -> Optional[_DT]:
         """Create an instance of DT object.
 
         Raise an exception if the string cannot be parsed or the argument
         is otherwise incompatible.
 
-        Parameters
-        ----------
-        val : str
-            A string conformant to the DT definition in the DICOM Standard,
-            Part 5, :dcm:`Table 6.2-1<part05/sect_6.2.html#table_6.2-1>`.
+        The arguments (``*args`` and ``**kwargs``) are either the ones
+        inherited from :class:`datetime.datetime`, or the first argument is
+        a string conformant to the DT definition in the DICOM Standard,
+        Part 5, :dcm:`Table 6.2-1<part05/sect_6.2.html#table_6.2-1>`,
+        or it is a :class:`datetime.datetime` object, or an object of type
+        :class:`~pydicom.valuerep.DT`.
         """
-        if val is None:
+        if not args or args[0] is None:
             return None
 
+        val = args[0]
         if isinstance(val, str):
             if val.strip() == '':
                 return None
@@ -233,13 +230,15 @@ class DT(_DateTimeBase, datetime.datetime):
             )
 
         try:
-            return super().__new__(cls, val)
+            return super().__new__(cls, *args, **kwargs)
         except Exception as exc:
             raise ValueError(
                 f"Unable to convert '{val}' to 'DT' object"
             ) from exc
 
-    def __init__(self, val: Union[str, _DT, datetime.datetime]) -> None:
+    def __init__(self, *args, **kwargs) -> None:
+        """Create a new **DT** element value."""
+        val = args[0]
         if isinstance(val, str):
             self.original_string = val
         elif isinstance(val, DT) and hasattr(val, 'original_string'):
@@ -274,23 +273,23 @@ class TM(_DateTimeBase, datetime.time):
         r"(?(7)(\.(?P<ms>([0-9]{1,6})?))?))$"
     )
 
-    def __new__(
-        cls: Type[_TM], val: Union[None, str, _TM, datetime.time]
-    ) -> Optional[_TM]:
+    def __new__(cls: Type[_TM], *args, **kwargs) -> Optional[_TM]:
         """Create an instance of TM object from a string.
 
         Raise an exception if the string cannot be parsed or the argument
         is otherwise incompatible.
 
-        Parameters
-        ----------
-        val : str
-            A string conformant to the TM definition in the DICOM Standard,
-            Part 5, :dcm:`Table 6.2-1<part05/sect_6.2.html#table_6.2-1>`.
+        The arguments (``*args`` and ``**kwargs``) are either the ones
+        inherited from :class:`datetime.time`, or the first argument is
+        a string conformant to the TM definition in the DICOM Standard,
+        Part 5, :dcm:`Table 6.2-1<part05/sect_6.2.html#table_6.2-1>`,
+        or it is a :class:`datetime.time` object, or an object of type
+        :class:`~pydicom.valuerep.TM`.
         """
-        if val is None:
+        if not args or args[0] is None:
             return None
 
+        val = args[0]
         if isinstance(val, str):
             if val.strip() == '':
                 return None  # empty time
@@ -325,13 +324,15 @@ class TM(_DateTimeBase, datetime.time):
             )
 
         try:
-            return super().__new__(cls, val)
+            return super().__new__(cls, *args, **kwargs)
         except Exception as exc:
             raise ValueError(
                 f"Unable to convert '{val}' to 'TM' object"
             ) from exc
 
-    def __init__(self, val: Union[str, _TM, datetime.time]) -> None:
+    def __init__(self, *args, **kwargs) -> None:
+        super().__init__()
+        val = args[0]
         if isinstance(val, str):
             self.original_string = val
         elif isinstance(val, TM) and hasattr(val, 'original_string'):
@@ -344,16 +345,6 @@ class TM(_DateTimeBase, datetime.time):
             if val.microsecond > 0:
                 self.original_string += f".{val.microsecond:06}"
 
-    if platform.python_implementation() == "PyPy":
-        # Workaround for CPython/PyPy bug in time.__reduce_ex__()
-        #   caused by returning (time, ...) rather than (self.__class__, ...)
-        def __reduce_ex__(self, protocol: int) -> Union[str, Tuple[Any, ...]]:
-            return (
-                self.__class__,
-                super()._getstate(protocol),
-                self.__getstate__()
-            )
-
 
 # Regex to match strings that represent valid DICOM decimal strings (DS)
 _DS_REGEX = re.compile(r'\s*[\+\-]?\d+(\.\d+)?([eE][\+\-]?\d+)?\s*$')


Test patch:
diff --git a/pydicom/tests/test_valuerep.py b/pydicom/tests/test_valuerep.py
index 9268d649c..2a9d14ac3 100644
--- a/pydicom/tests/test_valuerep.py
+++ b/pydicom/tests/test_valuerep.py
@@ -5,10 +5,7 @@
 import copy
 from datetime import datetime, date, time, timedelta, timezone
 from decimal import Decimal
-try:
-    import cPickle as pickle
-except ImportError:
-    import pickle
+import pickle
 import math
 import sys
 from typing import Union
@@ -17,7 +14,6 @@ from pydicom.tag import Tag
 from pydicom.values import convert_value
 
 import pydicom
-import platform
 from pydicom import config
 from pydicom import valuerep
 from pydicom.data import get_testdata_file
@@ -56,68 +52,70 @@ class TestTM:
     """Unit tests for pickling TM"""
     def test_pickling(self):
         # Check that a pickled TM is read back properly
-        x = pydicom.valuerep.TM("212223")
-        assert time(21, 22, 23) == x
-        x.original_string = "hello"
-        assert "hello" == x.original_string
-        assert time(21, 22, 23) == x
-        data1_string = pickle.dumps(x)
-        x2 = pickle.loads(data1_string)
-        assert x == x2
-        assert x.original_string == x2.original_string
-        assert str(x) == str(x2)
-
-    def test_str(self):
-        """Test str(TM)."""
-        assert "212223.1234" == str(pydicom.valuerep.TM("212223.1234"))
-        assert "212223" == str(pydicom.valuerep.TM("212223"))
-        assert "212223" == str(pydicom.valuerep.TM("212223"))
-        assert "2122" == str(pydicom.valuerep.TM("2122"))
-        assert "21" == str(pydicom.valuerep.TM("21"))
-        assert "212223" == str(pydicom.valuerep.TM(time(21, 22, 23)))
-        assert "212223.000024" == str(
-            pydicom.valuerep.TM(time(21, 22, 23, 24)))
-        assert "010203" == str(pydicom.valuerep.TM(time(1, 2, 3)))
+        tm = pydicom.valuerep.TM("212223")
+        assert tm == time(21, 22, 23)
+        assert tm.original_string == "212223"
+        assert tm == time(21, 22, 23)
+        loaded_tm = pickle.loads(pickle.dumps(tm))
+        assert loaded_tm == tm
+        assert loaded_tm.original_string == tm.original_string
+        assert str(loaded_tm) == str(tm)
+
+    def test_pickling_tm_from_time(self):
+        tm = pydicom.valuerep.TM(time(21, 22, 23))
+        assert tm.original_string == "212223"
+        time_string = pickle.dumps(tm)
+        loaded_tm = pickle.loads(time_string)
+        assert loaded_tm == tm
+        assert loaded_tm.original_string == tm.original_string
+        assert str(loaded_tm) == str(tm)
+
+    def test_str_and_repr(self):
+        assert str(pydicom.valuerep.TM("212223.1234")) == "212223.1234"
+        assert repr(pydicom.valuerep.TM("212223.1234")) == '"212223.1234"'
+        assert str(pydicom.valuerep.TM("212223")) == "212223"
+        assert repr(pydicom.valuerep.TM("212223")) == '"212223"'
+        assert str(pydicom.valuerep.TM("2122")) == "2122"
+        assert repr(pydicom.valuerep.TM("2122")) == '"2122"'
+        assert str(pydicom.valuerep.TM("21")) == "21"
+        assert str(pydicom.valuerep.TM(time(21, 22, 23))) == "212223"
+        assert str(pydicom.valuerep.TM(
+            time(21, 22, 23, 24))) == "212223.000024"
+        assert str(pydicom.valuerep.TM(time(1, 2, 3))) == "010203"
+        assert repr(pydicom.valuerep.TM(time(1, 2, 3))) == '"010203"'
 
     def test_new_empty_str(self):
         """Test converting an empty string."""
-        x = pydicom.valuerep.TM('')
-        assert x is None
+        assert pydicom.valuerep.TM('') is None
 
     def test_new_str_conversion(self):
         """Test converting strings to times."""
-        x = pydicom.valuerep.TM('00')
-        assert "00" == str(x)
-        assert time(0, 0, 0) == x
-        x = pydicom.valuerep.TM('23')
-        assert "23" == str(x)
-        assert time(23, 0, 0) == x
+        tm = pydicom.valuerep.TM('00')
+        assert tm == time(0, 0, 0)
+        tm = pydicom.valuerep.TM('23')
+        assert tm == time(23, 0, 0)
         msg = r"Unable to convert non-conformant value '24' to 'TM' object"
         with pytest.raises(ValueError, match=msg):
             pydicom.valuerep.TM('24')
 
-        x = pydicom.valuerep.TM('0000')
-        assert "0000" == str(x)
-        assert time(0, 0, 0) == x
-        x = pydicom.valuerep.TM('2359')
-        assert "2359" == str(x)
-        assert time(23, 59, 0) == x
+        tm = pydicom.valuerep.TM('0000')
+        assert tm == time(0, 0, 0)
+        tm = pydicom.valuerep.TM('2359')
+        assert tm == time(23, 59, 0)
         msg = r"Unable to convert non-conformant value '2360' to 'TM' object"
         with pytest.raises(ValueError, match=msg):
             pydicom.valuerep.TM('2360')
 
-        x = pydicom.valuerep.TM('000000')
-        assert "000000" == str(x)
-        assert time(0, 0, 0) == x
+        tm = pydicom.valuerep.TM('000000')
+        assert tm == time(0, 0, 0)
         # Valid DICOM TM seconds range is 0..60, but time is 0..59
         msg = (
             r"'datetime.time' doesn't allow a value of '60' for the "
             r"seconds component, changing to '59'"
         )
         with pytest.warns(UserWarning, match=msg):
-            x = pydicom.valuerep.TM('235960')
-        assert "235960" == str(x)
-        assert time(23, 59, 59) == x
+            tm = pydicom.valuerep.TM('235960')
+        assert tm == time(23, 59, 59)
 
         msg = r"Unable to convert non-conformant value '235' to 'TM' object"
         with pytest.raises(ValueError, match=msg):
@@ -126,45 +124,101 @@ class TestTM:
     def test_new_obj_conversion(self):
         """Test other conversion attempts."""
         assert pydicom.valuerep.TM(None) is None
-        x = pydicom.valuerep.TM("010203.123456")
-        assert time(1, 2, 3, 123456) == pydicom.valuerep.TM(x)
-        assert x == pydicom.valuerep.TM(x)
-        x = pydicom.valuerep.TM(time(1, 2, 3))
-        assert isinstance(x, pydicom.valuerep.TM)
-        assert time(1, 2, 3) == x
+        tm = pydicom.valuerep.TM("010203.123456")
+        assert pydicom.valuerep.TM(tm) == time(1, 2, 3, 123456)
+        assert tm == pydicom.valuerep.TM(tm)
+        tm = pydicom.valuerep.TM(time(1, 2, 3))
+        assert isinstance(tm, pydicom.valuerep.TM)
+        assert tm == time(1, 2, 3)
 
         msg = r"Unable to convert '123456' to 'TM' object"
         with pytest.raises(ValueError, match=msg):
             pydicom.valuerep.TM(123456)
 
+    def test_comparison(self):
+        tm = pydicom.valuerep.TM("010203.123456")
+        tm_object = time(1, 2, 3, 123456)
+        assert tm == tm
+        assert tm != 1
+        assert tm == tm_object
+        assert tm_object == tm
+        assert hash(tm) == hash(tm_object)
+        assert tm == pydicom.valuerep.TM(tm_object)
+        assert tm < time(1, 2, 3, 123457)
+        assert tm != time(1, 2, 3, 123457)
+        assert tm < pydicom.valuerep.TM(time(1, 2, 3, 123457))
+        assert tm <= time(1, 2, 3, 123457)
+        assert tm <= tm_object
+        assert tm > time(1, 2, 3)
+        assert tm > pydicom.valuerep.TM(time(1, 2, 3))
+        assert tm >= time(1, 2, 3)
+        assert time(1, 2, 3, 123457) > tm
+        assert tm_object >= tm
+        assert time(1, 2, 3) < tm
+        with pytest.raises(TypeError):
+            tm > 5
+
+    def test_time_behavior(self):
+        """Test that TM behaves like time."""
+        tm = pydicom.valuerep.TM("010203.123456")
+        assert tm.hour == 1
+        assert tm.second == 3
+        assert tm.microsecond == 123456
+        assert tm.replace(hour=23) == time(23, 2, 3, 123456)
+        assert "minute" in dir(tm)
+        assert "original_string" in dir(tm)
+
 
 class TestDT:
     """Unit tests for pickling DT"""
     def test_pickling(self):
         # Check that a pickled DT is read back properly
-        x = pydicom.valuerep.DT("19111213212123")
-        assert datetime(1911, 12, 13, 21, 21, 23) == x
-        x.original_string = "hello"
-        data1_string = pickle.dumps(x)
-        x2 = pickle.loads(data1_string)
-        assert x == x2
-        assert x.original_string == x2.original_string
-        assert str(x) == str(x2)
+        dt = pydicom.valuerep.DT("19111213212123")
+        assert dt == datetime(1911, 12, 13, 21, 21, 23)
+        data1_string = pickle.dumps(dt)
+        loaded_dt = pickle.loads(data1_string)
+        assert loaded_dt == dt
+        assert dt.original_string == loaded_dt.original_string
+        assert str(loaded_dt) == str(dt)
+
+    def test_pickling_with_timezone(self):
+        dt = pydicom.valuerep.DT("19111213212123-0630")
+        loaded_dt = pickle.loads(pickle.dumps(dt))
+        assert loaded_dt == dt
+        assert loaded_dt.original_string == dt.original_string
+        assert str(loaded_dt) == str(dt)
+
+    def test_pickling_dt_from_datetime(self):
+        dt = pydicom.valuerep.DT(datetime(2222, 11, 23, 1, 2, 3, 4))
+        assert dt.original_string == "22221123010203.000004"
+        loaded_dt = pickle.loads(pickle.dumps(dt))
+        assert loaded_dt == dt
+        assert loaded_dt.original_string == dt.original_string
+        assert str(dt) == str(loaded_dt)
+
+    def test_pickling_dt_from_datetime_with_timezone(self):
+        tz_info = timezone(timedelta(seconds=-23400), '-0630')
+        dt_object = datetime(2022, 12, 31, 23, 59, 59, 42, tzinfo=tz_info)
+        dt = pydicom.valuerep.DT(dt_object)
+        assert dt.original_string == "20221231235959.000042-0630"
+        loaded_dt = pickle.loads(pickle.dumps(dt))
+        assert dt == loaded_dt
+        assert dt.original_string == loaded_dt.original_string
+        assert str(dt) == str(loaded_dt)
 
     def test_new_empty_str(self):
         """Test converting an empty string."""
-        x = pydicom.valuerep.DT('')
-        assert x is None
+        assert pydicom.valuerep.DT('') is None
 
     def test_new_obj_conversion(self):
         """Test other conversion attempts."""
         assert pydicom.valuerep.DT(None) is None
-        x = pydicom.valuerep.DT("10010203")
-        assert datetime(1001, 2, 3) == pydicom.valuerep.DT(x)
-        assert x == pydicom.valuerep.DT(x)
-        x = pydicom.valuerep.DT(datetime(1001, 2, 3))
-        assert isinstance(x, pydicom.valuerep.DT)
-        assert datetime(1001, 2, 3) == x
+        dt = pydicom.valuerep.DT("10010203")
+        assert pydicom.valuerep.DT(dt) == datetime(1001, 2, 3)
+        assert dt == pydicom.valuerep.DT(dt)
+        dt = pydicom.valuerep.DT(datetime(1001, 2, 3))
+        assert isinstance(dt, pydicom.valuerep.DT)
+        assert dt == datetime(1001, 2, 3)
 
         msg = r"Unable to convert '123456' to 'DT' object"
         with pytest.raises(ValueError, match=msg):
@@ -178,9 +232,9 @@ class TestDT:
             r"seconds component, changing to '59'"
         )
         with pytest.warns(UserWarning, match=msg):
-            x = pydicom.valuerep.DT('20010101235960')
-        assert "20010101235960" == str(x)
-        assert datetime(2001, 1, 1, 23, 59, 59) == x
+            dt = pydicom.valuerep.DT('20010101235960')
+        assert str(dt) == "20010101235960"
+        assert dt == datetime(2001, 1, 1, 23, 59, 59)
 
         msg = (
             r"Unable to convert non-conformant value 'a2000,00,00' to 'DT' "
@@ -189,17 +243,61 @@ class TestDT:
         with pytest.raises(ValueError, match=msg):
             pydicom.valuerep.DT("a2000,00,00")
 
-    def test_str(self):
+    def test_str_and_repr(self):
         dt = datetime(1911, 12, 13, 21, 21, 23)
-        assert "19111213212123" == str(pydicom.valuerep.DT(dt))
-        assert "19111213212123" == str(pydicom.valuerep.DT("19111213212123"))
-        assert "1001.02.03" == str(pydicom.valuerep.DA("1001.02.03"))
+        assert str(pydicom.valuerep.DT(dt)) == "19111213212123"
+        assert repr(pydicom.valuerep.DT(dt)) == '"19111213212123"'
+        assert str(pydicom.valuerep.DT("19111213212123")) == "19111213212123"
+        assert str(pydicom.valuerep.DA("1001.02.03")) == "1001.02.03"
+        assert repr(pydicom.valuerep.DA("1001.02.03")) == '"1001.02.03"'
         tz_info = timezone(timedelta(seconds=21600), '+0600')
         dt = datetime(2022, 1, 2, 8, 9, 7, 123456, tzinfo=tz_info)
-        assert "20220102080907.123456+0600" == str(pydicom.valuerep.DT(dt))
+        assert str(pydicom.valuerep.DT(dt)) == "20220102080907.123456+0600"
+        assert repr(pydicom.valuerep.DT(dt)) == '"20220102080907.123456+0600"'
         tz_info = timezone(timedelta(seconds=-23400), '-0630')
         dt = datetime(2022, 12, 31, 23, 59, 59, 42, tzinfo=tz_info)
-        assert "20221231235959.000042-0630" == str(pydicom.valuerep.DT(dt))
+        assert str(pydicom.valuerep.DT(dt)) == "20221231235959.000042-0630"
+        assert repr(pydicom.valuerep.DT(dt)) == '"20221231235959.000042-0630"'
+
+    def test_comparison(self):
+        dt = pydicom.valuerep.DT("19111213212123")
+        dt_object = datetime(1911, 12, 13, 21, 21, 23)
+        assert dt == dt
+        assert dt != 1
+        assert dt == dt_object
+        assert dt_object == dt
+        assert hash(dt) == hash(dt_object)
+        assert dt == pydicom.valuerep.DT(dt_object)
+        assert dt < datetime(1911, 12, 13, 21, 21, 23, 123)
+        assert dt != datetime(1911, 12, 13, 21, 21, 24)
+        assert dt < pydicom.valuerep.DT(datetime(1911, 12, 13, 21, 21, 24))
+        assert dt <= datetime(1911, 12, 13, 21, 21, 23)
+        assert dt <= dt_object
+        assert dt > datetime(1911, 12, 13, 21, 21, 22)
+        assert dt > pydicom.valuerep.DT(datetime(1911, 12, 13, 21, 21, 22))
+        assert dt >= datetime(1911, 12, 13, 21, 21, 23)
+        assert datetime(1911, 12, 13, 21, 21, 24) > dt
+        assert dt_object >= dt
+        assert datetime(1911, 12, 13, 21, 21, 22) < dt
+        with pytest.raises(TypeError):
+            dt > 5
+
+    def test_datetime_behavior(self):
+        """Test that DT behaves like datetime."""
+        tz_info = timezone(timedelta(seconds=-23400), '-0630')
+        dt_object = datetime(2022, 12, 31, 23, 59, 59, 42, tzinfo=tz_info)
+        dt = pydicom.valuerep.DT(dt_object)
+        assert dt == dt_object
+        assert dt_object == dt
+        assert dt.year == 2022
+        assert dt.month == 12
+        assert dt.hour == 23
+        assert dt.second == 59
+        assert dt.microsecond == 42
+        assert dt.tzinfo == tz_info
+        assert dt.today().date() == dt_object.today().date()
+        assert "hour" in dir(dt)
+        assert "original_string" in dir(dt)
 
 
 class TestDA:
@@ -229,10 +327,48 @@ class TestDA:
         with pytest.raises(ValueError, match=msg):
             pydicom.valuerep.DA(123456)
 
-    def test_str(self):
-        assert "10010203" == str(pydicom.valuerep.DA(date(1001, 2, 3)))
-        assert "10010203" == str(pydicom.valuerep.DA("10010203"))
-        assert "1001.02.03" == str(pydicom.valuerep.DA("1001.02.03"))
+    def test_str_and_repr(self):
+        assert str(pydicom.valuerep.DA(date(1001, 2, 3))) == "10010203"
+        assert repr(pydicom.valuerep.DA(date(1001, 2, 3))) == '"10010203"'
+        assert str(pydicom.valuerep.DA("10010203")) == "10010203"
+        assert repr(pydicom.valuerep.DA("10010203")) == '"10010203"'
+        assert str(pydicom.valuerep.DA("1001.02.03")) == "1001.02.03"
+        assert repr(pydicom.valuerep.DA("1001.02.03")) == '"1001.02.03"'
+
+    def test_comparison(self):
+        da = pydicom.valuerep.DA("19111213")
+        da_object = date(1911, 12, 13)
+        assert da == da
+        assert da != 1
+        assert da == da_object
+        assert hash(da) == hash(da_object)
+        assert da_object == da
+        assert da == pydicom.valuerep.DA(da_object)
+        assert da < date(1911, 12, 14)
+        assert da != date(1901, 12, 13)
+        assert da < pydicom.valuerep.DA(date(1912, 12, 13))
+        assert da <= date(1911, 12, 13)
+        assert da <= da_object
+        assert da > date(1911, 12, 12)
+        assert da > pydicom.valuerep.DA(date(1911, 12, 12))
+        assert da >= date(1911, 12, 13)
+        assert date(1911, 12, 14) > da
+        assert da_object >= da
+        assert date(1911, 12, 12) < da
+        with pytest.raises(TypeError):
+            da > 5
+
+    def test_date_behavior(self):
+        da = pydicom.valuerep.DA("10010203")
+        da_object = date(1001, 2, 3)
+        assert da == da_object
+        assert da_object == da
+        assert da.year == 1001
+        assert da.month == 2
+        assert da.day == 3
+        assert da.today() == da_object.today()
+        assert "day" in dir(da)
+        assert "original_string" in dir(da)
 
 
 class TestIsValidDS:
@@ -338,8 +474,8 @@ class TestTruncateFloatForDS:
     def test_wrong_type(self):
         """Test calling with a string raises an error"""
         with pytest.raises(
-            TypeError,
-            match="'val' must be of type float or decimal.Decimal"
+                TypeError,
+                match="'val' must be of type float or decimal.Decimal"
         ):
             pydicom.valuerep.format_number_as_ds('1.0')
 
@@ -470,9 +606,9 @@ class TestDSfloat:
         ]
     )
     def test_enforce_valid_values_value(
-        self,
-        val: Union[float, str],
-        enforce_valid_true_fixture
+            self,
+            val: Union[float, str],
+            enforce_valid_true_fixture
     ):
         """Test that errors are raised when value is invalid."""
         with pytest.raises(ValueError):
@@ -568,9 +704,9 @@ class TestDSdecimal:
         ]
     )
     def test_enforce_valid_values_value(
-        self,
-        val: Union[Decimal, str],
-        enforce_valid_true_fixture
+            self,
+            val: Union[Decimal, str],
+            enforce_valid_true_fixture
     ):
         """Test that errors are raised when value is invalid."""
         with pytest.raises(ValueError):