Repository: getmoto/moto

Issue:
Calling the "getSendStatistics()" function on the Java SES SDKv2 client throws a date format exception.
### TLDR:
Calling the "getSendStatistics()" function using the Java SES SDKv2 client throws a date format exception.

The SDK expect the date to be an UTC date - that is, suffixed with 'Z', like `2024-04-29T11:53:35.987469Z`. What it get is `2024-04-29T11:53:35.987469`.

### Expect behavior:

A response that can be de-serialized/parsed by the SES client successfully

### Actual behavior:

An exception exception is thrown upon trying to parsing the response: 

```
java.time.format.DateTimeParseException: Text '2024-04-29T12:02:57.370491' could not be parsed at index 26
```

###  Cause
The response returned from moto looks like this:

```
<GetSendStatisticsResponse xmlns="http://ses.amazonaws.com/doc/2010-12-01/">
    <GetSendStatisticsResult>
        <SendDataPoints>
            <member>
                <DeliveryAttempts>1</DeliveryAttempts>
                <Rejects>0</Rejects>
                <Bounces>0</Bounces>
                <Complaints>0</Complaints>
                <Timestamp>2024-04-29T11:53:35.987469</Timestamp>
            </member>
        </SendDataPoints>
    </GetSendStatisticsResult>
    <ResponseMetadata>
        <RequestId>3oohUOjkXc4TaVWi2oyivhwS0CPTFiEit4fJC7wOLCCFxnSRoPWw</RequestId>
    </ResponseMetadata>
</GetSendStatisticsResponse>
```

The SDK expect the date to be a Iso8601Date - which isn't. `2024-04-29T11:53:35.987469Z` would have been acceptable, but without the UTC marker, Z, it all goes sideways.

###  "Full" Stacktrace

```
java.time.format.DateTimeParseException: Text '2024-04-29T12:19:14.993264' could not be parsed at index 26
	at java.base/java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:2046)
	at java.base/java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1948)
	at software.amazon.awssdk.utils.DateUtils.parseInstant(DateUtils.java:190)
	at software.amazon.awssdk.utils.DateUtils.parseIso8601Date(DateUtils.java:98)
	at software.amazon.awssdk.protocols.core.StringToInstant.convert(StringToInstant.java:52)
	at software.amazon.awssdk.protocols.core.StringToInstant.convert(StringToInstant.java:32)
	at software.amazon.awssdk.protocols.query.internal.unmarshall.SimpleTypeQueryUnmarshaller.unmarshall(SimpleTypeQueryUnmarshaller.java:43)
	at software.amazon.awssdk.protocols.query.internal.unmarshall.QueryProtocolUnmarshaller.unmarshall(QueryProtocolUnmarshaller.java:139)
	at software.amazon.awssdk.protocols.query.internal.unmarshall.QueryProtocolUnmarshaller.lambda$static$1(QueryProtocolUnmarshaller.java:65)
	at software.amazon.awssdk.protocols.query.internal.unmarshall.ListQueryUnmarshaller.lambda$unmarshall$0(ListQueryUnmarshaller.java:37)
```	

### Investigation of what the client does:

The relevant classes in the SDK are

https://github.com/aws/aws-sdk-java-v2/blob/master/utils/src/main/java/software/amazon/awssdk/utils/DateUtils.java
https://github.com/aws/aws-sdk-java-v2/blob/master/core/protocols/protocol-core/src/main/java/software/amazon/awssdk/protocols/core/StringToInstant.java
https://github.com/aws/aws-sdk-java-v2/blob/master/core/protocols/aws-query-protocol/src/main/java/software/amazon/awssdk/protocols/query/internal/marshall/SimpleTypeQueryMarshaller.java

The SDK will guess that the date is an Iso8601Date - which isn't, apparently. So why does it think that? Well, because that's the only thing the SDK handles by default it seems, as can be seen at 

https://github.com/aws/aws-sdk-java-v2/blob/e4fb225dc46c86eff09b40b038ba21c02add6130/core/protocols/aws-query-protocol/src/main/java/software/amazon/awssdk/protocols/query/internal/marshall/SimpleTypeQueryMarshaller.java#L77

It will try the following formats before it throws the error 

```
2011-12-03T10:15:30Z
2024-04-29T11:53:35.987469Z # I think it would accept fewer fraction digits too - the Java SDK ISO_INSTANCE parser implementation is a bit lengthy
2011-12-03T10:15:30+01:00
```

### Versions:

Moto version:  Moto 5.0.6 (Dockerized)
Java SES SDK version: 2.20.96
Java version: 11 (but I can see the behavior is same even in Java version 21)

### Additional information:

* The relevant date parsing code is shared among the AWS Java SDK v2 clients. My guess is that any place that returns dates in this format will flip the cart over.

* The user agent string sent is `aws-sdk-java/2.20.96 <loadsa stuff about my machine>`. It is conceivable that AWS adapts the response based on this, and that might could for why it apparently works for other clients. I haven't tried this with the v1 SDK, but I can, if requested.




