LCOV - code coverage report
Current view: top level - src - duration.c (source / functions) Hit Total Coverage
Test: Code coverage Lines: 158 158 100.0 %
Date: 2026-07-14 21:28:07 Functions: 9 9 100.0 %
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 136 136 100.0 %

           Branch data     Line data    Source code
       1                 :            : // Copyright 2019-2026 David Robillard <d@drobilla.net>
       2                 :            : // SPDX-License-Identifier: ISC
       3                 :            : 
       4                 :            : #include "char_utils.h"
       5                 :            : #include "number_utils.h"
       6                 :            : #include "read_utils.h"
       7                 :            : #include "result.h"
       8                 :            : #include "time_utils.h"
       9                 :            : #include "write_utils.h"
      10                 :            : 
      11                 :            : #include <exess/exess.h>
      12                 :            : 
      13                 :            : #include <stdbool.h>
      14                 :            : #include <stdint.h>
      15                 :            : #include <stdlib.h>
      16                 :            : #include <string.h>
      17                 :            : 
      18                 :            : typedef enum { NONE, YEAR, MONTH, DAY, HOUR, MINUTE, SECOND } Field;
      19                 :            : 
      20                 :            : static EXESS_NONBLOCKING ExessStatus
      21                 :       6270 : set_field(ExessDuration* const out,
      22                 :            :           const Field          last_field,
      23                 :            :           const Field          field,
      24                 :            :           const uint64_t       value)
      25                 :            : {
      26         [ +  + ]:       6270 :   if (value >= INT32_MAX) {
      27                 :          7 :     return EXESS_OUT_OF_RANGE;
      28                 :            :   }
      29                 :            : 
      30         [ +  + ]:       6263 :   if (field <= last_field) {
      31                 :          6 :     return EXESS_BAD_ORDER;
      32                 :            :   }
      33                 :            : 
      34                 :       6257 :   EXESS_CONSTEXPR uint64_t factors[7] = {
      35                 :            :     0UL, 12UL, 1UL, 24UL * 60UL * 60UL, 60UL * 60UL, 60UL, 1UL};
      36                 :            : 
      37                 :       6257 :   const uint64_t factor = factors[field];
      38         [ +  + ]:       6257 :   int32_t* const target = (field < DAY) ? &out->months : &out->seconds;
      39                 :            : 
      40                 :       6257 :   const int64_t new_value = (int64_t)*target + (int64_t)(factor * value);
      41         [ +  + ]:       6257 :   if (new_value >= INT32_MAX) {
      42                 :          4 :     return EXESS_OUT_OF_RANGE;
      43                 :            :   }
      44                 :            : 
      45                 :       6253 :   *target = (int32_t)new_value;
      46                 :       6253 :   return EXESS_SUCCESS;
      47                 :            : }
      48                 :            : 
      49                 :            : static EXESS_NONBLOCKING ExessResult
      50                 :       1178 : read_duration_date(ExessDuration* const out, const char* const str)
      51                 :            : {
      52                 :       1178 :   size_t      i          = 0U;
      53                 :       1178 :   ExessStatus st         = EXESS_SUCCESS;
      54                 :       1178 :   unsigned    last_field = 0U;
      55                 :            : 
      56   [ +  +  +  +  :       4336 :   while (!st && last_field < DAY && str[i] != 'T') {
                   +  + ]
      57                 :            :     // Read the unsigned integer value
      58                 :       3237 :     uint64_t          value = 0U;
      59                 :       3237 :     const ExessResult r     = read_digits(&value, str + i);
      60                 :       3237 :     i += r.count;
      61         [ +  + ]:       3237 :     if (r.status) {
      62         [ +  + ]:         74 :       st = r.count ? r.status : EXESS_SUCCESS;
      63                 :         74 :       break;
      64                 :            :     }
      65                 :            : 
      66                 :            :     // Read Y, M, or D field tag
      67                 :       6326 :     const Field field = (str[i] == 'Y')   ? YEAR
      68   [ +  +  +  + ]:       4254 :                         : (str[i] == 'M') ? MONTH
      69         [ +  + ]:       1091 :                         : (str[i] == 'D') ? DAY
      70                 :            :                                           : NONE;
      71         [ +  + ]:       3163 :     if (!field) {
      72                 :          5 :       return RESULT(EXESS_EXPECTED_DATE_TAG, i);
      73                 :            :     }
      74                 :            : 
      75                 :            :     // Set the field value and advance
      76                 :       3158 :     st         = set_field(out, (Field)last_field, field, value);
      77                 :       3158 :     last_field = field;
      78                 :       3158 :     ++i;
      79                 :            :   }
      80                 :            : 
      81         [ +  + ]:       1173 :   return i > 0 ? RESULT(st, i) : RESULT(EXESS_EXPECTED_DIGIT, i);
      82                 :            : }
      83                 :            : 
      84                 :            : static ExessResult
      85                 :       1113 : read_duration_time(ExessDuration* const out, const char* const str)
      86                 :            : {
      87                 :       1113 :   size_t      i          = 0U;
      88                 :       1113 :   ExessStatus st         = EXESS_SUCCESS;
      89                 :       1113 :   unsigned    last_field = 0U;
      90                 :            : 
      91   [ +  +  +  + ]:       4225 :   while (!st && last_field < SECOND) {
      92                 :            :     // Read the unsigned integer value
      93                 :       3149 :     uint64_t          value = 0U;
      94                 :       3149 :     const ExessResult r     = read_digits(&value, str + i);
      95                 :       3149 :     i += r.count;
      96         [ +  + ]:       3149 :     if (r.status) {
      97         [ +  + ]:         31 :       st = r.count ? r.status : EXESS_SUCCESS;
      98                 :         31 :       break;
      99                 :            :     }
     100                 :            : 
     101                 :       3118 :     Field field = YEAR;
     102         [ +  + ]:       3118 :     if (str[i] == '.') {
     103                 :       1049 :       uint32_t nanoseconds = 0U;
     104         [ +  + ]:       1049 :       if (!is_digit(str[++i])) {
     105                 :          1 :         return RESULT(EXESS_EXPECTED_DIGIT, i);
     106                 :            :       }
     107                 :            : 
     108                 :       1048 :       const ExessResult s = read_nanoseconds(&nanoseconds, str + i);
     109                 :       1048 :       i += s.count;
     110         [ +  + ]:       1048 :       if (str[i] != 'S') {
     111                 :          4 :         return RESULT(EXESS_EXPECTED_SECOND_TAG, i);
     112                 :            :       }
     113                 :            : 
     114                 :       1044 :       field            = SECOND;
     115                 :       1044 :       out->nanoseconds = (int32_t)nanoseconds;
     116         [ +  + ]:       2069 :     } else if (str[i] == 'H') {
     117                 :       1013 :       field = HOUR;
     118         [ +  + ]:       1056 :     } else if (str[i] == 'M') {
     119                 :       1030 :       field = MINUTE;
     120         [ +  + ]:         26 :     } else if (str[i] == 'S') {
     121                 :         25 :       field = SECOND;
     122                 :            :     } else {
     123                 :          1 :       return RESULT(EXESS_EXPECTED_TIME_TAG, i);
     124                 :            :     }
     125                 :            : 
     126                 :            :     // Set the field value and advance
     127                 :       3112 :     st         = set_field(out, (Field)last_field, field, value);
     128                 :       3112 :     last_field = (unsigned)field;
     129                 :       3112 :     ++i;
     130                 :            :   }
     131                 :            : 
     132         [ +  + ]:       1107 :   return i > 0 ? RESULT(st, i) : RESULT(EXESS_EXPECTED_DIGIT, i);
     133                 :            : }
     134                 :            : 
     135                 :            : static EXESS_NONBLOCKING ExessOrder
     136                 :         50 : compare_duration_partial(const ExessDuration lhs, const ExessDuration rhs)
     137                 :            : {
     138                 :            :   // See https://www.w3.org/TR/xmlschema11-2/#duration
     139                 :            : 
     140                 :            :   const ExessDateTime a = {1696, 9, 1, EXESS_TIMEZONE_LOCAL, 0, 0, 0, 0};
     141                 :            :   const ExessDateTime b = {1697, 2, 1, EXESS_TIMEZONE_LOCAL, 0, 0, 0, 0};
     142                 :            :   const ExessDateTime c = {1903, 3, 1, EXESS_TIMEZONE_LOCAL, 0, 0, 0, 0};
     143                 :            :   const ExessDateTime d = {1903, 7, 1, EXESS_TIMEZONE_LOCAL, 0, 0, 0, 0};
     144                 :            : 
     145                 :         50 :   const ExessOrder cmp = exess_compare_date_time(
     146                 :            :     exess_add_date_time_duration(a, lhs), exess_add_date_time_duration(a, rhs));
     147                 :            : 
     148         [ +  + ]:         50 :   if (cmp == exess_compare_date_time(exess_add_date_time_duration(b, lhs),
     149                 :         40 :                                      exess_add_date_time_duration(b, rhs)) &&
     150         [ +  + ]:         40 :       cmp == exess_compare_date_time(exess_add_date_time_duration(c, lhs),
     151                 :         28 :                                      exess_add_date_time_duration(c, rhs)) &&
     152         [ +  + ]:         28 :       cmp == exess_compare_date_time(exess_add_date_time_duration(d, lhs),
     153                 :            :                                      exess_add_date_time_duration(d, rhs))) {
     154                 :         26 :     return cmp;
     155                 :            :   }
     156                 :            : 
     157                 :         24 :   return (lhs.months < rhs.months)     ? EXESS_ORDER_MAYBE_LESS
     158   [ +  +  +  + ]:         32 :          : (rhs.months < lhs.months)   ? EXESS_ORDER_MAYBE_GREATER
     159         [ +  + ]:          8 :          : (lhs.seconds < rhs.seconds) ? EXESS_ORDER_MAYBE_LESS
     160                 :            :                                        : EXESS_ORDER_MAYBE_GREATER;
     161                 :            : }
     162                 :            : 
     163                 :            : EXESS_NONBLOCKING ExessOrder
     164                 :        104 : exess_compare_duration(const ExessDuration lhs, const ExessDuration rhs)
     165                 :            : {
     166                 :            :   // Exactly equal
     167   [ +  +  +  + ]:        104 :   if (lhs.months == rhs.months && lhs.seconds == rhs.seconds &&
     168         [ +  + ]:         26 :       lhs.nanoseconds == rhs.nanoseconds) {
     169                 :         18 :     return EXESS_ORDER_EQUAL;
     170                 :            :   }
     171                 :            : 
     172                 :            :   // Totally ordered yearMonthDuration
     173   [ +  +  +  +  :         86 :   if (!lhs.seconds && !lhs.nanoseconds && !rhs.seconds && !rhs.nanoseconds) {
             +  +  +  + ]
     174                 :         12 :     return (lhs.months < rhs.months) ? EXESS_ORDER_STRICTLY_LESS
     175         [ +  + ]:         12 :                                      : EXESS_ORDER_STRICTLY_GREATER;
     176                 :            :   }
     177                 :            : 
     178                 :            :   // Totally ordered dayTimeDuration
     179   [ +  +  +  + ]:         74 :   if (!lhs.months && !rhs.months) {
     180                 :         24 :     return (lhs.seconds < rhs.seconds)           ? EXESS_ORDER_STRICTLY_LESS
     181   [ +  +  +  + ]:         28 :            : (rhs.seconds < lhs.seconds)         ? EXESS_ORDER_STRICTLY_GREATER
     182         [ +  + ]:          4 :            : (lhs.nanoseconds < rhs.nanoseconds) ? EXESS_ORDER_STRICTLY_LESS
     183                 :            :                                                  : EXESS_ORDER_STRICTLY_GREATER;
     184                 :            :   }
     185                 :            : 
     186                 :         50 :   return compare_duration_partial(lhs, rhs);
     187                 :            : }
     188                 :            : 
     189                 :            : EXESS_NONBLOCKING ExessResult
     190                 :       1261 : exess_read_duration(const char* const str, ExessDuration* const out)
     191                 :            : {
     192                 :       1261 :   memset(out, 0, sizeof(*out));
     193                 :            : 
     194                 :       1261 :   size_t i           = skip_whitespace(str);
     195                 :       1261 :   bool   is_negative = false;
     196         [ +  + ]:       1261 :   if (str[i] == '-') {
     197                 :        514 :     is_negative = true;
     198                 :        514 :     ++i;
     199                 :            :   }
     200                 :            : 
     201         [ +  + ]:       1261 :   if (str[i] != 'P') {
     202                 :          7 :     return RESULT(EXESS_EXPECTED_DURATION, i);
     203                 :            :   }
     204                 :            : 
     205                 :       1254 :   ++i;
     206         [ +  + ]:       1254 :   if (str[i] != 'T') {
     207                 :       1178 :     ExessResult r = read_duration_date(out, str + i);
     208         [ +  + ]:       1178 :     if (r.status) {
     209                 :         20 :       return RESULT(r.status, i + r.count);
     210                 :            :     }
     211                 :            : 
     212                 :       1158 :     i += r.count;
     213                 :            :   }
     214                 :            : 
     215         [ +  + ]:       1234 :   if (str[i] == 'T') {
     216                 :       1113 :     ++i;
     217                 :            : 
     218                 :       1113 :     ExessResult r = read_duration_time(out, str + i);
     219         [ +  + ]:       1113 :     if (r.status) {
     220                 :         19 :       return RESULT(r.status, i + r.count);
     221                 :            :     }
     222                 :            : 
     223                 :       1094 :     i += r.count;
     224                 :            :   }
     225                 :            : 
     226         [ +  + ]:       1215 :   if (is_negative) {
     227                 :        514 :     out->months      = -out->months;
     228                 :        514 :     out->seconds     = -out->seconds;
     229                 :        514 :     out->nanoseconds = -out->nanoseconds;
     230                 :            :   }
     231                 :            : 
     232                 :       1215 :   return RESULT(EXESS_SUCCESS, i);
     233                 :            : }
     234                 :            : 
     235                 :            : static size_t
     236                 :      10548 : write_int_field(const uint32_t value,
     237                 :            :                 const char     tag,
     238                 :            :                 const size_t   buf_size,
     239                 :            :                 char* const    buf,
     240                 :            :                 const size_t   i)
     241                 :            : {
     242         [ +  + ]:      10548 :   if (!value) {
     243                 :        385 :     return 0;
     244                 :            :   }
     245                 :            : 
     246                 :      10163 :   const ExessResult r = write_digits(value, buf_size, buf, i);
     247                 :      10163 :   return r.count + write_char(tag, buf_size, buf, i + r.count);
     248                 :            : }
     249                 :            : 
     250                 :            : static size_t
     251                 :       2087 : write_seconds(const uint8_t  abs_seconds,
     252                 :            :               const uint32_t abs_nanoseconds,
     253                 :            :               const size_t   buf_size,
     254                 :            :               char* const    buf,
     255                 :            :               const size_t   i)
     256                 :            : {
     257                 :       2087 :   const ExessResult r = write_digits(abs_seconds, buf_size, buf, i);
     258                 :            : 
     259                 :       2087 :   size_t len = r.count;
     260         [ +  + ]:       2087 :   if (abs_nanoseconds) {
     261                 :       2067 :     len += write_nanoseconds(abs_nanoseconds, buf_size, buf, i + len);
     262                 :            :   }
     263                 :            : 
     264                 :       2087 :   return len + write_char('S', buf_size, buf, i + len);
     265                 :            : }
     266                 :            : 
     267                 :            : EXESS_NONBLOCKING ExessResult
     268                 :       2131 : exess_write_duration(const ExessDuration value,
     269                 :            :                      const size_t        buf_size,
     270                 :            :                      char* const         buf)
     271                 :            : {
     272                 :            :   // Write zero as a special case
     273                 :       2131 :   size_t i = 0;
     274   [ +  +  +  +  :       2131 :   if (value.months == 0 && value.seconds == 0 && value.nanoseconds == 0) {
                   +  + ]
     275                 :          4 :     i += write_string(3, "P0Y", buf_size, buf, i);
     276                 :          4 :     return end_write(EXESS_SUCCESS, buf_size, buf, i);
     277                 :            :   }
     278                 :            : 
     279   [ +  +  +  + ]:       2127 :   if (value.months == INT32_MIN || value.seconds == INT32_MIN) {
     280                 :          3 :     return end_write(EXESS_OUT_OF_RANGE, buf_size, buf, 0);
     281                 :            :   }
     282                 :            : 
     283                 :       2124 :   const bool is_negative =
     284   [ +  +  +  +  :       2124 :     (value.months < 0 || value.seconds < 0 || value.nanoseconds < 0);
                   +  + ]
     285                 :            : 
     286         [ +  + ]:       2124 :   if (is_negative &&
     287   [ +  +  +  +  :       1019 :       (value.months > 0 || value.seconds > 0 || value.nanoseconds > 0)) {
                   +  + ]
     288                 :          6 :     return end_write(EXESS_BAD_VALUE, buf_size, buf, 0);
     289                 :            :   }
     290                 :            : 
     291                 :            :   // Write duration prefix
     292         [ +  + ]:       2118 :   i += is_negative ? write_char('-', buf_size, buf, i) : 0U;
     293                 :       2118 :   i += write_char('P', buf_size, buf, i);
     294                 :            : 
     295                 :       2118 :   const uint32_t abs_years   = (uint32_t)(abs(value.months) / 12);
     296                 :       2118 :   const uint32_t abs_months  = (uint32_t)(abs(value.months) % 12);
     297                 :       2118 :   const uint32_t abs_days    = (uint32_t)(abs(value.seconds) / (24 * 60 * 60));
     298                 :       2118 :   const uint32_t abs_hours   = (uint32_t)(abs(value.seconds) / 60 / 60 % 24);
     299                 :       2118 :   const uint32_t abs_minutes = (uint32_t)(abs(value.seconds) / 60 % 60);
     300                 :       2118 :   const uint8_t  abs_seconds = (uint8_t)(abs(value.seconds) % 60);
     301                 :       2118 :   const uint32_t abs_nanoseconds = (uint32_t)abs(value.nanoseconds);
     302                 :            : 
     303                 :            :   // Write date segments if present
     304                 :       2118 :   i += write_int_field(abs_years, 'Y', buf_size, buf, i);
     305                 :       2118 :   i += write_int_field(abs_months, 'M', buf_size, buf, i);
     306                 :       2118 :   i += write_int_field(abs_days, 'D', buf_size, buf, i);
     307                 :            : 
     308                 :            :   // Write time segments if present
     309                 :       2118 :   const bool has_time = abs_hours + abs_minutes + abs_seconds + abs_nanoseconds;
     310         [ +  + ]:       2118 :   if (has_time) {
     311                 :       2097 :     i += write_char('T', buf_size, buf, i);
     312                 :       2097 :     i += write_int_field(abs_hours, 'H', buf_size, buf, i);
     313                 :       2097 :     i += write_int_field(abs_minutes, 'M', buf_size, buf, i);
     314         [ +  + ]:       2097 :     if (abs_seconds + abs_nanoseconds) {
     315                 :       2087 :       i += write_seconds(abs_seconds, abs_nanoseconds, buf_size, buf, i);
     316                 :            :     }
     317                 :            :   }
     318                 :            : 
     319                 :       2118 :   return end_write(EXESS_SUCCESS, buf_size, buf, i);
     320                 :            : }

Generated by: LCOV version 1.16