Repository: spdx/tools-python

Issue:
Incorrect timestamp format in generated documents
The format of timestamps is incorrect when using very specific datetime objects (i.e. datetime objects with microsecond precision) to generate documents - for example for the `created` field of a `CreationInfo` object, or the `build_date` of a `Package` object.

For example, consider the following code:
```python
#!/usr/bin/env python3

import datetime
from spdx_tools.spdx.writer.json     import json_writer
from spdx_tools.spdx.model import (
    Actor,
    ActorType,
    CreationInfo,
    Document,
    Package
)
from spdx_tools.spdx.model.spdx_no_assertion import SpdxNoAssertion

creator = Actor(ActorType.PERSON, "m10k", "m10k@example.org")
ci = CreationInfo(spdx_version="SPDX-2.3",
                  spdx_id="SPDXRef-DOCUMENT",
                  name="example",
                  data_license="CC0-1.0",
                  document_namespace="https://m10k.eu",
                  creators=[creator],
                  created=datetime.datetime.now())
doc = Document(ci)

pkg = Package(spdx_id="SPDXRef-0",
              name="hoge",
              download_location=SpdxNoAssertion())
pkg.built_date = datetime.datetime.now()
pkg.version = "1.0"
pkg.supplier = creator
pkg.files_analyzed = False

doc.packages += [pkg]

json_writer.write_document_to_file(doc, "/dev/stdout", validate=True)
```

This will generate the following SPDX file.
```json
{
    "SPDXID": "SPDXRef-DOCUMENT",
    "creationInfo": {
        "created": "2023-06-30T13:09:29.146514Z",
        "creators": [
            "Person: m10k (m10k@example.org)"
        ]
    },
    "dataLicense": "CC0-1.0",
    "name": "example",
    "spdxVersion": "SPDX-2.3",
    "documentNamespace": "https://m10k.eu",
    "packages": [
        {
            "SPDXID": "SPDXRef-0",
            "builtDate": "2023-06-30T13:09:29.146661Z",
            "downloadLocation": "NOASSERTION",
            "filesAnalyzed": false,
            "name": "hoge",
            "supplier": "Person: m10k (m10k@example.org)",
            "versionInfo": "1.0"
        }
    ]
}
```

The `creationInfo.created` and `packages[0].builtDate` fields contain the microseconds (the `.146...` part), but the specification requires the format `YYYY-MM-DDThh:mm:ssZ` [1].
Also note that `json_writer.write_document_to_file()` was called with `validate=True`, meaning that the validator did not catch the incorrect timestamp.

I have not had a look at the source code yet, but I will try to provide a patch as soon as I find the time for it.

#### References
[1] https://spdx.github.io/spdx-spec/v2.3/document-creation-information/#69-created-field

Discussion:

Patch:
diff --git a/src/spdx_tools/spdx/datetime_conversions.py b/src/spdx_tools/spdx/datetime_conversions.py
index 58ac883..7b54ae9 100644
--- a/src/spdx_tools/spdx/datetime_conversions.py
+++ b/src/spdx_tools/spdx/datetime_conversions.py
@@ -16,4 +16,7 @@ def datetime_to_iso_string(date: datetime) -> str:
     """
     Return an ISO-8601 representation of a datetime object.
     """
+    if date.microsecond != 0:
+        date = date.replace(microsecond=0)  # SPDX does not support microseconds
+
     return date.isoformat() + "Z"


Test patch:
diff --git a/tests/spdx/test_datetime_conversions.py b/tests/spdx/test_datetime_conversions.py
index 4c4b807..2c4858c 100644
--- a/tests/spdx/test_datetime_conversions.py
+++ b/tests/spdx/test_datetime_conversions.py
@@ -12,6 +12,10 @@ def test_datetime_to_iso_string():
     assert datetime_to_iso_string(datetime(2022, 12, 13, 1, 2, 3)) == "2022-12-13T01:02:03Z"
 
 
+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_datetime_from_str():
     date_str = "2010-03-04T05:45:11Z"