Repository: getmoto/moto

Issue:
Potential Bug? cloudwatch metrics timestamp format doesn't match Datapoint timestamp format
```
@mock_cloudwatch_b2
def test_cloudwatch_metrics():
    @mock_ec2
    def create_instances():
      session = boto3.Session(
          aws_access_key_id=access_key,
          aws_secret_access_key=secret_key,
      )
      ec2 = session.resource('ec2', CLOUD_REGION)
      response = ec2.create_instances(MinCount=1, MaxCount=1, InstanceType='t2.micro')
      instance_id = response[0].id
      # create VPC
      vpc = ec2.create_vpc(CidrBlock='192.168.0.0/16')
      # we can assign a name to vpc, or any resource, by using tag
      vpc.create_tags(Tags=[{"Key": "Name", "Value": "default_vpc"}])
      vpc.wait_until_available()

      sec_group = ec2.create_security_group(
          GroupName='test-group', Description='test sec group', VpcId=vpc.id)
      sec_group.authorize_ingress(
          CidrIp='0.0.0.0/0',
          IpProtocol='icmp',
          FromPort=-1,
          ToPort=-1
      )
      return instance_id

    instance_id = create_instances()

    cw = boto.ec2.cloudwatch.connect_to_region(CLOUD_REGION, aws_access_key_id=access_key,
                                               aws_secret_access_key=secret_key)
    cw.put_metric_data(
        namespace="AWS/test", # metric namespace start with AWS/, Stackdriver or customer_cloudwathc_metric can be monitored
        name="instance_metric",
        value=1.5,
        dimensions={"InstanceId": [instance_id]},
    )
```

When call metric.query(start, end, boto.ec2.cloudwatch.Metric.Statistics), it returned error like this:
```
ValueError: time data '2020-02-10T18:44:09.496Z' does not match format '%Y-%m-%dT%H:%M:%SZ'
"lib/python2.7/site-packages/boto/ec2/cloudwatch/datapoint.py", line 38, in endElement
    self[name] = datetime.strptime(value, '%Y-%m-%dT%H:%M:%SZ')
```
It seems like put_metric_data()'s timestamp format cannot match datapoints format?
Please let me know if I am doing wrong.


Discussion:

Resolving patch:
diff --git a/moto/cloudwatch/models.py b/moto/cloudwatch/models.py
--- a/moto/cloudwatch/models.py
+++ b/moto/cloudwatch/models.py
@@ -2,13 +2,14 @@
 
 from boto3 import Session
 
-from moto.core.utils import iso_8601_datetime_with_milliseconds
+from moto.core.utils import iso_8601_datetime_without_milliseconds
 from moto.core import BaseBackend, BaseModel
 from moto.core.exceptions import RESTError
 from datetime import datetime, timedelta
 from dateutil.tz import tzutc
 from uuid import uuid4
 from .utils import make_arn_for_dashboard
+from dateutil import parser
 
 from moto.core import ACCOUNT_ID as DEFAULT_ACCOUNT_ID
 
@@ -146,7 +147,7 @@ def __repr__(self):
 
 class Statistics:
     def __init__(self, stats, dt):
-        self.timestamp = iso_8601_datetime_with_milliseconds(dt)
+        self.timestamp = iso_8601_datetime_without_milliseconds(dt)
         self.values = []
         self.stats = stats
 
@@ -278,8 +279,7 @@ def put_metric_data(self, namespace, metric_data):
             # Preserve "datetime" for get_metric_statistics comparisons
             timestamp = metric_member.get("Timestamp")
             if timestamp is not None and type(timestamp) != datetime:
