时间戳

时间戳是一个表示特定时间点的数值,通常以数字形式存储。

基本概念

  • 定义:时间戳是一种在计算机系统中表示特定时间点的标准格式。通常是一个整数,表示自某个基准时间(通常为 Unix 元年,即 1970 年 1 月 1 日 00:00:00 UTC)以来经过的秒数或毫秒数。这个时间表示方法也被称为 UNIX 时间戳。
  • 标准:在 Unix 和类 Unix 系统中,时间戳一般以 UTC 时间来计算,即不考虑时区的偏移。
  • 单位:时间戳的单位通常是秒(常用于一般应用场景)或者毫秒(在需要高精度的应用,如金融交易、实时数据监控等场景中使用),但也可以是更高精度的微妙或者纳秒(一些高频交易系统可能需要这种精度)。

表现形式

  • 整数形式:如 Unix 时间戳,以自 1970 年以来的秒数表示。例如,1685112000 表示 2023 年 5 月 27 日的一个特定时刻。
  • 格式化字符串:时间戳也可以以特定格式的字符串表示,例如 ISO 8601 格式(如 “2023-10-05T12:00:00Z”)。

精度

  • 秒级:以秒为单位,例如 1609459200 代表 UTC 时间 2021-01-01 00:00:00。
  • 毫秒级:以毫秒为单位,例如 1609459200000 代表 UTC 时间 2021-01-01 00:00:00.000。
  • 微秒/纳秒级:提供更高精度的时间记录,但一般只有在需要高精度时间戳的场景中才会使用。

特点

  • 时区无关性:时间戳通常使用 UTC 时间来计算,即时间戳的数值本身与时区无关(“时区归零”),这样避免了因时区变化(如夏令时)导致的混淆。应用在不同地区时,通常依据用户所在的时区转换成本地时间再使用。
  • 唯一性:时间戳能够唯一地标识一个特定的时间点,适合于记录时间顺序的数据。
  • 跨平台:时间戳的格式统一标准,在不同系统和编程语言中广泛支持。
  • 便于计算:时间戳以整数表示,便于进行时间差的计算。

应用场景

时间戳是一种简洁而有效的方式,用于表示和管理时间数据。它在计算机系统和各种应用中广泛使用,使得时间的存储、比较和计算变得更加高效和一致。

  • 数据库:在数据库操作中,时间戳用于记录数据创建和修改的时间。
  • 日志系统:在各种系统日志中,时间戳记录日志条目的时间。
  • API:在数据交换中传递时间信息。
  • 缓存控制:用于缓存机制中,确定数据的有效期和刷新时间。
  • 时间序列数据:用于数据分析和预测模型。
  • 调试和监控:用于监控和调试系统的性能,记录事件发生的时间顺序。

基本操作(Python)

获取

import time

# 获取当前时间戳(秒)
current_timestamp = int(time.time())
print(current_timestamp)

# 获取当前时间戳(毫秒)
current_timestamp_ms = int(time.time() * 1000)
print(current_timestamp_ms)

# 转换时间戳为可读格式
readable_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(current_timestamp))
print(readable_time)

转换

  • 时间戳转日期时间:
import datetime

timestamp = 1631613830  # 示例时间戳
dt = datetime.datetime.fromtimestamp(timestamp)
print(dt)  # 输出类似:2021-09-14 00:03:50
  • 日期时间转时间戳:
import datetime

dt = datetime.datetime(2021, 9, 14, 0, 3, 50)
timestamp = int(dt.timestamp())
print(timestamp)  # 输出:1631613830

场景分析

ORM(SQLAlchemy)时间字段赋值

假设使用 SQLAlchemy 定义了一个对象,其中包含时间字段如下,那么在为该字段赋值时,是应该用 datetime.datetime.now(datetime.timezone.utc),还是直接用 datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None) 进行字段赋值?

...
updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)'))
...

先说结论,在细说分析过程:

updated_at = datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None)

首先我们理解一下 datetime.datetime.now(datetime.timezone.utc)datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None) 的区别:

  1. datetime.datetime.now(datetime.timezone.utc):获取当前的 UTC 时间。
  2. ....replace(tzinfo=None):将获取到的时间对象的时区信息移除,使其成为一个没有时区的本地 datetime 对象。

CURRENT_TIMESTAMP(0) 是 SQL 中的一个函数,表示当前的日期和时间。括号中的 0 表示“精确度”,即小数点后保留的秒数位数。在这里,(0) 表示不保留小数部分的秒,即时间精确到秒。具体来说:

  • CURRENT_TIMESTAMP:返回当前的日期和时间。
  • (0):不保留秒的小数部分(比如 2023-10-05 12:34:56.123456 会变为 2023-10-05 12:34:56)。

因此,CURRENT_TIMESTAMP(0) 会生成一个只精确到秒的当前时间戳。