Discussion:

Resolving patch:
diff --git a/moto/ses/responses.py b/moto/ses/responses.py
--- a/moto/ses/responses.py
+++ b/moto/ses/responses.py
@@ -548,7 +548,7 @@ def get_identity_verification_attributes(self) -> str:
                 <Rejects>{{ statistics["Rejects"] }}</Rejects>
                 <Bounces>{{ statistics["Bounces"] }}</Bounces>
                 <Complaints>{{ statistics["Complaints"] }}</Complaints>
-                <Timestamp>{{ statistics["Timestamp"].isoformat() }}</Timestamp>
+                <Timestamp>{{ statistics["Timestamp"].strftime('%Y-%m-%dT%H:%M:%S.%fZ') }}</Timestamp>
             </member>
         {% endfor %}
       </SendDataPoints>


Test patch:
diff --git a/other_langs/tests_java/pom.xml b/other_langs/tests_java/pom.xml
--- a/other_langs/tests_java/pom.xml
+++ b/other_langs/tests_java/pom.xml
@@ -13,8 +13,8 @@
 
   <properties>
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
-    <maven.compiler.source>1.8</maven.compiler.source>
-    <maven.compiler.target>1.8</maven.compiler.target>
+    <maven.compiler.source>1.9</maven.compiler.source>
+    <maven.compiler.target>1.9</maven.compiler.target>
   </properties>
 
   <dependencyManagement>
@@ -112,5 +112,15 @@
         </plugin>
       </plugins>
     </pluginManagement>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <configuration>
+          <source>9</source>
+          <target>9</target>
+        </configuration>
+      </plugin>
+    </plugins>
   </build>
 </project>
diff --git a/other_langs/tests_java/src/test/java/moto/tests/SESTest.java b/other_langs/tests_java/src/test/java/moto/tests/SESTest.java
--- a/other_langs/tests_java/src/test/java/moto/tests/SESTest.java
+++ b/other_langs/tests_java/src/test/java/moto/tests/SESTest.java
@@ -6,13 +6,15 @@
 import static org.junit.Assert.assertTrue;
 
 import org.junit.Test;
+import software.amazon.awssdk.services.dynamodb.model.Get;
 import software.amazon.awssdk.services.ses.SesClient;
 import software.amazon.awssdk.services.ses.model.Body;
 import software.amazon.awssdk.services.ses.model.Content;
 import software.amazon.awssdk.services.ses.model.Destination;
 import software.amazon.awssdk.services.ses.model.Message;
 import software.amazon.awssdk.services.ses.model.SendEmailRequest;
-import software.amazon.awssdk.services.ses.model.SendEmailResponse;
+import software.amazon.awssdk.services.ses.model.VerifyEmailAddressRequest;
+import software.amazon.awssdk.services.ses.model.GetSendStatisticsResponse;
 import software.amazon.awssdk.services.ses.model.SesException;
 
 public class SESTest {
@@ -42,4 +44,35 @@ public void testUnverifiedEmail() {
             assertEquals(e.awsErrorDetails().errorMessage(), "Email address not verified noreply@thedomain.com");
         }
     }
+
+    @Test
+    public void testGetSendStatistics() {
+        SesClient sesClient = DependencyFactory.sesClient();
+
+        VerifyEmailAddressRequest verifyEmailAddressRequest = VerifyEmailAddressRequest.builder().emailAddress("noreply@thedomain.com").build();
+        sesClient.verifyEmailAddress(verifyEmailAddressRequest);
+
+        Message message = Message
+                .builder()
+                .subject(Content.builder().charset("UTF-8").data("subject").build())
+                .body(Body.builder().text(Content.builder().charset("UTF-8").data("body").build()).build())
+                .build();
+
+        SendEmailRequest request = SendEmailRequest
+                .builder()
+                .source("noreply@thedomain.com")
+                .destination(Destination.builder().toAddresses(List.of("one@one.com")).build())
+                .message(message)
+                .build();
+
+        GetSendStatisticsResponse statisticsBefore = sesClient.getSendStatistics();
+        Long prev = statisticsBefore.sendDataPoints().get(0).deliveryAttempts().longValue();
+
+        sesClient.sendEmail(request);
+
+        GetSendStatisticsResponse statisticsAfter = sesClient.getSendStatistics();
+        Long after = statisticsAfter.sendDataPoints().get(0).deliveryAttempts().longValue();
+
+        assertTrue("Delivery attempts should increase", after > prev);
+    }
 }
diff --git a/tests/test_ses/test_server.py b/tests/test_ses/test_server.py
--- a/tests/test_ses/test_server.py
+++ b/tests/test_ses/test_server.py
@@ -22,4 +22,4 @@ def test_ses_get_send_statistics():
     # Timestamps must be in ISO 8601 format
     groups = re.search("<Timestamp>(.*)</Timestamp>", res.data.decode("utf-8"))
     timestamp = groups.groups()[0]
-    datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%S.%f")
+    datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%S.%fZ")