SQL Server - Convert date field to UTC

前端 未结 12 688
故里飘歌
故里飘歌 2020-12-23 13:39

I have recently updated my system to record date/times as UTC as previously they were storing as local time.

I now need to convert all the local stored date/times to

12条回答
  •  小蘑菇
    小蘑菇 (楼主)
    2020-12-23 13:47

    If you have to convert dates other than today to different timezones you have to deal with daylight savings. I wanted a solution that could be done without worrying about database version, without using stored functions and something that could easily be ported to Oracle.

    I think Warren is on the right track with getting the correct dates for daylight time, but to make it more useful for multiple time zone and different rules for countries and even the rule that changed in the US between 2006 and 2007, here a variation on the above solution. Notice that this not only has us time zones, but also central Europe. Central Europe follow the last sunday of april and last sunday of october. You will also notice that the US in 2006 follows the old first sunday in april, last sunday in october rule.

    This SQL code may look a little ugly, but just copy and paste it into SQL Server and try it. Notice there are 3 section for years, timezones and rules. If you want another year, just add it to the year union. Same for another time zone or rule.

    select yr, zone, standard, daylight, rulename, strule, edrule, yrstart, yrend,
        dateadd(day, (stdowref + stweekadd), stmonthref) dstlow,
        dateadd(day, (eddowref + edweekadd), edmonthref)  dsthigh
    from (
      select yrs.yr, z.zone, z.standard, z.daylight, z.rulename, r.strule, r.edrule, 
        yrs.yr + '-01-01 00:00:00' yrstart,
        yrs.yr + '-12-31 23:59:59' yrend,
        yrs.yr + r.stdtpart + ' ' + r.cngtime stmonthref,
        yrs.yr + r.eddtpart + ' ' + r.cngtime edmonthref,
        case when r.strule in ('1', '2', '3') then case when datepart(dw, yrs.yr + r.stdtpart) = '1' then 0 else 8 - datepart(dw, yrs.yr + r.stdtpart) end
        else (datepart(dw, yrs.yr + r.stdtpart) - 1) * -1 end stdowref,
        case when r.edrule in ('1', '2', '3') then case when datepart(dw, yrs.yr + r.eddtpart) = '1' then 0 else 8 - datepart(dw, yrs.yr + r.eddtpart) end
        else (datepart(dw, yrs.yr + r.eddtpart) - 1) * -1 end eddowref,
        datename(dw, yrs.yr + r.stdtpart) stdow,
        datename(dw, yrs.yr + r.eddtpart) eddow,
        case when r.strule in ('1', '2', '3') then (7 * CAST(r.strule AS Integer)) - 7 else 0 end stweekadd,
        case when r.edrule in ('1', '2', '3') then (7 * CAST(r.edrule AS Integer)) - 7 else 0 end edweekadd
    from (
        select '2005' yr union select '2006' yr -- old us rules
        UNION select '2007' yr UNION select '2008' yr UNION select '2009' yr UNION select '2010' yr UNION select '2011' yr
        UNION select '2012' yr UNION select '2013' yr UNION select '2014' yr UNION select '2015' yr UNION select '2016' yr
        UNION select '2017' yr UNION select '2018' yr UNION select '2019' yr UNION select '2020' yr UNION select '2021' yr
        UNION select '2022' yr UNION select '2023' yr UNION select '2024' yr UNION select '2025' yr UNION select '2026' yr
    ) yrs
    cross join (
        SELECT 'ET' zone, '-05:00' standard, '-04:00' daylight, 'US' rulename
        UNION SELECT 'CT' zone, '-06:00' standard, '-05:00' daylight, 'US' rulename
        UNION SELECT 'MT' zone, '-07:00' standard, '-06:00' daylight, 'US' rulename
        UNION SELECT 'PT' zone, '-08:00' standard, '-07:00' daylight, 'US' rulename
        UNION SELECT 'CET' zone, '+01:00' standard, '+02:00' daylight, 'EU' rulename
    ) z
    join (
        SELECT 'US' rulename, '2' strule, '-03-01' stdtpart, '1' edrule, '-11-01' eddtpart, 2007 firstyr, 2099 lastyr, '02:00:00' cngtime
        UNION SELECT 'US' rulename, '1' strule, '-04-01' stdtpart, 'L' edrule, '-10-31' eddtpart, 1900 firstyr, 2006 lastyr, '02:00:00' cngtime
        UNION SELECT  'EU' rulename, 'L' strule, '-03-31' stdtpart, 'L' edrule, '-10-31' eddtpart, 1900 firstyr, 2099 lastyr, '01:00:00' cngtime
    ) r on r.rulename = z.rulename
        and datepart(year, yrs.yr) between firstyr and lastyr
    ) dstdates
    

    For the rules, use 1, 2, 3 or L for first, second, third or last sunday. The date part gives the month and depending on the rule, the first day of the month or the last day of the month for rule type L.

    I put the above query into a view. Now, anytime I want a date with the time zone offset or converted to UTC time, I just join to this view and select get the date in the date format. Instead of datetime, I converted these to datetimeoffset.

    select createdon, dst.zone
        , case when createdon >= dstlow and createdon < dsthigh then dst.daylight else dst.standard end pacificoffsettime
        , TODATETIMEOFFSET(createdon, case when createdon >= dstlow and createdon < dsthigh then dst.daylight else dst.standard end) pacifictime
        , SWITCHOFFSET(TODATETIMEOFFSET(createdon, case when createdon >= dstlow and createdon < dsthigh then dst.daylight else dst.standard end), '+00:00')  utctime
    from (select '2014-01-01 12:00:00' createdon union select '2014-06-01 12:00:00' createdon) photos
    left join US_DAYLIGHT_DATES dst on createdon between yrstart and yrend and zone = 'PT'
    

提交回复
热议问题