logo

utils-std

Collection of commonly available Unix tools git clone https://anongit.hacktivis.me/git/utils-std.git

iso_parse.c (3679B)


  1. // utils-std: Collection of commonly available Unix tools
  2. // SPDX-FileCopyrightText: 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
  3. // SPDX-License-Identifier: MPL-2.0
  4. #define _DEFAULT_SOURCE // tm_gmtoff/tm_zone
  5. #define _XOPEN_SOURCE 700 // strptime (NetBSD)
  6. #define _POSIX_C_SOURCE 200809L // st_atim/st_mtim
  7. #include "./iso_parse.h"
  8. #include <assert.h>
  9. #include <ctype.h> /* isdigit */
  10. #include <errno.h> /* errno */
  11. #include <inttypes.h> /* PRId16 */
  12. #include <limits.h> /* TZNAME_MAX */
  13. #include <stdio.h> /* perror, sscanf */
  14. #include <stdlib.h> /* strtol */
  15. #include <string.h> /* memset */
  16. #include <time.h> /* strptime, tm */
  17. // Sets errstr on failure
  18. // YYYY-MM-DD[T ]hh:mm:SS([,\.]frac)?(Z|[+\-]hh:?mm)?
  19. char *
  20. iso_parse(char *arg, struct tm *time, long *nsec, const char **errstr)
  21. {
  22. // For Alpine's abuild compatibility
  23. if(arg[0] == '@')
  24. {
  25. arg++;
  26. assert(errno == 0);
  27. char *endptr = NULL;
  28. time_t now = strtol(arg, &endptr, 10);
  29. if(errno != 0)
  30. {
  31. *errstr = strerror(errno);
  32. errno = 0;
  33. return NULL;
  34. }
  35. nsec = 0;
  36. gmtime_r(&now, time);
  37. return endptr;
  38. }
  39. // No %F in POSIX
  40. char *s = strptime(arg, "%Y-%m-%d", time);
  41. if(s == NULL)
  42. {
  43. *errstr = "strptime(…, \"%Y-%m-%d\", …) returned NULL";
  44. errno = 0;
  45. return NULL;
  46. }
  47. if(s[0] != 'T' && s[0] != ' ')
  48. {
  49. *errstr = "Couldn't find time-separator (T or space) after date (Y-m-d)";
  50. errno = 0;
  51. return NULL;
  52. }
  53. s++;
  54. s = strptime(s, "%H:%M:%S", time);
  55. if(s == NULL)
  56. {
  57. *errstr = "strptime(…, \"%H:%M:%S\", …) returned NULL";
  58. errno = 0;
  59. return NULL;
  60. }
  61. if(s[0] == ',' || s[0] == '.')
  62. {
  63. double fraction = 0.0;
  64. int parsed = 0;
  65. if(s[0] == ',') s[0] = '.';
  66. assert(errno == 0);
  67. if(sscanf(s, "%10lf%n", &fraction, &parsed) < 1)
  68. {
  69. if(errno == 0)
  70. {
  71. *errstr = "Failed to parse fractional seconds";
  72. }
  73. else
  74. {
  75. *errstr = strerror(errno);
  76. errno = 0;
  77. }
  78. return NULL;
  79. }
  80. *nsec = (long)(fraction * 1000000000);
  81. s += parsed;
  82. // too many digits
  83. if(isdigit(s[0]))
  84. {
  85. *errstr = "Too many digits (> 10) for fractional seconds";
  86. return NULL;
  87. }
  88. }
  89. if(s != NULL && s[0] != '\0')
  90. {
  91. if(s[0] == 'Z' && s[1] == '\0')
  92. {
  93. time->tm_gmtoff = 0;
  94. time->tm_zone = "UTC";
  95. }
  96. else
  97. {
  98. int neg;
  99. if(*s == '+')
  100. neg = 0;
  101. else if(*s == '-')
  102. neg = 1;
  103. else
  104. {
  105. *errstr = "Invalid timezone offset, must start with + or -";
  106. return NULL;
  107. }
  108. char *o = s + 1;
  109. if(isdigit(o[0]) && isdigit(o[1]))
  110. {
  111. time->tm_gmtoff = (o[0] - '0') * 36000 + (o[1] - '0') * 3600;
  112. o += 2;
  113. }
  114. else
  115. {
  116. *errstr = "Invalid timezone offset, no digits after [+|-]";
  117. return NULL;
  118. }
  119. if(o[0] == ':') o++;
  120. if(isdigit(o[0]) && isdigit(o[1]))
  121. {
  122. time->tm_gmtoff += (o[0] - '0') * 600 + (o[1] - '0') * 60;
  123. o += 2;
  124. }
  125. else
  126. {
  127. *errstr = "Invalid timezone offset, no digits after [+|-]";
  128. return NULL;
  129. }
  130. if(neg) time->tm_gmtoff = -time->tm_gmtoff;
  131. #ifndef TZNAME_MAX
  132. #define TZNAME_MAX _POSIX_TZNAME_MAX
  133. #endif
  134. static char offname[TZNAME_MAX + 1] = "";
  135. assert(o - s < TZNAME_MAX);
  136. memcpy(offname, s, o - s);
  137. time->tm_zone = offname;
  138. }
  139. }
  140. assert(errno == 0);
  141. return s;
  142. }
  143. // Because mktime() messes with tm_gmtoff yet doesn't applies it, even in POSIX.1-2024
  144. // Returns (time_t)-1 on failure
  145. time_t
  146. mktime_tz(struct tm *tm)
  147. {
  148. long gmtoff = tm->tm_gmtoff;
  149. const char *zone = tm->tm_zone;
  150. time_t res = mktime(tm);
  151. tm->tm_gmtoff = gmtoff;
  152. tm->tm_zone = zone;
  153. if(res == (time_t)-1) return res;
  154. // 12:00+02:00 corresponds to 10:00Z so needs to be reversed
  155. res += -gmtoff;
  156. return res;
  157. }