LCOV - code coverage report
Current view: top level - src - date_time.c (source / functions) Hit Total Coverage
Test: Code coverage Lines: 150 150 100.0 %
Date: 2026-07-14 21:28:07 Functions: 15 15 100.0 %
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 84 84 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 "date_utils.h"
       6                 :            : #include "result.h"
       7                 :            : #include "time_utils.h"
       8                 :            : #include "write_utils.h"
       9                 :            : 
      10                 :            : #include <exess/exess.h>
      11                 :            : 
      12                 :            : #include <stdint.h>
      13                 :            : #include <string.h>
      14                 :            : 
      15                 :            : static inline ExessDateTime
      16                 :          6 : infinite_future(const ExessTimezone zone)
      17                 :            : {
      18                 :          6 :   const ExessDateTime r = {INT16_MAX,
      19                 :            :                            UINT8_MAX,
      20                 :            :                            UINT8_MAX,
      21                 :            :                            zone,
      22                 :            :                            UINT8_MAX,
      23                 :            :                            UINT8_MAX,
      24                 :            :                            UINT8_MAX,
      25                 :            :                            UINT32_MAX};
      26                 :            : 
      27                 :          6 :   return r;
      28                 :            : }
      29                 :            : 
      30                 :            : static inline ExessDateTime
      31                 :          6 : infinite_past(const ExessTimezone zone)
      32                 :            : {
      33                 :          6 :   const ExessDateTime r = {INT16_MIN, 0, 0, zone, 0, 0, 0, 0};
      34                 :            : 
      35                 :          6 :   return r;
      36                 :            : }
      37                 :            : 
      38                 :            : static int32_t
      39                 :       1533 : modulo(const int32_t a, const int32_t low, const int32_t high)
      40                 :            : {
      41                 :       1533 :   return ((a - low) % (high - low)) + low;
      42                 :            : }
      43                 :            : 
      44                 :            : static int32_t
      45                 :       1470 : quotient(const int32_t a, const int32_t low, const int32_t high)
      46                 :            : {
      47                 :       1470 :   return (a - low) / (high - low);
      48                 :            : }
      49                 :            : 
      50                 :            : static ExessOrder
      51                 :       1785 : compare_field(const unsigned lhs, const unsigned rhs)
      52                 :            : {
      53                 :            :   return lhs < rhs    ? EXESS_ORDER_STRICTLY_LESS
      54   [ +  +  +  + ]:       1785 :          : lhs == rhs ? EXESS_ORDER_EQUAL
      55                 :            :                       : EXESS_ORDER_STRICTLY_GREATER;
      56                 :            : }
      57                 :            : 
      58                 :            : static ExessOrder
      59                 :        506 : compare_date_time_total(const ExessDateTime lhs, const ExessDateTime rhs)
      60                 :            : {
      61         [ +  + ]:        506 :   if (lhs.year != rhs.year) {
      62                 :         37 :     return lhs.year < rhs.year ? EXESS_ORDER_STRICTLY_LESS
      63         [ +  + ]:         37 :                                : EXESS_ORDER_STRICTLY_GREATER;
      64                 :            :   }
      65                 :            : 
      66                 :        469 :   const ExessDateTime lhz = exess_date_time_to_utc(lhs);
      67                 :        469 :   const ExessDateTime rhz = exess_date_time_to_utc(rhs);
      68                 :        469 :   ExessOrder          cmp = compare_field(lhz.month, rhz.month);
      69                 :            : 
      70         [ +  + ]:        469 :   cmp = cmp ? cmp : compare_field(lhz.day, rhz.day);
      71         [ +  + ]:        469 :   cmp = cmp ? cmp : compare_field(lhz.hour, rhz.hour);
      72         [ +  + ]:        469 :   cmp = cmp ? cmp : compare_field(lhz.minute, rhz.minute);
      73         [ +  + ]:        469 :   cmp = cmp ? cmp : compare_field(lhz.second, rhz.second);
      74         [ +  + ]:        469 :   cmp = cmp ? cmp : compare_field(lhz.nanosecond, rhz.nanosecond);
      75                 :        469 :   return cmp;
      76                 :            : }
      77                 :            : 
      78                 :            : static ExessDateTime
      79                 :       1070 : to_utc(const ExessDateTime s, const ExessDuration offset)
      80                 :            : {
      81                 :       1070 :   ExessDateTime r = exess_add_date_time_duration(s, offset);
      82                 :       1070 :   r.zone          = EXESS_TIMEZONE_UTC;
      83                 :       1070 :   return r;
      84                 :            : }
      85                 :            : 
      86                 :            : EXESS_NONBLOCKING ExessDateTime
      87                 :        948 : exess_date_time_to_utc(const ExessDateTime datetime)
      88                 :            : {
      89                 :        948 :   const ExessDuration offset = {0U, -datetime.zone * 15 * 60, 0};
      90                 :        948 :   return to_utc(datetime, offset);
      91                 :            : }
      92                 :            : 
      93                 :            : static EXESS_NONBLOCKING ExessOrder
      94                 :         70 : compare_date_time_partial(const ExessDateTime lhs, const ExessDateTime rhs)
      95                 :            : {
      96                 :            :   // See https://www.w3.org/TR/xmlschema-2/#dateTime-order
      97                 :            :   // and https://www.w3.org/TR/xmlschema11-2/#theSevenPropertyModel
      98                 :            : 
      99                 :         70 :   EXESS_CONSTEXPR ExessDuration plus_14h  = {0U, 14 * 60 * 60, 0};
     100                 :         70 :   EXESS_CONSTEXPR ExessDuration minus_14h = {0U, -14 * 60 * 60, 0};
     101                 :            : 
     102                 :         70 :   ExessOrder order = EXESS_ORDER_EQUAL;
     103                 :            : 
     104         [ +  + ]:         70 :   if (lhs.zone != EXESS_TIMEZONE_LOCAL) {
     105                 :         35 :     order = compare_date_time_total(lhs, to_utc(rhs, minus_14h));
     106         [ +  + ]:         35 :     if (order < 0) {
     107                 :         12 :       return order;
     108                 :            :     }
     109                 :            : 
     110                 :         23 :     order = compare_date_time_total(lhs, to_utc(rhs, plus_14h));
     111         [ +  + ]:         23 :     if (order > 0) {
     112                 :          6 :       return order;
     113                 :            :     }
     114                 :            : 
     115                 :            :     // Incomparable, arbitrarily put local time first
     116                 :         17 :     return EXESS_ORDER_MAYBE_GREATER;
     117                 :            :   }
     118                 :            : 
     119                 :         35 :   order = compare_date_time_total(to_utc(lhs, plus_14h), rhs);
     120         [ +  + ]:         35 :   if (order < 0) {
     121                 :          6 :     return order;
     122                 :            :   }
     123                 :            : 
     124                 :         29 :   order = compare_date_time_total(to_utc(lhs, minus_14h), rhs);
     125         [ +  + ]:         29 :   if (order > 0) {
     126                 :         12 :     return order;
     127                 :            :   }
     128                 :            : 
     129                 :            :   // Incomparable, arbitrarily put local time first
     130                 :         17 :   return EXESS_ORDER_MAYBE_LESS;
     131                 :            : }
     132                 :            : 
     133                 :            : EXESS_NONBLOCKING ExessOrder
     134                 :        454 : exess_compare_date_time(const ExessDateTime lhs, const ExessDateTime rhs)
     135                 :            : {
     136         [ +  + ]:         88 :   return ((lhs.zone == rhs.zone) || (lhs.zone != EXESS_TIMEZONE_LOCAL &&
     137         [ +  + ]:         53 :                                      rhs.zone != EXESS_TIMEZONE_LOCAL))
     138                 :        384 :            ? compare_date_time_total(lhs, rhs)
     139         [ +  + ]:        908 :            : compare_date_time_partial(lhs, rhs);
     140                 :            : }
     141                 :            : 
     142                 :            : static int32_t
     143                 :       5864 : add_field(const int32_t  lhs,
     144                 :            :           const int32_t  rhs,
     145                 :            :           const int32_t  max,
     146                 :            :           int32_t* const carry)
     147                 :            : {
     148                 :       5864 :   const int32_t temp = lhs + rhs + *carry;
     149                 :            : 
     150         [ +  + ]:       5864 :   if (temp < 0) {
     151                 :        953 :     *carry = (temp / max) - 1;
     152                 :        953 :     return max + (temp % max);
     153                 :            :   }
     154                 :            : 
     155                 :       4911 :   *carry = temp / max;
     156                 :       4911 :   return temp % max;
     157                 :            : }
     158                 :            : 
     159                 :            : /**
     160                 :            :    Set the day, carrying into into months and years as necessary.
     161                 :            : 
     162                 :            :    Note that the algorithm in the spec first clamps here, but we don't because
     163                 :            :    no such dateTime should exist (exess_read_date_time refuses to read them).
     164                 :            :    This might return the infinite past or future.
     165                 :            : */
     166                 :            : EXESS_CONST_FUNC static ExessDateTime
     167                 :       1466 : carry_set_day(ExessDateTime e, int day)
     168                 :            : {
     169   [ +  +  +  + ]:       2262 :   while (day < 1 || day > days_in_month(e.year, e.month)) {
     170         [ +  + ]:        804 :     if (day < 1) {
     171         [ +  + ]:        276 :       if (--e.month == 0) {
     172         [ +  + ]:         41 :         if (e.year == INT16_MIN) {
     173                 :          4 :           return infinite_past(e.zone);
     174                 :            :         }
     175                 :            : 
     176                 :         37 :         --e.year;
     177                 :         37 :         e.month = 12;
     178                 :            :       }
     179                 :        272 :       day += days_in_month(e.year, e.month);
     180                 :            :     } else {
     181                 :        528 :       day -= days_in_month(e.year, e.month);
     182         [ +  + ]:        528 :       if (++e.month > 12) {
     183         [ +  + ]:         67 :         if (e.year == INT16_MAX) {
     184                 :          4 :           return infinite_future(e.zone);
     185                 :            :         }
     186                 :            : 
     187                 :         63 :         ++e.year;
     188                 :         63 :         e.month = (uint8_t)modulo(e.month, 1, 13);
     189                 :            :       }
     190                 :            :     }
     191                 :            :   }
     192                 :            : 
     193                 :       1458 :   e.day = (uint8_t)day;
     194                 :       1458 :   return e;
     195                 :            : }
     196                 :            : 
     197                 :            : EXESS_NONBLOCKING ExessDateTime
     198                 :       1470 : exess_add_date_time_duration(const ExessDateTime s, const ExessDuration d)
     199                 :            : {
     200                 :            :   // See https://www.w3.org/TR/xmlschema-2/#adding-durations-to-dateTimes
     201                 :            :   // and https://www.w3.org/TR/xmlschema11-2/#sec-dt-arith
     202                 :            :   // This algorithm is modified here to support subtraction when d is negative
     203                 :            : 
     204                 :       1470 :   EXESS_CONSTEXPR int32_t giga = 1000000000;
     205                 :            : 
     206                 :       1470 :   const int32_t d_year   = d.months / 12;
     207                 :       1470 :   const int32_t d_month  = d.months % 12;
     208                 :       1470 :   const int32_t d_day    = d.seconds / (24 * 60 * 60);
     209                 :       1470 :   const int32_t d_hour   = d.seconds / 60 / 60 % 24;
     210                 :       1470 :   const int32_t d_minute = d.seconds / 60 % 60;
     211                 :       1470 :   const int32_t d_second = d.seconds % 60;
     212                 :            : 
     213                 :       1470 :   ExessDateTime e     = {0, 0U, 0U, s.zone, 0U, 0U, 0U, 0U};
     214                 :       1470 :   int32_t       temp  = 0;
     215                 :       1470 :   int32_t       carry = 0;
     216                 :            : 
     217                 :            :   // Months (may be modified additionally below)
     218                 :       1470 :   temp = s.month + d_month;
     219         [ +  + ]:       1470 :   if (temp <= 0) {
     220                 :          3 :     e.month = (uint8_t)(12 + modulo(temp, 1, 13));
     221                 :          3 :     carry   = quotient(temp, 1, 13) - 1;
     222                 :            :   } else {
     223                 :       1467 :     e.month = (uint8_t)modulo(temp, 1, 13);
     224                 :       1467 :     carry   = quotient(temp, 1, 13);
     225                 :            :   }
     226                 :            : 
     227                 :            :   // Years (may be modified additionally below)
     228                 :       1470 :   temp  = s.year + d_year + carry;
     229                 :       1470 :   carry = 0;
     230         [ +  + ]:       1470 :   if (temp > INT16_MAX) {
     231                 :          2 :     return infinite_future(s.zone);
     232                 :            :   }
     233                 :            : 
     234         [ +  + ]:       1468 :   if (temp < INT16_MIN) {
     235                 :          2 :     return infinite_past(s.zone);
     236                 :            :   }
     237                 :            : 
     238                 :       1466 :   e.year = (int16_t)temp;
     239                 :            : 
     240                 :            :   // Day time
     241                 :            : 
     242                 :       1466 :   e.nanosecond =
     243                 :       1466 :     (uint32_t)add_field((int32_t)s.nanosecond, d.nanoseconds, giga, &carry);
     244                 :            : 
     245                 :       1466 :   e.second = (uint8_t)add_field(s.second, d_second, 60, &carry);
     246                 :       1466 :   e.minute = (uint8_t)add_field(s.minute, d_minute, 60, &carry);
     247                 :       1466 :   e.hour   = (uint8_t)add_field(s.hour, d_hour, 24, &carry);
     248                 :            : 
     249                 :       1466 :   return carry_set_day(e, s.day + d_day + carry);
     250                 :            : }
     251                 :            : 
     252                 :            : EXESS_NONBLOCKING ExessResult
     253                 :        227 : exess_read_date_time(const char* const str, ExessDateTime* const out)
     254                 :            : {
     255                 :        227 :   memset(out, 0, sizeof(*out));
     256                 :            : 
     257                 :            :   // Read date
     258                 :        227 :   ExessDate         date = {0, 0U, 0U, EXESS_TIMEZONE_LOCAL};
     259                 :        227 :   const ExessResult dr   = read_date_numbers(&date, str);
     260                 :            : 
     261                 :        227 :   out->year  = date.year;
     262                 :        227 :   out->month = date.month;
     263                 :        227 :   out->day   = date.day;
     264         [ +  + ]:        227 :   if (dr.status) {
     265                 :          4 :     return dr;
     266                 :            :   }
     267                 :            : 
     268                 :        223 :   size_t i = dr.count;
     269         [ +  + ]:        223 :   if (str[i] != 'T') {
     270                 :          3 :     return RESULT(EXESS_EXPECTED_TIME_SEP, i);
     271                 :            :   }
     272                 :            : 
     273                 :        220 :   ++i;
     274                 :            : 
     275                 :            :   // Read time
     276                 :        220 :   ExessTime         time = {EXESS_TIMEZONE_LOCAL, 0U, 0U, 0U, 0U};
     277                 :        220 :   const ExessResult tr   = read_time(&time, str + i);
     278                 :            : 
     279                 :        220 :   out->zone       = time.zone;
     280                 :        220 :   out->hour       = time.hour;
     281                 :        220 :   out->minute     = time.minute;
     282                 :        220 :   out->second     = time.second;
     283                 :        220 :   out->nanosecond = time.nanosecond;
     284                 :        220 :   i += tr.count;
     285         [ +  + ]:        220 :   if (tr.status) {
     286                 :          2 :     return RESULT(tr.status, i);
     287                 :            :   }
     288                 :            : 
     289                 :        218 :   return RESULT(EXESS_SUCCESS, i);
     290                 :            : }
     291                 :            : 
     292                 :            : EXESS_NONBLOCKING ExessResult
     293                 :        120 : exess_write_date_time(const ExessDateTime value,
     294                 :            :                       const size_t        buf_size,
     295                 :            :                       char* const         buf)
     296                 :            : {
     297                 :        120 :   const ExessDate date = {
     298                 :        120 :     value.year, value.month, value.day, EXESS_TIMEZONE_LOCAL};
     299                 :            : 
     300                 :        120 :   const ExessTime time = {
     301                 :        120 :     value.zone, value.hour, value.minute, value.second, value.nanosecond};
     302                 :            : 
     303   [ +  +  +  + ]:        120 :   if (!in_range(value.month, 1, 12) || !in_range(value.day, 1, 31) ||
     304   [ +  +  +  + ]:        116 :       !in_range(value.hour, 0, 24) || !in_range(value.minute, 0, 59) ||
     305   [ +  +  +  + ]:        114 :       !in_range(value.second, 0, 59) || value.nanosecond > 999999999 ||
     306         [ +  + ]:        112 :       (value.hour == 24 &&
     307   [ +  +  +  +  :          6 :        (value.minute || value.second || value.nanosecond))) {
                   +  + ]
     308                 :         11 :     return end_write(EXESS_BAD_VALUE, buf_size, buf, 0);
     309                 :            :   }
     310                 :            : 
     311                 :        109 :   const ExessResult dr = exess_write_date(date, buf_size, buf);
     312                 :        109 :   const size_t      o  = dr.count + write_char('T', buf_size, buf, dr.count);
     313                 :        109 :   const ExessResult tr = write_time(time, buf_size, buf, o);
     314                 :        109 :   return end_write(
     315         [ +  + ]:        109 :     dr.status ? dr.status : tr.status, buf_size, buf, o + tr.count);
     316                 :            : }

Generated by: LCOV version 1.16