获取给定日期的最后一个星期三
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)。
如果传递了日期时间字段,我如何确定最后一个星期三是什么时候并将其设置为当天上午 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)。