Azure SQL - Convert time zone to 'Central Standard Time'? - sql

Server time is 'UTC' and cannot be changed due to my database being Azure SQL. I know you can convert to other time zones. However, I'm not sure how to apply this correctly.
In short. I need to be able to pull and job based on 'start date' but by Central Standard Time.
I did comment out where I tried some things. My issue is, I'm not sure how to apply the offset to the 'Start Date' for what is returned in the front end.
The 'WHERE' statement does technically work to only return 'Today's Jobs', however returns based on UTC time which won't work for people using this in Central Standard Time Zone. How do I offset this properly?
Much appreciation for the help.
begin
--declare #dto datetimeoffset = switchoffset (CONVERT(datetimeoffset, GETDATE()), '-6:00');
--DECLARE #dto datetimeoffset
--SET #dto = (SELECT GETUTCDATE() AT TIME ZONE 'Central Standard Time')
--SELECT SWITCHOFFSET(SYSDATETIMEOFFSET(), '-05:00')
declare #today datetimeoffset;
--set #today = switchoffset (convert(time, getdate()), '-05:00');
set #today = getdate();
SELECT dbo.Projects.ProjectID, dbo.Projects.ProjectName, dbo.Jobs.JobID, dbo.Jobs.Status, dbo.Jobs.StartDate, dbo.Jobs.EndDate, dbo.Jobs.CompletedDate, dbo.JobType.JobTypeID, dbo.JobType.JobTypeDescription
FROM dbo.Jobs INNER JOIN dbo.Projects ON dbo.Jobs.ProjectID = dbo.Projects.ProjectID
INNER JOIN dbo.JobType ON dbo.Jobs.JobTypeID = dbo.JobType.JobTypeID
WHERE convert(varchar(10), StartDate, 102)
= convert(varchar(10), #today, 102)
ORDER BY JobID DESC
End

Azure SQL database always follows UTC. Use AT TIME ZONE in Azure SQL Database if you need to convert date and time information in a non-UTC time zone.
AT TIME ZONE converts input date to target time zone. It returns datetimeoffset value in the target time zone.
Query:
declare #current_cst datetimeoffset;
set #current_cst = (SELECT getdate() AT TIME ZONE 'UTC' AT TIME ZONE 'Central Standard Time')
declare #current_utc datetimeoffset;
set #current_utc = getutcdate();
--retunrs datetimeoffset format
select #current_utc current_utc, #current_cst current_cst
--retunrs 102 format(yyyy.mm.dd)
select convert(varchar(10), #current_utc, 102) as current_utc, convert(varchar(10), #current_cst, 102) as current_cst
Note: A list of installed time zones are available in sys.time_zone_info view.

Related

Default to last sunday taking client time zone into account if no datetime is passed in

I have a procedure that has #from and #to date input parameters. The data type for both these types is DateTime. This procedure can be called in from any time zone and the values in the DB are stored in UTC. The application converts the client side time to UTC and calls the procedure.
If no from date is sent in the input parameter then I have to default it to last Sunday.
Here's the current logic that I have
Declare #currentDate Date = getdate()
Declare #fromDate DateTime = Null
Set #fromDate = Coalesce(#fromDate, DATEADD(wk, DATEDIFF(wk, 0, #currentDate), -1))
Select #fromDate
This works well in most situations except a few:
If I'm a user in
--EST timezone,
--running the procedure at 10PM EST on 26-Feb-2022 and,
--I'm passing a null value for #from date.
The code above would be something like this:
Declare #currentDate Date = '27-Feb-2022 3:00:00' --UTC
Declare #fromDate DateTime = Null
Set #fromDate = Coalesce(#fromDate, DATEADD(wk, DATEDIFF(wk, 0, #currentDate), -1))
Select #fromDate
The result would be 27-Feb-2022 00:00:00 UTC which when translated to EST would be 26-Feb-2022 19:00:00. But since my date has to default to last Sunday it should technically be 20-Feb-2022 00:00:00 EST which is 20-Feb-2022 5:00:00 UTC. I'm very confused as to how to achieve this since I have to consider the client time zones when doing the calculations and it can be anything. I would appreciate some insights into how I can achieve this. Thank you!
You can do the conversion SQL side. If you change your declaration to:
Declare #currentDate Date = cast('27-Feb-2022 3:00:00' as datetime)
at time zone 'utc'
at time zone 'eastern standard time';
everything should work as you expect. To talk through it, you're explicitly telling SQL that the date time is in the UTC TZ with the first at time zone and then converting it to EST with the second.

Day light savings time is resulting in incorrect dates in database

BACKGROUND:
I have an issue with the day light savings time change. Any records entered into the database (side note: I have no access to the script/code which enters this data to fix it) between 2021-03-28 and 2021-10-31 get entered into the database with the incorrect date. For example:
Records entered in on 2021-03-26 end up in the database as 2021-03-26 00:00:00, which is correct.
Records entered in on 2021-03-29 end up in the database as 2021-03-28 23:00:00 which is incorrect.
So when I try to search for records entered in on2021-03-26, the query works fine, but if I try to search for records entered in on 2021-03-29, it returns records from the wrong date because of the hour change.
SAMPLE DATA:
ColDate, ColName
2021-03-26 00:00:00, SomeName -- CORRECT DATE
2021-03-26 00:00:00, SomeName -- CORRECT DATE
2021-03-26 00:00:00, SomeName -- CORRECT DATE
2021-03-28 23:00:00, SomeName -- INCORRECT DATE
2021-03-28 23:00:00, SomeName -- INCORRECT DATE
2021-03-28 23:00:00, SomeName -- INCORRECT DATE
WORKING EXAMPLE:
DECLARE #StartDate datetime
DECLARE #EndDate datetime
SET #StartDate = '2021-03-26 00:00:00'
SET #EndDate = '2021-03-26 23:59:59'
SELECT *
FROM tblName
WHERE ColDate BETWEEN #StartDate AND #EndDate
The above will return:
ColDate, ColName
2021-03-26 00:00:00, SomeName -- CORRECT DATE
2021-03-26 00:00:00, SomeName -- CORRECT DATE
2021-03-26 00:00:00, SomeName -- CORRECT DATE
NONE WORKING EXAMPLE:
DECLARE #StartDate datetime
DECLARE #EndDate datetime
SET #StartDate = '2021-03-29 00:00:00'
SET #EndDate = '2021-03-29 23:59:59'
SELECT *
FROM tblName
WHERE ColDate BETWEEN #StartDate AND #EndDate
The above will return nothing from the sample data.
QUESTION:
How do I get around this issue? As mentioned earlier, I have no control of the data entry and the developers have no interest in fixing the issue.
Do I need to use IF statements and check if the date is between 2021-03-28 and 2021-10-31 and adjust the date by 1 hour accordingly? Or is there a better way to resolve this?
UPDATE - POSSIBLE SOLUTION:
The following query seems to work (2021-03-26):
DECLARE #StartDate datetime
DECLARE #EndDate datetime
SET #StartDate = '2021-03-26 00:00:00'
SET #EndDate = '2021-03-26 23:59:59'
SELECT
ColDate AT TIME ZONE 'UTC' AT TIME ZONE 'GMT Standard Time',
ColName
FROM tblName
WHERE ColDate BETWEEN #StartDate AT TIME ZONE 'GMT Standard Time' AT TIME ZONE 'UTC' AND #EndDate AT TIME ZONE 'GMT Standard Time' AT TIME ZONE 'UTC'
The following query seems to work (2021-03-29):
DECLARE #StartDate datetime
DECLARE #EndDate datetime
SET #StartDate = '2021-03-29 00:00:00'
SET #EndDate = '2021-03-29 23:59:59'
SELECT
ColDate AT TIME ZONE 'UTC' AT TIME ZONE 'GMT Standard Time',
ColName
FROM tblName
WHERE ColDate BETWEEN #StartDate AT TIME ZONE 'GMT Standard Time' AT TIME ZONE 'UTC' AND #EndDate AT TIME ZONE 'GMT Standard Time' AT TIME ZONE 'UTC'
UPDATE - QUESTION:
The above update seems to work, but am I overlooking anything?
Your updated answer seems great. But if the correct entered date is always without time and only incorrect ones are entered with time part then you can also just subtract 1 hour from #startdate while using it in where clause.
DECLARE #StartDate datetime
DECLARE #EndDate datetime
SET #StartDate = '2021-03-29 00:00:00'
SET #EndDate = '2021-03-29 23:59:59'
SELECT *
FROM tblName
WHERE ColDate BETWEEN dateadd(day,-1,#StartDate) AND #EndDate
But it won't work if there are rows with time part other than day light saving issue.
if I am overlooking anything
You might.
You should try to understand how exactly the data entry app works. You can't change it, but you should be able to reverse engineer the code. Once you understand how the code transforms the timestamps, you may try to come up with the reversing transformation.
In your proposed solution you convert timestamp to a GMT Standard Time time zone. You should verify if your data entry app operates with this time zone. Different time zones switch between day light savings on different dates. It is not like the whole world moves their clock on the same day of the year. Some time zones don't have a day light saving at all.
Also, rules for time zones tend to change over time. Governments like to make changes. So, if your database spans across lengthy period you may discover that the rules for the time zones that are built into SQL Server now are not the same that were in place when the data was recorded. For example, Perth in Western Australia moved their clocks in 1991-1992, then again in 2006-2009, then stopped doing it. At least, DST is not observed now.
So, things to consider:
what time zone to use?
does all of your data fall into the same time zone? If the data comes from several different time zones, their rules may be different and you need to know which transformation to apply to which parts of the data.
does your data span across the period when the rules for your chosen time zone changed?
So, in general case it becomes pretty hard to fix the timestamps after the fact.
You can't change your app now, but a note for the future.
In my system I make sure to store both local and UTC time. When an entry is generated by a local computer, it usually knows its local time zone and UTC time accurately. When I have a central database with timestamps from various locations I do not attempt to convert local->UTC or UTC->local. I use appropriate field. Some reports use local times, some reports use UTC.
Daylight (DST) time is actually an offset comparing with Local time. The majority of SQL databases has UTC internal conversion when using datetime in column type.
A very nice practice to resolve your issue is to convert your datetime string to milliseconds!!!
var myDate1 = Date.parse("2021-03-26 00:00:00")
//1616709600000
var myDate2 = Date.parse("2021-03-28 00:00:00")
//1616882400000
TIP: calculate here the time zone offset programmatic and convert it to milliseconds, now you can add it to MyDate1, MyDate2 before run your Query
var time_zone_correction = today.getTimezoneOffset()*60*1000;
When you are ready call your query with the following syntax:
DECLARE #UTC1 BIGINT
DECLARE #UTC2 BIGINT
SET #UTC1 = '"+myDate1+"'
SET #UTC2 = '"+myDate2+"'
SELECT * FROM yourTableHere WHERE [DATE]
BETWEEN DATEADD(SECOND, #UTC1/1000, '19700101')AND DATEADD(SECOND, #UTC2/1000, '19700101') order by [DATE]"
Moreover you can check your SQL for registered timeZones by running this select * from sys.time_zone_info
Note: Milliseconds has precision over Date() object conversion from strings, also the execution time of a BIGINT compared to string datetime conversion is much faster.
In terms of code re-usability, if you add your offset (milliseconds calculation) for time zones before executing your query and not inside, you end up with a dynamic and more "template" approach of writing a query.
Give it a try and research the web, there are tons of tutorials for dealing with UTC, DST and timezone manipulation!
I hope my approach will help you!
I think you need 2 time funcs (see bellow)
dbo.fnToDbTime for Filter times
dbo.fnToYourTime for format Result time
DECLARE #StartDate datetime = '2021-03-28'
DECLARE #EndDate datetime = #StartDate + 1
SET #StartDate = dbo.fnToDbTime(#StartDate)
SET #EndDate = dbo.fnToDbTime(#EndDate)
SELECT [ColDate], [ColName], dbo.fnToYourTime([ColDate]) GoodTime
FROM tblName
WHERE #StartDate <= ColDate AND ColDate < #EndDate
GO
CREATE FUNCTION dbo.fnToDbTime (#YourTime datetime)
RETURNS datetime
AS
BEGIN
DECLARE #DbTime datetime
IF #YourTime < '2021-03-29 00:00:00'
SET #DbTime = #YourTime
ELSE IF #YourTime < '2021-10-31 00:00:00'
SET #DbTime = DATEADD(HH, -1, #YourTime)
RETURN #DbTime
END
GO
CREATE FUNCTION dbo.fnToYourTime (#DbTime datetime)
RETURNS datetime
AS
BEGIN
DECLARE #YourTime datetime
IF #DbTime < '2021-03-28 23:00:00'
SET #YourTime = #DbTime
ELSE IF #DbTime < '2021-10-30 23:00:00'
SET #YourTime = DATEADD(HH, 1, #DbTime)
RETURN #YourTime
END
GO

Proper way of getting rows since a date accounting for DST?

I have a datetime column, changedate, that I need to use to get rows that have changed in the last week since 1PM. This column is unfortunately in local time (EST). The server is Microsoft SQL Server 2016.
Here is the query I have now:
DECLARE #since datetime = DATEADD(week,-1,SMALLDATETIMEFROMPARTS(YEAR(GETDATE()), MONTH(GETDATE()), DAY(GETDATE()), 13, 0)) AT TIME ZONE 'Eastern Standard Time'
SELECT * FROM table WHERE changedate AT TIME ZONE 'Eastern Standard Time' >= #since
Since I'm using AT TIME ZONE for both the column and #since, will this properly account for DST changes? That's my understanding per the documentation I've found, but I'm not 100% sure if that's how it works or if I'm missing something.
First, figure out the time you're wanting to compare against:
-- Get the current date in the given time zone
DECLARE #today date = convert(date, sysdatetimeoffset() AT TIME ZONE 'Eastern Standard Time')
-- Get the date one week ago
DECLARE #dateOneWeekAgo date = DATEADD(week, -1, #today)
-- Join the date with the desired time (local to the same time zone)
DECLARE #since datetime = convert(datetime, #dateOneWeekAgo) + convert(datetime, timefromparts(1, 0, 0, 0, 0))
Then just compare it:
SELECT * FROM table WHERE changedate >= #since
That assumes your changedate field is a datetime or datetime2. If it's a datetimeoffset, you should first convert the target value to a datetimeoffset in the same time zone and use that instead:
DECLARE #sinceDTO datetimeoffset = #since AT TIME ZONE 'Eastern Standard Time'
Regarding the approach you gave in the question, there two issues:
getdate() gives the time based on the server's local time zone. It's possible that it's not the same day in Eastern Time.
You should never apply a function (whether an intrinsic like AT TIME ZONE or something else) against a table field in a where clause, because it makes the query non-sargable. In other words, SQL would have to scan the entire table, rather than using an index. The bigger the table, the slower the query would take.

TimeZone Implementation For a SaaS [duplicate]

I want to add my time zone with the function GETUTCDATE() in SQL Server. I searched several times, but did not found any relevant solution. Thanks in advance.
only for sql 2016, it takes into account daylight savings.
CREATE FUNCTION GetBelgiumTime
(
)
RETURNS datetime2
AS BEGIN
declare #dateoffset datetimeoffset
SET #dateoffset = convert(VARCHAR(2000),(SELECT GETUTCDATE() AT TIME ZONE 'Central European Standard Time'),126 )
declare #date datetime2
set #date = convert(datetime2, LEFT(#dateoffset,28),126)
set #date = DATEADD(HOUR, convert(int,LEFT(RIGHT(#dateoffset,5), 2)), #date)
RETURN #date
END
select dbo.GetBelgiumTime() as BelgiumDateAndTime
From SQL Server 2016 forward (and Azure SQL DB), you can do this:
SELECT SYSDATETIMEOFFSET() AT TIME ZONE #tz
where #tz is a valid Windows time zone identifier, such as 'Pacific Standard Time', 'Central European Standard Time', etc.
However, if you are on an older version of SQL Server, or prefer to use IANA time zone identifiers, you can use my SQL Server Time Zone Support project to do the following:
SELECT Tzdb.UtcToLocal(GETUTCDATE(), #tz)
where #tz is an IANA standard time zone name, such as 'America/Los_Angeles' or 'Europe/Budapest'.
Use GETDATE() instead GETUTCDATE(). see this link
You can try to use switchoffset like this:
select switchoffset(CAST(myDate as datetimeoffset),'+05:30') from someTable
Instead of '+05:30' you can specify your timezone value.
If you want to use the timezone with GETUTCDATE() then simply add it like this
select cast(GETUTCDATE() as varchar(20)) + '+5:30'
and if you want to keep it as date only then
select switchoffset(CAST(GETUTCDATE() as datetimeoffset),'+05:30')

DateDiff - DST issue

Background
I have a scenario where I am calculating the difference between two dates. While the increment differences are spot on, the final calculation has introduced a 60 minute (1 hour) disparity.
After investigation and "hair pulling" episodes, I have identified that the DST transition in November is the cause for the 60 minute (1 hr) disparity.
Scenario
declare #sdate datetime = '2016-10-29 06:03:00.000PM'
declare #edate datetime = '2016-11-29 11:59:00.000PM'
select
DATEDIFF(HOUR, #sdate, #edate),
DATEDIFF(Minute, #sdate, #edate),
DATEDIFF(Second, #sdate, #edate)
Question
Ultimately, I need to simply return the number of seconds, or minutes, between the #sdate and #edate variables. I know there will be two times during the year, where the difference value will be off by 60 minutes (1 hour), plus or minus, and want to account for that known disparity within my sql statement.
How can i account for the DST adjustment within a set-based operation, if possible?
Currently, I am getting 44996 as the difference, but that is the un-adjusted time-change difference. I am looking for 45056, which is the adjusted time-change difference.
The datetime type in SQL Server has no awareness of time zone or offset from UTC. In order to take DST into account, you need to use the datetimeoffset type.
declare #sdate datetimeoffset = '2016-10-29 06:03:00.000PM -05:00'
declare #edate datetimeoffset = '2016-11-29 11:59:00.000PM -06:00'
select
DATEDIFF(Hour, #sdate, #edate),
DATEDIFF(Minute, #sdate, #edate),
DATEDIFF(Second, #sdate, #edate)
This will give the results you asked for, taking into account that the offsets for the start and end differed by an hour.
The tricky part is how to determine the offset to begin with. If you are running SQL 2016, or Azure SQL DB, then you could use the AT TIME ZONE function to determine it.
declare #dt datetime = '2016-10-29 06:03:00.000PM'
declare #dto = #dt AT TIME ZONE 'Central Standard Time'
But since you said you are running SQL 2012, you'll have to either write your own functions that understand when DST starts and ends in your time zone, or you can use my SQL Server Time Zone Support package to do that:
declare #dt datetime = '2016-10-29 06:03:00.000PM'
declare #tz varchar = 'America/Chicago'
declare #dto = Tzdb.SwitchZone(Tzdb.LocalToUtc(#dt, #tz, 1, 1), #tz)
Note there is an open item to simplify this to a single function, but the above should work for now.