获取给定日期的最后一个星期三

Get the last Wednesday from a given date

如果传递了日期时间字段,我如何确定最后一个星期三是什么时候并将其设置为当天上午 10 点?

Declare DateTime @datetime

例如:
如果我传入 @datetime = '2022-Feb-7 5:00:00' 那么我应该得到 2022-Feb-2 10:00:00
如果我传入 @datetime = '2022-Feb-10 16:00:00' 那么我应该得到 2022-Feb-9 10:00:00

我这里有一个极端情况:
如果 @datetime 是星期三并且时间小于上午 10 点,则应将其设置为上周三,如果时间大于上午 10 点,则应将其设置为当前星期三上午 10 点。

例如:
如果我传入 @datetime='2022-Feb-9 5:00:000' 那么我应该得到 2022-Feb-2 10:00:00
如果我传入 @datetime = '2022-Feb-9 16:00:00' 那么我应该得到 2022-Feb-9 10:00:00

您可以这样计算前一个星期三上午 10 点:

DECLARE @DateTime DATETIME = GETDATE();
DECLARE @LastWednesday DATETIME;

SET @LastWednesday = CAST(DATEADD(day, -(3+@@DATEFIRST+DATEPART(weekday,@DateTime-'10:00'))%7, 
CAST(@DateTime-'10:00' AS DATE)) AS DATETIME)+'10:00';

SELECT @DateTime, @LastWednesday;

演示 db<>fiddle here

工作原理

首先从@DateTime 中减去 10 小时以获得星期三的截止时间效果。
@DateTime-'10:00' 因此,在星期三上午 10 点之后,它会计算当前星期三。

现在,要计算一周的第一天,您可以从日期中减去工作日+1。
但一周的第一天取决于 DATEFIRST 设置。如果是 1 则为星期一,如果为 7 则为星期日。 所以变量@@datefirst 用于规范化计算。因此计算不依赖于 DATEFIRST 设置。
这将得到前一个星期六:
@datetime-(0+@@datefirst+DATEPART(weekday,@datetime))%7
所以这是前一个星期三:
@datetime-(3+@@datefirst+DATEPART(weekday,@datetime))%7
模数 7 (%7) 确保它不能减去超过 6。

--
-- Test over date range
--
DECLARE @StartDatetime SMALLDATETIME;
DECLARE @EndDatetime SMALLDATETIME;
SET @StartDateTime = DATEADD(month, -1, DATEADD(day, 1, EOMONTH(GETDATE())));
SET @EndDatetime = EOMONTH(@StartDatetime);

with cte as (
  select @StartDatetime+'11:00' as [Dt]
  union all
  select dateadd(day, 1, [Dt]) from cte
  where [Dt] < @EndDatetime
)
select top 16
  [Dt] 
, datepart(weekday, [Dt]) AS weekday
, datename(weekday, [Dt]) AS weekdayname
, -(3+@@DATEFIRST+DATEPART(weekday,[Dt]-'10:00'))%7 AS daydiff
, prevWed = CAST(DATEADD(day,-(3+@@DATEFIRST+DATEPART(weekday,[Dt]-'10:00'))%7,CAST([Dt]-'10:00' AS DATE)) AS DATETIME)+'10:00'
, @@DATEFIRST AS df
from cte;
Dt weekday weekdayname daydiff prevWed df
2022-02-01 11:00 3 Tuesday -6 2022-01-26 10:00:00.000 7
2022-02-02 11:00 4 Wednesday 0 2022-02-02 10:00:00.000 7
2022-02-03 11:00 5 Thursday -1 2022-02-02 10:00:00.000 7
2022-02-04 11:00 6 Friday -2 2022-02-02 10:00:00.000 7
2022-02-05 11:00 7 Saturday -3 2022-02-02 10:00:00.000 7
2022-02-06 11:00 1 Sunday -4 2022-02-02 10:00:00.000 7
2022-02-07 11:00 2 Monday -5 2022-02-02 10:00:00.000 7
2022-02-08 11:00 3 Tuesday -6 2022-02-02 10:00:00.000 7
2022-02-09 11:00 4 Wednesday 0 2022-02-09 10:00:00.000 7
2022-02-10 11:00 5 Thursday -1 2022-02-09 10:00:00.000 7
2022-02-11 11:00 6 Friday -2 2022-02-09 10:00:00.000 7
2022-02-12 11:00 7 Saturday -3 2022-02-09 10:00:00.000 7
2022-02-13 11:00 1 Sunday -4 2022-02-09 10:00:00.000 7
2022-02-14 11:00 2 Monday -5 2022-02-09 10:00:00.000 7
2022-02-15 11:00 3 Tuesday -6 2022-02-09 10:00:00.000 7
2022-02-16 11:00 4 Wednesday 0 2022-02-16 10:00:00.000 7

鉴于此数据:

CREATE TABLE #dates(source smalldatetime);

INSERT #dates(source) VALUES
('20220207 05:00:00'), -- should be 2/2
('20220210 16:00:00'), -- should be 2/9
('20220209 05:00:00'), -- should be 2/2
('20220209 16:00:00'), -- should be 2/9
('20220209 09:59:00'), -- should be 2/2
('20220209 10:00:00'); -- should be 2/9

此查询会将源日期时间值偏移 14 小时,因此从技术上讲,从上午 10 点开始的任何内容都将被视为第二天。这样就“简化”了计算,让我们只在当天是星期三,调整后的时间仍然是星期三的情况下,再减去一个星期。适用于任何 SET DATEFIRST n 设置。

SELECT source, prev_wed = DATEADD(HOUR, 10, DATEADD(DAY, 
  COALESCE(NULLIF((-@@DATEFIRST-DATEPART(WEEKDAY,adj)-3)%7,0),-7),adj))
FROM 
(
  SELECT source, adj = CONVERT(smalldatetime, 
    CONVERT(date, DATEADD(HOUR, 14, source)))
  FROM #dates
) AS adj;

结果(示例db<>fiddle):

source prev_wed
2022-02-07 05:00 2022-02-02 10:00
2022-02-10 16:00 2022-02-09 10:00
2022-02-09 05:00 2022-02-02 10:00
2022-02-09 16:00 2022-02-09 10:00
2022-02-09 09:59 2022-02-02 10:00
2022-02-09 10:00 2022-02-09 10:00

一个稍微简单一点的避免 @@DATEFIRST 并发症的方法是取一个 已知的 过去的星期三,看看从那时起有多少个 7 天的间隔.

DECLARE @base date = '20200101'; -- known Wednesday

SELECT source, prev_wed = DATEADD(DAY,DATEDIFF(DAY,@base,
    CONVERT(date, DATEADD(HOUR, -10, source)))/7*7, @base)
FROM #dates;

结果相同 (db<>fiddle)。