第8节-PostgreSQL数据类型-Timestamp-with-a-time-zone

摘要:在本教程中,你将学习如何使用 PostgreSQLTIMESTAMP WITH TIME ZONE 数据类型来处理带时区的时间戳值。

PostgreSQL 的带时区时间戳(TIMESTAMP WITH TIME ZONE)数据类型入门

PostgreSQL 中,您可以使用 TIMESTAMP WITH TIME ZONE 数据类型来存储具有时区信息的时间戳值。

以下是定义类型为 TIMESTAMP WITH TIME ZONE 的列的语法:

CREATE TABLE table_name(
    column_name TIMESTAMP WITH TIME ZONE,
    ...
);

TIMESTAMPTZTIMESTAMP WITH TIME ZONE 的缩写,因此您可以用它来减少一些输入量:

CREATE TABLE table_name(
    column_name TIMESTAMPTZ,
    ...
);

当你向 TIMESTAMPTZ 列中插入时间戳时,PostgreSQL 会将其转换为 UTC 时间进行存储。这种转换确保了存储的时间戳具有一致性,且不受时区差异的影响。

当你从 TIMESTAMPTZ 列中检索时间戳时,PostgreSQL 会将存储的 UTC 时间戳转换回你会话的时区。这种转换意味着你得到的时间会根据数据库会话设置的时区进行调整。

需要注意的是,PostgreSQL 不会在 TIMESTAMPTZ 列中存储时区,而是在存储和检索时进行时区转换。

您可以使用 SET TIME ZONE 语句来设置数据库会话的时区。例如:

SET TIME ZONE 'America/New_York';

TIMESTAMPTZ 能够自动处理夏令时变更和其他与时区相关的调整。

实际上,在处理跨时区时间的应用程序中,你会经常使用 TIMESTAMP WITH TIME ZONE 数据类型,例如日程安排应用、国际交易和事件日志记录。

获取带时区的当前时间戳

CURRENT_TIMESTAMP 函数返回带时区的当前时间。

SELECT CURRENT_TIMESTAMP;

输出:

       current_timestamp
-------------------------------
 2024-12-08 02:42:59.773965-08

它显示的是 US/Pacific 时区的当前时间。

要将数据库会话的时区设置为另一个时区,请使用 SET TIME ZONE 语句:

SET TIME ZONE 'America/New_York';

如果你再次获取当前时间戳,你会看到时间已调整为 America/New_York 时区:

SELECT CURRENT_TIMESTAMP;

输出:

       current_timestamp
-------------------------------
 2024-12-08 05:43:11.587639-05

要获取受支持的时区名称列表,请使用以下语句:

SELECT
  name,
  abbrev,
  utc_offset,
  is_dst
FROM
  pg_timezone_names;

TIMESTAMPTZ 列设置默认值

要为 TIMESTAMPTZ 列设置默认值,您可以使用 DEFAULT 约束:

CREATE TABLE table_name(
    column_name TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);

在这个示例中,如果您插入或更新 TIMESTAMPTZ 列时未提供值,PostgreSQL 会使用 CURRENT_TIMESTAMP 函数返回的当前时间戳来进行插入或更新操作。

PostgreSQL TIMESTAMPTZ 数据类型示例

首先,创建一个名为 inventory_scans 的表,用于跟踪库存中已扫描的产品:

CREATE TABLE inventory_scans (
  scan_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  warehouse_id INT NOT NULL,
  product_id INT NOT NULL,
  serial_no VARCHAR(25) NOT NULL,
  quantity INT NOT NULL,
  scanned_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);

其次,向 inventory_scans 表中插入一些行:

INSERT INTO
  inventory_scans (warehouse_id, product_id, serial_no, quantity)
VALUES
  (1, 1, 'F2LZK3H8N72Q', 1),
  (2, 1, 'R58N40Y1A2B3', 1) 
RETURNING *;

输出:

 scan_id | warehouse_id | product_id |  serial_no   | quantity |          scanned_at
---------+--------------+------------+--------------+----------+-------------------------------
       1 |            1 |          1 | F2LZK3H8N72Q |        1 | 2024-12-08 05:44:11.998853-05
       2 |            2 |          1 | R58N40Y1A2B3 |        1 | 2024-12-08 05:44:11.998853-05

