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

           Branch data     Line data    Source code
       1                 :            : // Copyright 2019-2026 David Robillard <d@drobilla.net>
       2                 :            : // SPDX-License-Identifier: ISC
       3                 :            : 
       4                 :            : #include "time_utils.h"
       5                 :            : #include "char_utils.h"
       6                 :            : #include "number_utils.h"
       7                 :            : #include "result.h"
       8                 :            : #include "timezone.h"
       9                 :            : #include "write_utils.h"
      10                 :            : 
      11                 :            : #include <exess/exess.h>
      12                 :            : 
      13                 :            : #include <assert.h>
      14                 :            : #include <stdbool.h>
      15                 :            : #include <stddef.h>
      16                 :            : #include <stdint.h>
      17                 :            : 
      18                 :            : EXESS_NONBLOCKING ExessResult
      19                 :       6851 : read_nanoseconds(uint32_t* const out, const char* const str)
      20                 :            : {
      21                 :            :   // Read available digits
      22                 :       6851 :   size_t i = 0U;
      23   [ +  +  +  + ]:      68021 :   for (; i < 9U && is_digit(str[i]); ++i) {
      24                 :      61170 :     *out = (*out * 10U) + (uint32_t)(str[i] - '0');
      25                 :            :   }
      26                 :            : 
      27   [ +  +  +  + ]:       6851 :   if (i == 9U && is_digit(str[i])) {
      28                 :          3 :     return RESULT(EXESS_OUT_OF_RANGE, i);
      29                 :            :   }
      30                 :            : 
      31                 :            :   // "Read" imagined trailing zeros to reach 9 digit magnitude
      32         [ +  + ]:       7337 :   for (size_t j = i; j < 9U; ++j) {
      33                 :        489 :     *out *= 10U;
      34                 :            :   }
      35                 :            : 
      36         [ +  + ]:       6848 :   return RESULT(i > 0 ? EXESS_SUCCESS : EXESS_EXPECTED_DIGIT, i);
      37                 :            : }
      38                 :            : 
      39                 :            : EXESS_NONBLOCKING size_t
      40                 :       8097 : write_nanoseconds(const uint32_t nanosecond,
      41                 :            :                   const size_t   buf_size,
      42                 :            :                   char* const    buf,
      43                 :            :                   const size_t   i)
      44                 :            : {
      45         [ -  + ]:       8097 :   assert(nanosecond <= 999999999);
      46                 :            : 
      47         [ +  + ]:       8097 :   if (nanosecond == 0) {
      48                 :        252 :     return 0;
      49                 :            :   }
      50                 :            : 
      51                 :       7845 :   char frac_digits[10] = {'0', '0', '0', '0', '0', '0', '0', '0', '0', 0};
      52                 :            : 
      53                 :            :   // Write digits right to left, but replace trailing zeros with null
      54                 :       7845 :   uint32_t remaining     = nanosecond;
      55                 :       7845 :   uint32_t n_trailing    = 0;
      56                 :       7845 :   bool     wrote_nonzero = false;
      57         [ +  + ]:      77346 :   for (uint32_t j = 0; remaining > 0; ++j) {
      58                 :      69501 :     const char digit = (char)('0' + (remaining % 10));
      59   [ +  +  +  + ]:      69501 :     if (!wrote_nonzero && digit == '0') {
      60                 :        287 :       frac_digits[8 - j] = '\0';
      61                 :        287 :       ++n_trailing;
      62                 :            :     } else {
      63                 :      69214 :       frac_digits[8 - j] = digit;
      64                 :            :     }
      65                 :            : 
      66   [ +  +  +  + ]:      69501 :     wrote_nonzero = wrote_nonzero || digit != '0';
      67                 :      69501 :     remaining /= 10;
      68                 :            :   }
      69                 :            : 
      70                 :       7845 :   size_t n = write_char('.', buf_size, buf, i);
      71                 :            : 
      72                 :       7845 :   n += write_string(9 - n_trailing, frac_digits, buf_size, buf, i + n);
      73                 :            : 
      74                 :       7845 :   return n;
      75                 :            : }
      76                 :            : 
      77                 :            : EXESS_NONBLOCKING ExessResult
      78                 :       6272 : read_time(ExessTime* const out, const char* const str)
      79                 :            : {
      80                 :            :   // Read hour
      81                 :       6272 :   size_t      i = 0;
      82                 :       6272 :   ExessResult r = read_two_digit_number(&out->hour, 0, 24, str + i);
      83         [ +  + ]:       6272 :   if (r.status) {
      84                 :          7 :     return RESULT(r.status, i + r.count);
      85                 :            :   }
      86                 :            : 
      87                 :       6265 :   const bool midnight = out->hour == 24;
      88                 :            : 
      89                 :            :   // Read hour-minute delimiter
      90                 :       6265 :   i += r.count;
      91         [ +  + ]:       6265 :   if (str[i] != ':') {
      92                 :          1 :     return RESULT(EXESS_EXPECTED_COLON, i);
      93                 :            :   }
      94                 :            : 
      95                 :            :   // Read minute
      96                 :       6264 :   ++i;
      97         [ +  + ]:       6264 :   r = read_two_digit_number(&out->minute, 0, midnight ? 0 : 59, str + i);
      98         [ +  + ]:       6264 :   if (r.status) {
      99                 :          2 :     return RESULT(r.status, i + r.count);
     100                 :            :   }
     101                 :            : 
     102                 :            :   // Read minute-second delimiter
     103                 :       6262 :   i += r.count;
     104         [ +  + ]:       6262 :   if (str[i] != ':') {
     105                 :          4 :     return RESULT(EXESS_EXPECTED_COLON, i);
     106                 :            :   }
     107                 :            : 
     108                 :            :   // Read second
     109                 :       6258 :   ++i;
     110         [ +  + ]:       6258 :   r = read_two_digit_number(&out->second, 0, midnight ? 0 : 59, str + i);
     111                 :       6258 :   i += r.count;
     112         [ +  + ]:       6258 :   if (r.status) {
     113                 :          2 :     return RESULT(r.status, i);
     114                 :            :   }
     115                 :            : 
     116                 :            :   // Read nanoseconds if present
     117         [ +  + ]:       6256 :   if (str[i] == '.') {
     118                 :       5803 :     ++i;
     119                 :       5803 :     r = read_nanoseconds(&out->nanosecond, str + i);
     120                 :       5803 :     i += r.count;
     121                 :            :   }
     122         [ +  + ]:       6256 :   if (r.status) {
     123                 :          3 :     return RESULT(r.status, i);
     124                 :            :   }
     125                 :            : 
     126                 :            :   // Ensure midnight has zero nanoseconds
     127   [ +  +  +  + ]:       6253 :   if (midnight && out->nanosecond) {
     128                 :          2 :     return RESULT(EXESS_OUT_OF_RANGE, i);
     129                 :            :   }
     130                 :            : 
     131                 :            :   // Read timezone offset if present
     132                 :       6251 :   r = read_optional_timezone(&out->zone, str + i);
     133                 :       6251 :   i += r.count;
     134                 :            : 
     135                 :       6251 :   return RESULT(r.status, i);
     136                 :            : }
     137                 :            : 
     138                 :            : EXESS_NONBLOCKING ExessResult
     139                 :       6034 : write_time(const ExessTime value,
     140                 :            :            const size_t    buf_size,
     141                 :            :            char* const     buf,
     142                 :            :            const size_t    offset)
     143                 :            : {
     144   [ +  +  +  +  :       6034 :   if (value.hour > 24 || value.minute > 59 || value.second > 59 ||
                   +  + ]
     145         [ +  + ]:       6031 :       value.nanosecond > 999999999) {
     146                 :          4 :     return RESULT(EXESS_BAD_VALUE, 0);
     147                 :            :   }
     148                 :            : 
     149                 :       6030 :   size_t o = offset;
     150                 :            : 
     151                 :            :   // Write integral hour, minute, and second
     152                 :       6030 :   o += write_two_digit_number(value.hour, buf_size, buf, o);
     153                 :       6030 :   o += write_char(':', buf_size, buf, o);
     154                 :       6030 :   o += write_two_digit_number(value.minute, buf_size, buf, o);
     155                 :       6030 :   o += write_char(':', buf_size, buf, o);
     156                 :       6030 :   o += write_two_digit_number(value.second, buf_size, buf, o);
     157                 :       6030 :   o += write_nanoseconds(value.nanosecond, buf_size, buf, o);
     158                 :            : 
     159                 :       6030 :   const ExessResult r = write_timezone(value.zone, buf_size, buf, o);
     160                 :            : 
     161                 :       6030 :   return RESULT(r.status, o - offset + r.count);
     162                 :            : }

Generated by: LCOV version 1.16