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

Generated by: LCOV version 1.16