Repository: cherrypy/cherrypy

Issue:
Local time is used as utc time in LazyRfc3339UtcTime
**I'm submitting a ...**
- [x] bug report
- [ ] feature request
- [ ] question about the decisions made in the repository

**Do you want to request a *feature* or report a *bug*?**
Report a *bug*

**What is the current behavior?**
`LazyRfc3339UtcTime` returns server local time as utc time in RFC3339 format.

**If the current behavior is a bug, please provide the steps to reproduce and if possible a screenshots and logs of the problem. If you can, show us your code.**

```python
import datetime
from cherrypy._cplogging import LazyRfc3339UtcTime

# Both datetime are out by timezone offset
print(LazyRfc3339UtcTime())
print(f"{datetime.datetime.utcnow().isoformat()}Z")
```

**What is the expected behavior?**
LazyRfc3339UtcTime should return utc time string in RFC3339 format. 

**What is the motivation / use case for changing the behavior?**
Log timestamp should display the correct timestamp of an event.

**Please tell us about your environment:**

```console
cheroot: 9.0.0
CherryPy: 18.8.0
Python 3.11.2 (tags/v3.11.2:878ead1, Feb  7 2023, 16:38:35) [MSC v.1934 64 bit (AMD64)] on win32
OS: Windows 10
```


**Other information** (e.g. detailed explanation, stacktraces, related issues, suggestions how to fix, links for us to have context, e.g. stackoverflow, gitter, etc.)

Suggestions on bug fix:
Replace `now()` with `utcnow()`
```python
class LazyRfc3339UtcTime(object):
    def __str__(self):
        """Return utcnow() in RFC3339 UTC Format."""
        return f"{datetime.datetime.utcnow().isoformat('T')}Z"
```

Discussion:

Patch:
diff --git a/cherrypy/_cplogging.py b/cherrypy/_cplogging.py
index 151d3b40..bce1c87b 100644
--- a/cherrypy/_cplogging.py
+++ b/cherrypy/_cplogging.py
@@ -452,6 +452,6 @@ class WSGIErrorHandler(logging.Handler):
 
 class LazyRfc3339UtcTime(object):
     def __str__(self):
-        """Return now() in RFC3339 UTC Format."""
-        now = datetime.datetime.now()
-        return now.isoformat('T') + 'Z'
+        """Return utcnow() in RFC3339 UTC Format."""
+        iso_formatted_now = datetime.datetime.utcnow().isoformat('T')
+        return f'{iso_formatted_now!s}Z'


Test patch:
diff --git a/cherrypy/test/test_logging.py b/cherrypy/test/test_logging.py
index 2d4aa56f..49d41d0a 100644
--- a/cherrypy/test/test_logging.py
+++ b/cherrypy/test/test_logging.py
@@ -1,5 +1,6 @@
 """Basic tests for the CherryPy core: request handling."""
 
+import datetime
 import logging
 
 from cheroot.test import webtest
@@ -197,6 +198,33 @@ def test_custom_log_format(log_tracker, monkeypatch, server):
     )
 
 
+def test_utc_in_timez(monkeypatch):
+    """Test that ``LazyRfc3339UtcTime`` is rendered as ``str`` using UTC timestamp."""
+    utcoffset8_local_time_in_naive_utc = (
+        datetime.datetime(
+            year=2020,
+            month=1,
+            day=1,
+            hour=1,
+            minute=23,
+            second=45,
+            tzinfo=datetime.timezone(datetime.timedelta(hours=8)),
+        )
+        .astimezone(datetime.timezone.utc)
+        .replace(tzinfo=None)
+    )
+
+    class mock_datetime:
+        @classmethod
+        def utcnow(cls):
+            return utcoffset8_local_time_in_naive_utc
+
+    monkeypatch.setattr('datetime.datetime', mock_datetime)
+    rfc3339_utc_time = str(cherrypy._cplogging.LazyRfc3339UtcTime())
+    expected_time = '2019-12-31T17:23:45Z'
+    assert rfc3339_utc_time == expected_time
+
+
 def test_timez_log_format(log_tracker, monkeypatch, server):
     """Test a customized access_log_format string, which is a
     feature of _cplogging.LogManager.access()."""