总结

  • 使用 PostgreSQLTIMESTAMP WITH TIME ZONE 数据类型(或 TIMESTAMPTZ )来处理带时区的时间戳值。
  • 使用 CURRENT_TIMESTAMP 获取带时区的当前时间戳。
<think> 我们面对的问题:使用SQLAlchemy ORM插入数据时,如何为TIMESTAMP类型设置时区。 根据引用,我们知道: 1. SQLAlchemy是一个ORM库,允许我们使用Python类表示数据库表。 2. 我们使用asyncpg作为PostgreSQL的异步驱动。 3. 目标是在插入TIMESTAMP类型数据时正确设置时区。 在PostgreSQL中,TIMESTAMP WITH TIME ZONE(简称TIMESTAMPTZ)类型会存储带时区的时间,并且在存储时会被转换为UTC时间。而TIMESTAMP WITHOUT TIME ZONE(简称TIMESTAMP)则不带时区信息。 在SQLAlchemy中,我们可以使用`DateTime`类型来映射数据库TIMESTAMP。但为了支持时区,我们需要指定`timezone=True`,这样会映射到TIMESTAMPTZ。 步骤: 1. 在定义模型时,对于需要时区的字段,使用`Column(DateTime(timezone=True))`。 2. 当我们插入数据时,应该传入一个时区感知的datetime对象(即带有tzinfo的datetime对象)。Python的datetime模块提供了时区支持,可以使用`datetime.now(timezone.utc)`或使用第三方库如`pytz`来创建带时区的datetime。 注意:如果传入的是不带时区的datetime对象,SQLAlchemy会假设它是数据库的默认时区(通常为服务器的时区)下的时间,然后将其转换为UTC存储(对于TIMESTAMPTZ类型)。为了避免混淆,建议始终使用时区感知的datetime。 示例代码: 假设我们有一个名为`Event`的模型,其中有一个带时区的时间戳字段`event_time`。 首先,导入必要的模块: ```python from datetime import datetime, timezone from sqlalchemy import Column, Integer, DateTime from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() class Event(Base): __tablename__ = 'events' id = Column(Integer, primary_key=True) event_time = Column(DateTime(timezone=True)) ``` 然后,在插入数据时,创建一个带有时区信息的datetime对象: ```python # 创建UTC时区的当前时间 utc_now = datetime.now(timezone.utc) # 或者指定其他时区,例如: # from datetime import timedelta # beijing_time = datetime.now(timezone(timedelta(hours=8))) # UTC+8 # 创建事件对象 event = Event(event_time=utc_now) ``` 如果我们使用asyncpg驱动,并且使用SQLAlchemy的异步API(如1.4版本以上),那么我们需要一个异步会话。但无论同步还是异步,处理时区的方式是一样的。 关于数据库连接配置: 在创建引擎时,确保连接字符串正确指定了驱动(asyncpg)和数据库类型(postgresql+asyncpg)。 例如: ```python from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession from sqlalchemy.orm import sessionmaker DATABASE_URL = "postgresql+asyncpg://user:password@localhost/dbname" engine = create_async_engine(DATABASE_URL, echo=True) AsyncSessionLocal = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) ``` 然后使用异步会话来添加和提交: ```python async with AsyncSessionLocal() as session: session.add(event) await session.commit() ``` 这样,event_time字段就会被正确地存储为带有时区的时间戳(TIMESTAMPTZ)。 总结: 关键点在于: - 在模型定义中使用`DateTime(timezone=True)`。 - 插入时传入时区感知的datetime对象。 这样,PostgreSQL会将其转换为UTC时间存储,并在查询时根据当前会话的时区设置返回相应时区的时间(如果需要,也可以指定时区)。 注意:在查询时,SQLAlchemy返回的也是时区感知的datetime对象(UTC时区)。如果需要转换为其他时区,可以在Python中进行转换。 参考引用[^2]中提到了scoped_session和线程局部存储,但我们在异步环境中通常使用异步会话,并且每个会话通常在一个异步上下文中使用,不会跨线程(但会跨协程)。因此,在异步环境中,我们使用AsyncSession并注意其生命周期。 另外,引用[^3]展示了asyncpg的原始用法,但我们的问题是通过ORM,所以不需要直接使用asyncpg的API。 引用[^4]提到了PostgreSQL和SQLAlchemy的高效配置,但时区问题更多是模型定义和数据类型使用的问题。 因此,按照上述方法即可设置时区。 </think> 在 SQLAlchemy ORM 中使用 PostgreSQL 的 asyncpg 驱动为 `TIMESTAMP` 类型设置时区时,关键是通过 **时区感知的 datetime 对象** 配合正确的列类型定义实现。以下是具体方法: --- ### 解决方案 #### 1. **模型定义阶段:声明带时区的列** 使用 `DateTime(timezone=True)` 定义列,这会映射到 PostgreSQL 的 `TIMESTAMP WITH TIME ZONE`(TIMESTAMPTZ)类型: ```python from sqlalchemy import Column, DateTime from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() class Event(Base): __tablename__ = "events" id = Column(Integer, primary_key=True) # 关键:设置 timezone=True created_at = Column(DateTime(timezone=True)) ``` #### 2. **插入数据阶段:传入时区感知对象** 插入数据时,使用 `datetime.datetime` 并指定时区(如 `timezone.utc`): ```python from datetime import datetime, timezone async def insert_data(): async with AsyncSession(engine) as session: # 创建带时区的 datetime 对象(UTC 示例) utc_timestamp = datetime.now(timezone.utc) new_event = Event(created_at=utc_timestamp) session.add(new_event) await session.commit() ``` #### 3. **驱动配置:确保连接支持时区** 在创建异步引擎时,需指定 `postgresql+asyncpg` 驱动: ```python from sqlalchemy.ext.asyncio import create_async_engine DATABASE_URL = "postgresql+asyncpg://user:password@localhost/db" engine = create_async_engine(DATABASE_URL) ``` --- ### 关键说明 1. **时区处理逻辑**: - PostgreSQL 收到带时区的 `datetime` 对象后,自动将其存储为 **UTC 时间**。 - 查询时,数据库按需转换为目标时区(通过 `SET TIME ZONE 'Asia/Shanghai';` 等会话级设置控制)。 2. **TIMESTAMP 类型选择**: | 列定义 | PostgreSQL 类型 | 行为 | |-------------------------------|-------------------------|--------------------------| | `DateTime(timezone=True)` | `TIMESTAMP WITH TIME ZONE` | 自动转换时区并存储为 UTC | | `DateTime(timezone=False)` | `TIMESTAMP` | 原始值直接存储(无视时区)| 3. **时区对象要求**: - 必须使用 `datetime` 的 `tzinfo` 属性指定时区(如 `timezone.utc`、`pytz.timezone('Asia/Shanghai')`)。 - 不支持原生时区字符串(如 `"2024-01-01 12:00:00+08:00"`),需转换为 datetime 对象。 --- ### 完整示例 ```python # 模型定义 class LogRecord(Base): __tablename__ = "logs" id = Column(Integer, primary_key=True) event_time = Column(DateTime(timezone=True)) # 带时区的 TIMESTAMP # 插入数据 from datetime import datetime, timezone import pytz async def add_log(): async with AsyncSession(engine) as session: # 创建时区感知时间戳(UTC 或具体时区) utc_time = datetime.now(timezone.utc) shanghai_time = datetime.now(pytz.timezone("Asia/Shanghai")) record = LogRecord(event_time=shanghai_time) # 自动转换为 UTC 存储 session.add(record) await session.commit() ``` --- ### 常见问题 1. **查询时如何显示特定时区?** 在 SQL 中运行时区转换: ```sql SELECT event_time AT TIME ZONE 'Asia/Shanghai' FROM logs; ``` 2. **数据库存储的时间格式是什么?** 所有带时区的时间戳最终统一存储为 **UTC 时间**,与时区输入无关。 3. **能否在列定义中指定默认时区?** SQLAlchemy 不直接支持,需通过传入带时区的 `datetime` 对象或数据库端的默认值(如 `DEFAULT CURRENT_TIMESTAMP`)实现。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

盒马coding

你的支持是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值