12

I have an insert proc that passes in GETDATE() as one of the values because each insert also stores when it was inserted. This is hosted on SQL Azure - which uses GMT.

Now, when I am receiving messages, I have the GMT date stored for each of them in their timestamp columns, how do I convert this to the local datetime for wherever you are when you are accessing my page?

Thanks.

Alex Aza
  • 76,499
  • 26
  • 155
  • 134
slandau
  • 23,528
  • 42
  • 122
  • 184

5 Answers5

11

You could do something like this:

declare @InputUtcDateTime datetime2 = '2011-05-20 06:30:18'

declare @LocalDateTime datetime2 = dateadd(minute, datepart(TZoffset, sysdatetimeoffset()), @InputUtcDateTime)
print @LocalDateTime

or

declare @InputUtcDateTime datetime2 = '2011-05-20 06:30:18'

declare @LocalDateTime datetime2 = dateadd(minute, datediff(minute, sysutcdatetime(), sysdatetime()), @InputUtcDateTime)
print @LocalDateTime
Alex Aza
  • 76,499
  • 26
  • 155
  • 134
  • 30
    Both of these are incorrect. They rely on the assumption that the offset for the local timezone of today is the same as the offset for the local timezone at the time referred to by the input UTC date. Since the local timezone might be subject to DST, it's offset might be different in the past than it is today. – Jerome Haltom May 24 '12 at 22:27
  • 3
    @wasabi +1 - you are correct about the limitation of this approach. However I disagree that it is incorrect. It depends on what you are trying to achieve. – Alex Aza May 25 '12 at 21:13
  • 6
    Trying to get the correct time back for a local timezone, given an arbitrary input time? – Jerome Haltom Jun 26 '12 at 18:00
  • To handle DST conversions you will need a lookup table like this: http://www.codeproject.com/Articles/14769/SQL-Server-Convert-UTC-to-Local-Time – hross Sep 25 '13 at 12:01
  • 1
    As @wasabi explains, this approach won't work. It assumes that the server's local time always had the same offset from GMT/UTC - even for Greenwich, this is not true, due to daylight savings! – decates Jul 02 '14 at 10:06
  • without knowing client timezone how could we convert utc datetime to client local date time ? – Mou Jan 24 '17 at 12:06
  • I do not think this should be listed as the accepted answer when it does not work. – High Plains Grifter Jul 30 '20 at 14:29
6

Aside from the Daylight Savings issue, why not simplify with:

yourDateTime - getutcdate() + getdate()
bwperrin
  • 680
  • 5
  • 12
1

For MST as an example... considering each DTM is stored in GMT already, that simplifies things..

SWITCHOFFSET(CONVERT(DATETIMEOFFSET, [ColumnName]), '-07:00')

Now, if your local date/time is something other than GMT/UTC, you'll likely want to use the following...

SWITCHOFFSET(TODATETIMEOFFSET([ColumnName], datepart(tz,sysdatetimeoffset())),'+00:00')

Here's the breakdown.

  • SWITCHOFFSET - converts a DateTimeOffset value to a different timezone, while preserving the offset.
  • TODATETIMEOFFSET - converts a DateTime value to a DateTimeOffset value at a specified timezone.
  • DATEPART - in this case is getting the timezone part of the local datetime.
  • '+00:00' - the target offset, in the second example is UTC/GMT target, from local... the former example is to MST.

NOTE/WARNING: I don't believe that this accounts for Daylight Savings Time, which could be an issue for you. If absolute preservation isn't necessary, you may want to simply add a secondary column, with the rough conversion and go forward safely.

You may want to abstract the logic into a function call, in order to account for the preservation of DST... it shouldn't be excessively difficult to do, though.

Tracker1
  • 19,103
  • 12
  • 80
  • 106
