|
| 1 | +CREATE OR ALTER PROCEDURE tSQLcron.usp_is_date_in_cron_period |
| 2 | + @cron_expression NVARCHAR(100) |
| 3 | + /*{second} {minute} {hour} {day} {month} {day-of-week}*/ |
| 4 | + ,@validate_date DATETIME = NULL |
| 5 | + ,@out_is_cron_true BIT OUTPUT |
| 6 | + |
| 7 | +AS |
| 8 | +/******************************************************************************* |
| 9 | +--- |
| 10 | +Name: "[tSQLcron].[usp_is_date_in_cron_period]" |
| 11 | +
|
| 12 | +Description: | |
| 13 | + This stored procedure can be used to evaluate if a given date is true for |
| 14 | + a given cron_expression. It will return true (1) if it is. |
| 15 | + |
| 16 | + WARNING: This is a best effort implementation and has NO validation of |
| 17 | + the inputs, not all cron expressions are supported so please |
| 18 | + test your expressions. |
| 19 | +
|
| 20 | +Parameters: | |
| 21 | + - @cron_expression : a valid cron expression (see more in 'Cron Expression') |
| 22 | + - @validate_date : optional parameter of the date to validate against the cron expression |
| 23 | + defaults to current UTC datetime |
| 24 | + - out_is_cron_true : OUTPUT variable that returns true or false |
| 25 | +
|
| 26 | + Format : {second} {minute} {hour} {day} {month} {day-of-week*} |
| 27 | + Value-Options : * * * * * * |
| 28 | + : {0-59} {0-59} {0-23} {1-31} {1-12} {1-7} |
| 29 | + : 1,2... 1,2... 1,2... 1,2... 1,2... 1,2... |
| 30 | + |
| 31 | + See more information below |
| 32 | + |
| 33 | +CRON element allowed values: |
| 34 | + - second : "{*|0|0,1,2..59|1-59|(1-59|0,1..)/1-59}" |
| 35 | + - minute : "{*|0|0,1,2..59|1-59|(1-59|0,1..)/1-59}" |
| 36 | + - hour : "{*|0|0,1,2..23|1-23|(1-23|0,1..)/1-23}" |
| 37 | + - day : "{*|0|1,2,3..31|1-31|(1-31|1,2..)/1-31}" |
| 38 | + - month : "{*|0|1,2,3..12|1-12|(1-12|1,2..)/1-12}" |
| 39 | + - day-of-week : "{*|0|1,2,3..7|1-7|1-7/1-7}" |
| 40 | + - note*: | |
| 41 | + The value of `{day-of-week}` depends on settings of SQL Server. |
| 42 | +
|
| 43 | + You can identify what `{day-of-week}` monday is using the below snippet |
| 44 | + `SELECT DATEPART(WEEKDAY,'2020-04-13') AS Monday_The_13th_Of_April_2020` |
| 45 | + |
| 46 | + Usually : 1:Sunday,2:Monday,3:Tuesday,4:Wednesday,5:Thursday,6:Friday,7:Saturday |
| 47 | +
|
| 48 | +Value-Options : | |
| 49 | + Any Or Ignore |
| 50 | + `*` means that the section is ignored / is any |
| 51 | +
|
| 52 | + Exactly this (Second/Minute/...) |
| 53 | + `0` means that the section is run exactly when the (Second/Minute/...) is 0. i.e. When the (Second/Minute/...) is exactly 0 |
| 54 | +
|
| 55 | + Exactly one of these comma separated (Second/Minute/...) |
| 56 | + `1,4,6` means that the section is run exactly when the (Second/Minute/...) is 0. i.e. When the (Second/Minute/...) is exactly 1 or 4 or 6 |
| 57 | +
|
| 58 | + Is Between this range (Second/Minute/...) |
| 59 | + `0-5` means that the section is run within this interval, between 0 and 5 |
| 60 | +
|
| 61 | + Is Between Ranges and is divisible by (n) (Second/Minute/...) |
| 62 | + `0-30/5` means that the section is run within this interval, between 0 and 30 AND is exactly divisible by 5 i.e every 5th (Second/Minute/...) between 0-30 |
| 63 | + `* /5` means every 5th (Second/Minute/...) [Note Remove the space between * and /] |
| 64 | + `0/5` means every 5th (Second/Minute/...) [Same as above] |
| 65 | +
|
| 66 | +Sample CRON expressions: | |
| 67 | + - always/any-time - N'* * * * * *' |
| 68 | + - every 15th minute - N'* 0/15 * * * *' (remove space between * and / since it is close SQL Comment) |
| 69 | + - everyday at a 13:10 - N'* 10 13 * * *' |
| 70 | + - everyday at the 10th minute between 13H-23H range - N'* 10 13-23 * * *' |
| 71 | +
|
| 72 | + - on the 27th of June at anytime - N'* * * 27 6 *' |
| 73 | + - on the 27th of June at 13:10 - N'* 10 13 27 6 *' |
| 74 | + - on the 27th of June at 13:10:30 - N'30 10 13 27 6 *' |
| 75 | +
|
| 76 | + - every monday at 13:10 - N'* 10 13 * * 2' |
| 77 | + - every monday,wed,thu at 13:10 - N'* 10 13 * * 2,4,5' |
| 78 | + - weekdays (mon-fri) at 13:10 - N'* 10 13 * * 2-6' |
| 79 | + - weekdays alternate (mon-fri) at 13:10 - N'* 10 13 * * 2-6/2' |
| 80 | + - midnight on weekends - N'* 10 13 * * 2-6/2' |
| 81 | + |
| 82 | +Example: | |
| 83 | + DECLARE @out_is_cron_true BIT ; |
| 84 | + EXEC tSQLcron.sp_is_date_in_cron_period |
| 85 | + @cron_expression = N'* 0/15 * * * *' -- nvarchar(100) |
| 86 | + , @validate_date = '2020-01-01 13:15:00' -- datetime |
| 87 | + , @out_is_cron_true = @out_is_cron_true OUTPUT -- bit |
| 88 | +
|
| 89 | + IF (@out_is_cron_true = 1 ) |
| 90 | + BEGIN |
| 91 | + -- DO SOMETHING |
| 92 | + END |
| 93 | +
|
| 94 | +... |
| 95 | +********************************************************************************/ |
| 96 | +BEGIN |
| 97 | + SET NOCOUNT ON; |
| 98 | + |
| 99 | + DROP TABLE IF EXISTS |
| 100 | + #hours |
| 101 | + , #minute_second ; |
| 102 | + |
| 103 | + DECLARE |
| 104 | + @is_cron_true BIT = 0 |
| 105 | + , @is_success BIT = 0 ; |
| 106 | + |
| 107 | + DECLARE |
| 108 | + @Seconds NVARCHAR(100) |
| 109 | + , @Minutes NVARCHAR(100) |
| 110 | + , @Hours NVARCHAR(100) |
| 111 | + , @DayOfMonth NVARCHAR(100) |
| 112 | + , @Month NVARCHAR(100) |
| 113 | + , @DayOfWeek NVARCHAR(100) ; |
| 114 | + |
| 115 | + SET @validate_date = ISNULL(@validate_date,GETUTCDATE()); |
| 116 | + |
| 117 | + DECLARE |
| 118 | + @now_second INT = DATEPART( SECOND, @validate_date) |
| 119 | + , @now_minute INT = DATEPART( MINUTE, @validate_date) |
| 120 | + , @now_hour INT = DATEPART( HOUR, @validate_date) |
| 121 | + , @now_day INT = DATEPART( DAY, @validate_date) |
| 122 | + , @now_month INT = DATEPART( MONTH, @validate_date) |
| 123 | + , @now_day_of_week INT = DATEPART( WEEKDAY, @validate_date) |
| 124 | + , @now_year INT = DATEPART( YEAR, @validate_date) ; |
| 125 | + |
| 126 | + |
| 127 | + |
| 128 | + |
| 129 | + EXEC tSQLcron.usp_write_verbose '::START::' , 'tSQLcron.sp_is_date_in_cron_period'; |
| 130 | + EXEC tSQLcron.usp_write_verbose '@cron_expression' , @cron_expression; |
| 131 | + EXEC tSQLcron.usp_write_verbose '@validate_date' , @validate_date; |
| 132 | + |
| 133 | + |
| 134 | + BEGIN TRY |
| 135 | + SELECT |
| 136 | + @Seconds = cron_seconds |
| 137 | + , @Minutes = cron_minutes |
| 138 | + , @Hours = cron_hours |
| 139 | + , @DayOfMonth = cron_day_of_month |
| 140 | + , @Month = cron_month |
| 141 | + , @DayOfWeek = cron_day_of_week |
| 142 | + FROM tSQLcron.fn_parse_cron_expression(@cron_expression); |
| 143 | + |
| 144 | + EXEC tSQLcron.usp_write_verbose ' INFO' , 'fn_parse_cron_expression'; |
| 145 | + EXEC tSQLcron.usp_write_verbose ' @Seconds' , @Seconds; |
| 146 | + EXEC tSQLcron.usp_write_verbose ' @Minutes' , @Minutes; |
| 147 | + EXEC tSQLcron.usp_write_verbose ' @Hours' , @Hours; |
| 148 | + EXEC tSQLcron.usp_write_verbose ' @DayOfMonth', @DayOfMonth; |
| 149 | + EXEC tSQLcron.usp_write_verbose ' @Month' , @Month; |
| 150 | + EXEC tSQLcron.usp_write_verbose ' @DayOfWeek' , @DayOfWeek; |
| 151 | + |
| 152 | + EXEC tSQLcron.usp_write_verbose ' INFO' , 'NOW(@validate_date)'; |
| 153 | + EXEC tSQLcron.usp_write_verbose ' @now_second' , @now_second; |
| 154 | + EXEC tSQLcron.usp_write_verbose ' @now_minute' , @now_minute; |
| 155 | + EXEC tSQLcron.usp_write_verbose ' @now_hour' , @now_hour; |
| 156 | + EXEC tSQLcron.usp_write_verbose ' @now_day' , @now_day; |
| 157 | + EXEC tSQLcron.usp_write_verbose ' @now_month' , @now_month; |
| 158 | + EXEC tSQLcron.usp_write_verbose ' @now_day_of_week' , @now_day_of_week; |
| 159 | + EXEC tSQLcron.usp_write_verbose ' @now_year' , @now_year; |
| 160 | + |
| 161 | + |
| 162 | + |
| 163 | + |
| 164 | + EXEC tSQLcron.usp_write_verbose ' INFO' , 'Check RETURN True if expressions is any time'; |
| 165 | + IF ( |
| 166 | + 1 = 1 |
| 167 | + AND @Seconds IN ( N'*', N'0/1', N'*/1' ) |
| 168 | + AND @Minutes IN ( N'*', N'0/1', N'*/1' ) |
| 169 | + AND @Hours IN ( N'*', N'0/1', N'*/1' ) |
| 170 | + AND @DayOfMonth IN ( N'*', N'0/1', N'*/1' ) |
| 171 | + AND @Month IN ( N'*', N'0/1', N'*/1' ) |
| 172 | + AND @DayOfWeek IN ( N'*', N'0/1', N'*/1' ) |
| 173 | + ) |
| 174 | + BEGIN |
| 175 | + EXEC tSQLcron.usp_write_verbose ' INFO' , N'Returning, any time expressions passed'; |
| 176 | + SELECT @is_cron_true = 1 , @is_success = 1 , @out_is_cron_true = 1; |
| 177 | + RETURN @is_success; |
| 178 | + END ; |
| 179 | + |
| 180 | + EXEC tSQLcron.usp_write_verbose ' INFO' , 'CREATE #Hours and #minute_second TABLE'; |
| 181 | + SELECT CAST(value AS INT) AS hours INTO #hours FROM |
| 182 | + STRING_SPLIT(N'0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23', ',') ; |
| 183 | + |
| 184 | + SELECT |
| 185 | + CAST(value AS INT) AS minutes |
| 186 | + , CAST(value AS INT) AS seconds |
| 187 | + INTO #minute_second |
| 188 | + FROM |
| 189 | + STRING_SPLIT(N'0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59', ',') ; |
| 190 | + |
| 191 | + EXEC tSQLcron.usp_write_verbose ' INFO' , 'PROCESSING cron expression'; |
| 192 | + |
| 193 | + WITH dates |
| 194 | + AS (SELECT DISTINCT |
| 195 | + dd.date_key |
| 196 | + , dd.calendar_year |
| 197 | + , dd.calendar_month |
| 198 | + , dd.calendar_day |
| 199 | + , dd.day_of_week |
| 200 | + FROM |
| 201 | + tSQLcron.dim_date dd |
| 202 | + CROSS APPLY tSQLcron.fn_parse_cron_expression_element( @DayOfMonth ) cDm |
| 203 | + CROSS APPLY tSQLcron.fn_parse_cron_expression_element( @DayOfWeek ) cDw |
| 204 | + CROSS APPLY tSQLcron.fn_parse_cron_expression_element( @Month ) cM |
| 205 | + WHERE |
| 206 | + 1 = 1 |
| 207 | + -- Look at Todays Date |
| 208 | + AND date_key = CAST(@validate_date AS DATE) |
| 209 | + |
| 210 | + -- |
| 211 | + -- DayOfMonth |
| 212 | + AND (cDm.is_any = 1 |
| 213 | + OR dd.calendar_day BETWEEN cDm.start_period AND cDm.end_period |
| 214 | + OR dd.calendar_day = cDm.this_period |
| 215 | + ) |
| 216 | + AND dd.calendar_day % ISNULL( cDm.interval_period, 1 ) = 0 |
| 217 | + -- |
| 218 | + -- |
| 219 | + -- DayOfweek |
| 220 | + AND (cDw.is_any = 1 |
| 221 | + OR dd.day_of_week BETWEEN cDw.start_period AND cDw.end_period |
| 222 | + OR dd.day_of_week = cDw.this_period |
| 223 | + ) |
| 224 | + AND dd.day_of_week % ISNULL( cDw.interval_period, 1 ) = 0 |
| 225 | + |
| 226 | + -- Month |
| 227 | + AND (cM.is_any = 1 |
| 228 | + OR dd.calendar_month BETWEEN cM.start_period AND cM.end_period |
| 229 | + OR dd.calendar_month = cM.this_period |
| 230 | + ) |
| 231 | + AND dd.calendar_month % ISNULL( cM.interval_period, 1 ) = 0) |
| 232 | + , Times |
| 233 | + AS (SELECT |
| 234 | + H.hours AS time_hour |
| 235 | + , M.minutes AS time_minute |
| 236 | + , S.seconds AS time_second |
| 237 | + , TIMEFROMPARTS( H.hours, M.minutes, S.seconds, 0, 0 ) AS valid_times |
| 238 | + FROM |
| 239 | + #hours H |
| 240 | + CROSS APPLY #minute_second M |
| 241 | + CROSS APPLY #minute_second S |
| 242 | + CROSS APPLY tSQLcron.fn_parse_cron_expression_element( @Hours ) cH |
| 243 | + CROSS APPLY tSQLcron.fn_parse_cron_expression_element( @Minutes ) cM |
| 244 | + CROSS APPLY tSQLcron.fn_parse_cron_expression_element( @Seconds ) cS |
| 245 | + WHERE |
| 246 | + 1 = 1 |
| 247 | + -- Hours |
| 248 | + AND (cH.is_any = 1 OR H.hours BETWEEN cH.start_period AND cH.end_period OR H.hours = cH.this_period) |
| 249 | + AND H.hours % ISNULL( cH.interval_period, 1 ) = 0 |
| 250 | + |
| 251 | + -- Minutes |
| 252 | + AND (cM.is_any = 1 OR M.minutes BETWEEN cM.start_period AND cM.end_period OR M.minutes = cM.this_period) |
| 253 | + AND M.minutes % ISNULL( cM.interval_period, 1 ) = 0 |
| 254 | + |
| 255 | + -- Seconds |
| 256 | + AND (cS.is_any = 1 OR S.seconds BETWEEN cS.start_period AND cS.end_period OR S.seconds = cS.this_period) |
| 257 | + AND S.seconds % ISNULL( cS.interval_period, 1 ) = 0) |
| 258 | + , time_object |
| 259 | + AS (SELECT |
| 260 | + DATETIMEFROMPARTS( |
| 261 | + d.calendar_year, d.calendar_month, d.calendar_day, t.time_hour, t.time_minute, t.time_second, 0 |
| 262 | + ) AS valid_time |
| 263 | + FROM |
| 264 | + dates d |
| 265 | + CROSS APPLY Times t |
| 266 | + WHERE |
| 267 | + 1 = 1 |
| 268 | + AND (@Seconds IN ( N'*', N'0/1', N'*/1' ) OR t.time_second = @now_second) |
| 269 | + AND (@Minutes IN ( N'*', N'0/1', N'*/1' ) OR t.time_minute = @now_minute) |
| 270 | + AND (@Hours IN ( N'*', N'0/1', N'*/1' ) OR t.time_hour = @now_hour) |
| 271 | + AND (@DayOfMonth IN ( N'*', N'0/1', N'*/1' ) OR d.calendar_day = @now_day) |
| 272 | + AND (@Month IN ( N'*', N'0/1', N'*/1' ) OR d.calendar_month = @now_month) |
| 273 | + AND (@DayOfWeek IN ( N'*', N'0/1', N'*/1' ) OR d.day_of_week = @now_day_of_week) |
| 274 | + AND (@now_year = d.calendar_year)) |
| 275 | + SELECT TOP (1) @is_cron_true = 1 FROM time_object |
| 276 | + WHERE time_object.valid_time >= @validate_date |
| 277 | + OPTION(RECOMPILE) ; |
| 278 | + |
| 279 | + SELECT @is_success = 1 , @out_is_cron_true = @is_cron_true; |
| 280 | + END TRY |
| 281 | + BEGIN CATCH |
| 282 | + SET @is_success = 0; |
| 283 | + |
| 284 | + THROW; |
| 285 | + |
| 286 | + END CATCH; |
| 287 | + EXEC tSQLcron.usp_write_verbose '::END::' , 'tSQLcron.sp_is_date_in_cron_period'; |
| 288 | + RETURN @is_success |
| 289 | +END; |
0 commit comments