Repository: localstack/localstack

Issue:
bug: Received SQS message attribute ApproximateFirstReceiveTimestamp is a floating point value instead of an integer timestamp
### Is there an existing issue for this?

- [X] I have searched the existing issues

### Current Behavior

SQS message attribute `ApproximateFirstReceiveTimestamp` has a decimal value in addition to the timestamp integer. Example response payload from a `sqs receive-message` request:

```
{
    "Messages": [
        {
            "MessageId": "4392033b-e522-4f76-bd93-f9ea24acecec",
            "ReceiptHandle": "MmJiYmI1YzUtMWFkYy00YWMzLTlmNGUtMjA4ZjM2MGNlNzRlIGFybjphd3M6c3FzOnVzLWVhc3QtMTowMDAwMDAwMDAwMDA6dGVzdC1xdWV1ZSA0MzkyMDMzYi1lNTIyLTRmNzYtYmQ5My1mOWVhMjRhY2VjZWMgMTY1MzQ5OTMyOS44MzI4NzAy",
            "MD5OfBody": "4e121647549e21089f8d89c271f2e344",
            "Body": "this is a test",
            "Attributes": {
                "SenderId": "000000000000",
                "SentTimestamp": "1653499304",
                "ApproximateReceiveCount": "1",
                "ApproximateFirstReceiveTimestamp": "1653499329.8328702"
            }
        }
    ]
}
```

