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

Generated by: LCOV version 1.16