MySQL-将ISO-8601持续时间转换为DATE_ADD的时间间隔

我有两个存储在数据库中的字段,其中一个包含日期时间,另一个包含代表ISO-8601格式的持续时间的字符串(例如“P1MT2H”)。我希望能够将持续时间添加到SQL中的datetime列中,看来我应该可以使用DATE_ADD做到这一点,但是我不知道是否存在将其转换为间隔的好方法。我希望不必定义自己的sql函数(如果可能的话)来解析时间间隔。

最佳答案

我仍然很想听到这个问题的一些不错的答案,但是就目前而言,我已经创建了一个函数来执行此操作。我添加了第三个可选参数,以在特定时区运行计算。这不是一个通用的解决方案,但我希望它能使其他人走上正确的道路。

CREATE FUNCTION ADD_ISO_DURATION(StartDate DATETIME, Duration VARCHAR(45), Timezone VARCHAR(45))
RETURNS DATETIME
BEGIN
  DECLARE TimeStr VARCHAR(45) DEFAULT '';
  DECLARE Pos INTEGER;

  IF StartDate IS NULL OR Duration IS NULL OR LENGTH(Duration) = 0 THEN
    RETURN StartDate;
  END IF;

  IF Timezone IS NOT NULL THEN SET StartDate = CONVERT_TZ(StartDate, 'UTC', Timezone); END IF;

  IF Duration REGEXP '^P[0-9]+W$' THEN
    SET StartDate = DATE_ADD(StartDate, INTERVAL SUBSTR(Duration, 2, LENGTH(Duration) - 2) WEEK);
    RETURN IF(Timezone IS NULL, StartDate, CONVERT_TZ(StartDate, Timezone, 'UTC'));
  END IF;

  IF Duration NOT REGEXP '^P([0-9]+Y)?([0-9]+M)?([0-9]+D)?(T([0-9]+H)?([0-9]+M)?([0-9]+S)?)?$' THEN
    RETURN NULL;
  END IF;

  SET Pos = LOCATE('T', Duration);
  IF Pos <> 0 THEN
    SET TimeStr = SUBSTR(Duration, Pos + 1);
    SET Duration = SUBSTR(Duration, 2, Pos);
  ELSE
    SET Duration = SUBSTR(Duration, 2);
  END IF ;

  SET Pos = LOCATE('Y', Duration);
  IF Pos <> 0 THEN
    SET StartDate = DATE_ADD(StartDate, INTERVAL SUBSTR(Duration, 1, Pos - 1) YEAR);
    SET Duration = SUBSTR(Duration, Pos + 1);
  END IF;

  SET Pos = LOCATE('M', Duration);
  IF Pos <> 0 THEN
    SET StartDate = DATE_ADD(StartDate, INTERVAL SUBSTR(Duration, 1, Pos - 1) MONTH);
    SET Duration = SUBSTR(Duration, Pos + 1);
  END IF;

  SET Pos = LOCATE('D', Duration);
  IF Pos <> 0 THEN
    SET StartDate = DATE_ADD(StartDate, INTERVAL SUBSTR(Duration, 1, Pos - 1) DAY);
  END IF;

  IF LENGTH(TimeStr) <> 0 THEN
    SET Pos = LOCATE('H', TimeStr);
    IF Pos <> 0 THEN
      SET StartDate = DATE_ADD(StartDate, INTERVAL SUBSTR(TimeStr, 1, Pos - 1) HOUR);
      SET TimeStr = SUBSTR(TimeStr, Pos + 1);
    END IF;

    SET Pos = LOCATE('M', TimeStr);
    IF Pos <> 0 THEN
      SET StartDate = DATE_ADD(StartDate, INTERVAL SUBSTR(TimeStr, 1, Pos - 1) MINUTE);
      SET TimeStr = SUBSTR(TimeStr, Pos + 1);
    END IF;

    SET Pos = LOCATE('S', TimeStr);
    IF Pos <> 0 THEN
      SET StartDate = DATE_ADD(StartDate, INTERVAL SUBSTR(TimeStr, 1, Pos - 1) SECOND);
      SET TimeStr = SUBSTR(TimeStr, Pos + 1);
    END IF;
  END IF ;

  RETURN IF(Timezone IS NULL, StartDate, CONVERT_TZ(StartDate, Timezone, 'UTC'));
END $$