gns3-server/docs/gns3-copilot/troubleshooting/datetime-timezone-issue.md
YueGuobin 13a032ea2c chore: update author name and copyright headers
Updated the author name and copyright statements across the
gns3_copilot module. The name has been standardized from
"Guobin Yue" to "Yue Guobin (岳国宾)" to reflect the correct
author attribution including Chinese characters.
2026-03-09 11:46:28 +08:00

181 lines
4.7 KiB
Markdown

# Datetime Timezone Issue
## Problem Description
The API returns datetime strings without timezone suffixes, causing frontend JavaScript to parse them as local time instead of UTC time.
### Example
| Aspect | Value |
|--------|-------|
| Actual time (Beijing) | 2026-03-07 01:30 |
| Backend returns | `2026-03-06T16:31:36.547762` (no timezone) |
| Frontend displays | March 6, 16:31 |
| Should display | March 7, 00:31 (UTC 16:31 + 8 hours) |
### Root Cause
- Backend stores UTC time in database as naive datetime (no timezone info)
- FastAPI's `jsonable_encoder` serializes datetime as ISO 8601 **without timezone suffix**
- JavaScript `new Date()` treats strings without 'Z' or timezone offset as **local time**
```
Backend intention: 2026-03-06T16:31:36Z (UTC)
Actually returned: 2026-03-06T16:31:36.547762 (no timezone)
JavaScript parses as: local time 16:31
Should be: UTC 16:31 → Beijing time 00:31 (next day)
```
---
## Frontend Solutions
### Solution 1: Use dayjs UTC Parsing (Recommended)
```typescript
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';
dayjs.extend(utc);
dayjs.extend(timezone);
// Parse naive datetime as UTC
const createdAt = '2026-03-06T16:31:36.547762';
const date = dayjs.utc(createdAt).tz('Asia/Shanghai');
console.log(date.format('YYYY-MM-DD HH:mm')); // 2026-03-07 00:31
```
### Solution 2: Add 'Z' Suffix Manually
```typescript
// Utility function to normalize API dates
function parseAPIDate(dateStr: string): Date {
// Add 'Z' suffix if no timezone info present
const normalized = dateStr.includes('Z') || dateStr.includes('+')
? dateStr
: `${dateStr}Z`;
return new Date(normalized);
}
// Usage
const date = parseAPIDate('2026-03-06T16:31:36.547762');
console.log(date.toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' }));
```
### Solution 3: Global Axios Interceptor (Most Thorough)
```typescript
import axios from 'axios';
api.interceptors.response.use((response) => {
// Recursively normalize all date fields
function normalizeDates(obj: any): any {
// Match ISO 8601 datetime pattern without timezone
if (typeof obj === 'string' && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(obj)) {
return obj.includes('Z') || obj.includes('+') ? obj : `${obj}Z`;
}
if (Array.isArray(obj)) {
return obj.map(normalizeDates);
}
if (obj && typeof obj === 'object') {
return Object.fromEntries(
Object.entries(obj).map(([k, v]) => [k, normalizeDates(v)])
);
}
return obj;
}
response.data = normalizeDates(response.data);
return response;
});
```
---
## Backend Context
### Current Implementation
**File**: `gns3server/api/server.py:58-64`
```python
application = FastAPI(
title="GNS3 controller API",
description="This page describes the public controller API for GNS3",
version="v3",
docs_url=None,
redoc_url=None
)
```
**File**: `gns3server/db/models/base.py`
```python
from fastapi.encoders import jsonable_encoder
class Base:
def asjson(self):
return jsonable_encoder(self.asdict())
```
### Datetime Flow
1. Database stores naive datetime (no timezone)
2. FastAPI uses `jsonable_encoder` to serialize
3. Output format: `YYYY-MM-DDTHH:MM:SS.ffffff` (no 'Z' suffix)
4. JavaScript interprets as local time
### Affected Fields
- `created_at`
- `updated_at`
- `last_login`
- Any other datetime fields in API responses
---
## Testing Checklist
- [ ] Verify datetime displays correctly in Beijing timezone (UTC+8)
- [ ] Test with other timezones (e.g., UTC-5, UTC+0)
- [ ] Check daylight saving time transitions (if applicable)
- [ ] Verify datetime input/insertion still works correctly
---
## Related Files
- Backend: `gns3server/api/server.py`
- Backend: `gns3server/db/models/base.py`
- Schemas: `gns3server/schemas/controller/base.py`
---
## License
**Copyright © 2025 Yue Guobin (岳国宾)**
This work is licensed under the [Creative Commons Attribution-ShareAlike 4.0
International License (CC BY-SA 4.0)](https://creativecommons.org/licenses/by-sa/4.0/).
![CC BY-SA 4.0](https://i.creativecommons.org/l/by-sa/4.0/88x31.png)
### Summary
You are free to:
- **Share** — Copy and redistribute the material in any medium or format
- **Adapt** — Remix, transform, and build upon the material for any purpose
Under the following terms:
- **Attribution** — You must give appropriate credit to **Yue Guobin (岳国宾)**, provide
a link to the license, and indicate if changes were made.
- **ShareAlike** — If you remix, transform, or build upon the material, you must
distribute your contributions under the **same license** (CC BY-SA 4.0).
Full license text: [DESIGN_DOCS_LICENSE](../DESIGN_DOCS_LICENSE.md)