我有一些想要报告的数据(为简洁起见,进行了简化):
MyDateTime (UTC) MyValue
------ -------
2020-10-01 10:00:00 24
2020-10-01 15:00:00 53
2020-10-02 12:00:00 26
etc
所有日期均存储为 UTC。通常,我会尽可能长时间地保留 UTC 格式的所有内容,并根据用户区域设置在用户浏览器中转换为本地时间。
但是对于这个特定的报告,我想按整个日期聚合数据:
SELECT CAST(MyDateTime AS DATE), AVG(MyValue)
FROM MyTable
GROUP BY CAST(MyDateTime AS DATE)
因此,在 chop MyDateTime
列并对其进行聚合之前,我需要将日期转换为用户的本地时间。
在 SQL Server 中,我可以将 UTC datetime
转换为本地 chop 日期,如下所示:
CAST(CONVERT(datetime2, (SeizeTime AT TIME ZONE 'GMT Standard Time'), 1) AS DATE)
SQL Server 有一个 AT TIME ZONE
子句接受的时区名称列表,在我的示例中,我使用 GMT 标准时间
,这是包括夏令时的英国时间.
浏览器中运行的 JavaScript 有另一种命名时区的方法:
console.log(Intl.DateTimeFormat().resolvedOptions().timeZone)
--> Europe/London
所以我的问题是 SQL Server 和 Javascript 之间没有命名时区的通用方法。如何可靠地将欧洲/伦敦
映射到GMT标准时间
以及世界上所有其他可能的时区?
到目前为止,我解决这个问题的选择是:
- 使用硬编码查找表 - 如果将来时区信息发生变化,可能会中断。
- 将所有预先聚合的数据发送到浏览器,将时区应用于日期,然后在客户端聚合数据 - 我希望避免不必要地移动太多数据。
还有我缺少的其他方法吗?
最佳答案
鉴于您的用户配置文件中的问题历史记录,我假设您在应用程序层中使用 .NET 来查询 SQL Server。如果不是这种情况,请发表评论,我会相应调整我的答案。
您问题的核心由 How to translate between Windows and IANA time zones? 解决。 。总之,你可以用我的TimeZoneConverter应用程序层中的库,用于转换来自 Intl
的 IANA 时区名称在浏览器中,更改为 SQL Server 能够识别的 Windows 时区名称。
但是,我还会针对您问题的 SQL 部分添加几点:
在许多情况下,在应用程序层中进行时区转换比在 SQL Server 中更好。 .NET
TimeZoneInfo
在 Linux 上运行时,该类可以处理转换并理解 IANA 标识符。您可以使用TZConvert.GetTimeZoneInfo
在 Windows 上运行时,可以使用 TimeZoneConverter 来使用它们。 (或者,您可以使用 Noda Time 进行转换。)如果您使用的数据点数量相对较少,请从 SQL 查询所有数据点,不进行分组,然后在应用程序层中对它们进行转换,然后进行分组并执行聚合。这将带来内存和网络开销 - 但将使您的查询更高效、转换更容易。
如果您正在处理更大的数据集,那么聚合和转换确实必须在查询时完成。使用
TZConvert.IanaToWindows
来自 TimeZoneConverter ,并将其作为参数传递给您的查询以与AT TIME ZONE
一起使用.为了防止查询引擎必须扫描整个表,您需要在 UTC 值上建立索引。您还需要一个
WHERE
使用该原始值的子句,因此您的查询是 "sargable" 。因此,在执行查询之前,您需要将所需的查询范围从客户端时区转换为 UTC。当源值为
datetime
时或datetime2
,您需要使用AT TIME ZONE
两次。例如SomeDateTimeValue AT TIME ZONE 'UTC' AT TIME ZONE 'Pacific Standard Time'
。第一个将创建一个datetimeoffset
使用给定的时区,分配正确的偏移量 (+00:00
),同时保留日期和时间值。第二个将转换datetimeoffset
到另一个datetimeoffset
在给定的时区中,相应地调整日期、时间和偏移量。
这是一个演示 SQL 方面的示例。我设置了一个包含一些附加值的临时表,以明确最终查询中包含和排除的内容。
--Temp table for demo purposes
CREATE TABLE #MyTable (UtcDateTime datetime2, MyValue int)
INSERT INTO #MyTable VALUES ('2020-10-01 06:00:00', 1000) -- excluded
INSERT INTO #MyTable VALUES ('2020-10-01 07:00:00', 100) -- included
INSERT INTO #MyTable VALUES ('2020-10-01 10:00:00', 24) -- included
INSERT INTO #MyTable VALUES ('2020-10-01 15:00:00', 53) -- included
INSERT INTO #MyTable VALUES ('2020-10-02 12:00:00', 26) -- included
INSERT INTO #MyTable VALUES ('2020-10-03 06:00:00', 100) -- included
INSERT INTO #MyTable VALUES ('2020-10-03 07:00:00', 1000) -- excluded
-- These are the input values of the query
DECLARE @ClientTZ varchar(50) = 'Pacific Standard Time' -- Converted from 'America/Los_Angeles' in the application layer with TimeZoneConverter
DECLARE @FromClientDate date = '2020-10-01'
DECLARE @ToClientDate date = '2020-10-02'
-- START OF QUERY
-- First you'll need to know the UTC datetimes of the range you are querying over.
-- This pair of values creates a half-open UTC datetime range to be used in the WHERE clause below.
DECLARE @FromUtcDateTime datetime2 = CAST(@FromClientDate AS datetime2) AT TIME ZONE @ClientTZ AT TIME ZONE 'UTC'
DECLARE @ToUtcDateTime datetime2 = CAST(DATEADD(DAY, 1, @ToClientDate) AS datetime2) AT TIME ZONE @ClientTZ AT TIME ZONE 'UTC'
-- Now you can do the query. The inner subquery does the filtering and conversion. The outer query does the aggregation.
SELECT ClientDate, AVG(MyValue) as AvgValue
FROM (
SELECT CAST(UtcDateTime AT TIME ZONE 'UTC' AT TIME ZONE @ClientTZ AS date) as ClientDate, MyValue
FROM #MyTable
WHERE UtcDateTime >= @FromUtcDateTime AND UtcDateTime < @ToUtcDateTime
) q
GROUP BY q.ClientDate
-- END OF QUERY
-- Drop the temp table for demo cleanup
DROP TABLE #MyTable
关于javascript - SQL Server 和 Javascript 之间的时区名称转换,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64206430/