Repository: twisted/twisted

Issue:
Python 3.12: Deprecated utcnow and utcfromtimestamp
**Describe the incorrect behavior you saw**
`datetime.datetime.utcnow` and `datetime.datetime.utcfromtimestamp` raise deprecation warnings in Python 3.12.

**Describe how to cause this behavior**

While running trial on buildbot this generates lots of deprecation warnings like:
```
Traceback (most recent call last):
  File "C:\P\twisted\src\twisted\logger\_observer.py", line 81, in __call__
    observer(event)
  File "C:\P\twisted\src\twisted\logger\_legacy.py", line 90, in __call__
    self.legacyObserver(event)
  File "C:\P\twisted\src\twisted\python\log.py", line 544, in emit
    timeStr = self.formatTime(eventDict["time"])
  File "C:\P\twisted\src\twisted\python\log.py", line 514, in formatTime
    tzOffset = -self.getTimezoneOffset(when)
  File "C:\P\twisted\src\twisted\python\log.py", line 493, in getTimezoneOffset
    offset = datetime.utcfromtimestamp(when) - datetime.fromtimestamp(when)
builtins.DeprecationWarning: datetime.utcfromtimestamp() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.fromtimestamp(timestamp, datetime.UTC).
E           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
```

Running buildbot trial with current HEAD & current trunk of twisted.

**Describe the correct behavior you'd like to see**
No deprecation warning reported while running trial under python 3.12.

**Testing environment**
 - Operating System and Version; paste the output of these commands:
```
OS Name:                   Microsoft Windows 11 Home
OS Version:                10.0.22621 N/A Build 22621
OS Manufacturer:           Microsoft Corporation
OS Configuration:          Standalone Workstation
OS Build Type:             Multiprocessor Free
System Manufacturer:       Microsoft Corporation
BIOS Version:              Microsoft Corporation 15.11.140, 8. 7. 2022
```
 - Twisted version
   22.10.0.post0
   https://github.com/twisted/twisted/commit/a9ee8e59a5cd1950bf1a18c4b6ca813e5f56ad08
 - Reactor [e.g. select, iocp]
  `twisted-iocpsupport           1.0.4`


**Additional context**
 - Deprecate utcnow and utcfromtimestamp python/cpython#103857
 - https://blog.ganssle.io/articles/2019/11/utcnow.html



Discussion:
Thanks for the report.

Happy to receive a PR with a fix for this :)

Resolving patch:
diff --git a/src/twisted/python/_tzhelper.py b/src/twisted/python/_tzhelper.py
--- a/src/twisted/python/_tzhelper.py
+++ b/src/twisted/python/_tzhelper.py
@@ -6,7 +6,12 @@
 Time zone utilities.
 """
 
-from datetime import datetime as DateTime, timedelta as TimeDelta, tzinfo as TZInfo
+from datetime import (
+    datetime as DateTime,
+    timedelta as TimeDelta,
+    timezone,
+    tzinfo as TZInfo,
+)
 from typing import Optional
 
 __all__ = [
@@ -68,9 +73,9 @@ def fromLocalTimeStamp(cls, timeStamp: float) -> "FixedOffsetTimeZone":
         Create a time zone with a fixed offset corresponding to a time stamp in
         the system's locally configured time zone.
         """
-        offset = DateTime.fromtimestamp(timeStamp) - DateTime.utcfromtimestamp(
-            timeStamp
-        )
+        offset = DateTime.fromtimestamp(timeStamp) - DateTime.fromtimestamp(
+            timeStamp, timezone.utc
+        ).replace(tzinfo=None)
         return cls(offset)
 
     def utcoffset(self, dt: Optional[DateTime]) -> TimeDelta:
diff --git a/src/twisted/python/log.py b/src/twisted/python/log.py
--- a/src/twisted/python/log.py
+++ b/src/twisted/python/log.py
@@ -11,7 +11,7 @@
 import time
 import warnings
 from abc import ABC, abstractmethod
-from datetime import datetime
+from datetime import datetime, timezone
 from typing import Any, BinaryIO, Dict, Optional, cast
 
 from zope.interface import Interface
@@ -490,7 +490,9 @@ def getTimezoneOffset(self, when):
         @return: The number of seconds offset from UTC.  West is positive,
         east is negative.
         """
-        offset = datetime.utcfromtimestamp(when) - datetime.fromtimestamp(when)
+        offset = datetime.fromtimestamp(when, timezone.utc).replace(
+            tzinfo=None
+        ) - datetime.fromtimestamp(when)
         return offset.days * (60 * 60 * 24) + offset.seconds
 
     def formatTime(self, when):
@@ -512,7 +514,9 @@ def formatTime(self, when):
             return datetime.fromtimestamp(when).strftime(self.timeFormat)
 
         tzOffset = -self.getTimezoneOffset(when)
-        when = datetime.utcfromtimestamp(when + tzOffset)
+        when = datetime.fromtimestamp(when + tzOffset, timezone.utc).replace(
+            tzinfo=None
+        )
         tzHour = abs(int(tzOffset / 60 / 60))
         tzMin = abs(int(tzOffset / 60 % 60))
         if tzOffset < 0:


Test patch: