Get ISO-8601(SCORM 2004) Duration Format in Seconds using MySQL

时光毁灭记忆、已成空白 提交于 2019-12-10 14:03:21

问题


I have a sql column with values like

PT2M52.37S
PT21.79S
PT53M29.68S
P
PT9S

Is it possible with some MySQL Functions to convert the above format to seconds?

I have searched everything but didn't find something in exactly the same format as above. Any date mysql function I tried isn't working.

More information regarding this format: http://www.ostyn.com/standards/scorm/samples/ISOTimeForSCORM.htm

PHP Function (it always will be P, the first char)

function scorm_format_duration($duration) {
    // Fetch date/time strings.
    $stryears = get_string('years');
    $strmonths = get_string('nummonths');
    $strdays = get_string('days');
    $strhours = get_string('hours');
    $strminutes = get_string('minutes');
    $strseconds = get_string('seconds');

    if ($duration[0] == 'P') {
        // If timestamp starts with 'P' - it's a SCORM 2004 format
        // this regexp discards empty sections, takes Month/Minute ambiguity into consideration,
        // and outputs filled sections, discarding leading zeroes and any format literals
        // also saves the only zero before seconds decimals (if there are any) and discards decimals if they are zero.
        $pattern = array( '#([A-Z])0+Y#', '#([A-Z])0+M#', '#([A-Z])0+D#', '#P(|\d+Y)0*(\d+)M#',
                            '#0*(\d+)Y#', '#0*(\d+)D#', '#P#', '#([A-Z])0+H#', '#([A-Z])[0.]+S#',
                            '#\.0+S#', '#T(|\d+H)0*(\d+)M#', '#0*(\d+)H#', '#0+\.(\d+)S#',
                            '#0*([\d.]+)S#', '#T#' );
        $replace = array( '$1', '$1', '$1', '$1$2 '.$strmonths.' ', '$1 '.$stryears.' ', '$1 '.$strdays.' ',
                            '', '$1', '$1', 'S', '$1$2 '.$strminutes.' ', '$1 '.$strhours.' ',
                            '0.$1 '.$strseconds, '$1 '.$strseconds, '');
    } else {
        // Else we have SCORM 1.2 format there
        // first convert the timestamp to some SCORM 2004-like format for conveniency.
        $duration = preg_replace('#^(\d+):(\d+):([\d.]+)$#', 'T$1H$2M$3S', $duration);
        // Then convert in the same way as SCORM 2004.
        $pattern = array( '#T0+H#', '#([A-Z])0+M#', '#([A-Z])[0.]+S#', '#\.0+S#', '#0*(\d+)H#',
                            '#0*(\d+)M#', '#0+\.(\d+)S#', '#0*([\d.]+)S#', '#T#' );
        $replace = array( 'T', '$1', '$1', 'S', '$1 '.$strhours.' ', '$1 '.$strminutes.' ',
                            '0.$1 '.$strseconds, '$1 '.$strseconds, '' );
    }

    $result = preg_replace($pattern, $replace, $duration);

    return $result;
}

回答1:


To make the STR_TO_DATE works, you need to add %f for the Microseconds.

SELECT duration,
CASE
    WHEN duration LIKE 'P%DT%H%M%.%S' THEN STR_TO_DATE(duration, 'P%dDT%HH%iM%s.%fS')
    WHEN duration LIKE 'P%DT%H%M%S' THEN STR_TO_DATE(duration, 'P%dDT%HH%iM%sS')
    WHEN duration LIKE 'PT%H%M%.%S' THEN STR_TO_DATE(duration, 'PT%HH%iM%s.%fS')
    WHEN duration LIKE 'PT%H%M%S' THEN STR_TO_DATE(duration, 'PT%HH%iM%sS')
    WHEN duration LIKE 'PT%M%.%S' THEN STR_TO_DATE(duration, 'PT%iM%s.%fS')
    WHEN duration LIKE 'PT%M%S' THEN STR_TO_DATE(duration, 'PT%iM%sS')
    WHEN duration LIKE 'PT%.%S' THEN STR_TO_DATE(duration, 'PT%s.%fS')
    WHEN duration LIKE 'PT%S' THEN STR_TO_DATE(duration, 'PT%sS')
END as session
FROM db

PT27M59.08S => 00:27:59.080000

PT2H27M37.11S => 02:27:37.110000

P4DT19H46M18.22S => 115:46:18.220000




回答2:


I've been created 2 functions to convert the result of cmi.session_time and cmi.total_time form ISO8601 to seconds:

CREATE FUNCTION 'ISO8601_duration_delete_miliseconds'('isoDuration' VARCHAR(512)) RETURNS varchar(512) CHARSET latin1
BEGIN
  DECLARE strIndex INT;
  DECLARE strRes varchar(512);

  SET strIndex = INSTR(isoDuration, '.');
  if (strIndex > 0) then
    set strRes = substring(isoDuration, 1, strIndex-1);
    set strRes = concat(strRes, 'S');
  else
    set strRes = isoDuration;
  end if;

  RETURN strRes;
END

And the final function:

CREATE FUNCTION `iso8601duration_to_seconds`(`isoDuration` VARCHAR(512)) RETURNS int(11)
BEGIN
  DECLARE strIndex INT;
  DECLARE strRes varchar(512);
  DECLARE intRes INT;

  set strRes =  ISO8601_duration_delete_miliseconds(isoDuration);

  if (INSTR(isoDuration, 'H') > 0) then
    set intRes = time_to_sec(str_to_date(strRes, 'PT%HH%iM%sS'));
  elseif (INSTR(isoDuration, 'M') > 0) then
    set intRes = time_to_sec(str_to_date(strRes, 'PT%iM%sS'));
  else
    set intRes = time_to_sec(str_to_date(strRes, 'PT%sS'));
  end if;

  RETURN intRes;
END



回答3:


I don't think there is a standard function for doing this. Using str_to_date() is a possibility, but it will fail on missing time components. If you only have minute and second components, you can do this painfully with string operations.

select ((case when p like '%M%S'
              then substring_index(substring_index(p, 'M', 2), 'M', -1)
              when p like '%T%S'
              then substring_index(substring_index(p, 'T', 2), 'T', -1)
              else ''
         end) +
        (case when p like 'T%M'
              then substring_index(substring_index(p, 'T', 2), 'T', -1) + 0
              else 0
         end) * 60
        ) as seconds

This does not generalize really easily as more time components are added. The seconds component, for instance, is the second element when the splitter is 'M' or 'T'. Sometimes, you just have to wonder what the ISO committee is thinking when they make these standards.

Note that MySQL converts a string to numbers using leading digits (if any). The above uses this feature.

If you are supporting more complex period formats, then I would suggest writing a user-defined function and posting it here as an answer.

I will also note that SAS supports this format. Better yet, they have really good documentation explaining it.

EDIT:

You can handle all formats more easily with this kludge. It inserts a space after each component and then uses substring_index():

select (case when newp like '%S'
             then substring_index(substring_index(newp, 'S', 1), ' ', -1) + 1
             else 0
        end) as seconds,
       (case when newp like '%M%'
             then substring_index(substring_index(newp, 'M', 1), ' ', -1) + 1
             else 0
        end) as minutes,
       . . .
from (select replace(replace(replace(replace(replace(p, 'T', 'T '
                                                    ), 'Y', 'Y '
                                            ), 'D', 'D '
                                    ), 'H', 'H '
                            ), 'S', 'S '
                    ) as newp
      from t
     ) t


来源:https://stackoverflow.com/questions/33172258/get-iso-8601scorm-2004-duration-format-in-seconds-using-mysql

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!