I have a table with users, UTC time offset, and if they observe daylight saving time. Is there a built in way to get the correct user time?
The sys.time_zone_info table introduced in SQL 2016 allows to determine the offset from UTC and whether DST is currently in effect.
SELECT * FROM sys.time_zone_info
The query result will be:
Right now I'm doing this:
SELECT
CASE USEDAYLIGHTSAVING
WHEN 1 THEN
CASE DATEDIFF(HH,GETUTCDATE(),GETDATE())
-- I know the server is set to Mountan time and follows daylight saving time
-- MDT = -6
-- MST = -7
WHEN -6 THEN
DATEADD(HH,TIMEZONEOFFSET+1,GETUTCDATE())
ELSE
DATEADD(HH,TIMEZONEOFFSET,GETUTCDATE())
END
ELSE
DATEADD(HH,TIMEZONEOFFSET,GETUTCDATE())
END
FROM
USERS
It works but if the server get's moved to another timezone or doesn't fallow daylight saving time I'm hosed.
I have a table with users, UTC time offset, and if they observe daylight saving time. Is there a built in way to get the correct user time?
Different time zones around the world observe DST in different ways. They start and stop at different times of the day. Simply having an offset and DST flag is not enough to reliably convert.
Instead, you need a time zone identifier, such as America/Los_Angeles, or one of the others listed here. Once you have that, you can use my SQL Server Time Zone Support project to do the conversions.
This won't work in all cases, but is a quick fix for those in the US w/ DST defined as between the 2nd Sunday of March and the 1st Sunday of November.
DECLARE #Date datetime2 = '2018-11-04 02:01:00'
SELECT
CASE
WHEN #Date between
DATETIMEFROMPARTS(YEAR(#Date), 3, (8 - DATEPART(WEEKDAY, DATEFROMPARTS(YEAR(#Date), 3, 1))) + 8, 02, 00, 00, 00)
and
DATETIMEFROMPARTS(YEAR(#Date), 11, (8 - DATEPART(WEEKDAY, DATEFROMPARTS(YEAR(#Date), 11, 1))) + 1, 02, 00, 00, 00)
THEN 1
ELSE 0
END --Is it dst?
using SQL 2016 system table (noted by #anacv),
To get current time for a timezone (DST or PDT) if server/timestamp is GMT
declare #currentDatetime datetime
declare #tzone varchar(100)
set #tzone = 'Pacific Standard Time'
set #currentDatetime = dateadd(MINUTE, (select cast(right(current_utc_offset,2) as int) FROM sys.time_zone_info where name=#tzone) , dateadd(HOUR,(select cast(left(current_utc_offset,3) as int) FROM sys.time_zone_info where name=#tzone), CURRENT_TIMESTAMP))
Related
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.
This question is a follow up from this question.
I have a UTC time column and I want convert to into current local time (Central Time Zone or America/Chicago). I tried to use the function from #Ron Smith's answer, which is [dbo].[fn_UTC_to_DST] function.
In that function, it needs two arguments such as UTC time and offset. I input both like this,
SELECT dbo.fn_UTC_to_DST([time(UTC)],5) as Date
FROM tbl
Since we are in Day Light Saving time, I am using 5 as my offset. My output looks like this,
2017-09-27 20:55:00.000
2017-09-27 20:56:00.000
2017-09-27 20:57:00.000
2017-09-27 20:58:00.000
...
Which should be (central time),
2017-09-27 09:55:00.000
2017-09-27 09:56:00.000
2017-09-27 09:57:00.000
2017-09-27 09:58:00.000
...
So, I changed #Ron Smith's function like this,
CREATE FUNCTION [dbo].[fn_UTC_to_DST]
(
#UTC datetime,
#StandardOffset int
)
RETURNS datetime
AS
BEGIN
declare
#DST datetime,
#SSM datetime, -- Second Sunday in March
#FSN datetime -- First Sunday in November
-- get DST Range
set #SSM = datename(year,#UTC) + '0314'
set #SSM = dateadd(hour,-5,dateadd(day,datepart(dw,#SSM)*-1+1,#SSM)) -- Changed from 2 to -5
set #FSN = datename(year,#UTC) + '1107'
set #FSN = dateadd(second,-6,dateadd(hour,2,dateadd(day,datepart(dw,#FSN)*-1+1,#FSN))) -- changed from 1 to -6
-- add an hour to #StandardOffset if #UTC is in DST range
if #UTC between #SSM and #FSN
set #StandardOffset = #StandardOffset + 1
-- convert to DST
set #DST = dateadd(hour,#StandardOffset,#UTC)
-- return converted datetime
return #DST
END
GO
However, this still gives me the same result as above.
1. What should I change for Central time?
2. Is there a way to automatically change to -5 during daylight saving time and -6 during standard time?
EDIT:
After looking at the Answer and the reference link from #Siyual,I created the dbo.TZCalendar table and I tried to create a function like this (takes one argument and returns a date from refrence link)
CREATE FUNCTION dbo.ConvertUTCToLocal
(
#utc DATETIME
)
RETURNS Datetime
AS BEGIN
SELECT UTCToLocal = DATEADD(HOUR, CASE
-- within Daylight Savings Time
WHEN #utc >= UTC_DST_Start AND #utc < UTC_DST_End
THEN -5
-- within Standard Time
ELSE -6 END, #utc)
FROM dbo.TZCalendar
WHERE CONVERT(DATE,#utc) >= [Year]
AND CONVERT(DATE,#utc) < DATEADD(YEAR, 1, [Year])
END
GO
This, does not work. The logic seems right for me, but, I just need a function without SCHEMABINDING (which is done in the reference link). How can I do that?
If you're using SQL Server 2016+ (or Azure SQL Database) this is built in:
SELECT YourInputDatetimeInUTC AT TIME ZONE 'UTC' AT TIME ZONE 'Central Standard Time'
The first one asserts your input is UTC, the second one converts it to US Central time, inclusive of DST when applicable (using a Windows time zone identifier).
However, since you said SQL 2012, I'll recommend my SQL Server Time Zone Support project, where this is a single operation and uses IANA identifiers:
SELECT Tzdb.UtcToLocal(YourInputDatetimeInUTC, 'America/Chicago')
The linked answer (Sql Server Specify time in another timezone) will get you most of the way there, but to answer the rest of your question, you'll have to make some modifications.
Firstly, I would create a DST calendar, since the DST start and end dates are something that we can compute:
CREATE TABLE dbo.TZCalendar
(
Year Int PRIMARY KEY,
UTC_DST_Start SMALLDATETIME NOT NULL,
UTC_DST_End SMALLDATETIME NOT NULL
);
SET DATEFIRST 7;
;WITH cte(d,p) AS
(
-- all the years from 2000 through 50 years after the current year:
SELECT TOP (YEAR(GETDATE())-2000+51) DATEADD(YEAR,number,'20000101'),
CASE WHEN number < 7 THEN 1 ELSE 0 END -- year < 2007 = 1, else 0
FROM [master].dbo.spt_values WHERE [type] = N'P' ORDER BY number
)
INSERT dbo.TZCalendar([Year],UTC_DST_Start,UTC_DST_End)
SELECT Year(d),
-- First Sunday in April (< 2007) or second Sunday in March (>= 2007):
DATEADD(HOUR, 7, DATEADD(DAY,(7-DATEPART(WEEKDAY,DATEADD(MONTH,2+p,d))+1)%7
+(7*ABS(p-1)),DATEADD(MONTH,2+p,d))),
-- Last Sunday in October (< 2007) or first Sunday in November (>= 2007):
DATEADD(HOUR, 6, DATEADD(DAY,(7-DATEPART(WEEKDAY,DATEADD(MONTH,10,d))+1)%7
-(7*p),DATEADD(MONTH,10,d)))
FROM cte
ORDER BY d;
This will generate the DST times from the year 2000 to 2067 (this can be expanded based on your needs).
Next, I would create a function to take a DATE in UTC and return the value in either CST or CDT, depending on the time of the year.
Create Function dbo.fnConvertUTCToCT(#UTC DateTime)
Returns DateTime
As Begin
Declare #Offset Int = 0
Select #Offset = Case When #UTC Between UTC_DST_Start And UTC_DST_End Then -5 Else -6 End
From dbo.TZCalendar
Where Year = Year(#UTC)
Set #UTC = DateAdd(Hour, #Offset, #UTC)
Return #UTC
End
Then you can just call that function with any time specified and get the CST or CDT translation returned:
Select dbo.fnConvertUTCToCT(GetUTCDate())
2017-09-27 12:24:26.377
UTC time to Central time including DST adjustments
This SQL expression converts UTC time to Central Standard time which also adjusts itself for the Day Light Saving Time period.
DATEADD(HOUR,datediff(HOUR,[HTMPOSTEDDATETIME] at time zone 'Central Standard Time',[HTMPOSTEDDATETIME]),[HTMPOSTEDDATETIME]) [JNL Posted Date]
My database have date values saved in GMT time zone in int format. I am trying to convert the date to local timezone, but the issue arises when the date is a past date, For instance in my case offset for date Dec 1, 2012 will be -5 and for June 15, 2010 will be -4 due to daylight savings. I am currently in EST.
So i need to know what was the UTC date of a previous date to be able to determine whether -4 or -5 will be the offset for that date.
SELECT Test_Number,
Last_Test_Date, dateAdd(hour,
datediff(hour, GETUTCDATE(), getdate()), --UTC offset
dateadd(second, Last_Test_Date, '1/1/1970 12:00 AM'))
FROM TestTable
I am not entirely sure if it is even possible. Any opinion ?
It sounds like you are actually looking to convert a UTC time stored in the database to a local date/time with the correct offset (-5 or -4).
There is no good way to do this in SQL Server. At least, not yet.
The best advice I can offer is to not do this in conversion in the database. Instead, pass the UTC value as-is back to your application code, and do the conversion there.
For example, if your application is written in .NET, you can use TimeZoneInfo or Noda Time to handle the conversions to any time zone you wish. If you're using something else, let me know and I will update the question.
I am trying to get tomorrows date in a sql statement for a date comparison but it is not working.
Below is my code:
select *
from tblcalendarentries
where convert(varchar,tblcalendarentries.[Start Time],101)
= convert(varchar, GETDATE() +1, 101)
To get tomorrows date you can use the below code that will add 1 day to the current system date:
SELECT DATEADD(day, 1, GETDATE())
GETDATE()
Returns the current database system timestamp as a datetime value without the database time zone offset. This value is derived from the operating system of the computer on which the instance of SQL Server is running.
DATEADD(datepart , number , date)
Returns a specified date with the specified number interval (signed integer) added to a specified datepart of that date.
So adding this to your code in the WHERE clause:
WHERE CONVERT(VARCHAR, tblcalendarentries.[Start Time], 101) =
CONVERT(VARCHAR, DATEADD(DAY, 1, GETDATE()), 101);
First off, GETDATE() will get you today's date in the following format:
2013-04-16 10:10:02.047
Then using DATEADD(), allows you to add (or subtract if required) a date or time interval from a specified date. So the interval could be: year, month, day, hour, minute etc.
Working with Timezones?
If you are working with systems that cross timezones, you may also want to consider using GETUTCDATE():
GETUTCDATE()
Returns the current database system timestamp as a datetime value. The database time zone offset is not included. This value represents the current UTC time (Coordinated Universal Time). This value is derived from the operating system of the computer on which the instance of SQL Server is running.
Try the below:
SELECT GETDATE() + 1
This adds one day to current date
Specify size of varchar in convert()
where convert(varchar(11),tblcalendarentries.[Start Time],101) = convert(varchar(11), GETDATE() +1, 101)
I would write:
where
DATEADD(day,DATEDIFF(day,0,tblcalendarentries.[Start Time]),0) =
DATEADD(day,DATEDIFF(day,0,GETDATE()),1)
This avoids converting dates to strings entirely, whilst also removing the time portion from both values.
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).