Запись времени в базу данных с помощью model.DateTimeField() в Python Django, часовой пояс UTC отображается в базе данных некорректно

settings.py

TIME_ZONE = "Asia/Taipei"

USE_I18N = True

USE_TZ = True

models.py

from django.db import models

class ApiLog(models.Model):
    endpoint = models.CharField(max_length=255)
    method = models.CharField(max_length=10)
    request_body = models.TextField()
    response_body = models.TextField()
    timestamp = models.DateTimeField()

    class Meta:
        db_table = 'api_log'
        managed = False  # 若資料表為手動建立,將 managed 設為 False



    def __str__(self):
        return f"{self.method} {self.endpoint} at {self.timestamp}"

views.py

def simple_api(request):
    endpoint = request.path
    method = request.method
    request_body = request.body.decode('utf-8') if request.body else ""

    if method == 'GET':
        response_data = {'message': 'This is a GET request'}
    elif method == 'POST':
        try:
            data = json.loads(request.body)
            response_data = {'message': 'Data received', 'data': data}
        except json.JSONDecodeError:
            response_data = {'error': 'Invalid JSON format'}
    else:
        response_data = {'error': 'Method not allowed'}

    response_body = json.dumps(response_data)

    # def UTC+8 
    tz = pytz.timezone('Asia/Taipei')
    # get utc+8 time
    current_utc_plus_8_time = timezone.now().astimezone(tz)

    print("Current UTC+8 Time:", current_utc_plus_8_time)

    # record ApiLog
    ApiLog.objects.create(
        endpoint=endpoint,
        method=method,
        request_body=request_body,
        response_body=response_body,
        timestamp=current_utc_plus_8_time
    )

    return JsonResponse(response_data)

Исходя из этой схемы, я ожидаю, что записанная временная метка в базе данных будет иметь формат «yyyy-MM-DD HH:mm:ss.ssssss+08:00.»

Однако при прямом запросе с помощью оператора SELECT в базе данных результат преобразуется в UTC+0. Я пробовал несколько методов решения этой проблемы.

Например:

  1. На Java, используя mybatis

mapper

@Mapper
public interface ApiLogMapper {

  @Insert("INSERT INTO api_log (endpoint, method, request_body, response_body, timestamp) " +
      "VALUES (#{endpoint}, #{method}, #{requestBody}, #{responseBody}, #{timestamp})")
  @Options(useGeneratedKeys = true, keyProperty = "id")
  void insertApiLog(ApiLog apiLog);
}

контроллер

  @PostMapping("/message")
  public ResponseEntity<String> handleMessage(@RequestBody String message) {
    // def UTC+8  OffsetDateTime
    OffsetDateTime dateTimeUTC8 = OffsetDateTime.now(ZoneOffset.ofHours(8));
    
    String response = "message";  
    // record ApiLog 
    ApiLog log = new ApiLog();
    log.setEndpoint("/api/chatbox/message");
    log.setMethod("POST");
    log.setRequestBody(message);
    log.setResponseBody(response);
    log.setTimestamp(dateTimeUTC8);
    System.out.println(log.getTimestamp());
    apiLogMapper.insertApiLog(log);

    return ResponseEntity.ok(response);
  }

этот метод корректно отображает результаты UTC+8 в базе данных.

  1. В Python использование SQL-скрипта для вставки также отображает результаты в UTC+8.

def log_api_call(endpoint: str, method: str, request_body: str,
    response_body: str):
  connection = get_connection()
  cursor = connection.cursor()
  # def utc+8 timezone
  timezone_utc8 = pytz.timezone('Asia/Taipei')
  utc8_time = datetime.now(timezone_utc8)

  print(utc8_time)


  query = """
    INSERT INTO api_log (endpoint, method, request_body, response_body, timestamp)
    VALUES (?, ?, ?, ?, ? AT TIME ZONE 'Taipei Standard Time' )
    """
  cursor.execute(query, (
  endpoint, method, request_body, response_body, utc8_time))
  connection.commit()
  cursor.close()
  connection.close()


@app.post("/example-endpoint")
async def example_endpoint(request: Request):
  request_body = await request.json()

  response_body = {"message": "This is a response"}

  response_body_str = json.dumps(response_body)

  # record APILog
  log_api_call(
      endpoint="/example-endpoint",
      method="POST",
      request_body=json.dumps(request_body),
      response_body=response_body_str
  )

  return response_body

дополнительная информация: mcr.microsoft.com/mssql/server:2022-lates TZ=Asia/Taipei

SELECT SYSDATETIMEOFFSET() AS CurrentDateTimeWithOffset;

CurrentDateTimeWithOffset    |
-----------------------------+
2024-11-14 10:25:44.588 +0800|

SELECT CURRENT_TIMEZONE() AS TimeZone;

TimeZone          |
------------------+
(UTC+08:00) Taipei|

table

create table api_log
(
    id            bigint identity
        primary key,
    endpoint      nvarchar(255),
    method        nvarchar(10),
    request_body  nvarchar(max),
    response_body nvarchar(max),
    timestamp     datetimeoffset
)
go

Есть ли способ добиться этого во фреймворке Django?

Как описано в вопросе, я ожидаю, что время, отображаемое в базе данных, будет соответствовать часовому поясу UTC, в котором оно было записано.

если время AP UTC+8 равно 2024-11-14 09:53:31.916319**+08:00**

, тогда временная метка Select DB должна быть 2024-11-14 09:53:31.916319**+08:00**

Когда вы храните дату/время в Django, она всегда преобразуется в время utc+0.

Это кажется намеренным и может привести к преобразованию времени из часового пояса Django во время utc, установленное в настройках.

from django.utils import timezone

timezone.now() # utc +0 time
timezone.localtime(timezone.now()) # utc +{timzeon in settings} time
Вернуться на верх