Surpassing MySQL's TIME value limit of 838:59:59
Asked Answered
D

7

16

The title might be a bit confusing so allow me to explain. I'm using a table to record my work logs. Every day I'll create an entry stating from what time to what time I have worked and I'll add a comment describing what I did.

I then use a query to compare the timestamps to figure out exactly how many hours and minutes I have worked that day. Additionally, I use a query to calculate the sum of hours and minutes I have worked the entire year. That's where I'm running into a problem. My query is as follows.

SELECT TIME_FORMAT(SEC_TO_TIME(SUM(TIME_TO_SEC(TIMEDIFF(entry_end_time, entry_start_time)))), '%H:%i') 
AS total FROM entry 
WHERE entry_date BETWEEN '2012-01-01' AND '2012-12-31' AND user_id = 3

By default, MySQL TIME fields allow a time range of '-838:59:59' to '838:59:59'. I have currently logged more than 900 hours of work this year though, and I want the result of my query to reflect this. Instead, the result is 838:59:59, which makes sense because that is the limit.

Is there any way around this so the result of the query can go beyond 839 hours, or would I have to use something like PHP to go over the entire table and add it all up? I kind of want to avoid that if possible.

Denyse answered 5/12, 2012 at 21:54 Comment(0)
G
6

I'd just retrieve the total number of seconds worked, and convert to hours/minutes as required in the presentation layer of my application (it is, after all, a simple case of division by 60):

