问题
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