Repository: locustio/locust

Issue:
The time of the downloaded html report is not correct
### Prerequisites

- [x] I am using [the latest version of Locust](https://github.com/locustio/locust/releases/)
- [X] I am reporting a bug, not asking a question

### Description

After I finish running with locust, I need to download the html of the report for subsequent report comparisons, but I realized that the time of the downloaded report is not correct

* Server time
![image](https://github.com/locustio/locust/assets/167045125/40903298-d78c-4788-8bbf-4f9ae5a251e4)

* The timing of the charts is also correct
![image](https://github.com/locustio/locust/assets/167045125/449ecfbb-ab9d-45d4-b033-7df02e0d0d6e)

* The downloaded html reports the time incorrectly.
![image](https://github.com/locustio/locust/assets/167045125/55aaa993-802d-4267-bc31-f7292c4eeed0)



### Command line

locust -f 1_gloria_test.py 

### Locustfile contents

```python3
Nothing to do with content.
```


### Python version

3.11.0

### Locust version

2.26.0

### Operating system

amzn2023.x86_64


Discussion:
I'm the same, have you solved it now
@andrewbaldwin44 Can I bother you to have a look?
I'm the same
![image](https://github.com/locustio/locust/assets/28440805/f12ab2ed-499b-463e-9c8e-29f9d1eea72b)

Python version
3.9

Locust version
2.27.0

Operating system
Windows 10 x64



Resolving patch:
diff --git a/locust/html.py b/locust/html.py
--- a/locust/html.py
+++ b/locust/html.py
@@ -12,6 +12,7 @@
 from .runners import STATE_STOPPED, STATE_STOPPING, MasterRunner
 from .stats import sort_stats, update_stats_history
 from .user.inspectuser import get_ratio
+from .util.date import format_utc_timestamp
 
 PERCENTILES_FOR_HTML_REPORT = [0.50, 0.6, 0.7, 0.8, 0.9, 0.95, 0.99, 1.0]
 ROOT_PATH = os.path.dirname(os.path.abspath(__file__))
@@ -32,11 +33,10 @@ def get_html_report(
 ):
     stats = environment.runner.stats
 
-    start_ts = stats.start_time
-    start_time = datetime.datetime.fromtimestamp(start_ts, tz=datetime.timezone.utc).strftime("%Y-%m-%d %H:%M:%S")
+    start_time = format_utc_timestamp(stats.start_time)
 
     if end_ts := stats.last_request_timestamp:
-        end_time = datetime.datetime.fromtimestamp(end_ts, tz=datetime.timezone.utc).strftime("%Y-%m-%d %H:%M:%S")
+        end_time = format_utc_timestamp(end_ts)
     else:
         end_time = start_time
 
diff --git a/locust/util/date.py b/locust/util/date.py
new file mode 100644
--- /dev/null
+++ b/locust/util/date.py
@@ -0,0 +1,5 @@
+from datetime import datetime
+
+
+def format_utc_timestamp(unix_timestamp):
+    return datetime.utcfromtimestamp(unix_timestamp).strftime("%Y-%m-%dT%H:%M:%SZ")


Test patch:
diff --git a/locust/webui/src/pages/tests/HtmlReport.test.tsx b/locust/webui/src/pages/tests/HtmlReport.test.tsx
--- a/locust/webui/src/pages/tests/HtmlReport.test.tsx
+++ b/locust/webui/src/pages/tests/HtmlReport.test.tsx
@@ -3,6 +3,7 @@ import { describe, expect, test } from 'vitest';
 
 import HtmlReport from 'pages/HtmlReport';
 import { swarmReportMock } from 'test/mocks/swarmState.mock';
+import { formatLocalDate } from 'utils/date';
 
 describe('HtmlReport', () => {
   test('renders a report', () => {
@@ -15,10 +16,21 @@ describe('HtmlReport', () => {
     expect(getByRole('heading', { name: 'Final ratio' })).toBeTruthy();
     expect(getByRole('link', { name: 'Download the Report' }));
     expect(getByText(swarmReportMock.locustfile)).toBeTruthy();
-    expect(getByText(`${swarmReportMock.startTime} - ${swarmReportMock.endTime}`)).toBeTruthy();
     expect(getByText(swarmReportMock.host)).toBeTruthy();
   });
 
+  test('formats the start and end time as expected', () => {
+    const { getByText } = render(<HtmlReport {...swarmReportMock} />);
+
+    expect(
+      getByText(
+        `${formatLocalDate(swarmReportMock.startTime)} - ${formatLocalDate(
+          swarmReportMock.endTime,
+        )}`,
+      ),
+    ).toBeTruthy();
+  });
+
   test('does not render the download link when showDownloadLink is false', () => {
     const { queryByRole } = render(<HtmlReport {...swarmReportMock} showDownloadLink={false} />);