SQLite Forum

Calculating duration in ISO8601 timestamp
Login
Here are some snippets of an UDF that does various calculations with elapsedTime values.  An alternate calling format can also provide the output in a format that can be used directly as a *modifier* by the strftime/datetime functions.

```
/*
** The elaspsedTime function will take either an input text string or an
** integer/floating point value.
**
** Input text in the d:h:m:s format will return a floating point value
** which is the total number of seconds represented.  Each of d/h/m/s may
** be arbitrary floating point numbers.
**
** Note that the d:h:m:s result will be calculated based on the absolute
** value of each field and that the sign of the leftmost field determines
** the sign of the result.
**
** Input numbers (integer or float) are a total number of seconds (which
** must be in the range -1e21 to 1e21) and will return the elapsed time
** string as d:hh:mm:ss.sss where d may be of arbitrary length, hh will be
** zero filled and between 0 and 23, mm will be zero filled and between 0
** and 59.  ss.sss will be between 00.000 and 59.999, zero filled, with
** exactly three decimal places.
*/

static void _elapsedTime(sqlite3_context *context, int argc, sqlite3_value **argv) {

    const double maxspan = 464269060799.999;

    switch (sqlite3_value_type(argv[0]))
    {
        case SQLITE_NULL:
        {
            return;
        }

        case SQLITE_BLOB:
        {
            return;
        }

        case SQLITE_TEXT:
        {
            double factors[4] = {86400.0, 3600.0, 60.0, 1.0};
            double total = 0.0;
            double sgn = 1.0;
            char *start, *end;


            /* Force conversion to utf-8 and make a copy of the text arg so we can modify it */
            sqlite3_value_text(argv[0]);
            start = sqlite3_malloc(sqlite3_value_bytes(argv[0]) + 1);
            strcpy(start, sqlite3_value_text(argv[0]));
            end = start + strlen(start);

            /* Compute totalseconds by parsing colon separated floats from the right */
            for (int j=3; j >= 0; j--)
            {
                double value;
                char *i;

                for (i=end; ((*i != ':') && (i >= start)); i--) ;
                value = atof(i + 1);
                total += fabs(value * factors[j]);
                sgn = (value < 0) || (*(i + 1) == '-') ? -1.0 : 1.0;
                if (i > start)
                    *i = 0;
                else
                    break;
            }
            sqlite3_free(start);
            if (total > maxspan)
                return;
            total *= sgn;

            if ((intptr_t)sqlite3_user_data(context) == 0)
            {
                sqlite3_result_double(context, total);
            }
            else
            {
                char out[32];
                /* Output datetime modifier format */
                sqlite3_snprintf(sizeof(out), out, "%+.3f seconds", total);
                sqlite3_result_text(context, out, -1, SQLITE_TRANSIENT);
            }
            return;
        }

        default: /* numeric */
        {
            double s;
            int d, h, m;
            char out[32];
            char *sgn;

            /* Get our total seconds as a float */
            s = sqlite3_value_double(argv[0]);

            if (fabs(s) > maxspan)
                return;

            /* Return datetime modifier format */
            if ((intptr_t)sqlite3_user_data(context) == 1)
            {
                sqlite3_snprintf(sizeof(out), out, "%+.3f seconds", s);
                sqlite3_result_text(context, out, -1, SQLITE_TRANSIENT);
                return;
            }

            /* Save our sign and use only absolute value */
            sgn = s < 0 ? "-" : "";
            s = fabs(s);

            /* convert s to d/h/m/s */
            d = (int)(s / 86400.0);
            s = fmod(s, 86400.0);
            h = (int)(s / 3600.0);
            s = fmod(s, 3600.0);
            m = (int)(s / 60.0);
            s = fmod(s, 60.0);

            sqlite3_snprintf(sizeof(out), out, "%s%d:%02d:%02d:%06.3f", sgn, d, h, m, s);
            sqlite3_result_text(context, out, -1, SQLITE_TRANSIENT);
            return;
        }
    }
}
```

With the following registration functions:

```
    sqlite3_create_function(db, "elapsedTime",  1, SQLITE_UTF8|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS, (void*)0, _elapsedTime, 0, 0);
    sqlite3_create_function(db, "timeModifier", 1, SQLITE_UTF8|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS, (void*)1, _elapsedTime, 0, 0);
```

Note that the input is either the specified elapsedTime string *or* an integer/float duration.  You could, for example, calculate the number of seconds between `"a"` and `"b"` as `"((julianday(b) - julianday(a))*86400"` so that `elapsedTime((julianday(b)-julianday(a))*86400)` will return the d:hh:mm:ss.sss string representing the difference.

Similarly timeModifier will return this in a format that can be used by the datetime family of functions such that `"datetime(a, timeModifier((julianday(b)-julianday(a))*86400))"` returns the datetime string b ...

The boundary 464269060799.999 represents the maximum number of seconds that the datetime functions can deal (the date `'9999-12-31 23:59:59.999'`)