Вот реализация SQL-Only, которую я недавно собрал, которую вы можете использовать (форумы предполагают, что CLR - единственный метод, поскольку TSQL неоправданно сложен для достижения этой цели - не совсем так). Я реализовал через встроенную функцию, которая избегает RBAR (Вы можете профилировать и проверить это, чтобы подтвердить).
Производительность великолепна даже по сравнению с распределенными многораздельными представлениями старой школы.
Убедитесь, что ваша индексация хороша, даже при работе со строками в полях DateTime (чтобы обойти зависимости DatePart от Year) я получаю желаемые результаты поиска. Некоторые из лежащих в основе секционированных таблиц имеют размер более 80 ГБ.
Конечно, вам нужно будет добавлять строки часовых поясов по мере необходимости, и не забывайте обновлять даты начала и окончания перехода на летнее время (они могут измениться).
В обоих случаях для часового пояса и перехода на летнее время смещения выполняются в минутах, поэтому это работает для всех сценариев, с которыми я сталкивался до сих пор.
Наконец, смещение Летнего времени всегда является положительным числом, обратите внимание, что функция учитывает это, чтобы соответствовать эмпирическому правилу (Spring Forward, Fall Back)
If Not Exists (Select Name from sys.objects where name = 'tblTimeZones' and type = 'U')
Begin
Create Table tblTimeZones(
[ID] Int Identity (0,1) NOT NULL,
[UserID] Int NOT NULL,
[Description] NVarchar(128) NOT NULL,
[TZ_OffSet_Mins] Int NOT NULL,
[Use_DST] Bit NOT NULL,
[DST_AddOffSet] Int NOT NULL,
[DST_StartDate] DateTime NOT NULL Constraint DF_DST_StartDate Default ('1900-01-01 00:00:00.000'),
[DST_EndDate] DateTime NOT NULL Constraint DF_DST_EndDate Default ('1900-01-01 00:00:00.000'),
Constraint PK_tblTimeZones Primary Key NonClustered (ID),
Constraint UQ_tblTimeZones_Description Unique Clustered ([Description])
)
End
Go
If Exists (Select Name from sys.objects where name = 'fncV1_iCalcDateInTimeZone' and type = 'IF')
Begin
Drop Function fncV1_iCalcDateInTimeZone
End
Go
Create Function fncV1_iCalcDateInTimeZone
(
@UserID Int, @DateAndTime DateTime, @EntID Int
)
Returns Table
With SchemaBinding
As
Return (
Select TZDateAndTime =
DateAdd(
mi,
tz.TZ_OffSet_Mins +
-- Daylight Savings STARTS earlier in the Year than Ends (So, Northern Hemisphere), In Daylight Savings Time Period and Daylight Savings In Use
Case when
tz.Use_DST = 1
And SubString(Convert(Varchar(23),tz.DST_StartDate,21), 6, 18) < SubString(Convert(Varchar(23),tz.DST_EndDate,21), 6, 18)
And SubString(Convert(Varchar(23),@DateAndTime,21), 6, 18) >= SubString(Convert(Varchar(23),tz.DST_StartDate,21), 6, 18)
And SubString(Convert(Varchar(23),@DateAndTime,21), 6, 18) < SubString(Convert(Varchar(23),tz.DST_EndDate,21), 6, 18)
then tz.DST_AddOffSet
Else 0
End
+
-- Daylight Savings STARTS later in the Year than Ends (So, Southern Hemisphere), In Daylight Savings Surround Period
Case when
tz.Use_DST = 1
And SubString(Convert(Varchar(23),tz.DST_StartDate,21), 6, 18) > SubString(Convert(Varchar(23),tz.DST_EndDate,21), 6, 18)
And
(
SubString(Convert(Varchar(23),@DateAndTime,21), 6, 18) >= SubString(Convert(Varchar(23),tz.DST_StartDate,21), 6, 18)
Or
SubString(Convert(Varchar(23),@DateAndTime,21), 6, 18) < SubString(Convert(Varchar(23),tz.DST_EndDate,21), 6, 18)
)
then tz.DST_AddOffSet
Else 0
End
,@DateAndTime
)
From dbo.tblSomeEntityTable rd
Inner Join dbo.tblBranch b on rd.BranchID = b.ID
Inner Join dbo.tblUsers u on u.ID = @UserID
Inner Join dbo.tblTimeZones tz on tz.ID = case when u.UserTZOverBranchTZ = 1 then u.TimeZoneID else b.TimeZoneID End
Where
rd.ID = Case when ISNULL(@EntID, -1) = -1 then rd.ID else @EntID End
)
Go