Database PostgreSQL应用程序中的不同时区
我们有一个服务于许多配送中心的系统。配送中心是全国任何地方的物理空间。一个客户可能有多个配送中心。现在我们正在扩展到更多的地方,我们将不得不面对不同时区的问题。同一客户机也可能在不同时区拥有中心 在客户中心使用我们的系统可以创建和保存很多事件(包括日期和时间)。同一客户端中不同时区的理想行为如下: 考虑一个发生在时区A中午的事件。如果时区B中另一个配送中心的主管去检查该事件,他应该看到该事件的日期和时间与最初创建该事件的时区有关(包括DST更改,如果存在)。 这是因为需要知道的是事件是否在事件时区的中午创建。对于主管来说,事件创建时间是否为其时区的下午2点并不重要 我们使用PostgreSQL作为数据库,我发现存在两种不同的类型来保存时间戳<代码>时间戳和Database PostgreSQL应用程序中的不同时区,database,postgresql,Database,Postgresql,我们有一个服务于许多配送中心的系统。配送中心是全国任何地方的物理空间。一个客户可能有多个配送中心。现在我们正在扩展到更多的地方,我们将不得不面对不同时区的问题。同一客户机也可能在不同时区拥有中心 在客户中心使用我们的系统可以创建和保存很多事件(包括日期和时间)。同一客户端中不同时区的理想行为如下: 考虑一个发生在时区A中午的事件。如果时区B中另一个配送中心的主管去检查该事件,他应该看到该事件的日期和时间与最初创建该事件的时区有关(包括DST更改,如果存在)。 这是因为需要知道的是事件是否在事件时
时间戳
。我们所有的数据库只使用类型时间戳
另一种可能(或不可能)发生的情况是配送中心在地理位置上发生变化。这可能会影响其时区的变化
我做了一些研究,发现更“正确”的方法(至少看起来是这样)是在配送中心
表中保存每个中心的时区。将数据库中的所有类型从TIMESTAMP
更改为TIMESTAMP,在每次插入保存时间戳的事件时,我们应该使用创建事件的中心的时区来保存事件的时区偏移量(TIMESTAMP tz
仅保存时区偏移量,而不是时区本身)
我不完全相信这是处理不同时区的正确(或最佳)方法。因为我从来没有实现过这样的事情,所以我不能说
如果我必须遵循这种方法,我必须将数据库中的所有列类型从TIMESTAMP
更改为TIMESTAMP
。在中以某种方式依赖于该列的所有视图也必须重新创建,因为我们将更改列类型。我还必须更改所有处理该列的查询,以使用时区应用中心时区
数据库现在已经设置了美国/圣保罗
时区,我担心在更改时间戳
的列类型时会出错。此更改是否可能破坏数据一致性?我应该首先更改列类型还是更改UTC的数据库时区
我描述的解决方案是实现这一点的最佳方法吗
该方法是否也正确处理DST变更
额外信息:我们的服务器使用java(jersey)。这些问题在Stack Overflow和姐妹站点上已经讨论过多次。请在下次发帖前彻底搜索。所以,这里只是一个回顾
首先,你必须理解和之间的区别。偏移量仅是距离的小时数、分钟数和秒数。时区是特定地区人民使用的偏移量的过去、现在和未来变化的历史。因此,使用时区总是比仅仅使用偏移更可取
存在两种不同的类型来保存时间戳。时间戳和时间戳
不完全是这样。SQL标准定义的实际类型是带时区的时间戳和不带时区的时间戳。其他名称是Postgres特有的同义词。为了清晰起见,我建议使用标准名称。日期-时间处理非常混乱,而且最后没有读取/记住z
的模糊性
SQL规范很少涉及日期时间处理的主题。因此,数据库实现之间的行为差异很大
博士后的工作方式非常简单
- 对于带有时区的时间戳类型为
的列,任何带有UTC或时区偏移量的输入都会自动调整为。调整后,原始值的偏移/分区信息将被丢弃。“带时区”实际上是指“关于传入数据的时区”,而不是“与时区一起存储”如果必须知道原始偏移量/区域,则必须将其存储在单独的列中。我建议使用ISO 8601标准偏移格式或。如果输入缺少区域/偏移的任何指示器,则应用会话的当前默认时区,然后对UTC进行调整——正如我模糊地记得的那样;您不应该传递缺少区域/偏移的输入李>
- 对于不带时区的
时间戳列
,将忽略任何带输入的区域/偏移信息(如果有)。同样,检索时,此值没有分区/偏移。这种类型没有分区/偏移的概念。当您打算在时间线上存储一个时刻、一个点时,请勿使用此类型。这种类型是为了模糊地了解26-27小时范围内的潜在时刻,例如“2018年12月25日午夜后开始圣诞节”。这样的句子没有真正的意义,除非您附加“在日本”、“在印度”或“在法国”(从而在另一种类型中创建一个值,带时区的时间戳
)。这种类型也适用于未来几周以上的任命,届时政客可能会改变其地区的补偿(他们经常这样做,而且几乎没有预先警告)
注意:令人困惑的是,某些工具或驱动程序可能会将会话的当前默认时区应用于eit值
ZoneId zoneSaoPaulo = ZoneId.of( "America/Sao_Paulo" ) ;
ZoneId zoneLisbon = ZoneId.of( "Europe/Lisbon" ) ;
ZoneId zoneKolkata = ZoneId.of( "Asia/Kolkata" ) ;
LocalDateTime ldt = myResultSet.getObject( … , LocalDateTime.class ) ;
public enum DistributionCenter {
// List the constants to be constructed automatically when this class loads.
SAOPAULO( ZoneId.of( "America/Sao_Paulo" ) ) ,
LISBON( ZoneId.of( "Europe/Lisbon" ) ) ,
KOLKATA( ZoneId.of( "Asia/Kolkata" ) )
final public ZoneId zoneId ; // Make public, or add a getter method to access private member.
// Add constructor taking the passed `ZoneId` and storing in the variable.
}
DistributionCenter dc = … ;
ZonedDateTime zdt = ldt.atZone( dc.zoneId ) ;
Instant instant = zdt.toInstant() ;
myPreparedStatement.setObject( … , instant ) ;