SQL - Get custom day start datetime for passed datetime - sql

I am working on requirement where the day starts at 23:00 last day.
I need to get day start date time for passed date to SQL function. We are storing custom day start time (i.e. 23:00) and offset = -1 (means starting last day) in table now.
e.g. If I pass :
10/30/2013 22:00 it should return 10/29/2013 23:00
10/30/2013 23:20 it should return 10/30/2013 23:00
10/31/2013 01:00 it should return 10/30/2013 23:00
Currently I am using following statement : (DayStartOffset = -1 and DayStartTime = 23:00:00)
declare #datetime datetime
set #datetime = '2013-10-30 23:59:59'
declare #date date
declare #time time(3)
set #date = #datetime
set #time = #datetime
declare #dayStartDateTime datetime
--DayStartOffset is set to -1
--DayStartTime set to '23:00'
SELECT #dayStartDateTime = DATEADD(dd,DayStartOffset,CAST(#date AS DATETIME)) + CAST(DayStartTime AS DATETIME)
from table_name
print #dayStartDateTime
But it is not working correctly for e.g. 1 and 3 above
Can you please help me on this function.

In one line
Create Function dbo.MyStartOfDay(#datetime datetime) returns datetime as
begin
return dateadd(hour, -1, dateadd(day, datediff(day, 0, dateadd(hour, 1, #datetime)), 0))
end
Go
Select dbo.MyStartOfDay('2013-10-30 22:00');
Broken down
Create Function dbo.MyStartOfDay(#datetime datetime) returns datetime as
begin
declare #hourLater datetime = dateadd(hour, 1, #datetime)
declare #roundToDay datetime = dateadd(day, datediff(day, 0, #hourLater), 0);
declare #hourBack datetime = dateadd(hour, -1, #roundToDay)
return #hourBack
end
In other words, add an hour on, round down to the nearest day then take an hour off.
Example SQLFiddle

Related

How to get date to be the same day and month from one variable but in the year based on another variable?

I am trying to get the specific day of a Year.
Here's what I have tried till now:-
-- Declare few variables
DECLARE #Currentdate AS DATETIME
DECLARE #DueDate AS DATETIME
DECLARE #NewDate AS DATETIME
-- Set the variables properly, just for testing
SET #Currentdate = GETDATE()
SET #DueDate = DATEADD(MONTH, 2, DATEADD(YEAR, 1, #Currentdate))
-- Check the output
SELECT #Currentdate -- 2013-09-30 00:00:00.000
SELECT #DueDate -- 2014-11-30 00:00:00.000
So, I want to get the #NewDate based on the #Currentdate year.
For this I tried:-
SELECT #NewDate = DATEADD(DAY, DAY(DATEDIFF(day, 1, #DueDate)), DATEADD(MONTH, DATEDIFF(MONTH, 0, #Currentdate), 0))
SELECT #NewDate -- 2013-09-30 00:00:00.000
But it didn't worked. :(
My expected result is like:
-- 2013-11-30 00:00:00.000
-- Having the due date month and date same, but the year as current date one.
Any help is appreciated!
UPDATE
Sorry for all the confusion I have created. My question in simple words is:-
I want to get the a new date variable having the date and the month same as #DueDate variable but the year as given in the #Currentdate variable.
I hope that would clear things up a bit.
If the question is "given I have a particular datetime value in one variable, can I set another variable to be for the same day and month but in the current year" then the answer would be:
declare #DueDate datetime
declare #NewDate datetime
set #DueDate = '20141130'
--Need to set #NewDate to the same month and day in the current year
set #NewDate = DATEADD(year,
--Here's how you work out the offset
DATEPART(year,CURRENT_TIMESTAMP) - DATEPART(year,#DueDate),
#DueDate)
select #DueDate,#NewDate
I want to get the a new date variable having the date and the month same as #DueDate variable but the year as given in the #Currentdate variable.
Well, that's simply the above query with a single tweak:
set #NewDate = DATEADD(year,
--Here's how you work out the offset
DATEPART(year,#Currentdate) - DATEPART(year,#DueDate),
#DueDate)
Try this instead ?
DECLARE #Currentdate AS DATETIME
DECLARE #DueDate AS DATETIME
-- Set the variables properly, just for testing
SET #Currentdate = GETDATE()
SET #DueDate = DATEADD(MONTH, 2, DATEADD(YEAR, 1,
DateAdd(day, datediff(day, 0, #currentDate), 0)))
-- Check the output
SELECT #Currentdate -- 2013-09-30 18:32:35.310
SELECT #DueDate
Using DateAdd(day, datediff(day, 0, #DateTime), 0) strips off the time portion. You should also check out this SO Question/answer.
Try this one:
CAST(CAST( -- cast INT to VARCHAR and then to DATE
YEAR(GETDATE()) * 10000 + MONTH(#DueDate) * 100 + DAY(#DueDate) -- convert current year + #DueDate's month and year parts to YYYYMMDD integer representation
+ CASE -- change 29th of February to 28th if current year is a non-leap year
WHEN MONTH(#DueDate) = 2 AND DAY(#DueDate) = 29 AND ((YEAR(GETDATE()) % 4 = 0 AND YEAR(GETDATE()) % 100 <> 0) OR YEAR(GETDATE()) % 400 = 0) THEN 0
ELSE -1
END
AS VARCHAR(8)) AS DATE)

Convert Datetime column from UTC to local time in select statement

I'm doing a few SQL select queries and would like to convert my UTC datetime column into local time to be displayed as local time in my query results. Note, I am NOT looking to do this conversion via code but rather when I am doing manual and random SQL queries against my databases.
You can do this as follows on SQL Server 2008 or greater:
SELECT CONVERT(datetime,
SWITCHOFFSET(CONVERT(datetimeoffset,
MyTable.UtcColumn),
DATENAME(TzOffset, SYSDATETIMEOFFSET())))
AS ColumnInLocalTime
FROM MyTable
You can also do the less verbose:
SELECT DATEADD(mi, DATEDIFF(mi, GETUTCDATE(), GETDATE()), MyTable.UtcColumn)
AS ColumnInLocalTime
FROM MyTable
Whatever you do, do not use - to subtract dates, because the operation is not atomic, and you will on occasion get indeterminate results due to race conditions between the system datetime and the local datetime being checked at different times (i.e., non-atomically).
Please note that this answer does not take DST into account. If you want to include a DST adjustment, please also see the following SO question:
How to create Daylight Savings time Start and End function in SQL Server
I didn't find any of these example helpful in getting a datetime stored as UTC to a datetime in a specified timezone (NOT the timezone of the server because Azure SQL databases run as UTC). This is how I handled it. It's not elegant but it's simple and gives you the right answer without maintaining other tables:
select CONVERT(datetime, SWITCHOFFSET(dateTimeField, DATEPART(TZOFFSET,
dateTimeField AT TIME ZONE 'Eastern Standard Time')))
If your local date time is say Eastern Standard Time and you want to convert from UTC to that, then in Azure SQL and SQL Server 2016 and above, you can do:
SELECT YourUtcColumn AT TIME ZONE 'UTC' AT TIME ZONE 'Eastern Standard Time' AS
LocalTime
FROM YourTable
The full list of timezone names can be found with:
SELECT * FROM sys.time_zone_info
And yes, the timezones are badly named - even though it is Eastern Standard Time, daylight savings is taken into account.
If you need a conversion other than your server's location, here is a function that allows you to pass a standard offset and accounts for US Daylight Savings Times:
-- =============================================
-- Author: Ron Smith
-- Create date: 2013-10-23
-- Description: Converts UTC to DST
-- based on passed Standard offset
-- =============================================
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,2,dateadd(day,datepart(dw,#SSM)*-1+1,#SSM))
set #FSN = datename(year,#UTC) + '1107'
set #FSN = dateadd(second,-1,dateadd(hour,2,dateadd(day,datepart(dw,#FSN)*-1+1,#FSN)))
-- 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
Using new SQL Server 2016 opportunities:
CREATE FUNCTION ToLocalTime(#dtUtc datetime, #timezoneId nvarchar(256))
RETURNS datetime
AS BEGIN
return #dtUtc AT TIME ZONE 'UTC' AT TIME ZONE #timezoneId
/* -- second way, faster
return SWITCHOFFSET(#dtUtc , DATENAME(tz, #dtUtc AT TIME ZONE #timezoneId))
*/
/* -- third way
declare #dtLocal datetimeoffset
set #dtLocal = #dtUtc AT TIME ZONE #timezoneId
return dateadd(minute, DATEPART (TZoffset, #dtLocal), #dtUtc)
*/
END
GO
But clr procedure works in 5 times faster :'-(
Pay attention that Offset for one TimeZone can change to winter or summer time. For example
select cast('2017-02-08 09:00:00.000' as datetime) AT TIME ZONE 'Eastern Standard Time'
select cast('2017-08-08 09:00:00.000' as datetime) AT TIME ZONE 'Eastern Standard Time'
results:
2017-02-08 09:00:00.000 -05:00
2017-08-08 09:00:00.000 -04:00
You can't just add constant offset.
If enabling CLR on your database is an option as well as using the sql server's timezone, it can be written in .Net quite easily.
public partial class UserDefinedFunctions
{
[Microsoft.SqlServer.Server.SqlFunction]
public static SqlDateTime fn_GetLocalFromUTC(SqlDateTime UTC)
{
if (UTC.IsNull)
return UTC;
return new SqlDateTime(UTC.Value.ToLocalTime());
}
}
A UTC datetime value goes in and the local datetime value relative to the server comes out. Null values return null.
None of these worked for me but this below worked 100%. Hope this can help others trying to convert it like I was.
CREATE FUNCTION [dbo].[fn_UTC_to_EST]
(
#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 = DATEADD(dd,7 + (6-(DATEDIFF(dd,0,DATEADD(mm,(YEAR(GETDATE())-1900) * 12 + 2,0))%7)),DATEADD(mm,(YEAR(GETDATE())-1900) * 12 + 2,0))+'02:00:00'
set #FSN = DATEADD(dd, (6-(DATEDIFF(dd,0,DATEADD(mm,(YEAR(GETDATE())-1900) * 12 + 10,0))%7)),DATEADD(mm,(YEAR(GETDATE())-1900) * 12 + 10,0)) +'02:00:00'
-- 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
Well if you store the data as UTC date in the database you can do something as simple as
select
[MyUtcDate] + getdate() - getutcdate()
from [dbo].[mytable]
this was it's always local from the point of the server and you are not fumbling with AT TIME ZONE 'your time zone name',
if your database get moved to another time zone like a client installation a hard coded time zone might bite you.
There is no simple way to do this in a correct AND generic way.
First of all it must be understood that the offset depends on the date in question, the Time Zone AND DST.
GetDate()-GetUTCDate only gives you the offset today at the server's TZ, which is not relevant.
I have seen only two working solution and I have search a lot.
1) A custom SQL function with a a couple of tables of base data such as Time Zones and DST rules per TZ.
Working but not very elegant. I can't post it since I don't own the code.
EDIT: Here is an example of this method
https://gist.github.com/drumsta/16b79cee6bc195cd89c8
2) Add a .net assembly to the db, .Net can do this very easily. This is working very well but the downside is that you need to configure several parameters on server level and the config is easily broken e.g. if you restore the database.
I use this method but I cant post it since I don't own the code.
This function will convert a UTC time to EST time with DST adjustment. You can change your designed time zone name in this function, or get it from registry:
Create Function fnConvertUTCTimetoESTTime(
#UTCTime as datetime
)
returns datetime
as
begin
return convert(datetime, convert(varchar(23), #UTCTime AT TIME ZONE 'UTC' AT TIME ZONE 'Eastern Standard Time', 121), 121)
end
go
select dbo.fnConvertUTCTimetoESTTime ('2020-3-8 5:00:00.000')
, dbo.fnConvertUTCTimetoESTTime ('2020-3-8 6:00:00.000')
, dbo.fnConvertUTCTimetoESTTime ('2020-3-8 7:00:00.000')
, dbo.fnConvertUTCTimetoESTTime ('2020-3-8 8:00:00.000')
--returns 0:00am, 1:00am, 3:00am, 4:00am
select dbo.fnConvertUTCTimetoESTTime ('2020-11-1 4:00:00.000')
, dbo.fnConvertUTCTimetoESTTime ('2020-11-1 5:00:00.000')
, dbo.fnConvertUTCTimetoESTTime ('2020-11-1 6:00:00.000')
, dbo.fnConvertUTCTimetoESTTime ('2020-11-1 7:00:00.000')
--returns 0:00am, 1:00am, 1:00am, 2:00am
Note you can not just return "#UTCTime AT TIME ZONE 'UTC' AT TIME ZONE 'Eastern Standard Time'" as the result because this result is actually a UTC time in EST format (when you compare this "fake" EST time or include it in an order clause it will be converted back to a UTC time).
The easiest answer is not always at the bottom, but this time it is, and can be seen already somewhere hidden in above comments.
Take your own 'AT TIME ZONE' to capture the TzOffset for your column/data field, and not the current SYSDATETIME.
In below data, 2 queries, one on feb data (DST is off, winter in Amsterdam) +1 diff
and 2nd query on april data in Amsterdam, so +2 hour diff.
select top 2 month(receiveTimeUTC) as MonthInWinterOrSpring
, receiveTimeUTC
, CONVERT(datetime, SWITCHOFFSET(CONVERT(datetimeoffset, receiveTimeUTC), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) as LocalTimeWrongNoDST
, CONVERT(datetime, SWITCHOFFSET(CONVERT(datetimeoffset, receiveTimeUTC), DATENAME(TzOffset, receiveTimeUTC AT TIME ZONE 'Central European Standard Time' ))) as LocalTimeWithDST
from sensordetails order by id
select top 2 month(receiveTimeUTC) as MonthInWinterOrSpring, receiveTimeUTC
, CONVERT(datetime, SWITCHOFFSET(CONVERT(datetimeoffset, receiveTimeUTC), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) as LocalTimeWrongNoDST
, CONVERT(datetime, SWITCHOFFSET(CONVERT(datetimeoffset, receiveTimeUTC), DATENAME(TzOffset, receiveTimeUTC AT TIME ZONE 'Central European Standard Time' ))) as LocalTimeWithDST
from sensordetails order by id desc
Results:
So this is a T-SQL (SQL Server Answer), no need for storedproc of functions.
For Azure SQL and ##Version >= SQL Server 2016 users, Below is a simple function using AT TIME ZONE.
CREATE FUNCTION [dbo].[Global_Convert_UTCTimeTo_LocalTime]
(
#LocalTimeZone VARCHAR(50),
#UTCDateTime DATETIME
)
RETURNS DATETIME
AS
BEGIN
DECLARE #ConvertedDateTime DATETIME;
SELECT #ConvertedDateTime = #UTCDateTime AT TIME ZONE 'UTC' AT TIME ZONE #LocalTimeZone
RETURN #ConvertedDateTime
END
GO
For types of values that #LocalTimeZone can take, please go to this link or Go to KEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones
Here's a version that accounts for daylight savings, UTC offset, and is not locked into a particular year.
---------------------------------------------------------------------------------------------------
--Name: udfToLocalTime.sql
--Purpose: To convert UTC to local US time accounting for DST
--Author: Patrick Slesicki
--Date: 3/25/2014
--Notes: Works on SQL Server 2008R2 and later, maybe SQL Server 2008 as well.
-- Good only for US States observing the Energy Policy Act of 2005.
-- Function doesn't apply for years prior to 2007.
-- Function assumes that the 1st day of the week is Sunday.
--Tests:
-- SELECT dbo.udfToLocalTime('2014-03-09 9:00', DEFAULT)
-- SELECT dbo.udfToLocalTime('2014-03-09 10:00', DEFAULT)
-- SELECT dbo.udfToLocalTime('2014-11-02 8:00', DEFAULT)
-- SELECT dbo.udfToLocalTime('2014-11-02 9:00', DEFAULT)
---------------------------------------------------------------------------------------------------
ALTER FUNCTION udfToLocalTime
(
#UtcDateTime AS DATETIME
,#UtcOffset AS INT = -8 --PST
)
RETURNS DATETIME
AS
BEGIN
DECLARE
#PstDateTime AS DATETIME
,#Year AS CHAR(4)
,#DstStart AS DATETIME
,#DstEnd AS DATETIME
,#Mar1 AS DATETIME
,#Nov1 AS DATETIME
,#MarTime AS TIME
,#NovTime AS TIME
,#Mar1Day AS INT
,#Nov1Day AS INT
,#MarDiff AS INT
,#NovDiff AS INT
SELECT
#Year = YEAR(#UtcDateTime)
,#MarTime = CONVERT(TIME, DATEADD(HOUR, -#UtcOffset, '1900-01-01 02:00'))
,#NovTime = CONVERT(TIME, DATEADD(HOUR, -#UtcOffset - 1, '1900-01-01 02:00'))
,#Mar1 = CONVERT(CHAR(16), #Year + '-03-01 ' + CONVERT(CHAR(5), #MarTime), 126)
,#Nov1 = CONVERT(CHAR(16), #Year + '-11-01 ' + CONVERT(CHAR(5), #NovTime), 126)
,#Mar1Day = DATEPART(WEEKDAY, #Mar1)
,#Nov1Day = DATEPART(WEEKDAY, #Nov1)
--Get number of days between Mar 1 and DST start date
IF #Mar1Day = 1 SET #MarDiff = 7
ELSE SET #MarDiff = 15 - #Mar1Day
--Get number of days between Nov 1 and DST end date
IF #Nov1Day = 1 SET #NovDiff = 0
ELSE SET #NovDiff = 8 - #Nov1Day
--Get DST start and end dates
SELECT
#DstStart = DATEADD(DAY, #MarDiff, #Mar1)
,#DstEnd = DATEADD(DAY, #NovDiff, #Nov1)
--Change UTC offset if #UtcDateTime is in DST Range
IF #UtcDateTime >= #DstStart AND #UtcDateTime < #DstEnd SET #UtcOffset = #UtcOffset + 1
--Get Conversion
SET #PstDateTime = DATEADD(HOUR, #UtcOffset, #UtcDateTime)
RETURN #PstDateTime
END
GO
I found the one off function way to be too slow when there is a lot of data. So I did it through joining to a table function that would allow for a calculation of the hour diff. It is basically datetime segments with the hour offset. A year would be 4 rows. So the table function
dbo.fn_getTimeZoneOffsets('3/1/2007 7:00am', '11/5/2007 9:00am', 'EPT')
would return this table:
startTime endTime offset isHr2
3/1/07 7:00 3/11/07 6:59 -5 0
3/11/07 7:00 11/4/07 6:59 -4 0
11/4/07 7:00 11/4/07 7:59 -5 1
11/4/07 8:00 11/5/07 9:00 -5 0
It does account for daylight savings. A sample of how it is uses is below and the full blog post is here.
select mt.startTime as startUTC,
dateadd(hh, tzStart.offset, mt.startTime) as startLocal,
tzStart.isHr2
from MyTable mt
inner join dbo.fn_getTimeZoneOffsets(#startViewUTC, #endViewUTC, #timeZone) tzStart
on mt.startTime between tzStart.startTime and tzStart.endTime
declare #mydate2 datetime
set #mydate2=Getdate()
select #mydate2 as mydate,
dateadd(minute, datediff(minute,getdate(),#mydate2),getutcdate())
In postgres this works very nicely..Tell the server the time at which the time is saved, 'utc', and then ask it to convert to a specific timezone, in this case 'Brazil/East'
quiz_step_progresses.created_at at time zone 'utc' at time zone 'Brazil/East'
Get a complete list of timezones with the following select;
select * from pg_timezone_names;
See details here.
https://popsql.com/learn-sql/postgresql/how-to-convert-utc-to-local-time-zone-in-postgresql
Ron's answer contains an error. It uses 2:00 AM local time where the UTC equivalent is required. I don't have enough reputation points to comment on Ron's answer so a corrected version appears below:
-- =============================================
-- Author: Ron Smith
-- Create date: 2013-10-23
-- Description: Converts UTC to DST
-- based on passed Standard offset
-- =============================================
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,2 - #StandardOffset,dateadd(day,datepart(dw,#SSM)*-1+1,#SSM))
set #FSN = datename(year,#UTC) + '1107'
set #FSN = dateadd(second,-1,dateadd(hour,2 - (#StandardOffset + 1),dateadd(day,datepart(dw,#FSN)*-1+1,#FSN)))
-- 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
The UNIX timestamp is merely the number of seconds between a particular date and the Unix Epoch,
SELECT DATEDIFF(SECOND,{d '1970-01-01'},GETDATE()) // This Will Return the UNIX timestamp In SQL server
you can create a function for local date time to Unix UTC conversion using Country Offset
Function to Unix Time Stamp In SQL server
It's simple. Try this for Azure SQL Server:
SELECT YourDateTimeColumn AT TIME ZONE 'Eastern Standard Time' FROM YourTable
For Local SQL Server :
SELECT CONVERT(datetime2, SWITCHOFFSET(CONVERT(datetimeoffset, gETDATE()), DATENAME(TzOffset, gETDATE() AT TIME ZONE 'Eastern Standard Time'))) FROM YourTable
For anyone still trying to solve this issue, here's a proof of concept that works in SQL Server 2017
declare
#StartDate date = '2020-01-01'
;with cte_utc as
(
select
1 as i
,CONVERT(datetime, #StartDate) AS UTC
,datepart(weekday, CONVERT(datetime, #StartDate)) as Weekday
,datepart(month, CONVERT(datetime, #StartDate)) as [Month]
,datepart(YEAR, CONVERT(datetime, #StartDate)) as [Year]
union all
Select
i + 1
,dateadd(d, 1, utc)
,datepart(weekday, CONVERT(datetime, dateadd(d, 1, utc))) as Weekday
,datepart(month, CONVERT(datetime, dateadd(d, 1, utc))) as [Month]
,datepart(YEAR, CONVERT(datetime, dateadd(d, 1, utc))) as [Year]
from
cte_utc
where
(i + 1) < 32767
), cte_utc_dates as
(
select
*,
DENSE_RANK()OVER(PARTITION BY [Year], [Month], [Weekday] ORDER BY Utc) WeekDayIndex
from
cte_utc
), cte_hours as (
select 0 as [Hour]
union all
select [Hour] + 1 from cte_hours where [Hour] < 23
)
select
d.*
, DATEADD(hour, h.Hour, d.UTC) AS UtcTime
,CONVERT(datetime, DATEADD(hour, h.Hour, d.UTC) AT TIME ZONE 'UTC' AT TIME ZONE 'Central Standard Time') CST
,CONVERT(datetime, DATEADD(hour, h.Hour, d.UTC) AT TIME ZONE 'UTC' AT TIME ZONE 'Eastern Standard Time') EST
from
cte_utc_dates d, cte_hours h
where
([Month] = 3 and [Weekday] = 1 and WeekDayIndex = 2 )-- dst start
or
([Month] = 11 and [Weekday] = 1 and WeekDayIndex = 1 )-- dst end
order by
utc
OPTION (MAXRECURSION 32767)
GO
As a warning - if you're going to use the following (note the milliseconds instead of minutes):
SELECT DATEADD(ms, DATEDIFF(ms, GETUTCDATE(), GETDATE()), MyTable.UtcColumn)
AS ColumnInLocalTime
FROM MyTable
Keep in mind that the DATEDIFF part will not always return the same number. So don't use it to compare DateTimes down to milliseconds.
This should be able to get server time with DST
declare #dt datetime
set #dt = getutcdate() -- GMT equivalent
sysdatetimeoffset takes DST into account
select [InputTime] = #dt
, [LocalTime2] = dateadd(mi, datediff(mi, sysdatetimeoffset(),getdate()), #dt)
First function: configured for italian time zone (+1, +2), switch dates: last sunday of march and october, return the difference between the current time zone and the datetime as parameter.
Returns:
current timezone < parameter timezone ==> +1
current timezone > parameter timezone ==> -1
else 0
The code is:
CREATE FUNCTION [dbo].[UF_ADJUST_OFFSET]
(
#dt_utc datetime2(7)
)
RETURNS INT
AS
BEGIN
declare #month int,
#year int,
#current_offset int,
#offset_since int,
#offset int,
#yearmonth varchar(8),
#changeoffsetdate datetime2(7)
declare #lastweek table(giorno datetime2(7))
select #current_offset = DATEDIFF(hh, GETUTCDATE(), GETDATE())
select #month = datepart(month, #dt_utc)
if #month < 3 or #month > 10 Begin Set #offset_since = 1 Goto JMP End
if #month > 3 and #month < 10 Begin Set #offset_since = 2 Goto JMP End
--If i'm here is march or october
select #year = datepart(yyyy, #dt_utc)
if #month = 3
Begin
Set #yearmonth = cast(#year as varchar) + '-03-'
Insert Into #lastweek Values(#yearmonth + '31 03:00:00.000000'),(#yearmonth + '30 03:00:00.000000'),(#yearmonth + '29 03:00:00.000000'),(#yearmonth + '28 03:00:00.000000'),
(#yearmonth + '27 03:00:00.000000'),(#yearmonth + '26 03:00:00.000000'),(#yearmonth + '25 03:00:00.000000')
--Last week of march
Select #changeoffsetdate = giorno From #lastweek Where datepart(weekday, giorno) = 1
if #dt_utc < #changeoffsetdate
Begin
Set #offset_since = 1
End Else Begin
Set #offset_since = 2
End
End
if #month = 10
Begin
Set #yearmonth = cast(#year as varchar) + '-10-'
Insert Into #lastweek Values(#yearmonth + '31 03:00:00.000000'),(#yearmonth + '30 03:00:00.000000'),(#yearmonth + '29 03:00:00.000000'),(#yearmonth + '28 03:00:00.000000'),
(#yearmonth + '27 03:00:00.000000'),(#yearmonth + '26 03:00:00.000000'),(#yearmonth + '25 03:00:00.000000')
--Last week of october
Select #changeoffsetdate = giorno From #lastweek Where datepart(weekday, giorno) = 1
if #dt_utc > #changeoffsetdate
Begin
Set #offset_since = 1
End Else Begin
Set #offset_since = 2
End
End
JMP:
if #current_offset < #offset_since Begin
Set #offset = 1
End Else if #current_offset > #offset_since Set #offset = -1 Else Set #offset = 0
Return #offset
END
Then the function that convert date
CREATE FUNCTION [dbo].[UF_CONVERT]
(
#dt_utc datetime2(7)
)
RETURNS datetime
AS
BEGIN
declare #offset int
Select #offset = dbo.UF_ADJUST_OFFSET(#dt_utc)
if #dt_utc >= '9999-12-31 22:59:59.9999999'
set #dt_utc = '9999-12-31 23:59:59.9999999'
Else
set #dt_utc = (SELECT DATEADD(mi, DATEDIFF(mi, GETUTCDATE(), GETDATE()), #dt_utc) )
if #offset <> 0
Set #dt_utc = dateadd(hh, #offset, #dt_utc)
RETURN #dt_utc
END
-- get indian standard time from utc
CREATE FUNCTION dbo.getISTTime
(
#UTCDate datetime
)
RETURNS datetime
AS
BEGIN
RETURN dateadd(minute,330,#UTCDate)
END
GO
You have to reformat the string as well as converting to the correct time. In this case I needed Zulu time.
Declare #Date datetime;
Declare #DateString varchar(50);
set #Date = GETDATE();
declare #ZuluTime datetime;
Declare #DateFrom varchar (50);
Declare #DateTo varchar (50);
set #ZuluTime = DATEADD(second, DATEDIFF(second, GETDATE(), GETUTCDATE()), #Date);
set #DateString = FORMAT(#ZuluTime, 'yyyy-MM-ddThh:mm:ssZ', 'en-US' )
select #DateString;
Best way for Oracle:
With hardcoded datetime:
SELECT TO_CHAR(CAST((FROM_TZ(CAST(TO_DATE('2018-10-27 21:00', 'YYYY-MM-DD HH24:MI') AS TIMESTAMP), 'UTC') AT TIME ZONE 'EET') AS DATE), 'YYYY-MM-DD HH24:MI') UTC_TO_EET FROM DUAL
Result:
2018-10-28 00:00
With column and table names:
SELECT TO_CHAR(CAST((FROM_TZ(CAST(COLUMN_NAME AS TIMESTAMP), 'UTC') AT TIME ZONE 'EET') AS DATE), 'YYYY-MM-DD HH24:MI') UTC_TO_EET FROM TABLE_NAME
I have code to perform UTC to Local and Local to UTC times which allows conversion using code like this
DECLARE #usersTimezone VARCHAR(32)='Europe/London'
DECLARE #utcDT DATETIME=GetUTCDate()
DECLARE #userDT DATETIME=[dbo].[funcUTCtoLocal](#utcDT, #usersTimezone)
and
DECLARE #usersTimezone VARCHAR(32)='Europe/London'
DECLARE #userDT DATETIME=GetDate()
DECLARE #utcDT DATETIME=[dbo].[funcLocaltoUTC](#userDT, #usersTimezone)
The functions can support all or a subset of timezones in the IANA/TZDB as provided by NodaTime - see the full list at https://nodatime.org/TimeZones
Be aware that my use case means I only need a 'current' window, allowing the conversion of times within the range of about +/- 5 years from now. This means that the method I've used probably isn't suitable for you if you need a very wide period of time, due to the way it generates code for each timezone interval in a given date range.
The project is on GitHub: https://github.com/elliveny/SQLServerTimeConversion
This generates SQL function code as per this example
I used switchoffset to convert from utc time to local time. The time zone offset can be determined by using datename(tzoffset,systemdatetimeoffset()). Likewise if you want to get the elapsed time then use getutcdate keeping the time in utc time for the datediff function.
select
,[Field1]
,Format(SWITCHOFFSET([MyDateOnUTC],DATENAME(TZOFFSET, SYSDATETIMEOFFSET())),'MM/dd/yyyy hh:mm:ss tt') UtcToLocalTime
,datediff(minute,[ClaimedOnUTC],getutcdate()) ElapsedMinutes
from dbo.my_table
Here's a simpler one that takes dst in to account
CREATE FUNCTION [dbo].[UtcToLocal]
(
#p_utcDatetime DATETIME
)
RETURNS DATETIME
AS
BEGIN
RETURN DATEADD(MINUTE, DATEDIFF(MINUTE, GETUTCDATE(), #p_utcDatetime), GETDATE())
END
I've found that this function is faster than other solutions using a separate table or loops. It's just a basic case statement. Given that all months between April and October have a -4-hour offset (Eastern Time) we just need to add a few more case lines for the fringe days. Otherwise, the offset is -5 hours.
This is specific to a conversion from UTC to Eastern time, but additional time zone functions can be added as needed.
USE [YourDatabaseName]
GO
/****** Object: UserDefinedFunction [dbo].[ConvertUTCtoEastern] Script Date: 11/2/2016 5:21:52 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION [dbo].[ConvertUTCtoEastern]
(
#dtStartDate DATETIME
)
RETURNS DATETIME
AS
BEGIN
DECLARE #Working DATETIME
DECLARE #Returned DATETIME
SET #Working = #dtStartDate
SET #Working =
case when month(#Working) between 4 and 10 then dateadd(HH,-4,#Working)
when #Working between '2017-03-12' and '2017-11-05' then dateadd(HH,-4,#Working)
when #Working between '2016-03-13' and '2016-11-06' then dateadd(HH,-4,#Working)
when #Working between '2015-03-08' and '2015-11-01' then dateadd(HH,-4,#Working)
when #Working between '2014-03-09' and '2014-11-02' then dateadd(HH,-4,#Working)
when #Working between '2013-03-10' and '2013-11-03' then dateadd(HH,-4,#Working)
when #Working between '2012-03-11' and '2012-11-04' then dateadd(HH,-4,#Working)
else dateadd(HH,-5,#Working) end
SET #Returned = #Working
RETURN #Returned
END
GO

How to get Saturday's Date (Or any other weekday's Date)- SQL Server

How to get Saturday's Date. I have today's date with me.
GETDATE()
How to do this.
For eg. TODAY is 08-08-2011
I want output as 08-13-2011
This is a function that will return the next Saturday if you call it like this:
SELECT dbo.fn_Get_NextWeekDay('2011-08-08', 6)
The "6" comes from the list of possible values you can set for DATEFIRST.
You can get any other day of the week by changing the second parameter accordingly.
This is the function:
IF OBJECT_ID('dbo.fn_Get_NextWeekDay') IS NOT NULL
DROP FUNCTION dbo.fn_Get_NextWeekDay
GO
CREATE FUNCTION dbo.fn_Get_NextWeekDay(
#aDate DATETIME
, #dayofweek INT
/*
#dw - day of the week
1 - Monday
2 - Tuesday
3 - Wednesday
4 - Thursday
5 - Friday
6 - Saturday
7 - Sunday
*/
)
RETURNS DATETIME
AS
/*
SELECT dbo.fn_Get_NextWeekDay('2011-08-08', 6)
SELECT dbo.fn_Get_NextWeekDay('2011-08-08', 1)
*/
BEGIN
RETURN
DATEADD(day
, ( #dayofweek + 8 - DATEPART(dw, #aDate) - ##DATEFIRST ) % 7
, #aDate
)
END
GO
[EDIT]
This might be another solution. This should work in any language:
IF OBJECT_ID('dbo.fn_NextWeekDay') IS NOT NULL
DROP FUNCTION dbo.fn_NextWeekDay
GO
CREATE FUNCTION dbo.fn_NextWeekDay(
#aDate DATE
, #dayofweek NVARCHAR(30)
)
RETURNS DATE
AS
/*
SELECT dbo.fn_NextWeekDay('2016-12-14', 'fri')
SELECT dbo.fn_NextWeekDay('2016-03-15', 'mon')
*/
BEGIN
DECLARE #dx INT = 6
WHILE UPPER(DATENAME(weekday,#aDate)) NOT LIKE UPPER(#dayofweek) + '%'
BEGIN
SET #aDate = DATEADD(day,1,#aDate)
SET #dx=#dx-1
if #dx < 0
BEGIN
SET #aDate = NULL
BREAK
END
END
RETURN #aDate
END
GO
Use DATEPART to get the day of week of today and add the difference to the desired day of week to todays date.
DECLARE #Today date = 'TODAYS-DATE';
DECLARE #TodayNumber int = DATEPART(dw, #Today) -- Get the day number
DECLARE #Saturday date = DATEADD(DAY, (6-#TodayNumber)%7, #Today)
-- Add the number of days between today and saturday (the 6th day), modulus 7 to stop you adding negative days
Hope that helps!
Use a Calendar table (table with one row per date):
SELECT MIN(DateValue) DateValue
FROM Calendar
WHERE DateValue >= CURRENT_TIMESTAMP
AND DayOfWeek = 'Saturday';
Another approach to this takes two steps, but might be more readable (look ma, no modulus):
Go back to last saturday: DATEADD(DAY, -1 * datepart(weekday, GETDATE()), getdate())
Then, add on a week: DATEADD(WEEK, 1, #lastSaturday, getdate()))
The whole thing:
declare #today DATETIME = GETDATE()
declare #lastSaturday DATETIME = DATEADD(DAY, -1 * datepart(weekday, #today), #today)
declare #nextSaturday DATETIME = DATEADD(WEEK, 1, #lastSaturday)
Or, if you're ok with #today being GETDATE(), you can do the calculation all at once:
SELECT DATEADD(WEEK, 1, DATEADD(DAY, -1 * datepart(weekday, GETDATE()), getdate()))
Checkout the SQL DATEADD function.
DATEADD (Transact-SQL)
Which you can use this along with DATEPART function to return the correct date.
DATEPART (Transact-SQL)
Try this :
SET DATEFIRST 7
DECLARE #d DATETIME
SET #d = '2011-08-08' --GETDATE()
SELECT NEXT_SAT = DATEADD(day, (7 + ##DATEFIRST - DATEPART(dw, #d)) % 7, #d )
declare #Curdate date=( SELECT SWITCHOFFSET(SYSDATETIMEOFFSET(),'+05:30') )
declare #nextsaturdaydate date=(select dateadd(d, 7-datepart(WEEKDAY, #CurDate),#Curdate))
select #nextsaturdaydate

ADD time 23:59:59.999 to end date for between

I have been having an issue with using the following:
Column_Name BETWEEN #StartDate AND #EndDate.
This is because the #EndDate = 00:00:00.000 for the time, which doesn't pick up all the values for that day.
How would I convert the #EndDate (Always 00:00:00.000) to always be Date + 23:59:59.999?
One option that avoids needing to add EndDate + 23:59:59.999 is to not use the between comparison and instead use column_name >= #StartDate and column_name < #EndDate +1
Please note the accuracy and rounding of the DATETIME type in SQL Server 2005:
datetime values are rounded to increments of .000, .003, or .007 seconds
SQL Server 2008 introduced the DATETIME2 type which has an accuracy of 100 nanoseconds. So in SQL Server 2008 you could do:
DECLARE #d DATETIME = '2011-10-07 00:00:00.000'
SELECT DATEADD(MS, -1, DATEADD(D, 1, CONVERT(DATETIME2, #d)))
Alternatively you may want to avoid the BETWEEN operator in this case:
#StartDate <= Column_Name AND Column_Name < DATEADD(D, 1, #EndDate)
Since the advent of datetime2 datatype, I have been struggling with this problem. To calculate the end of day as a datetime2 datatype I add the number of seconds in a day to the =date= then subtract 100 nanoseconds. Voila:
declare #bod datetime2
declare #eod datetime2
set #bod = cast (GETDATE() as DATE)
set #eod = DATEADD(ns, -100, DATEADD(s, 86400, #bod))
print #bod
print #eod
-- answer:
2013-12-01 00:00:00.0000000
2013-12-01 23:59:59.9999999
Now I'm off to datetimeoffset data type.
You can change the time in a date like this (I'm using getdate() as an example):
select cast(convert(char(8), getdate(), 112) + ' 23:59:59.99' as datetime)
Explanation:
convert(char(8), getdate(), 112) converts the date to yyyymmdd format (as string).
Then you can just append the desired time, and convert the whole string to datetime again.
EDIT:
It slows the performance when you do the casting on a database column, yes.
But he has a datetime variable and he just uses the casting to change the time in the variable once
--> I see no performance issue if he uses my code to change his #EndDate variable.
Valid point, however. Casting is not a good solution in all situations.
You could also do this:
select #endDate = dateadd(ms,-3,dateadd(day,1,DATEADD(dd, DATEDIFF(dd,0,#endDate), 0)))
when #endDate is '5/3/2013'
--Execution / post date is 1 Feb. 2020
-- sql server this month start and end
select DATEADD(month, DATEDIFF(month, 0, getdate()), 0) -- 2020-02-01 00:00:00.000
select DATEADD(second,-1, datediff(day,0,EOMONTH(getdate()))+1) -- 2020-02-29 23:59:59.000
-- sql server this day start and end
select DATEADD(day, DATEDIFF(day, 0, getdate()), 0) -- 2020-02-01 00:00:00.000
select DATEADD(second,-1, datediff(dd,0,getdate())+1) -- 2020-02-01 23:59:59.000
-- sql server last 30 days start and end
select DATEADD(day, -30, DATEDIFF(day, 0, getdate())) -- 2020-01-02 00:00:00.000
select DATEADD(second,-1, datediff(dd,0,getdate())+1) -- 2020-02-01 23:59:59.000
You can use between if your end date is set to 00:00:00 of the next day:
ColumnName between #StartDate and convert(datetime, convert(date, #EndDate + 1))
This converts the next day to a date, which removes the hours information, then you convert it back to a datetime which adds default hour information: 00:00:00.
You can use any variant of the date from parts functions. For example, let's use DATETIMEFROMPARTS:
DECLARE #d DATETIME2(0) = '2011-10-07 04:32:12.000';
SELECT DATETIMEFROMPARTS(YEAR(#d), MONTH(#d), DAY(#d), 23, 59, 59, 0);
-- 2011-10-07 23:59:59
I first convert the original datetime to begin of the day, then add hours and seconds to it:
DECLARE #start DATETIME, #end DATETIME
SET #start = DATEADD(DAY, DATEDIFF(DAY, 0, GETDATE()), 0)
SET #end = DATEADD(HOUR, 23, DATEADD(n, 59, #start))
PRINT #start
PRINT #end
Oct 27 2017 12:00AM
Oct 27 2017 11:59PM

How to calculate the local datetime from a utc datetime in tsql (sql 2005)?

i want to loop over a period of time in tsql, and print the utc datetimes and our local variant.
We live in UTC +1, so i could easily add 1 hour, but in the summertime we live in UTC +2.
In C# i can create a datetime and use a method to ask for the UTC variant and vice versa.
Till now i have this:
declare #counter int
declare #localdate datetime
declare #utcdate datetime
set #counter = 0
while #counter < 100
begin
set #counter = #counter + 1
print 'The counter is ' + cast(#counter as char)
set #utcdate = DATEADD(day,#counter,GETUTCDATE())
--set #localdate = ????
print #localdate
print #utcdate
end
I've been waiting for 5 years for a more elegant solution but since one has not emerged, I'll post what I've been using thus far...
CREATE FUNCTION [dbo].[UDTToLocalTime](#UDT AS DATETIME)
RETURNS DATETIME
AS
BEGIN
--====================================================
--Set the Timezone Offset (NOT During DST [Daylight Saving Time])
--====================================================
DECLARE #Offset AS SMALLINT
SET #Offset = -5
--====================================================
--Figure out the Offset Datetime
--====================================================
DECLARE #LocalDate AS DATETIME
SET #LocalDate = DATEADD(hh, #Offset, #UDT)
--====================================================
--Figure out the DST Offset for the UDT Datetime
--====================================================
DECLARE #DaylightSavingOffset AS SMALLINT
DECLARE #Year as SMALLINT
DECLARE #DSTStartDate AS DATETIME
DECLARE #DSTEndDate AS DATETIME
--Get Year
SET #Year = YEAR(#LocalDate)
--Get First Possible DST StartDay
IF (#Year > 2006) SET #DSTStartDate = CAST(#Year AS CHAR(4)) + '-03-08 02:00:00'
ELSE SET #DSTStartDate = CAST(#Year AS CHAR(4)) + '-04-01 02:00:00'
--Get DST StartDate
WHILE (DATENAME(dw, #DSTStartDate) <> 'sunday') SET #DSTStartDate = DATEADD(day, 1,#DSTStartDate)
--Get First Possible DST EndDate
IF (#Year > 2006) SET #DSTEndDate = CAST(#Year AS CHAR(4)) + '-11-01 02:00:00'
ELSE SET #DSTEndDate = CAST(#Year AS CHAR(4)) + '-10-25 02:00:00'
--Get DST EndDate
WHILE (DATENAME(dw, #DSTEndDate) <> 'sunday') SET #DSTEndDate = DATEADD(day,1,#DSTEndDate)
--Get DaylightSavingOffset
SET #DaylightSavingOffset = CASE WHEN #LocalDate BETWEEN #DSTStartDate AND #DSTEndDate THEN 1 ELSE 0 END
--====================================================
--Finally add the DST Offset
--====================================================
RETURN DATEADD(hh, #DaylightSavingOffset, #LocalDate)
END
GO
Notes:
This is for North American servers that observer Daylight Saving Time. Please change the variable #Offest to the Timezone offset of the server running the SQL function (While NOT Observing the Daylight Savings time)...
--====================================================
--Set the Timezone Offset (NOT During DST [Daylight Saving Time])
--====================================================
DECLARE #Offset AS SMALLINT
SET #Offset = -5
As the DST rules change update them here...
--Get First Possible DST StartDay
IF (#Year > 2006) SET #DSTStartDate = CAST(#Year AS CHAR(4)) + '-03-08 02:00:00'
ELSE SET #DSTStartDate = CAST(#Year AS CHAR(4)) + '-04-01 02:00:00'
--Get DST StartDate
WHILE (DATENAME(dw, #DSTStartDate) <> 'sunday') SET #DSTStartDate = DATEADD(day, 1,#DSTStartDate)
--Get First Possible DST EndDate
IF (#Year > 2006) SET #DSTEndDate = CAST(#Year AS CHAR(4)) + '-11-01 02:00:00'
ELSE SET #DSTEndDate = CAST(#Year AS CHAR(4)) + '-10-25 02:00:00'
--Get DST EndDate
WHILE (DATENAME(dw, #DSTEndDate) <> 'sunday') SET #DSTEndDate = DATEADD(day,1,#DSTEndDate)
Cheers,
Assuming you are using SQL 2005 upwards, you can develop a SQL CLR function to take a UTC date and convert to the local date.
This link is an MSDN How-To explaining how you can create a scalar UDF in C#.
Create a SQL function along the lines of
[SqlFunction()]
public static SqlDateTime ConvertUtcToLocal(SqlDateTime utcDate)
{
// over to you to convert SqlDateTime to DateTime, specify Kind
// as UTC, convert to local time, and convert back to SqlDateTime
}
Your sample above would then become
set #localdate = dbo.ConvertUtcToLocal(#utcdate)
SQL CLR has its overheads in terms of deployment, but I feel cases like this are where it fits in best.
This solution seems too obvious.
If you can get UTC Date with GETUTCDATE() and you can get your local date with GETDATE() you have an offset that you can apply for any datetime
SELECT DATEADD(hh, DATEPART(hh, GETDATE() - GETUTCDATE()) - 24, GETUTCDATE())
this should return the local time you executed the query,
SELECT DATEADD(hh, DATEPART(hh, GETDATE() - GETUTCDATE()) - 24, N'1/14/2011 7:00:00' )
this will return 2011-01-14 02:00:00.000 because i'm in UTC +5
Unless I'm missing something?
You can use my SQL Server Time Zone Support project to convert between IANA standard time zones, as listed here.
Example:
SELECT Tzdb.UtcToLocal('2015-07-01 00:00:00', 'America/Los_Angeles')
While the question's title mentions SQL Server 2005, the question is tagged with SQL Server in general.
For SQL Server 2016 and later, you can use:
SELECT yourUtcDateTime AT TIME ZONE 'Mountain Standard Time'
A list of time zones is available with SELECT * FROM sys.time_zone_info
Here is a function (again US ONLY) but it is a bit more flexible. It will convert a UTC date to the server local time.
It starts by adjusting the appointment date based on the current offset and then adjusts based on the difference of the current offset and the offset of the date of the appointment.
CREATE FUNCTION [dbo].[fnGetServerTimeFromUTC]
(
#AppointmentDate AS DATETIME,
#DateTimeOffset DATETIMEOFFSET
)
RETURNS DATETIME
AS
BEGIN
--DECLARE #AppointmentDate DATETIME;
--SET #AppointmentDate = '2016-12-01 12:00:00'; SELECT #AppointmentDate;
--Get DateTimeOffset from Server
--DECLARE #DateTimeOffset; SET #DateTimeOffset = SYSDATETIMEOFFSET();
DECLARE #DateTimeOffsetStr NVARCHAR(34) = #DateTimeOffset;
--Set a standard DatePart value for Sunday (server configuration agnostic)
DECLARE #dp_Sunday INT = 7 - ##DATEFIRST + 1;
--2006 DST Start First Sunday in April (earliest is 04-01) Ends Last Sunday in October (earliest is 10-25)
--2007 DST Start Second Sunday March (earliest is 03-08) Ends First Sunday Nov (earliest is 11-01)
DECLARE #Start2006 NVARCHAR(6) = '04-01-';
DECLARE #End2006 NVARCHAR(6) = '10-25-';
DECLARE #Start2007 NVARCHAR(6) = '03-08-';
DECLARE #End2007 NVARCHAR(6) = '11-01-';
DECLARE #ServerDST SMALLINT = 0;
DECLARE #ApptDST SMALLINT = 0;
DECLARE #Start DATETIME;
DECLARE #End DATETIME;
DECLARE #CurrentMinuteOffset INT;
DECLARE #str_Year NVARCHAR(4) = LEFT(#DateTimeOffsetStr,4);
DECLARE #Year INT = CONVERT(INT, #str_Year);
SET #CurrentMinuteOffset = CONVERT(INT, SUBSTRING(#DateTimeOffsetStr,29,3)) * 60 + CONVERT(INT, SUBSTRING(#DateTimeOffsetStr,33,2)); --Hours + Minutes
--Determine DST Range for Server Offset
SET #Start = CASE
WHEN #Year <= 2006 THEN CONVERT(DATETIME, #Start2006 + #str_Year + ' 02:00:00')
ELSE CONVERT(DATETIME, #Start2007 + #str_Year + ' 02:00:00')
END;
WHILE #dp_Sunday <> DATEPART(WEEKDAY, #Start) BEGIN
SET #Start = DATEADD(DAY, 1, #Start)
END;
SET #End = CASE
WHEN #Year <= 2006 THEN CONVERT(DATETIME, #End2006 + #str_Year + ' 02:00:00')
ELSE CONVERT(DATETIME, #End2007 + #str_Year + ' 02:00:00')
END;
WHILE #dp_Sunday <> DATEPART(WEEKDAY, #End) BEGIN
SET #End = DATEADD(DAY, 1, #End)
END;
--Determine Current Offset based on Year
IF #DateTimeOffset >= #Start AND #DateTimeOffset < #End SET #ServerDST = 1;
--Determine DST status of Appointment Date
SET #Year = YEAR(#AppointmentDate);
SET #Start = CASE
WHEN #Year <= 2006 THEN CONVERT(DATETIME, #Start2006 + #str_Year + ' 02:00:00')
ELSE CONVERT(DATETIME, #Start2007 + #str_Year + ' 02:00:00')
END;
WHILE #dp_Sunday <> DATEPART(WEEKDAY, #Start) BEGIN
SET #Start = DATEADD(DAY, 1, #Start)
END;
SET #End = CASE
WHEN #Year <= 2006 THEN CONVERT(DATETIME, #End2006 + #str_Year + ' 02:00:00')
ELSE CONVERT(DATETIME, #End2007 + #str_Year + ' 02:00:00')
END;
WHILE #dp_Sunday <> DATEPART(WEEKDAY, #End) BEGIN
SET #End = DATEADD(DAY, 1, #End)
END;
--Determine Appointment Offset based on Year
IF #AppointmentDate >= #Start AND #AppointmentDate < #End SET #ApptDST = 1;
SET #AppointmentDate = DATEADD(MINUTE, #CurrentMinuteOffset + 60 * (#ApptDST - #ServerDST), #AppointmentDate)
RETURN #AppointmentDate
END
GO
For those stuck in SQL Server 2005 and don't want or can't use a udf - and particularly does outside of the USA - I've taken #Bobman's approach and generalized it. The following will work in the USA, Europe, New Zealand and Australia, with the caveat that not all Australian states observe DST, even states that are in the same "base" timezone. It's also easy to add DST-rules that aren't yet supported, just add a line to the #calculation values.
-- =============================================
-- Author: Herman Scheele
-- Create date: 20-08-2016
-- Description: Convert UTC datetime to local datetime
-- based on server time-distance from utc.
-- =============================================
create function dbo.UTCToLocalDatetime(#UTCDatetime datetime)
returns datetime as begin
declare #LocalDatetime datetime, #DSTstart datetime, #DSTend datetime
declare #calculation table (
frm smallint,
til smallint,
since smallint,
firstPossibleStart datetime,-- Put both of these with local non-DST time!
firstPossibleEnd datetime -- (In Europe we turn back the clock from 3 AM to 2 AM, which means it happens 2 AM non-DST time)
)
insert into #calculation
values
(-9, -2, 1967, '1900-04-24 02:00', '1900-10-25 01:00'), -- USA first DST implementation
(-9, -2, 1987, '1900-04-01 02:00', '1900-10-25 01:00'), -- USA first DST extension
(-9, -2, 2007, '1900-03-08 02:00', '1900-11-01 01:00'), -- USA second DST extension
(-1, 3, 1900, '1900-03-25 02:00', '1900-10-25 02:00'), -- Europe
(9.5,11, 1971, '1900-10-01 02:00', '1900-04-01 02:00'), -- Australia (not all Aus states in this time-zone have DST though)
(12, 13, 1974, '1900-09-24 02:00', '1900-04-01 02:00') -- New Zealand
select top 1 -- Determine if it is DST /right here, right now/ (regardless of input datetime)
#DSTstart = dateadd(year, datepart(year, getdate())-1900, firstPossibleStart), -- Grab first possible Start and End of DST period
#DSTend = dateadd(year, datepart(year, getdate())-1900, firstPossibleEnd),
#DSTstart = dateadd(day, 6 - (datepart(dw, #DSTstart) + ##datefirst - 2) % 7, #DSTstart),-- Shift Start and End of DST to first sunday
#DSTend = dateadd(day, 6 - (datepart(dw, #DSTend) + ##datefirst - 2) % 7, #DSTend),
#LocalDatetime = dateadd(hour, datediff(hour, getutcdate(), getdate()), #UTCDatetime), -- Add hours to current input datetime (including possible DST hour)
#LocalDatetime = case
when frm < til and getdate() >= #DSTstart and getdate() < #DSTend -- If it is currently DST then we just erroneously added an hour above,
or frm > til and (getdate() >= #DSTstart or getdate() < #DSTend) -- substract 1 hour to get input datetime in current non-DST timezone,
then dateadd(hour, -1, #LocalDatetime) -- regardless of whether it is DST on the date of the input datetime
else #LocalDatetime
end
from #calculation
where datediff(minute, getutcdate(), getdate()) between frm * 60 and til * 60
and datepart(year, getdate()) >= since
order by since desc
select top 1 -- Determine if it was/will be DST on the date of the input datetime in a similar fashion
#DSTstart = dateadd(year, datepart(year, #LocalDatetime)-1900, firstPossibleStart),
#DSTend = dateadd(year, datepart(year, #LocalDatetime)-1900, firstPossibleEnd),
#DSTstart = dateadd(day, 6 - (datepart(dw, #DSTstart) + ##datefirst - 2) % 7, #DSTstart),
#DSTend = dateadd(day, 6 - (datepart(dw, #DSTend) + ##datefirst - 2) % 7, #DSTend),
#LocalDatetime = case
when frm < til and #LocalDatetime >= #DSTstart and #LocalDatetime < #DSTend -- If it would be DST on the date of the input datetime,
or frm > til and (#LocalDatetime >= #DSTstart or #LocalDatetime < #DSTend) -- add this hour to the input datetime.
then dateadd(hour, 1, #LocalDatetime)
else #LocalDatetime
end
from #calculation
where datediff(minute, getutcdate(), getdate()) between frm * 60 and til * 60
and datepart(year, #LocalDatetime) >= since
order by since desc
return #LocalDatetime
end
This function looks at the difference between local and utc time at the moment it runs to determine which DST-rules to apply. It then knows whether doing datediff(hour, getutcdate(), getdate()) includes a DST hour or not and subtracts it if it does. Then it determines whether it was or will be DST at the date of the input UTC datetime and if so adds the DST hour back.
This comes with one quirk, which is that during the last hour of DST and the first hour of non-DST, the function has no way of determining which it is and assumes the latter. So regardless of input-datetime, if this codes runs during the last hour of DST it will give the wrong outcome. Which means this works 99.9886% of the time.
GETUTCDATE() just gives you the current time in UTC, any DATEADD() you do to this value will not include any daylight savings time shifts.
Your best bet is build your own UTC conversion table or just use something like this:
http://www.codeproject.com/KB/database/ConvertUTCToLocal.aspx
Bobman's answer is close, but has a couple bugs:
1) You must compare local DAYLIGHT time (instead of local STANDARD time) to the Daylight Saving End DateTime.
2) SQL BETWEEN is Inclusive so you should be comparing using ">= and <" instead of BETWEEN.
Here is a working modified version along with some test cases:
(Again, this only works for United States)
-- Test cases:
-- select dbo.fn_utc_to_est_date('2016-03-13 06:59:00.000') -- -> 2016-03-13 01:59:00.000 (Eastern Standard Time)
-- select dbo.fn_utc_to_est_date('2016-03-13 07:00:00.000') -- -> 2016-03-13 03:00:00.000 (Eastern Daylight Time)
-- select dbo.fn_utc_to_est_date('2016-11-06 05:59:00.000') -- -> 2016-11-06 01:59:00.000 (Eastern Daylight Time)
-- select dbo.fn_utc_to_est_date('2016-11-06 06:00:00.000') -- -> 2016-11-06 01:00:00.000 (Eastern Standard Time)
CREATE FUNCTION [dbo].[fn_utc_to_est_date]
(
#utc datetime
)
RETURNS datetime
as
begin
-- set offset in standard time (WITHOUT daylight saving time)
declare #offset smallint
set #offset = -5 --EST
declare #localStandardTime datetime
SET #localStandardTime = dateadd(hh, #offset, #utc)
-- DST in USA starts on the second sunday of march and ends on the first sunday of november.
-- DST was extended beginning in 2007:
-- https://en.wikipedia.org/wiki/Daylight_saving_time_in_the_United_States#Second_extension_.282005.29
-- If laws/rules change, obviously the below code needs to be updated.
declare #dstStartDate datetime,
#dstEndDate datetime,
#year int
set #year = datepart(year, #localStandardTime)
-- get the first possible DST start day
if (#year > 2006) set #dstStartDate = cast(#year as char(4)) + '-03-08 02:00:00'
else set #dstStartDate = cast(#year as char(4)) + '-04-01 02:00:00'
while ((datepart(weekday,#dstStartDate) != 1)) begin --while not sunday
set #dstStartDate = dateadd(day, 1, #dstStartDate)
end
-- get the first possible DST end day
if (#year > 2006) set #dstEndDate = cast(#year as char(4)) + '-11-01 02:00:00'
else set #dstEndDate = cast(#year as char(4)) + '-10-25 02:00:00'
while ((datepart(weekday,#dstEndDate) != 1)) begin --while not sunday
set #dstEndDate = dateadd(day, 1, #dstEndDate)
end
declare #localTimeFinal datetime,
#localTimeCompare datetime
-- if local date is same day as #dstEndDate day,
-- we must compare the local DAYLIGHT time to the #dstEndDate (otherwise we compare using local STANDARD time).
-- See: http://www.timeanddate.com/time/change/usa?year=2016
if (datepart(month,#localStandardTime) = datepart(month,#dstEndDate)
and datepart(day,#localStandardTime) = datepart(day,#dstEndDate)) begin
set #localTimeCompare = dateadd(hour, 1, #localStandardTime)
end
else begin
set #localTimeCompare = #localStandardTime
end
set #localTimeFinal = #localStandardTime
-- check for DST
if (#localTimeCompare >= #dstStartDate and #localTimeCompare < #dstEndDate) begin
set #localTimeFinal = dateadd(hour, 1, #localTimeFinal)
end
return #localTimeFinal
end
I recently had to do the same thing. The trick is figuring out the offset from UTC, but it's not a hard trick. You simply use DateDiff to get the difference in hours between local and UTC. I wrote a function to take care of this.
Create Function ConvertUtcDateTimeToLocal(#utcDateTime DateTime)
Returns DateTime
Begin
Declare #utcNow DateTime
Declare #localNow DateTime
Declare #timeOffSet Int
-- Figure out the time difference between UTC and Local time
Set #utcNow = GetUtcDate()
Set #localNow = GetDate()
Set #timeOffSet = DateDiff(hh, #utcNow, #localNow)
DECLARE #localTime datetime
Set #localTime = DateAdd(hh, #timeOffset, #utcDateTime)
-- Check Results
return #localTime
End
GO
This does have on crucial short coming: If a time zone uses a fractional offset, such as Nepal which is GMT+5:45, this will fail because this only deals with whole hours. However, it should fit your needs just fine.