-                timestamp = datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%S.%fZ")
-                timestamp = timestamp.replace(tzinfo=tzutc())
+                timestamp = parser.parse(timestamp)
             self.metric_data.append(
                 MetricDatum(
                     namespace,


Test patch:
diff --git a/tests/test_cloudwatch/test_cloudwatch.py b/tests/test_cloudwatch/test_cloudwatch.py
--- a/tests/test_cloudwatch/test_cloudwatch.py
+++ b/tests/test_cloudwatch/test_cloudwatch.py
@@ -1,117 +1,148 @@
-import boto
-from boto.ec2.cloudwatch.alarm import MetricAlarm
-import sure  # noqa
-
-from moto import mock_cloudwatch_deprecated
-
-
-def alarm_fixture(name="tester", action=None):
-    action = action or ["arn:alarm"]
-    return MetricAlarm(
-        name=name,
-        namespace="{0}_namespace".format(name),
-        metric="{0}_metric".format(name),
-        comparison=">=",
-        threshold=2.0,
-        period=60,
-        evaluation_periods=5,
-        statistic="Average",
-        description="A test",
-        dimensions={"InstanceId": ["i-0123456,i-0123457"]},
-        alarm_actions=action,
-        ok_actions=["arn:ok"],
-        insufficient_data_actions=["arn:insufficient"],
-        unit="Seconds",
-    )
-
-
-@mock_cloudwatch_deprecated
-def test_create_alarm():
-    conn = boto.connect_cloudwatch()
-
-    alarm = alarm_fixture()
-    conn.create_alarm(alarm)
-
-    alarms = conn.describe_alarms()
-    alarms.should.have.length_of(1)
-    alarm = alarms[0]
-    alarm.name.should.equal("tester")
-    alarm.namespace.should.equal("tester_namespace")
-    alarm.metric.should.equal("tester_metric")
-    alarm.comparison.should.equal(">=")
-    alarm.threshold.should.equal(2.0)
-    alarm.period.should.equal(60)
-    alarm.evaluation_periods.should.equal(5)
-    alarm.statistic.should.equal("Average")
-    alarm.description.should.equal("A test")
-    dict(alarm.dimensions).should.equal({"InstanceId": ["i-0123456,i-0123457"]})
-    list(alarm.alarm_actions).should.equal(["arn:alarm"])
-    list(alarm.ok_actions).should.equal(["arn:ok"])
-    list(alarm.insufficient_data_actions).should.equal(["arn:insufficient"])
-    alarm.unit.should.equal("Seconds")
-
-
-@mock_cloudwatch_deprecated
-def test_delete_alarm():
-    conn = boto.connect_cloudwatch()
-
-    alarms = conn.describe_alarms()
-    alarms.should.have.length_of(0)
-
-    alarm = alarm_fixture()
-    conn.create_alarm(alarm)
-
-    alarms = conn.describe_alarms()
-    alarms.should.have.length_of(1)
-
-    alarms[0].delete()
-
-    alarms = conn.describe_alarms()
-    alarms.should.have.length_of(0)
-
-
-@mock_cloudwatch_deprecated
-def test_put_metric_data():
-    conn = boto.connect_cloudwatch()
-
-    conn.put_metric_data(
-        namespace="tester",
-        name="metric",
-        value=1.5,
-        dimensions={"InstanceId": ["i-0123456,i-0123457"]},
-    )
-
-    metrics = conn.list_metrics()
-    metrics.should.have.length_of(1)
-    metric = metrics[0]
-    metric.namespace.should.equal("tester")
-    metric.name.should.equal("metric")
-    dict(metric.dimensions).should.equal({"InstanceId": ["i-0123456,i-0123457"]})
-
-
-@mock_cloudwatch_deprecated
-def test_describe_alarms():
-    conn = boto.connect_cloudwatch()
-
-    alarms = conn.describe_alarms()
-    alarms.should.have.length_of(0)
-
-    conn.create_alarm(alarm_fixture(name="nfoobar", action="afoobar"))
-    conn.create_alarm(alarm_fixture(name="nfoobaz", action="afoobaz"))
-    conn.create_alarm(alarm_fixture(name="nbarfoo", action="abarfoo"))
-    conn.create_alarm(alarm_fixture(name="nbazfoo", action="abazfoo"))
-
-    alarms = conn.describe_alarms()
-    alarms.should.have.length_of(4)
-    alarms = conn.describe_alarms(alarm_name_prefix="nfoo")
-    alarms.should.have.length_of(2)
-    alarms = conn.describe_alarms(alarm_names=["nfoobar", "nbarfoo", "nbazfoo"])
-    alarms.should.have.length_of(3)
-    alarms = conn.describe_alarms(action_prefix="afoo")
-    alarms.should.have.length_of(2)
-
-    for alarm in conn.describe_alarms():
-        alarm.delete()
-
-    alarms = conn.describe_alarms()
-    alarms.should.have.length_of(0)
+import boto
+from boto.ec2.cloudwatch.alarm import MetricAlarm
+from datetime import datetime
+import sure  # noqa
+
+from moto import mock_cloudwatch_deprecated
+
+
+def alarm_fixture(name="tester", action=None):
+    action = action or ["arn:alarm"]
+    return MetricAlarm(
+        name=name,
+        namespace="{0}_namespace".format(name),
+        metric="{0}_metric".format(name),
+        comparison=">=",
+        threshold=2.0,
+        period=60,
+        evaluation_periods=5,
+        statistic="Average",
+        description="A test",
+        dimensions={"InstanceId": ["i-0123456,i-0123457"]},
+        alarm_actions=action,
+        ok_actions=["arn:ok"],
+        insufficient_data_actions=["arn:insufficient"],
+        unit="Seconds",
+    )
+
+
+@mock_cloudwatch_deprecated
+def test_create_alarm():
+    conn = boto.connect_cloudwatch()
+
+    alarm = alarm_fixture()
+    conn.create_alarm(alarm)
+
+    alarms = conn.describe_alarms()
+    alarms.should.have.length_of(1)
+    alarm = alarms[0]
+    alarm.name.should.equal("tester")
+    alarm.namespace.should.equal("tester_namespace")
+    alarm.metric.should.equal("tester_metric")
+    alarm.comparison.should.equal(">=")
+    alarm.threshold.should.equal(2.0)
+    alarm.period.should.equal(60)
+    alarm.evaluation_periods.should.equal(5)
+    alarm.statistic.should.equal("Average")
+    alarm.description.should.equal("A test")
+    dict(alarm.dimensions).should.equal({"InstanceId": ["i-0123456,i-0123457"]})
+    list(alarm.alarm_actions).should.equal(["arn:alarm"])
+    list(alarm.ok_actions).should.equal(["arn:ok"])
+    list(alarm.insufficient_data_actions).should.equal(["arn:insufficient"])
+    alarm.unit.should.equal("Seconds")
+
+
+@mock_cloudwatch_deprecated
+def test_delete_alarm():
+    conn = boto.connect_cloudwatch()
+
+    alarms = conn.describe_alarms()
+    alarms.should.have.length_of(0)
+
+    alarm = alarm_fixture()
+    conn.create_alarm(alarm)
+
+    alarms = conn.describe_alarms()
+    alarms.should.have.length_of(1)
+
+    alarms[0].delete()
+
+    alarms = conn.describe_alarms()
+    alarms.should.have.length_of(0)
+
+
+@mock_cloudwatch_deprecated
+def test_put_metric_data():
+    conn = boto.connect_cloudwatch()
+
+    conn.put_metric_data(
+        namespace="tester",
+        name="metric",
+        value=1.5,
+        dimensions={"InstanceId": ["i-0123456,i-0123457"]},
+    )
+
+    metrics = conn.list_metrics()
+    metrics.should.have.length_of(1)
+    metric = metrics[0]
+    metric.namespace.should.equal("tester")
+    metric.name.should.equal("metric")
+    dict(metric.dimensions).should.equal({"InstanceId": ["i-0123456,i-0123457"]})
+
+
+@mock_cloudwatch_deprecated
+def test_describe_alarms():
+    conn = boto.connect_cloudwatch()
+
+    alarms = conn.describe_alarms()
+    alarms.should.have.length_of(0)
+
+    conn.create_alarm(alarm_fixture(name="nfoobar", action="afoobar"))
+    conn.create_alarm(alarm_fixture(name="nfoobaz", action="afoobaz"))
+    conn.create_alarm(alarm_fixture(name="nbarfoo", action="abarfoo"))
+    conn.create_alarm(alarm_fixture(name="nbazfoo", action="abazfoo"))
+
+    alarms = conn.describe_alarms()
+    alarms.should.have.length_of(4)
+    alarms = conn.describe_alarms(alarm_name_prefix="nfoo")
+    alarms.should.have.length_of(2)
+    alarms = conn.describe_alarms(alarm_names=["nfoobar", "nbarfoo", "nbazfoo"])
+    alarms.should.have.length_of(3)
+    alarms = conn.describe_alarms(action_prefix="afoo")
+    alarms.should.have.length_of(2)
+
+    for alarm in conn.describe_alarms():
+        alarm.delete()
+
+    alarms = conn.describe_alarms()
+    alarms.should.have.length_of(0)
+
+
+@mock_cloudwatch_deprecated
+def test_get_metric_statistics():
+    conn = boto.connect_cloudwatch()
+
+    metric_timestamp = datetime(2018, 4, 9, 13, 0, 0, 0)
+
+    conn.put_metric_data(
+        namespace="tester",
+        name="metric",
+        value=1.5,
+        dimensions={"InstanceId": ["i-0123456,i-0123457"]},
+        timestamp=metric_timestamp,
+    )
+
+    metric_kwargs = dict(
+        namespace="tester",
+        metric_name="metric",
+        start_time=metric_timestamp,
+        end_time=datetime.now(),
+        period=3600,
+        statistics=["Minimum"],
+    )
+
+    datapoints = conn.get_metric_statistics(**metric_kwargs)
+    datapoints.should.have.length_of(1)
+    datapoint = datapoints[0]
+    datapoint.should.have.key("Minimum").which.should.equal(1.5)
+    datapoint.should.have.key("Timestamp").which.should.equal(metric_timestamp)