计算两个日期时间之间的时差,不包括周末,以天时分秒格式

问题描述 投票:0回答:3

我想计算两个日期时间之间的时差。星期六和星期日需要排除在计算之外。

例如

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.

如何确定天数?

小提琴:https://www.db-fiddle.com/f/3V6QVdE1PPETKS6yN33zdE/0

sql postgresql php-carbon
3个回答
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

输出:


|输出 |

| 7天4小时37分48秒|


0
投票

你总是得到 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;
$$;

0
投票

由于您的差异时间是通过将

end date
提取到
MAX(date)
来计算的-其值与
end date
相同的日期或
end date
之前的最后一个工作日期,因此无法正确计算差异天数。

您可以使用以下查询获得所需的结果。

  1. 使用
    generate_series
    函数生成从
    start date
    end date 的日期系列,然后从该系列中仅获取工作日。
  2. 对于日期系列中的每个工作日期,计算每个日期的
    working time
    working time
    的数据类型将是Postgres的interval)。
  3. 然后计算日期系列中所有工作日期的总数
    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;

看演示这里.

© www.soinside.com 2019 - 2024. All rights reserved.