This is troublesome when attempting to parse the attribute string value, as libraries such as [amazon-sqs-java-temporary-queues-client](https://github.com/awslabs/amazon-sqs-java-temporary-queues-client) perform a direct `Long.parseLong(string)` on the attribute value, which results in an exception being thrown since the input string contains a decimal value.

### Expected Behavior

The SQS message attribute `ApproximateFirstReceiveTimestamp` value should be an integer timestamp, just like the `SentTimestamp` attribute value. Example expected response payload from a `sqs receive-message` request:

```
{
    "Messages": [
        {
            "MessageId": "4392033b-e522-4f76-bd93-f9ea24acecec",
            "ReceiptHandle": "MmJiYmI1YzUtMWFkYy00YWMzLTlmNGUtMjA4ZjM2MGNlNzRlIGFybjphd3M6c3FzOnVzLWVhc3QtMTowMDAwMDAwMDAwMDA6dGVzdC1xdWV1ZSA0MzkyMDMzYi1lNTIyLTRmNzYtYmQ5My1mOWVhMjRhY2VjZWMgMTY1MzQ5OTMyOS44MzI4NzAy",
            "MD5OfBody": "4e121647549e21089f8d89c271f2e344",
            "Body": "this is a test",
            "Attributes": {
                "SenderId": "000000000000",
                "SentTimestamp": "1653499304",
                "ApproximateReceiveCount": "1",
                "ApproximateFirstReceiveTimestamp": "1653499329"
            }
        }
    ]
}
```

### How are you starting LocalStack?

Custom (please describe below)

### Steps To Reproduce

#### How are you starting localstack (e.g., `bin/localstack` command, arguments, or `docker-compose.yml`)
I am deploying LocalStack in a local Kubernetes cluster via Minikube.

##### localstack.yaml
```
apiVersion: v1
kind: ConfigMap
metadata:
  name: localstack-config
data:
  init.sh: |
    #!bin/bash
    set -e

    echo "Creating AWS profile."
    aws configure set aws_access_key_id test
    aws configure set aws_secret_access_key test
    aws configure set region us-east-1
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: localstack
spec:
  selector:
    matchLabels:
      app: localstack
  template:
    metadata:
      labels:
        app: localstack
    spec:
      containers:
        - name: localstack
          image: localstack/localstack:latest
          imagePullPolicy: Always
          ports:
            - containerPort: 4566
          env:
            - name: SERVICES
              value: "sqs"
          volumeMounts:
            - name: localstack-init-volume
              mountPath: /docker-entrypoint-initaws.d
      volumes:
        - name: localstack-init-volume
          configMap:
            name: localstack-config
  serviceName: localstack-service
---
apiVersion: v1
kind: Service
metadata:
  name: localstack-service
  labels:
    app: localstack
spec:
  selector:
    app: localstack
  ports:
    - port: 4566
      protocol: TCP
```    
`kubectl apply -f kubernetes/localstack.yaml`

#### Client commands (e.g., AWS SDK code snippet, or sequence of "awslocal" commands)

    kubectl exec localstack-0 -- awslocal sqs create-queue --queue-name test-queue
    kubectl exec localstack-0 -- awslocal sqs send-message --queue-url "http://localhost:4566/000000000000/test-queue" --message-body "this is a test"
    kubectl exec localstack-0 -- awslocal sqs receive-message --queue-url "http://localhost:4566/000000000000/test-queue" --attribute-names All

I haven't tried this on docker-compose as I don't use that, but given the nature of this bug I'd suspect that the same issue would exist there regardless of how LocalStack is started.

### Environment

```markdown
- OS: macOS Monterey 12.4
- LocalStack: latest
```


### Anything else?

_No response_


Discussion:
Welcome to LocalStack! Thanks for reporting your first issue and our team will be working towards fixing the issue for you or reach out for more background information. We recommend joining our [Slack Community](https://localstack.cloud/contact/) for real-time help and drop a message to LocalStack Pro Support if you are a Pro user! If you are willing to contribute towards fixing this issue, please have a look at our [contributing guidelines](https://github.com/localstack/.github/blob/main/CONTRIBUTING.md) and our [developer guide](https://docs.localstack.cloud/developer-guide/).
This appears to originate from https://github.com/localstack/localstack/blob/master/localstack/services/sqs/provider.py#L424

The call to [`time.time()`](https://docs.python.org/3/library/time.html#time.time) returns fractional seconds since epoch, but [the API documentation for Message](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_Message.html) states that this should be integer milliseconds since epoch.

Resolving patch:
diff --git a/localstack/aws/forwarder.py b/localstack/aws/forwarder.py
--- a/localstack/aws/forwarder.py
+++ b/localstack/aws/forwarder.py
@@ -56,7 +56,9 @@ def _call(context, req) -> ServiceResponse:
             # implemented, we try to fall back to forwarding the request to the backend
             return handler(context, req)
         except NotImplementedError:
-            return fallthrough_handler(context, req)
+            pass
+
+        return fallthrough_handler(context, req)
 
     return _call
 
diff --git a/localstack/services/sqs/provider.py b/localstack/services/sqs/provider.py
--- a/localstack/services/sqs/provider.py
+++ b/localstack/services/sqs/provider.py
@@ -161,6 +161,8 @@ class SqsMessage:
     visibility_timeout: int
     receive_times: int
     receipt_handles: Set[str]
+    last_received: Optional[float]
+    first_received: Optional[float]
     deleted: bool
     priority: float
     message_deduplication_id: str
@@ -442,7 +444,7 @@ def get(self, block=True, timeout=None, visibility_timeout: int = None) -> SqsMe
             ] = str(standard_message.receive_times)
             copied_message.message["Attributes"][
                 MessageSystemAttributeName.ApproximateFirstReceiveTimestamp
-            ] = standard_message.first_received
+            ] = str(int(standard_message.first_received * 1000))
             copied_message.message["ReceiptHandle"] = receipt_handle
 
             return copied_message
@@ -1287,7 +1289,7 @@ def _create_message_attributes(
     ) -> Dict[MessageSystemAttributeName, str]:
         result: Dict[MessageSystemAttributeName, str] = {
             MessageSystemAttributeName.SenderId: context.account_id,  # not the account ID in AWS
-            MessageSystemAttributeName.SentTimestamp: str(now()),
+            MessageSystemAttributeName.SentTimestamp: str(now(millis=True)),
         }
 
         if message_system_attributes is not None:


Test patch:
diff --git a/tests/integration/test_sqs.py b/tests/integration/test_sqs.py
--- a/tests/integration/test_sqs.py
+++ b/tests/integration/test_sqs.py
@@ -174,6 +174,21 @@ def test_send_receive_message(self, sqs_client, sqs_queue):
         assert message["MessageId"] == send_result["MessageId"]
         assert message["MD5OfBody"] == send_result["MD5OfMessageBody"]
 
+    @pytest.mark.aws_validated
+    def test_receive_message_attributes_timestamp_types(self, sqs_client, sqs_queue):
+        sqs_client.send_message(QueueUrl=sqs_queue, MessageBody="message")
+
+        r0 = sqs_client.receive_message(
+            QueueUrl=sqs_queue, VisibilityTimeout=0, AttributeNames=["All"]
+        )
+        attrs = r0["Messages"][0]["Attributes"]
+        assert float(attrs["ApproximateFirstReceiveTimestamp"]).is_integer()
+        assert float(attrs["SentTimestamp"]).is_integer()
+
+        assert float(attrs["SentTimestamp"]) == pytest.approx(
+            float(attrs["ApproximateFirstReceiveTimestamp"]), 2
+        )
+
     def test_send_receive_message_multiple_queues(self, sqs_client, sqs_create_queue):
         queue0 = sqs_create_queue()
         queue1 = sqs_create_queue()