我想计算两个日期时间之间的时差。星期六和星期日需要排除在计算之外。
例如
2023-01-10 15:12:24
和2023-01-01 10:34:36
之间的区别是6 days 4 hours 37 minutes 48 seconds
根据PHP碳。
<?php
require 'vendor\carbon\autoload.php';
use Carbon\CarbonImmutable;
use Carbon\CarbonInterval;
$created = CarbonImmutable::parse("2023-01-02 10:34:36");
$firstResponse = CarbonImmutable::parse("2023-01-10 15:12:24");
$diffInSeconds = 0;
$step = $created;
while ($step < $firstResponse) {
if ($step->isWeekend()) {
$step = $step->next('Monday');
continue;
}
$nextStep = min($firstResponse, $step->addDay()->startOfDay());
$diffInSeconds += $step->diffInSeconds($nextStep);
$step = $nextStep;
}
echo CarbonInterval::seconds($diffInSeconds)->cascade()->forHumans(); //6 days 4 hours 37 minutes 48 seconds
目标是使用SQL计算这个值。
我来到以下查询:
WITH RECURSIVE date_range AS (
SELECT '2023-01-02 10:34:36'::timestamp AS date
UNION ALL
SELECT CASE
WHEN EXTRACT(ISODOW FROM date) IN (6, 7) THEN date + INTERVAL '1 day'*(8-EXTRACT(ISODOW FROM date))
ELSE date + INTERVAL '1 DAY'
END
FROM date_range
WHERE date + INTERVAL '1 DAY' < '2023-01-10 15:12:24'::timestamp
)
SELECT
CONCAT(
FLOOR(diff / 86400), ' days ',
FLOOR((diff % 86400) / 3600), ' hours ',
FLOOR((diff % 3600) / 60), ' minutes ',
FLOOR(diff % 60), ' seconds'
) AS duration
FROM (
SELECT
EXTRACT(EPOCH FROM ('2023-01-10 15:12:24'::timestamp - MAX(date))::interval) AS diff
FROM date_range
) t;
输出:
----------------------------------------
| duration |
----------------------------------------
| 0 days 4 hours 37 minutes 48 seconds |
----------------------------------------
我不明白为什么 days 的值等于 0.
如何确定天数?
真正有趣的问题完全分散了我的工作注意力!这个问题很相似,可能对你有用:Get all dates between two dates in SQL Server。 我认为构建日历表的建议会对您有很大帮助!
要直接回答您的问题,我认为您需要在此处将最小值更改为最大值:
FROM (
SELECT
EXTRACT(EPOCH FROM ('2023-01-10 15:12:24'::timestamp - MIN(date))::interval) AS diff
FROM date_range) t;
然而,即使这样,当我认为答案应该是 7 时,你会返回 8 天....
我对递归不太熟悉,所以我选择了下面这样的东西。绝对可以优化 case 语句。 {注意我是雪花方言}
SET start_date = '2023-01-02 10:34:36';
SET end_date = '2023-01-10 15:12:24';
WITH DateRange(DateData) AS
(
SELECT $start_date::DATe as Date
UNION ALL
SELECT DATEADD(DAYS ,1,DateData)::DATE
FROM DateRange
WHERE DateData < $end_date
)
,date_array AS (
SELECT datedata
, DAYOFWEEK(datedata) AS day_index
, dayname(datedata) AS day_name
, $start_date
, $end_date
FROM daterange
)
SELECT
$start_date
, substring($start_date ,12,8)::TIME as start_time
, $end_date
, substring($end_date ,12,8)::TIME as end_time
, HOUR(end_time) - HOUR(start_time) as hours
, MINUTE(end_time) - MINUTE(start_time) as minutes
, SECOND(end_time) - SECOND(start_time) as seconds
, COUNT(DISTINCT CASE WHEN day_index NOT IN (6,0) THEN datedata END ) as days
, CONCAT (
CASE WHEN hours < 0 THEN days -1 ELSE days END , ' days ' ,
CASE
WHEN minutes < 0 AND hours < 0 THEN 24 + hours -1
WHEN minutes > 0 AND hours < 0 THEN 24 + hours
WHEN minutes < 0 AND hours > 0 THEN hours -1
ELSE hours END
, ' hours ' ,
CASE
WHEN seconds < 0 AND minutes < 0 THEN 60 + minutes -1
WHEN seconds > 0 AND minutes < 0 THEN 60 + minutes
WHEN seconds < 0 AND minutes > 0 THEN minutes -1
ELSE minutes END
, ' minutes ' ,
CASE WHEN seconds <0 THEN 60 + seconds ELSE seconds END , ' seconds') as output
FROM date_array
GROUP BY 1,2,3
输出:
你总是得到 0 天的原因是因为你选择了
MAX(date)
结果是 2023-01-10 10:34:36
(第一个满足退出条件的值),它距离 2023-01-10 15:12:24
0 天。也许您应该选择 MIN(date)。如果指定的开始和/或结束日期是在周末,我什至不确定这是否对所有时间戳都有效? epoch
,然后再进行“复杂”的日期/时间计算。您的流程以 2 个硬编码时间戳为中心。减去2个时间戳得到一个interval
然后你可以直接提取每个字段。您的查询减少到:(见demo)
with parms (start_date, end_date) as
( select '2023-01-02 10:34:36'::timestamp --- as parameter $1
, '2023-01-10 15:12:24'::timestamp --- as parameter $2
)
, weekend_days (wkend) as
( select sum(case when extract(isodow from d) in (6, 7) then 1 else 0 end)
from parms
cross join generate_series(start_date, end_date, interval '1 day') dn(d)
)
select concat( extract( day from diff) , ' days '
, extract( hours from diff) , ' hours '
, extract( minutes from diff) , ' minuets '
, extract( seconds from diff)::int , ' seconds '
)
from (
select (end_date-start_date)- ( wkend * interval '1 day') diff
from parms
join weekend_days on true
) sq;
您甚至可以将查询包装在 SQL 函数中并将其完全隐藏起来。
create or replace function diff_without_weekend( start_date_in timestamp
, end_date_in timestamp)
returns text
language sql
as $$
with weekend_days (wkend) as
( select sum(case when extract(isodow from d) in (6, 7) then 1 else 0 end)
from generate_series(start_date_in, end_date_in, interval '1 day') dn(d)
)
select CONCAT( extract( day from diff) , ' days '
, extract( hours from diff) , ' hours '
, extract( minutes from diff) , ' minuets '
, extract( seconds from diff)::int , ' seconds ')
from ( select (end_date_in -start_date_in )- ( wkend * interval '1 day') diff
from weekend_days
) sq;
$$;
由于您的差异时间是通过将
end date
提取到MAX(date)
来计算的-其值与end date
相同的日期或end date
之前的最后一个工作日期,因此无法正确计算差异天数。
您可以使用以下查询获得所需的结果。
generate_series函数生成从
start date
到 end date
的日期系列,然后从该系列中仅获取工作日。working time
(working time
的数据类型将是Postgres的interval)。working time
。SET intervalstyle = 'postgres_verbose'; -- format display style for interval
WITH date_range AS
(SELECT '2023-01-02 10:34:36'::timestamp AS start_date
, '2023-01-10 15:12:24'::timestamp AS end_date),
date_series AS
(SELECT d as date
, (CASE
WHEN start_date::date = d THEN interval '1 day' + (d - start_date)::interval
WHEN end_date::date = d THEN (end_date - d)::interval
ELSE interval '1 day'
END) AS working_time
FROM date_range
CROSS JOIN generate_series(start_date::date, end_date::timestamp, interval '1 day') d
WHERE EXTRACT (ISODOW FROM d) BETWEEN 1 AND 5)
SELECT SUM(working_time) AS working_time
FROM date_series;
看演示这里.