Repository: spdx/tools-python

Issue:
spdx_tools created field does not matches the specification
spdx `created` [specification](https://spdx.github.io/spdx-spec/v2.3/document-creation-information/#69-created-field) expects 

> YYYY-MM-DDThh:mm:ssZ

as in the example:

> Created: 2010-01-29T18:30:22Z

However, the current implementation does not produce time in the expected format.

**Steps to reproduce**
1. Take attached example or [sbom from scratch](https://github.com/spdx/tools-python/blob/e08e4d25d721bb1ada1301d9d8e8515852387ea0/examples/spdx2_document_from_scratch.py) example and modify 

[Line 41](https://github.com/spdx/tools-python/blob/e08e4d25d721bb1ada1301d9d8e8515852387ea0/examples/spdx2_document_from_scratch.py#L41):     created=datetime.datetime.now(tz=datetime.timezone.utc),
[Line 75](https://github.com/spdx/tools-python/blob/e08e4d25d721bb1ada1301d9d8e8515852387ea0/examples/spdx2_document_from_scratch.py#L75): release_date=datetime.datetime.now(tz=datetime.timezone.utc),

2. Build SBOM
3. Resulting SBOM has 

>        "created": "2023-09-29T08:14:36+00:00Z",

This does not match the spec and is also warned by: https://tools.spdx.org/app/


[test_sbom.py.zip](https://github.com/spdx/tools-python/files/12762552/test_sbom.py.zip)


Discussion:
licquia: Looks like I didn't add the test dependency `tzdata` correctly, or missed something.  I'll look in the morning, or if someone wants to just ensure `tzdata` is installed as a test dependency everywhere it's needed, that should clear out the test failure.

Patch:
diff --git a/pyproject.toml b/pyproject.toml
index 19e1aa5..aff57e3 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -28,7 +28,7 @@ dependencies = ["click", "pyyaml", "xmltodict", "rdflib", "beartype", "uritools"
 dynamic = ["version"]
 
 [project.optional-dependencies]
-test = ["pytest", "pyshacl"]
+test = ["pytest", "pyshacl", "tzdata"]
 code_style = ["isort", "black", "flake8"]
 graph_generation = ["pygraphviz", "networkx"]
 development = ["black", "flake8", "isort", "networkx", "pytest"]
diff --git a/src/spdx_tools/spdx/datetime_conversions.py b/src/spdx_tools/spdx/datetime_conversions.py
index 7b54ae9..cce624d 100644
--- a/src/spdx_tools/spdx/datetime_conversions.py
+++ b/src/spdx_tools/spdx/datetime_conversions.py
@@ -1,7 +1,7 @@
 # SPDX-FileCopyrightText: 2022 spdx contributors
 #
 # SPDX-License-Identifier: Apache-2.0
-from datetime import datetime
+from datetime import datetime, timezone
 
 
 def datetime_from_str(date_str: str) -> datetime:
@@ -16,6 +16,10 @@ def datetime_to_iso_string(date: datetime) -> str:
     """
     Return an ISO-8601 representation of a datetime object.
     """
+    if date.tzinfo is not None:
+        date = date.astimezone(timezone.utc)  # Convert aware datetimes to UTC
+        date = date.replace(tzinfo=None)  # Convert to naive datetime
+
     if date.microsecond != 0:
         date = date.replace(microsecond=0)  # SPDX does not support microseconds
 


Test patch:
diff --git a/.github/workflows/install_and_test.yml b/.github/workflows/install_and_test.yml
index eadbc8b..4884b94 100644
--- a/.github/workflows/install_and_test.yml
+++ b/.github/workflows/install_and_test.yml
@@ -33,6 +33,7 @@ jobs:
           python -m pip install --upgrade ./dist/*.whl
           python -m pip install pytest
           python -m pip install pyshacl
+          python -m pip install tzdata
           python -m pip install networkx
         shell: bash
       - name: Run tests
diff --git a/tests/spdx/test_datetime_conversions.py b/tests/spdx/test_datetime_conversions.py
index 2c4858c..3967d59 100644
--- a/tests/spdx/test_datetime_conversions.py
+++ b/tests/spdx/test_datetime_conversions.py
@@ -1,12 +1,27 @@
 # SPDX-FileCopyrightText: 2022 spdx contributors
 #
 # SPDX-License-Identifier: Apache-2.0
-from datetime import datetime
+from datetime import datetime, timezone
 
 import pytest
 
 from spdx_tools.spdx.datetime_conversions import datetime_from_str, datetime_to_iso_string
 
+# The following is required as long as we support Python 3.8.x or
+# older.  Once Python 3.9 is the oldest version we support, we can
+# rely solely on the section which imports and uses zoneinfo.
+
+try:
+    # Python 3.9 and later
+    from zoneinfo import ZoneInfo
+
+    tz_nyc = ZoneInfo("America/New_York")
+except ImportError:
+    # Python 3.8 and earlier
+    from datetime import timedelta
+
+    tz_nyc = timezone(timedelta(hours=-4))
+
 
 def test_datetime_to_iso_string():
     assert datetime_to_iso_string(datetime(2022, 12, 13, 1, 2, 3)) == "2022-12-13T01:02:03Z"
@@ -16,6 +31,16 @@ def test_datetime_to_iso_string_with_microseconds():
     assert datetime_to_iso_string(datetime(2022, 12, 13, 1, 2, 3, 666666)) == "2022-12-13T01:02:03Z"
 
 
+def test_utc_datetime_to_iso_string():
+    dt = datetime(2023, 10, 4, 1, 2, 3, tzinfo=timezone.utc)
+    assert datetime_to_iso_string(dt) == "2023-10-04T01:02:03Z"
+
+
+def test_local_datetime_to_iso_string():
+    dt = datetime(2023, 10, 4, 1, 2, 3, tzinfo=tz_nyc)
+    assert datetime_to_iso_string(dt) == "2023-10-04T05:02:03Z"
+
+
 def test_datetime_from_str():
     date_str = "2010-03-04T05:45:11Z"