英文:
Calculate time difference bewteen 2 datetime excluding weekend in days hours minutes seconds format
问题
以下是你要翻译的代码部分:
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;
希望这能帮助你解决问题。
英文:
I want to calculate the time difference between two datetime. Saturday and sunday need to be excluded from the calculation.
For exemple difference between 2023-01-10 15:12:24
and 2023-01-01 10:34:36
is 6 days 4 hours 37 minutes 48 seconds
according to PHP carbon.
<?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
The goal is to calculate this value using SQL.
I've come to this following query :
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;
Output :
----------------------------------------
| duration |
----------------------------------------
| 0 days 4 hours 37 minutes 48 seconds |
----------------------------------------
I don't understand why days has value equal to 0.
How can I fix the days value ?
Fiddle : https://www.db-fiddle.com/f/3V6QVdE1PPETKS6yN33zdE/0
答案1
得分: 1
以下是代码的翻译部分:
"Really interesting question which completely distracted me from work! This question is similar and could be useful to you:https://stackoverflow.com/questions/23290454/get-all-dates-between-two-dates-in-sql-server .
I think the recommendation of building a calendar table would help you a lot!
To directly answer your question I think it you need to change from max to min, as I have, here:
FROM (
SELECT
EXTRACT(EPOCH FROM ('2023-01-10 15:12:24'::timestamp - MIN(date))::interval) AS diff
FROM date_range) t;
However even with this you return 8 days when I think the answer should be 7....
I'm not overly familiar with the recursive so I went for something like the below. The case statement can definitely be optimised. {note I'm snowflake dialect}
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"
请注意,代码中的HTML实体(如<
)未被正确翻译,因为这些实体没有明确的中文翻译。
英文:
Really interesting question which completely distracted me from work! This question is similar and could be useful to you:https://stackoverflow.com/questions/23290454/get-all-dates-between-two-dates-in-sql-server .
I think the recommendation of building a calendar table would help you a lot!
To directly answer your question I think it you need to change from max to min, as I have, here:
FROM (
SELECT
EXTRACT(EPOCH FROM ('2023-01-10 15:12:24'::timestamp - MIN(date))::interval) AS diff
FROM date_range) t;
However even with this you return 8 days when I think the answer should be 7....
I'm not overly familiar with the recursive so I went for something like the below. The case statement can definitely be optimised. {note I'm snowflake dialect}
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
Output :
| output |
| 7 days 4 hours 37 minutes 48 seconds |
答案2
得分: 1
以下是翻译好的部分:
-
"The reason you always get 0 days is because you selecting
MAX(date)
which turns out to be2023-01-10 10:34:36
(the first value that satisfies your exit condition) which is 0 days from2023-01-10 15:12:24
." -> "你总是得到0天的原因是因为你选择了MAX(date)
,结果是2023-01-10 10:34:36
(满足退出条件的第一个值),与2023-01-10 15:12:24
相差0天。" -
"Perhaps you should select MIN(date)." -> "也许你应该选择MIN(date)。"
-
"I am not even sure that is valid for all timestamps not if the start and/or end dates specified fall on a weekend?" -> "我甚至不确定这是否适用于所有时间戳,如果指定的起始日期和/或结束日期落在周末怎么办?"
-
"But why are you messing around with
epoch
from an interval then the "complicated" date/time calculations." -> "但为什么你要处理来自一个间隔的'epoch',然后进行'复杂'的日期/时间计算。" -
"Your process centers around 2 hard-coded timestamps. The subtraction of 2 timestamps gives an
interval
then you can directly extract each field. Your query reduces to:" -> "你的过程围绕着两个硬编码的时间戳。两个时间戳的减法会产生一个'interval',然后你可以直接提取每个字段。你的查询简化为:" -
"You can even wrap the query in a SQL function and completely hide it away." -> "你甚至可以将查询包装在一个SQL函数中,并完全隐藏它。"
-
"create or replace function diff_without_weekend( start_date_in timestamp, end_date_in timestamp) returns text language sql as $$" -> "创建或替换函数diff_without_weekend( start_date_in timestamp, end_date_in timestamp) 返回文本 语言sql 如下:$$"
-
"with weekend_days (wkend) as..." -> "使用weekend_days(wkend)..."
-
"1: https://dbfiddle.uk/SsNKfkmR" -> "1: https://dbfiddle.uk/SsNKfkmR"
英文:
The reason you always get 0 days is because you selecting MAX(date)
which turns out to be 2023-01-10 10:34:36
(the first value that satisfies your exit condition) which is 0 days from 2023-01-10 15:12:24
. Perhaps you should select MIN(date). I am not even sure that is valid for all timestamps not if the start and/or end dates specified fall on a weekend? <br/>
But why are you messing around with epoch
from an interval then the "complicated" date/time calculations. Your process centers around 2 hard-coded timestamps. The subtraction of 2 timestamps gives an interval
then you can directly extract each field. Your query reduces to: (see 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;
You can even wrap the query in a SQL function and completely hide it away.
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;
$$;
答案3
得分: 1
由于您的时间差是通过从“结束日期”提取MAX(date)
来计算的,其值与“结束日期”相同日期或“结束日期”之前的最后一个工作日相同,因此无法正确计算差异天数的值。
您可以通过以下查询来实现您想要的结果:
- 使用generate_series函数从“开始日期”生成日期系列,然后仅获取该系列中的工作日。
- 对日期系列中的每个工作日期,计算每个日期的“工作时间”(“工作时间”的数据类型将为Postgres的interval)。
- 然后计算日期系列中所有工作日期的总“工作时间”。
SET intervalstyle = 'postgres_verbose'; -- 间隔的显示样式设置
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;
查看演示链接。
英文:
Since your difference time is calculated by extract end date
to the MAX(date)
- which its value is same date as end date
or the last working date before end date
, so difference days value could not be calculated correctly.
You could achieve your desired result by using below query.
- Generating date series from
start date
toend date
by using generate_series function, then get only working days from this series. - For each working date in date series, calculate
working time
per each date (data type ofworking time
will be Postgres's interval). - Then calculating total
working time
of all working dates in date series.
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;
See demo here.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论