Forum Discussion
Convert Date/Time in UTC to Local Time with Daylight savings
- 7 years ago
Hi Anonymous ,
I think there are many ways, for example I tried to find a pattern in order to catch the November first Sunday or March second Sunday, and for your specific needs, maybe this custom function could work:
(datetimecolumn as datetime) => let date = DateTime.Date(datetimecolumn), time = DateTime.Time(datetimecolumn), firstSundayOfNovember = Date.StartOfWeek(#date(Date.Year(date), 11, 7), Day.Sunday), SecondSundayOfMarch = Date.StartOfWeek(#date(Date.Year(date), 3, 14), Day.Sunday), isSummerTime = (date = SecondSundayOfMarch and time >= #time(1,0,0)) or (date > SecondSundayOfMarch and date < firstSundayOfNovember) or (date = firstSundayOfNovember and time >= #time(1,0,0)), timeZone = (7 - Number.From(isSummerTime))*-1, MDT = DateTime.From(date) + #duration(0,Time.Hour(time),Time.Minute(time),Time.Second(time)) + #duration(0, timeZone, 0, 0) in MDT
So for dates from March Second Sunday at 1:00am until November First Sunday at 12:59:59am you will get your datetime - 6 hours and for dates from November First Sunday 1:00am until March Second Sunday at 12:59:59am you will get your datetime - 7 hoursAccording to Saint Google, the time is changed after 1:00am if you need it to be changed after 12:00am instead just remove first and last condition from isSummerTime
If you have any question or if you find any error on the code, just let me know.
Regards,
Gian Carlo Poggi
- 7 years ago
Sure Anonymous ,
Right click on Queries pane and add a new Blank Query:
Then right click on this new query and select Advanced Editor:
In this new window erase all, paste the my code and click DONE:
Now that query was converted into a function, you can rename it if you like, for example to "UTC_to_MDT":
Then in order to use this function in your table you have different options, one option is going to your query or table, then click on Add Column / Invoke Custom Function, then put a name to this new column, select your function (in my case UTC_to_MDT) and select the column from your table you need to apply this function to (in my case "Date"):
And then you will see the new date added :
Hope this helps.
Regards,
Gian Carlo Poggi
Above code seems working fine. However, some records in the date column are null which is giving me error. Work fine if it has value in the column.
Any condition set to solve this ?
Here is the updated code. The part trying to convert the isSummerTime to a number was causing an error, so I added another line that checks whether it is null, and and if it is, uses a value of zero. It worked fine for me.
Please make sure you validate the timing on those DST periods per the code I gave above. I am concerned still that on those days it changes, there will be a period of 10 or 11 hours that will have the wrong time.
(datetimecolumn as nullable datetime) =>
let
date = DateTime.Date(datetimecolumn),
time = DateTime.Time(datetimecolumn),
firstSundayOfOctober = Date.StartOfWeek(#date(Date.Year(date), 10, 7), Day.Sunday),
firstSundayOfApril = Date.StartOfWeek(#date(Date.Year(date), 4, 7), Day.Sunday),
isSummerTime =
(date = firstSundayOfOctober and time >= #time(2,0,0))
or
(date > firstSundayOfOctober)
or
(date < firstSundayOfApril)
or
(date = firstSundayOfApril and time >= #time(3,0,0)),
timeZoneAdj = if datetimecolumn is null then 0 else Number.From(isSummerTime),
timeZone = 10 + timeZoneAdj,
AEST =
DateTime.From(datetimecolumn) + #duration(0, timeZone, 0, 0)
in
AEST
- MTracy5 years ago
Advocate I
I really appreciate you sharing your code with all of us. This UTC/daylight savings conversion is rough!
In this new segment...
timeZone = 10 + timeZoneAdj,
... is where you have "10" supposed to be where we state our own offset to UTC? For example, I'm in the US Eastern time (EDT right now), so do I change that to 4?... or 5 and the "isSummerTime" part handles when to change it to 4 during our summer time?
Thanks in advance!
EDIT: Figured it out! Had to use timeZone = (-5 + timeZoneAdj), and that worked. Verified it's properly converting my historical date column either 4 or 5 hours, depending on whether we're in Daylight Savings Time or not for each date in the column. Works like a charm, thanks!
- jtao5 years ago
Helper I
You are champion. Thanks for your great help ! I will test thoroughly. Wish I have time to learn M language.
- freshwave5 years ago
Helper I
Very glad to help. I am better at tweaking others code, than to create myself. Please use the sample code that I provided above to analyze the periods, as it focuses on the dates around DST only. That will make it easier to discover issues with the timing, if there is any.
- freshwave5 years ago
Helper I
Sure, it is not actually my code, I only tweaked it from a post above.
What is your datasource for your data? Honestly, if anyone is using SQL Server 2016 or greater, changing the line in your SQL I feel is much more efficient.
Something like:
[startDate] AT TIME ZONE 'UTC' AT TIME ZONE 'Eastern Standard Time' AS [startDate]
This can be done for other timezones also, so please refer to https://bertwagner.com/posts/at-time-zone-the-easy-way-to-deal-with-time-zones-and-daylight-savings-time/.
Okay, so I looked over the code, and I think I have made the tweaks necessary for jtao for the correct DST for AST:
(datetimecolumn as nullable datetime) => let date = DateTime.Date(datetimecolumn), time = DateTime.Time(datetimecolumn), // The offset from UTC to your Timezone // ex. AEST is 11 hours later than UTC, so it would be +11 // ex. EST is 5 hours earlier than UTC, so it would be -5 UTCOffset = 11, firstSundayOfOctober = DateTime.From(Date.StartOfWeek(#date(Date.Year(date), 10, 7), Day.Sunday)) + #duration(0, -UTCOffset+1, 0, 0), firstSundayOfApril = DateTime.From(Date.StartOfWeek(#date(Date.Year(date), 4, 7), Day.Sunday)) + #duration(0, -UTCOffset+2, 0, 0), isDaylightSavingsTime = datetimecolumn >= firstSundayOfApril and datetimecolumn < firstSundayOfOctober, AdjustForDST = if datetimecolumn is null then 0 else Number.From(isDaylightSavingsTime), timeZone = UTCOffset + AdjustForDST, AEST = DateTime.From(datetimecolumn) + #duration(0, timeZone, 0, 0) in AESTI am also in EST, and I tweaked and tested the logic which works. Be aware that we changed DST on the second Sunday of March, and the first Sunday of November.
(datetimecolumn as nullable datetime) => let date = DateTime.Date(datetimecolumn), // The offset from UTC to your Timezone // ex. AEST is 11 hours later than UTC, so it would be +11 // ex. EST is 5 hours earlier than UTC, so it would be -5 UTCOffset = -5, firstSundayOfNovember = DateTime.From(Date.StartOfWeek(#date(Date.Year(date), 11, 7), Day.Sunday)) + #duration(0, -UTCOffset+1, 0, 0), secondSundayOfMarch = DateTime.From(Date.StartOfWeek(#date(Date.Year(date), 3, 14), Day.Sunday)) + #duration(0, -UTCOffset+2, 0, 0), isDaylightSavingsTime = datetimecolumn >= secondSundayOfMarch and datetimecolumn < firstSundayOfNovember, AdjustForDST = if datetimecolumn is null then 0 else Number.From(isDaylightSavingsTime), timeZone = UTCOffset + AdjustForDST, EST = DateTime.From(datetimecolumn) + #duration(0, timeZone, 0, 0) in ESTI realize my test script above was bad, and I below I have pasted a new one that correctly shows the dates with 1 hour increments which will correctly allow you to see if things are changing as expected.
First make sure you add the Local_To_UTC function
let Source = (datetimecolumn as nullable datetime) => let DateTimeAddZone = DateTime.AddZone( datetimecolumn, 0 ), DateTimetoLocal = DateTimeZone.ToLocal( DateTimeAddZone ), DateTimeRemoveZone = DateTimeZone.RemoveZone( DateTimetoLocal ), UTC_To_Local = DateTimeRemoveZone in UTC_To_Local in SourceSince they are different functions and date periods for AST and EST, you will need to use the corresponding test script.
For testing the AST, use:
let StartDate = #date(2019, 1, 1), EndDate = #date(2021, 12, 31), DayCount = Duration.Days(Duration.From(EndDate - StartDate)), Source = List.DateTimes(DateTime.From(StartDate),DayCount*24,#duration(0,1,0,0)), TableFromList = Table.FromList(Source, Splitter.SplitByNothing()), ChangedType = Table.TransformColumnTypes(TableFromList,{{"Column1", type datetime}}), #"Renamed Columns" = Table.RenameColumns(ChangedType,{{"Column1", "DateUTC"}}), #"Added Custom" = Table.AddColumn(#"Renamed Columns", "DayOfWeekName", each Date.DayOfWeekName( [DateUTC] )), #"Added Custom1" = Table.AddColumn(#"Added Custom", "FilterDates", each if ( DateTime.Date( [DateUTC] ) >= #date( Date.Year( DateTime.Date ( [DateUTC] )), 3, 31 ) and DateTime.Date( [DateUTC] ) <= #date( Date.Year( DateTime.Date ( [DateUTC] )), 4, 8 )) or ( DateTime.Date( [DateUTC] ) >= #date( Date.Year( DateTime.Date ( [DateUTC] )), 9, 30 ) and DateTime.Date( [DateUTC] ) <= #date( Date.Year( DateTime.Date ( [DateUTC] )), 10, 8 )) then "Y" else "N"), #"Filtered Rows" = Table.SelectRows(#"Added Custom1", each ([FilterDates] = "Y") and ([DayOfWeekName] = "Saturday" or [DayOfWeekName] = "Sunday")), #"Invoked Custom Function" = Table.AddColumn(#"Filtered Rows", "DateAST", each AST([DateUTC]), type datetime), #"Invoked Custom Function1" = Table.AddColumn(#"Invoked Custom Function", "UTC_to_Local", each UTC_to_Local([DateUTC]), type datetime) in #"Invoked Custom Function1"For testing the EST, use the same script as above, though change the section from:
#"Invoked Custom Function" = Table.AddColumn(#"Filtered Rows", "DateAST", each AST([DateUTC]), type datetime),
to be:
#"Invoked Custom Function" = Table.AddColumn(#"Filtered Rows", "EST", each EST([DateUTC]), type datetime),
Okay, need to get back to work. I have projects with deadlines, and I got carried away working on this. Hope this is helpful to you both, and perhaps others.