<?
  $dbh = new PDO("mysql:dbname=$dbname", $username, $password);
  $dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, FALSE);

  $qry = $dbh->prepare('
    SELECT SUM(TIME_TO_SEC(entry_end_time)-TIME_TO_SEC(entry_start_time))
    FROM   entry 
    WHERE  entry_date BETWEEN :start_date AND :end_date
       AND user_id = :user_id
  ');

  $qry->execute([
    ':start_date' => '2012-01-01',
    ':end_date'   => '2012-12-31',
    ':user_id'    => 3
  ]);

  list ($totalMins, $remngSecs) = gmp_div_qr($qry->fetchColumn(), 60);
  list ($totalHour, $remngMins) = gmp_div_qr($totalMins, 60);

  echo "Worked a total of $totalHour:$remngMins:$remngSecs.";
?>
Groome answered 5/12, 2012 at 22:34 Comment(1)
I was already working on the code when I noticed your update. Thanks, I'll go with this solution!Denyse
B
8

Have a look at timestampdiff which doesn't have the TIME limitation. I.e. something like (untested):

SELECT CONCAT(
        TIMESTAMPDIFF(HOURS, entry_end_time, entry_start_time), 
        ":",
        MOD(TIMESTAMPDIFF(MINUTES, entry_end_time, entry_start_time),60)
      )
AS total FROM entry 
WHERE entry_date BETWEEN '2012-01-01' AND '2012-12-31' AND user_id = 3

The concats not ideal, I'm sure there will be a more elegant solution.

Bernadette answered 5/12, 2012 at 22:19 Comment(4)
from the MySQL documentation: The result returned by TIMEDIFF() is limited to the range allowed for TIME valuesLeaf
@JohnBlythe Immediately after that it says Alternatively, you can use either of the functions TIMESTAMPDIFF() and UNIX_TIMESTAMP(), both of which return integers. Note that my answer uses TIMESTAMPDIFF() not TIMEDIFF().Bernadette
oi! i must've gotten turned around in my reading. thanks for the clarificationLeaf
As a minor correction, TIMESTAMPDIFF's first parameter is always singular units (HOUR, MINUTE, MONTH, etc.)Suziesuzuki
G
6

I'd just retrieve the total number of seconds worked, and convert to hours/minutes as required in the presentation layer of my application (it is, after all, a simple case of division by 60):

<?
  $dbh = new PDO("mysql:dbname=$dbname", $username, $password);
  $dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, FALSE);

  $qry = $dbh->prepare('
    SELECT SUM(TIME_TO_SEC(entry_end_time)-TIME_TO_SEC(entry_start_time))
    FROM   entry 
    WHERE  entry_date BETWEEN :start_date AND :end_date
       AND user_id = :user_id
  ');

  $qry->execute([
    ':start_date' => '2012-01-01',
    ':end_date'   => '2012-12-31',
    ':user_id'    => 3
  ]);

  list ($totalMins, $remngSecs) = gmp_div_qr($qry->fetchColumn(), 60);
  list ($totalHour, $remngMins) = gmp_div_qr($totalMins, 60);

  echo "Worked a total of $totalHour:$remngMins:$remngSecs.";
?>
Groome answered 5/12, 2012 at 22:34 Comment(1)
I was already working on the code when I noticed your update. Thanks, I'll go with this solution!Denyse
C
3

Some simple math can do the trick,I hardcoded a random number of seconds(10000000)

SELECT CONCAT(FLOOR(10000000/3600),':',FLOOR((10000000%3600)/60),':',(10000000%3600)%60)

Fiddle

2777:46:40
Castellatus answered 11/10, 2018 at 19:7 Comment(0)
L
0

Counting the days separately is enough.
Here's the concatenation I used.

I illustrated with a fully copy/pastable example to ease the understanding of the limit we hit (TIME format's max value)
A free unicorn is bundled to simplify comma management

SELECT 'pq7~' AS unicorn
#######################
##  Expected result  ##
#######################
## Total, formatted as days:hh:mm:ss ##
  ,CONCAT(
    FLOOR(TIMESTAMPDIFF(SECOND, '2017-01-01 09:17:45', '2017-03-07 17:06:24') / 86400)
    , ':'
    , SEC_TO_TIME(TIMESTAMPDIFF(SECOND, '2017-01-01 09:17:45', '2017-03-07 17:06:24') % 86400)
  ) AS Real_expected_result

#########################
## Calculation details ##
#########################
## Extracted days from diff ##
  ,FLOOR(TIMESTAMPDIFF(SECOND, '2017-01-01 09:17:45', '2017-03-07 17:06:24') / 86400) AS Real_days
## Extracted Hours/minutes/seconds from diff ##
  ,SEC_TO_TIME(TIMESTAMPDIFF(SECOND, '2017-01-01 09:17:45', '2017-03-07 17:06:24') % 86400) AS Real_hours_minutes_seconds

###################################
## Demo of wrong values returned ##
###################################
  ,TIMESTAMPDIFF(SECOND, '2017-01-01 09:17:45', '2017-03-07 17:06:24') AS Real_seconds_diff

## WRONG value returned. 5.64M is truncated to 3.02 ! ##
  ,TIME_TO_SEC(SEC_TO_TIME(5644119)) AS WRONG_result

## Result is effectively limited to 838h59m59s ##
  ,SEC_TO_TIME(TIMESTAMPDIFF(SECOND, '2017-01-01 09:17:45', '2017-03-07 17:06:24')) AS Limit_hit

## Lights on said limit ##
  ,SEC_TO_TIME( 3020398) AS Limit_value_check1
  ,SEC_TO_TIME( 3020400) AS Limit_value_check2
Lyceum answered 9/10, 2017 at 14:50 Comment(0)
A
0

Take a look at these functions. Credits:( https://gist.github.com/NaWer/8333736 )

DROP FUNCTION IF EXISTS BIG_SEC_TO_TIME;
DELIMITER $$
CREATE FUNCTION BIG_SEC_TO_TIME(SECS BIGINT)
RETURNS TEXT
READS SQL DATA
DETERMINISTIC
BEGIN
    DECLARE HEURES TEXT;
    DECLARE MINUTES CHAR(5);
    DECLARE SECONDES CHAR(5);

    IF (SECS IS NULL) THEN RETURN NULL; END IF;

    SET HEURES = FLOOR(SECS / 3600);

    SET MINUTES = FLOOR((SECS - (HEURES*3600)) / 60);

    SET SECONDES = MOD(SECS, 60);

    IF MINUTES < 10 THEN SET MINUTES = CONCAT( "0", MINUTES); END IF;
    IF SECONDES < 10 THEN SET SECONDES = CONCAT( "0", SECONDES); END IF;

    RETURN CONCAT(HEURES, ":", MINUTES, ":", SECONDES);
END;
$$
DELIMITER ;

DROP FUNCTION IF EXISTS BIG_TIME_TO_SEC;
DELIMITER $$
CREATE FUNCTION BIG_TIME_TO_SEC(TIME TEXT)
    RETURNS BIGINT
    DETERMINISTIC
    READS SQL DATA
BEGIN
    DECLARE HEURES TEXT;
    DECLARE MINUTES CHAR(5);
    DECLARE SECONDES CHAR(5);

    IF (TIME IS NULL) THEN RETURN NULL; END IF;

    SET HEURES = SUBSTRING_INDEX(TIME, ":", 1);

    SET MINUTES = SUBSTRING(TIME FROM -5 FOR 2);

    SET SECONDES = SUBSTRING(TIME FROM -2 FOR 2);

    RETURN CAST(HEURES AS  UNSIGNED INTEGER)*3600 + CAST(MINUTES AS  UNSIGNED INTEGER)*60 + CAST(SECONDES AS  UNSIGNED INTEGER);
END;
$$
DELIMITER ;
Auk answered 21/6, 2022 at 11:53 Comment(0)
B
-1

First calculating the days difference then multiply it with 24*60*60 to convert it into seconds then add to it time_to_sec value result

DATEDIFF(start_date,end_date)*24*60*60 + TIME_TO_SEC(TIMEDIFF(TIME(start_date),TIME(end_date))) 
AS sec_diff

For more details check codebucket- Surpassing time_to_sec() function maximum limit

Blythebm answered 3/9, 2013 at 8:48 Comment(0)
H
-1
select  concat(truncate(sum(time_to_sec(TIMEDIFF(hora_fim, hora_ini)))/3600,0), ':', 
TIME_FORMAT(sec_to_time(sum(time_to_sec(TIMEDIFF(hora_fim, hora_ini))) - truncate(sum(time_to_sec(TIMEDIFF(hora_fim, hora_ini)))/3600,0)*3600), '%i:%s'))
as hms from  tb_XXXXXX
Hers answered 5/7, 2017 at 20:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.