Here some SQL-code that does the job.
It assumes the DB server's local-time is Central European (Summer/Winter) Time (CET/CEST).
(it depends on whether you save UTC or local-time into your datetime-columns)
Note - Daylight saving time:
Change from winter-time to summer time (at end of march)
At the last sunday-morning in March, the clocks are put forward from 02:00 to 03:00 o'clock.
"One loses an hour. "
Change from summter-time to winter-time (at end of October)
At the last sunday morning in October, the clocks are being put backward from 03:00 to 02:00 o'clock.
"One wins an hour."
PRINT 'Begin Executing "01_fu_dtLastSundayInMonth.sql"'
GO
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[fu_dtLastSundayInMonth]') AND type in (N'FN', N'IF', N'TF', N'FS', N'FT'))
BEGIN
EXECUTE(N'CREATE FUNCTION [dbo].[fu_dtLastSundayInMonth]() RETURNS int BEGIN RETURN 0 END ')
END
GO
/*
SET DATEFIRST 3; -- Monday
WITH CTE AS (
SELECT 1 AS i, CAST('20190101' AS datetime) AS mydate
UNION ALL
SELECT i+1 AS i, DATEADD(month, 1, CTE.mydate) AS mydate
FROM CTE WHERE i < 100
)
SELECT -666 AS i, dbo.fu_dtLastSundayInMonth('17530101') AS lastSundayInMonth, dbo.fu_dtLastSundayInMonth('17530101') AS Control
UNION ALL
SELECT -666 AS i, dbo.fu_dtLastSundayInMonth('99991231') AS lastSundayInMonth, dbo.fu_dtLastSundayInMonth('99991231') AS Control
UNION ALL
SELECT
mydate
,dbo.fu_dtLastSundayInMonth(mydate) AS lastSundayInMonth
,dbo.fu_dtLastSundayInMonth(mydate) AS lastSundayInMonth
,CAST(NULL AS datetime) AS Control
--,DATEADD(day,DATEDIFF(day,'19000107', DATEADD(MONTH, DATEDIFF(MONTH, 0, mydate, 30))/7*7,'19000107') AS Control
FROM CTE
*/
-- =====================================================================
-- Author: Stefan Steiger
-- Create date: 01.03.2019
-- Last modified: 01.03.2019
-- Description: Return Datum von letztem Sonntag im Monat
-- mit gleichem Jahr und Monat wie @in_DateTime
-- =====================================================================
ALTER FUNCTION [dbo].[fu_dtLastSundayInMonth](@in_DateTime datetime )
RETURNS DateTime
AS
BEGIN
-- Abrunden des Eingabedatums auf 00:00:00 Uhr
DECLARE @dtReturnValue AS DateTime
-- 26.12.9999 SO
IF @in_DateTime >= CAST('99991201' AS datetime)
RETURN CAST('99991226' AS datetime);
-- @dtReturnValue is now last day of month
SET @dtReturnValue = DATEADD
(
DAY
,-1
,DATEADD
(
MONTH
,1
,CAST(CAST(YEAR(@in_DateTime) AS varchar(4)) + RIGHT('00' + CAST(MONTH(@in_DateTime) AS varchar(2)), 2) + '01' AS datetime)
)
)
;
-- SET DATEFIRST 1 -- Monday - Super easy !
-- SET DATEFIRST != 1 - PHUK THIS !
SET @dtReturnValue = DATEADD
(
day
,
-
(
(
-- DATEPART(WEEKDAY, @lastDayofMonth) -- with SET DATEFIRST 1
DATEPART(WEEKDAY, @dtReturnValue) + @@DATEFIRST - 2 % 7 + 1
)
%7
)
, @dtReturnValue
);
RETURN @dtReturnValue;
END
GO
GO
PRINT 'Done Executing "01_fu_dtLastSundayInMonth.sql"'
GO
PRINT 'Begin Executing "02_fu_dtIsCEST.sql"'
GO
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[fu_dtIsCEST]') AND type in (N'FN', N'IF', N'TF', N'FS', N'FT'))
BEGIN
EXECUTE(N'CREATE FUNCTION [dbo].[fu_dtIsCEST]() RETURNS int BEGIN RETURN 0 END ')
END
GO
-- =====================================================================
-- Author: Stefan Steiger
-- Create date: 01.03.2019
-- Last modified: 01.03.2019
-- Description: Ist @in_DateTime Mitteleuropäische Sommerzeit ?
-- =====================================================================
-- SELECT dbo.fu_dtIsCEST('2019-03-31T01:00:00'), dbo.fu_dtIsCEST('2019-03-31T04:00:00')
ALTER FUNCTION [dbo].[fu_dtIsCEST](@in_DateTime datetime )
RETURNS bit
AS
BEGIN
DECLARE @dtReturnValue AS bit
-- https://www.linker.ch/eigenlink/sommerzeit_winterzeit.htm
-- Umstellung von Winterzeit auf Sommerzeit (Ende März):
-- Am letzten Sonntagmorgen im März werden die Uhren von 02:00 auf 03:00 Uhr vorgestellt.
-- Man verliert eine Stunde.
-- Umstellung von Sommerzeit auf Winterzeit (Ende Oktober):
-- Am letzten Sonntagmorgen im Oktober werden die Uhren von 03:00 auf 02:00 Uhr zurückgestellt.
-- Man gewinnt eine Stunde.
DECLARE @beginSummerTime datetime
SET @beginSummerTime = dbo.fu_dtLastSundayInMonth(DATEADD(MONTH, 2, DATEADD(YEAR, YEAR(@in_DateTime)-1900, 0)) )
SET @beginSummerTime = DATEADD(HOUR, 2, @beginSummerTime)
DECLARE @beginWinterTime datetime
SET @beginWinterTime = dbo.fu_dtLastSundayInMonth(DATEADD(MONTH, 9, DATEADD(YEAR, YEAR(@in_DateTime)-1900, 0)) )
SET @beginWinterTime = DATEADD(HOUR, 2, @beginWinterTime)
SET @dtReturnValue = 0;
IF @in_DateTime >= @beginSummerTime AND @in_DateTime < @beginWinterTime
BEGIN
SET @dtReturnValue = 1;
END
RETURN @dtReturnValue;
END
GO
GO
PRINT 'Done Executing "02_fu_dtIsCEST.sql"'
GO
PRINT 'Begin Executing "03_fu_dtToEcmaTimeStamp.sql"'
GO
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[fu_dtToEcmaTimeStamp]') AND type in (N'FN', N'IF', N'TF', N'FS', N'FT'))
BEGIN
EXECUTE(N'CREATE FUNCTION [dbo].[fu_dtToEcmaTimeStamp]() RETURNS int BEGIN RETURN 0 END ')
END
GO
-- =====================================================================
-- Author: Stefan Steiger
-- Create date: 01.03.2019
-- Last modified: 01.03.2019
-- Description: Ist @in_DateTime Mitteleuropäische Sommerzeit ?
-- =====================================================================
-- SELECT dbo.fu_dtToEcmaTimeStamp('2019-03-31T01:00:00', 1), dbo.fu_dtToEcmaTimeStamp('2019-03-31T04:00:00', 1)
ALTER FUNCTION [dbo].[fu_dtToEcmaTimeStamp](@in_DateTime datetime, @in_convert_to_utc bit)
RETURNS bigint
AS
BEGIN
DECLARE @dtReturnValue AS bigint
IF @in_convert_to_utc = 1
BEGIN
SET @in_DateTime =
CASE WHEN dbo.fu_dtIsCEST(@in_DateTime) = 1
THEN DATEADD(HOUR, -2, @in_DateTime)
ELSE DATEADD(HOUR, -1, @in_DateTime)
END;
END
SET @dtReturnValue =
CAST
(
DATEDIFF
(
HOUR
,CAST('19700101' AS datetime)
,@in_DateTime
)
AS bigint
) *60*60*1000
+
DATEDIFF
(
MILLISECOND
,CAST(FLOOR(CAST(@in_DateTime AS float)) AS datetime)
,@in_DateTime
) % (60*60*1000)
;
RETURN @dtReturnValue;
END
GO
GO
PRINT 'Done Executing "03_fu_dtToEcmaTimeStamp.sql"'
GO
PRINT 'Begin Executing "04_fu_dtFromEcmaTimeStamp.sql"'
GO
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[fu_dtFromEcmaTimeStamp]') AND type in (N'FN', N'IF', N'TF', N'FS', N'FT'))
BEGIN
EXECUTE(N'CREATE FUNCTION [dbo].[fu_dtFromEcmaTimeStamp]() RETURNS int BEGIN RETURN 0 END ')
END
GO
-- =====================================================================
-- Author: Stefan Steiger
-- Create date: 01.03.2019
-- Last modified: 01.03.2019
-- Description: Ist @in_DateTime Mitteleuropäische Sommerzeit ?
-- =====================================================================
-- SELECT dbo.fu_dtFromEcmaTimeStamp('1551437088122', 1), dbo.fu_dtFromEcmaTimeStamp('1554069600000', 1)
ALTER FUNCTION [dbo].[fu_dtFromEcmaTimeStamp](@in_timestamp bigint, @in_convert_to_localtime bit)
RETURNS datetime
AS
BEGIN
DECLARE @dtReturnValue AS datetime
DECLARE @hours int
SET @hours = @in_timestamp /(1000*60*60);
DECLARE @milliseconds int
SET @milliseconds = @in_timestamp - (@in_timestamp /(1000*60*60))*(1000*60*60);
SET @dtReturnValue = DATEADD
(
MILLISECOND, @milliseconds,
DATEADD(hour, @hours, CAST('19700101' AS datetime))
)
IF @in_convert_to_localtime = 1
BEGIN
SET @dtReturnValue = DATEADD(HOUR, 1, @dtReturnValue)
SET @dtReturnValue =
CASE WHEN dbo.fu_dtIsCEST(@dtReturnValue) = 1
THEN DATEADD(HOUR, 1, @dtReturnValue)
ELSE @dtReturnValue
END;
END
RETURN @dtReturnValue;
END
GO
GO
PRINT 'Done Executing "04_fu_dtFromEcmaTimeStamp.sql"'
GO