【问题标题】:How should I store Go's time.Location in Postgres?我应该如何在 Postgres 中存储 Go 的 time.Location?
【发布时间】:2019-09-21 19:16:40
【问题描述】:

在 Postgres 中,我存储用户提供给我的数据:

   Column   |           Type           | Collation | Nullable | Default
------------+--------------------------+-----------+----------+---------
 id         | uuid                     |           | not null |
 value      | numeric                  |           |          |
 date       | timestamp with time zone |           |          |

现在我提出了维护生成数据的原始时区的要求。 timestamp with timezone 被规范化为数据库的时区,原始时区丢失了,所以我必须手动将 date 从规范化的时区恢复回来,然后再将其返回给用户。

大多数解决方案建议在表中添加一个额外的列,并将原始时区信息与时间戳一起存储:

   Column   |           Type           | Collation | Nullable | Default
------------+--------------------------+-----------+----------+---------
 id         | uuid                     |           | not null |
 value      | numeric                  |           |          |
 date       | timestamp with time zone |           |          |
 tz         | text                     |           |          |

因此,鉴于我正在使用 Go,我应该从 time.Time 中提取哪些信息以存储在 tz 中以进行最精确和无缝的恢复?

date.Location().String() 似乎不正确,因为它可能返回相对的值 Local

我应该如何将tz的信息恢复到time.Time

time.LoadLocation(tz) 的结果够好吗?

【问题讨论】:

  • github.com/lib/pq 驱动程序声明它“正确扫描time.Time(即timestamp[tz]time[tz]date)”。因此,如果在数据库中有 timestamp with time zone 类型的列,这应该会自动发生,不是吗?
  • 是的,我也应该提到这一点 - timestamp with time zone 将采用时区时间戳,将其更正为数据库时区,然后存储。它基本上只会让您在保存之前避免标准化到数据库时区。它实际上并没有保留原始时区数据,因此当您查询回信息时,它仍会出现在 DBs 时区中,除非查询指定不同的时区
  • 是否需要对恢复的时间戳应用操作?例如添加天数或计算自此经过的时间?
  • 是的,但仅限于彼此相关的批次内。我可以放心地假设,在对时间戳执行操作(例如持续时间)时,所有相关时间戳都在同一个时区。此数据的其他批次可能位于不同的时区

标签: postgresql datetime go time timezone


【解决方案1】:

保存后,我将使用Time.Zone() 获取区域名称和偏移量:

func (t Time) Zone() (name string, offset int)

那么在从数据库中查询这样一个时间戳的时候,可以使用time.FixedZone()构造一个time.Location

func FixedZone(name string, offset int) *Location

然后使用Time.In()切换到这个位置。

请注意!这将为您恢复一个“看似”在同一时区的时间戳,但如果您需要对其应用操作(例如添加天数),结果可能不一样。这样做的原因是因为time.FixedZone() 返回一个具有固定偏移量的时区,例如,它对daylight savings 一无所知,而您保存的原始时间戳可能有一个time.Location,它确实知道这些事情。

以下是此类偏差的示例。 3 月有夏令时,所以我们将使用指向 3 月 1 日的时间戳,并在其上加上 1 个月,这导致时间戳在夏令时之后。

cet, err := time.LoadLocation("CET")
if err != nil {
    panic(err)
}

t11 := time.Date(2019, time.March, 1, 12, 0, 0, 0, cet)
t12 := t11.AddDate(0, 1, 0)
fmt.Println(t11, t12)

name, offset := t11.Zone()
cet2 := time.FixedZone(name, offset)
t21 := t11.UTC().In(cet2)
t22 := t21.AddDate(0, 1, 0)
fmt.Println(t21, t22)

now := time.Date(2019, time.April, 2, 0, 0, 0, 0, time.UTC)
fmt.Println("Time since t11:", now.Sub(t11))
fmt.Println("Time since t21:", now.Sub(t21))
fmt.Println("Time since t12:", now.Sub(t12))
fmt.Println("Time since t22:", now.Sub(t22))

这将输出(在Go Playground 上尝试):

2019-03-01 12:00:00 +0100 CET 2019-04-01 12:00:00 +0200 CEST
2019-03-01 12:00:00 +0100 CET 2019-04-01 12:00:00 +0100 CET
Time since t11: 757h0m0s
Time since t21: 757h0m0s
Time since t12: 14h0m0s
Time since t22: 13h0m0s

如您所见,1个月相加后的输出时间是一样的,只是区域偏移量不同,所以他们指定了不同的时间瞬间(通过显示与任意时间的时间差来证明) )。原始有 2 小时的偏移量,因为它知道我们跳过的 1 个月内发生的夏令时,而“恢复”时间戳的区域不知道这一点,因此结果具有相同的 1 小时偏移量。在添加后的时间戳中,即使是现实生活中的区域名称也发生了变化:从CET变为CEST,同样,恢复的时间戳的区域也不知道这一点。

【讨论】:

  • 这是一个合理的风险
  • @noamt 还添加了一个示例来演示“风险”。
【解决方案2】:

一个更浪费、仍然容易出错但仍然有效的解决方案是将原始时间戳以 ISO 8601 格式存储,例如 2019-05-2T17:24:37+01:00 在单独的列 datetext 中:

   Column   |           Type           | Collation | Nullable | Default
------------+--------------------------+-----------+----------+---------
 id         | uuid                     |           | not null |
 value      | numeric                  |           |          |
 date       | timestamp with time zone |           |          |
 datetext   | text                     |           |          |

然后使用date查询本地时间戳列的强度,并返回给用户datetext,这是最初发送的确切值。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-02-26
    • 2010-09-22
    • 2010-09-29
    • 1970-01-01
    • 2023-03-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多