在 SQLAlchemy 的字段定义中,尤其是涉及到 db.ColumnDateTime 类型时,通常我们希望在数据库中保存一个标准时间格式。由于已经在数据库层面使用了 server_default=db.text('CURRENT_TIMESTAMP(0)'),这意味着数据库会自动给该字段赋值,即在一些未手动设置字段值的情况下,数据库会自动使用 UTC 时间计算时间戳并给该字段赋值。

“数据库会自动处理时间戳”意味着数据库在执行插入或更新操作时,会根据设定的默认值或触发器自动填充某个字段的时间戳,而无需在应用程序中手动提供。具体来说:

  • 自动填充:在数据库中创建表时,可以指定某个字段(如 updated_at)使用数据库函数(如 CURRENT_TIMESTAMP)作为默认值。这样,当插入新记录或更新已有记录时,该字段将自动设置为当前时间。
  • 一致性:使用数据库提供的函数可以确保时间戳的一致性,因为它始终由数据库直接生成,避免了在不同应用程序或环境中 timestamp 的时间差异。
  • 避免应用程序错误:这样可以减少手动设置时间戳的需要,避免由于应用程序逻辑错误而导致的时间戳不正确。

总之,通过使用 server_default=db.text('CURRENT_TIMESTAMP(0)'),数据库在特定事件(如插入或更新)发生时自动处理时间戳,而无需在代码中手动设置。


在程序中操作这个字段(赋值)时,建议使用 datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None),这样可以确保时间是一个不带时区的本地时间对象。保持了时间的一致性和可预见性:

  • 避免时区问题:直接使用 datetime.datetime.now(datetime.timezone.utc) 返回的对象是一个带有时区信息的 datetime 对象。如果将该对象插入到不带时区的数据库字段中,可能会导致数据不一致或错误。
  • 与数据库默认值一致性:在数据库中 CURRENT_TIMESTAMP(0) 是不带时区的。因此,使用不带时区的信息可以确保在应用层和数据库层保存的时间戳不产生混淆。
  • 清晰表达意图:通过将时区信息移除,代码中明确表明这是一个“本地”时间,而不是一个带有时区的时间。这对于维护代码和理解数据格式非常重要。

虽然也可以选择直接使用带时区的 datetime 对象,但在已有数据库自动处理时间戳的情况下,使用无时区的 datetime 更加安全和一致。在处理更新时,虽然数据库会自动生成时间戳,确保在代码中也使用一致的时间表示方式是个好的实践。

最佳实践

在处理时间类型字段时,遵循最佳实践可以确保数据一致性和减少潜在的时区问题。以下是对数据库服务端ORM 层应用层在时间类型字段处理上的综合分析和建议。

数据库服务端

默认值设置

  • 使用 CURRENT_TIMESTAMP 或类似函数设置字段的默认值,以自动填充插入或更新数据的时间戳。
  • 选择合适的精确度,如 CURRENT_TIMESTAMP(0),确保只保留到秒,不保留小数部分以避免不必要的复杂性。

时区管理

  • 尽量将所有时间字段设置为 UTC。大多数现代数据库支持时间戳与时区(如 TIMESTAMP WITH TIME ZONE),这有助于保持一致性,避免在不同地区和时区之间转换时的不便。
  • 进行时间存储时,使用 UTC,可以简化处理,因为 UTC 是一种标准时间,不受夏令时或地区性变化的影响。

ORM 层(如 SQLAlchemy)

字段定义

  • 对于带时区的字段,使用合适的类型(如 db.TIMESTAMP(timezone=True)),以确保 ORM 能够正确处理时区信息。
  • 在 ORM 模型中,字段通常使用 db.DateTime 类型。应当确保未明确带有时区信息的字段使用不带时区的 datetime 对象。

处理时间戳

  • 对于自动填充字段,使用 server_default=db.text('CURRENT_TIMESTAMP(0)') 设置数据库层的默认值。
  • 在代码中插入或更新操作时,使用与之匹配的时间对象,如db.DateTime 类型字段在赋值时最好采用 ...replace(tzinfo=None)db.TIMESTAMP(timezone=True)类型字段在赋值时则需要带有时区,以确保时间对象与数据库中的时间戳类型一致。

应用层

时间处理

  • 在应用层进行日期和时间的处理时,始终使用 UTC。初始化时间数据时,确保使用 datetime 库的 UTC 信息。
  • 在用户界面上提供时间时,考虑用户所在的时区,并在呈现时进行转换。可以使用类似 pytz 的库来帮助处理这种转换。

一致性和规范

  • 确保文档清晰说明时间存储和处理的方式,包括使用的时间格式和时区。
  • 在团队内部遵循一致的编码风格,确保所有开发人员在处理时间类型时遵循相同的最佳实践。

结语

时间戳是计算机系统中处理、记录和计算时间的重要工具,广泛应用于各种编程语言和平台中。

在整体工程上对时间戳的处理往往需要综合考虑“数据库服务端”、“ORM 层”和“应用层”的最佳实践,确保在数据库中存储时间以 UTC 格式,并在 ORM 和应用程序层面一致地使用时间。在展示给用户时,适当转换为用户所在的时区,从而最大程度地减少出错机会,确保数据的正确性和用户体验。


Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