SQL Server DateTimeOffset matching same "day" - sql

My application needs to collect "Tuesday's" purchases for all locations world wide, where "Tueday" is the location's Tuesday (regardless of time zone). And if the user needs to re-run the report next week, I need to still get "Last Tuesday's" data. All of our data is stored using DateTimeOffset.
So
9/4/12 00:00:00 -7 through 9/4/12 23:59:59 -7
must MATCH
9/4/12 00:00:00 +11 through 9/4/12 23:59:59 +11
when I am executing my WHERE clause.
I can't convert to UTC in the WHERE clause because that will pick up the data for "Tuesday" in London (depending on DST), not the location's Tuesday.
I tried converting from DateTimeOffset to DateTime, but that seems to convert to UTC. (In my tests, passing 9/1/12 through 9/30/12 picked up 8/31/12 data.)
Is there a trick to doing something like this with TSQL?
Thanks!

IMHO
DateTimeOffset = DateTime+Offset(from UTC)
So your data is already representing Client's Local date and time. Just cast it to DateTime and you will get the client's local Date and time.
But in-case if you want to add the Offset to the datetime and want the resultant Datetime then
DECLARE #PurchaseDate DATETIMEOFFSET(7) = CAST('2007-05-08 12:30:29.1234567 +5:00' AS datetimeoffset(7))
SELECT CAST(SWITCHOFFSET (#PurchaseDate , '+00:00') AS DATETIME)
have a look at this blog for further info.
http://blogs.msdn.com/b/bartd/archive/2009/03/31/the-death-of-datetime.aspx

When casting a DATETIMEOFFSET as DATETIME it takes the date and time as offset in the value and simply drops the time zone. The same is true when casting as DATE or TIME. So, I think you can simply cast the column as DATE and compare that to the date-only value you wish to match:
DECLARE #targetDate DATETIME2 = '2012-09-04' --Or we could use DATE here
SELECT [PurchaseId], [PurchaseTime], CAST([PurchaseTime] AS DATE) AS "PurchaseDate"
FROM [Purchases]
WHERE CAST([PurchaseTime] AS DATE) = #targetDate
I'm not sure how efficient this will be (hopefully not bad if the provider is truly clever--which SQL Server likely would be), but you might improve it by bounding the original column value as well:
DECLARE #targetDate DATETIME2 = '2012-09-04' --DATETIME2 so we can adjust by hours
SELECT [PurchaseId], [PurchaseTime], CAST([PurchaseTime] AS DATE) AS "PurchaseDate"
FROM [Purchases]
WHERE CAST([PurchaseTime] AS DATE) = #targetDate --Keep only the local-date matches
AND [PurchaseTime] >= DATEADD(hh, -14, #targetDate) --Up to 14-hour time zone offset
AND [PurchaseTime] <= DATEADD(hh, 38, #targetDate) --24 hours later plus 14
This should efficiently index down to the set of possibilities and then filter properly on the conversion to the local date. Note that time zone offsets can be up to 14 hours (New Zealand is furthest I know at +13:00, but they can go to +/- 14:00 according to MSDN) and the #targetDate will be at the start of the day, so it compares back to 14 hours earlier and 24+14=38 hours later. DATETIME2 has the same range and precision as DATETIMEOFFSET, so it's better for this purpose than the original DATETIME (but it may also work okay with DATETIME instead).

Related

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.

UTC and offset date time compare in sql server

I am storing UTC datetime in Database
e.g.
2018-06-05 11:37:00.000 (UTC)
2018-06-05 17:07 (+5:30 India standart time)
I am having offset as :
offset as +02:00
How can I compare in sql query that now offset time matched ?
e.g.
2018-06-05 13:37:00.000
My issue is X (IST) date time converted to UTC and now I want to covert to different time zone (Y)
The following functions helped me to resolve the issue:
1. SWITCHOFFSET
2. TODATETIMEOFFSET
SELECT GETDATE()
SELECT SWITCHOFFSET(GETDATE(), '+05:30')
SELECT CONVERT(VARCHAR(19), SWITCHOFFSET(TODATETIMEOFFSET(GETDATE(), '+05:30'),'+00:00'), 120)
SELECT GETUTCDATE()
If I understand your question correctly, you can use the DATEADD function to calculate the new datetime based on the UTC datetime and the offset
For example:
2 hours = 120 minutes
DATEADD(minute, 120, '2018-06-05 11:37:00.000')
Or using hours
DATEADD(hour, 2, '2018-06-05 11:37:00.000')
You can also go the other way using negative numbers
You don't have to use a literal value, you can supply a column name to parameter 3 for use in a query and also use this expression as part of a where clause
Here's a cheat sheet for the DATEADD function:
https://learn.microsoft.com/en-us/sql/t-sql/functions/dateadd-transact-sql?view=sql-server-2017

SQL DateTimes stored in UTC get beginning of day based on offset

This may be the most pathetic question ever asked related to SQL and date/time values, but I could use some help...
Trying to setup a function/job that will run at a specified time or times in eastern, mountain, central, and pacific time zones (in theory other zones would work too). The system will identify which users belong to each timezone and then output data from the system highlighting what they've accomplished for the current day.
Here is my challenge, I know all date/time values are stored on the SQL DB in UTC. I can apply the offset and convert those times to local time zones. Rather than convert tens of thousands of date/time values to local time and make comparisons there, it'd be cleaner (I think) to simply adjust the beginning and ending date/time values of UTC within the stored procedure.
On the west coast it is currently just about 2017-09-30 14:30:00 and in UTC is 2017-09-30 21:30:00, this clearly demonstrates a 7 hour time zone difference right now which means "today" from a user perspective technically started at 2017-09-30 07:00:00 and will end on 2017-10-01 06:59:999 in UTC.
What is the best way of establishing these date/time values for a users beginning of day and ending of day values?
UPDATES
I currently have this code...
DECLARE #InputDate as DateTime
DECLARE #InputEndDate as DateTime
DECLARE #InputDateWithOffset as DateTimeOffSet
DECLARE #InputEndDateWithOffset as DateTimeOffSet
SET #InputDate = '2017-09-28'
SET #InputDateWithOffset = #InputDate AT TIME ZONE 'UTC' AT TIME ZONE 'Pacific Standard Time'
SET #InputEndDate = DATEADD(day, 1, DATEADD(ms, -3, #InputDate))
SET #InputEndDateWithOffset = #InputEndDate AT TIME ZONE 'UTC' AT TIME ZONE 'Pacific Standard Time'
SELECT
#InputDate AS InputDate, #InputEndDate AS InputEndDate,
#InputDateWithOffset AS InputDateWithOffset,
#InputEndDateWithOffset AS InputEndDateWithOffset
Which outputs the following:
The last two columns appear to be correct as it would represent both the beginning of the Input Date and the ending of the Input Date as the Input Date is going to be the local date of the execution...
When I take the #InputDateWithOffset and #InputEndDateWithOffset against my table values with datetimes in UTC, it appears the only dates being returned are those that fall on 2017-09-28 and seems to disregard the comparisons to the Offset date/times.
Your update is mostly correct. However, you're missing a "start of day" operation, which needs to be done in local time.
Consider:
DECLARE #InputStartUTC as DATETIME, #InputEndUTC as DATETIME
DECLARE #InputStartDTO as DATETIMEOFFSET, #InputEndDTO as DATETIMEOFFSET
DECLARE #InputStartDTOatStartOfDay as DATETIMEOFFSET,
#InputEndDTOatStartOfDay as DATETIMEOFFSET
DECLARE #tz as VARCHAR(50) = 'Pacific Standard Time'
SET #InputStartUTC = '2017-09-28 00:00:00'
SET #InputStartDTO = #InputStartUTC AT TIME ZONE 'UTC' AT TIME ZONE #tz
SET #InputStartDTOatStartOfDay = CAST(CAST(#InputStartDTO as DATE) as DATETIME)
AT TIME ZONE #tz
SET #InputEndUTC = DATEADD(day, 1, #InputStartUTC)
SET #InputEndDTO = #InputEndUTC AT TIME ZONE 'UTC' AT TIME ZONE #tz
SET #InputEndDTOatStartOfDay = CAST(CAST(#InputEndDTO as DATE) as DATETIME)
AT TIME ZONE #tz
SELECT
#InputStartUTC as InputStartUTC, #InputEndUTC as InputEndUTC,
#InputStartDTO as InputStartDTO, #InputEndDTO as InputEndDTO,
#InputStartDTOatStartOfDay as InputStartDTOatStartOfDay,
#InputEndDTOatStartOfDay as InputEndDTOatStartOfDay
Also, notice I did not subtract three milliseconds from your end date. Rather than trying to figure out .997 or .999 or whatever, the better approach is to query using a half-open interval. In other words, start <= value AND end > value.

How can I convert a Sql Server 2008 DateTimeOffset to a DateTime

I'm hoping to convert a table which has a DATETIMEOFFSET field, down to a DATETIME field BUT recalculates the time by taking notice of the offset. This, in effect, converts the value to UTC.
eg.
CreatedOn: 2008-12-19 17:30:09.0000000 +11:00
that will get converted to
CreatedOn: 2008-12-19 06:30:09.0000000
or
CreatedOn: 2008-12-19 06:30:09.0000000 + 00:00 -- that's a `DATETIMEOFFSET`, but `UTC`.
Cheers :)
Converting using almost any style will cause the datetime2 value to be converted to UTC.
Also, conversion from datetime2 to datetimeoffset simply sets the offset at +00:00, per the below, so it is a quick way to convert from Datetimeoffset(offset!=0) to Datetimeoffset(+00:00)
declare #createdon datetimeoffset
set #createdon = '2008-12-19 17:30:09.1234567 +11:00'
select CONVERT(datetime2, #createdon, 1)
--Output: 2008-12-19 06:30:09.12
select convert(datetimeoffset,CONVERT(datetime2, #createdon, 1))
--Output: 2008-12-19 06:30:09.1234567 +00:00
I'd use the built in SQL option:
select SWITCHOFFSET(cast('2008-12-19 17:30:09.0000000 +11:00' as datetimeoffset),'+00:00')
I know this is an old question but, if you want to convert DateTimeOffset to a DateTime, I think you need to take into account the timezone of the server you are converting on. If you just do a CONVERT(datetime, #MyDate, 1) you will simply lose the time zone, which likely results in an incorrect conversion.
I think you first need to switch the offset of the DateTimeOffset value, then do the conversion.
DECLARE #MyDate DATETIMEOFFSET = '2013-11-21 00:00:00.0000000 -00:00';
SELECT CONVERT(DATETIME, SWITCHOFFSET(#MyDate, DATEPART(tz,SYSDATETIMEOFFSET())));
The result of converting '2013-11-21 00:00:00.0000000 -00:00' to a DateTime on a server who's offset is -7:00 will be 2013-11-20 17:00:00.000. With the above logic it doesn't mater what the time zone of the server or the offset of the DateTime value, it will be converted to DateTime in the servers time zone.
I believe you need to do this because a DateTime value includes an assumption that the value is in the time zone of the server.
DateTimeoffset (Timezone) conversion in SQL Server.
SQL Server 2016 (13.x) and later
Exmample
Select GETUTCDATE()
Select Convert(DATETIME, GETUTCDATE() AT TIME ZONE 'UTC' AT TIME ZONE 'Central European Standard Time')
Select Convert(DATETIME, GETUTCDATE() AT TIME ZONE 'UTC' AT TIME ZONE 'India Standard Time')
Result will be
2020-08-18 08:22:21.640
2020-08-18 10:22:21.640
2020-08-18 13:52:21.640
Note: The timezone information is discarded in conversion if no style ("126" here) is specified. It might also be discarded in some of the other styles, I don't know -- in any case the following correctly adjusts for the TZ information. See CAST and CONVERT.
select convert(datetime, cast('2008-12-19 17:30:09.0000000 +11:00' as datetimeoffset), 126) as utc;
Happy SQL'ing.
Edit
Not sure if it matters but ... datetime Can't actually store that level of precision/accuracy. If the above is run the fractional seconds will be truncated to 3 digits (and accuracy is less than that). The same-same with datetime2 (and datetimeoffset(7)) produces a non-truncated value:
select convert(datetime2, cast('2008-12-19 17:30:09.1234567 +11:00' as datetimeoffset(7)), 126) as utc;
In order to account for daylight savings time, I used the following:
CONVERT(
DateTime,
SWITCHOFFSET(
CONVERT(
DateTimeOffset,
CONVERT(
DateTime,
[time_stamp_end_of_interval],
120
)
),
DATENAME(
TzOffset,
CONVERT(
DateTime,
[time_stamp_end_of_interval],
120
) AT TIME ZONE 'Pacific Standard Time'
)
)
)
AS GOOD_PST
Note: time_stamp_end_of_interval is a varchar