1
CREATE FUNCTION [dbo].[fn_DateTime_GMTFromLocal](@LocalTime DATETIME)
RETURNS DATETIME2(3)
AS
BEGIN
/*
=============================================
 Author:        Mark Griffiths
 Create date:   29/05/2018
 Description:   BST runs from 02:00AM on the last Sunday of March to the same time on the last Sunday of October.       
    The Series of DATEDIFFs and DATEADDS below function as follows
    1   ●   Count the number of months there have been between the given date and start of computer time
    2   ●   Add that number of months to the end of the first month to get the end of the given month
    3   ●   Count the number of days there have been between the end of the given month and the first Saturday
    4   ●   Add that number of days to the calculated end of the given month
    5   ●   Add Two hours to that time as the clocks go back at 02:00 in the morning

    I know that the tabbing below makes it all look odd, but the description above is the best way I could find to comment things, given the nesting...
    The comments in the code below should help find the nesting levels and the numbers refer to the bullet points above.
=============================================
-- Test Variables --
DECLARE @GMTime DATETIME2(3) = '2018-05-01 12:00:00.000'
*/

    DECLARE @RealTime As DATETIME2(3)
    DECLARE @Year VARCHAR(4)
    SET @Year = CONVERT(VARCHAR,DATEPART(YEAR,@GMTime))
    DECLARE @StartOfBST AS DATETIME
    DECLARE @EndOfBST AS DATETIME
    SELECT
        @StartOfBST =
        DATEADD     -----------------------------------------------------------------------------------------
            (                                                       --                                      |
             HOUR                                                   --                                      |
            ,2                                                      --                                      |
            ,DATEADD    -----------------------------------------------------------------------------       |
                (                                                   --                              |       |
                 DAY                                                --                              |       |
                ,DATEDIFF       -------------------------------------------------------------       |       |
                    (                                               --                      |       |       |
                     DAY                                            --                      |       |       |
                    ,'19000107'                                     --                      |       |       5
                    ,DATEADD            ---------------------------------------------       |       |       |
                        (                                           --              |       3       4       |
                         MONTH                                      --              |       |       |       |
                        ,DATEDIFF(MONTH,0,CONVERT(DATE,'03/01/' + @Year)) -- 1      2       |       |       |
                        ,CONVERT(DATE,'01/31/1900')                 --              |       |       |       |
                        )               ---------------------------------------------       |       |       |
                    )/7*7       -------------------------------------------------------------       |       |
                    ,'19000107'                                     --                              |       |
                )               ---------------------------------------------------------------------       |
            ),      -----------------------------------------------------------------------------------------
    @EndOfBST =
        DATEADD(HOUR,2,DATEADD(day,DATEDIFF(day,'19000107',DATEADD(month,DATEDIFF(MONTH,0,CONVERT(DATE,'10/01/' + @Year)),30))/7*7,'19000107'))
    SET @RealTime = CASE
                    WHEN @GMTime BETWEEN @StartOfBST AND @EndOfBST THEN DATEADD(HOUR,-1,@GMTime)
                    ELSE @GMTime
                END
RETURN @RealTime;
--SELECT @RealTime
END

*EDIT: Changed CONVERT(DATE,'01/30/1900') to CONVERT(DATE,'01/31/1900') as since 45BC, January has had 31 days. This caused some times an incorrect result for 2019, and any others where the last Sunday in March is the 31st.

High Plains Grifter
  • 1,357
  • 1
  • 12
  • 36
  • 1
    this works perfectly although i had to change the -1 to +1 at the end to change utc time to GMT – Lewis Williams Sep 01 '21 at 14:19
  • yeah, you can go either way like that - I made two functions, one for each way and reference them separately in my scripts - they differ only by the `+-1`. PS. It amazes me that the accepted answer (which is upvoted) does not work! – High Plains Grifter Sep 01 '21 at 16:24
0

Here's a function which works on historic data. I wrote it for British Summer time - which unfortunately occurs on the last Sunday of the months of March and October, making the logic a little convoluted.

Basically the hard coded date part 01/03 is looking for the last Sunday in March and 01/10 is looking for the last Sunday in October (which is when the clocks go forward and back here). NOTE: IF YOUR SERVER IS USING NATIVE US DATES REVERSE THESE TWO DATE PARTS TO 03/01 and 10/01!!!!

So you feed it a UTC date and it'll automatically work out whether an historic date is BST or GMT. Not the best thing to use on a big data set but it's a solution.

Run this script to create the function and call it inline in your select. SQL 2008 has a problem with user defined functions, it seems, it puts a redline under the code, but it still runs it as long as you use the dbo prefix (SELECT dbo.UTCConvert(yourdate) to run it)

CREATE FUNCTION [dbo].[UTCConvert] 
(

    @p1 datetime
)
RETURNS datetime
AS
BEGIN

    DECLARE @Result datetime


RETURN CASE 
WHEN
@p1 >
(DATEADD(day,DATEDIFF(day,'19000107',DATEADD(month,DATEDIFF(MONTH,0,'01/03/' + CAST(DATEPART(year,@p1) as CHAR)),30))/7*7,'19000107'))
AND
@p1<
(DATEADD(day,DATEDIFF(day,'19000107',DATEADD(month,DATEDIFF(MONTH,0,'01/10/' + CAST(DATEPART(year,@p1) as CHAR)),30))/7*7,'19000107'))
THEN (DATEADD(HH, 1, @p1)) 
ELSE @p1
END
END
Rich
  • 11
  • 1
  • If your server is using native US dates because it's located in the US, you've most likely got far more severe headaches. Odds are if you're looking at this question, you're storing dates from at least one location where daylight savings apply. That means at the very least you have to worry about the 2007 change in the shift dates. You may well have to worry about multiple timezones, possibly Arizona and maybe even the Navajo Nation. You really want to be storing either UTC or datetimeoffset in SQL Server and doing your date math in your business layer. – MattW Oct 13 '15 at 20:44