logo

utils-std

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

date.c (6637B)


  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 _POSIX_C_SOURCE 200809L
  6. #define _XOPEN_SOURCE 700 // strptime is in XSI
  7. #include "../lib/iso_parse.h" /* iso_parse */
  8. #include <errno.h>
  9. #include <locale.h> /* setlocale() */
  10. #include <stdbool.h>
  11. #include <stdio.h> /* BUFSIZ, fprintf(), puts() */
  12. #include <stdlib.h> /* exit(), strtol() */
  13. #include <string.h> /* strerror */
  14. #include <time.h> /* time, localtime, tm, strftime, strptime, clock_settime */
  15. #include <unistd.h> /* getopt(), optarg, optind */
  16. const char *argv0 = "date";
  17. static void
  18. usage(void)
  19. {
  20. fprintf(stderr, "\
  21. Usage:\n\
  22. date [-jRu] [-d datetime | -r epoch] [+format]\n\
  23. date [-jRu] mmddHHMM[[CC]yy] [+format]\n\
  24. date [-jRu] -f now_format now [+format]\n\
  25. ");
  26. }
  27. int
  28. main(int argc, char *argv[])
  29. {
  30. char outstr[BUFSIZ];
  31. struct tm tm = {
  32. .tm_year = 0,
  33. .tm_mon = 0,
  34. .tm_mday = 0,
  35. .tm_hour = 0,
  36. .tm_min = 0,
  37. .tm_sec = 0,
  38. .tm_isdst = -1, // unknown if DST is in effect
  39. .tm_gmtoff = 0,
  40. .tm_zone = NULL,
  41. };
  42. struct timespec tp = {
  43. .tv_sec = 0,
  44. .tv_nsec = 0,
  45. };
  46. const char *format = "%c";
  47. const char *input_format = NULL;
  48. int uflag = 0;
  49. int dflag = 0, rflag = 0;
  50. int c;
  51. bool jflag = false;
  52. bool settime = false;
  53. setlocale(LC_ALL, "");
  54. errno = 0;
  55. tp.tv_sec = time(NULL);
  56. if(tp.tv_sec == (time_t)-1)
  57. {
  58. fprintf(stderr, "%s: error: Failed getting current time: %s\n", argv0, strerror(errno));
  59. exit(EXIT_FAILURE);
  60. }
  61. while((c = getopt(argc, argv, ":d:f:jr:Ru")) != -1)
  62. {
  63. const char *errstr = NULL;
  64. switch(c)
  65. {
  66. case 'd': /* Custom datetime */
  67. if(input_format != NULL)
  68. {
  69. fprintf(stderr, "%s: error: Cannot both use '-d' and '-f'\n", argv0);
  70. exit(EXIT_FAILURE);
  71. }
  72. if(rflag == 1)
  73. {
  74. fprintf(stderr, "%s: error: Cannot both use '-d' and '-r'\n", argv0);
  75. exit(EXIT_FAILURE);
  76. }
  77. iso_parse(optarg, &tm, &tp.tv_nsec, &errstr);
  78. dflag = 1;
  79. if(errstr != NULL)
  80. {
  81. fprintf(stderr, "%s: error: iso_parse(\"%s\", …): %s\n", argv0, optarg, errstr);
  82. exit(EXIT_FAILURE);
  83. }
  84. tp.tv_sec = mktime_tz(&tm);
  85. if(tp.tv_sec == (time_t)-1)
  86. {
  87. fprintf(stderr, "%s: error: mktime: %s\n", argv0, strerror(errno));
  88. exit(EXIT_FAILURE);
  89. }
  90. errno = 0;
  91. break;
  92. case 'f': /* input datetime format */
  93. if(dflag == 1)
  94. {
  95. fprintf(stderr, "%s: error: Cannot both use '-d' and '-f'\n", argv0);
  96. exit(EXIT_FAILURE);
  97. }
  98. if(rflag == 1)
  99. {
  100. fprintf(stderr, "%s: error: Cannot both use '-f' and '-r'\n", argv0);
  101. exit(EXIT_FAILURE);
  102. }
  103. input_format = optarg;
  104. settime = true;
  105. break;
  106. case 'r': /* seconds relative to epoch */
  107. {
  108. if(input_format != NULL)
  109. {
  110. fprintf(stderr, "%s: error: Cannot both use '-f' and '-r'\n", argv0);
  111. exit(EXIT_FAILURE);
  112. }
  113. if(dflag == 1)
  114. {
  115. fprintf(stderr, "%s: error: Cannot both use '-d' and '-r'\n", argv0);
  116. exit(EXIT_FAILURE);
  117. }
  118. char *endptr = NULL;
  119. errno = 0;
  120. tp.tv_sec = strtol(optarg, &endptr, 10);
  121. if(errno != 0)
  122. {
  123. fprintf(stderr, "%s: error: Failed parsing '-r %s'\n", argv0, optarg);
  124. exit(EXIT_FAILURE);
  125. }
  126. if(!(endptr == NULL || *endptr == '\0'))
  127. {
  128. fprintf(stderr, "%s: error: Invalid characters in '-r %s': %s\n", argv0, optarg, endptr);
  129. exit(EXIT_FAILURE);
  130. }
  131. break;
  132. }
  133. case 'R': /* Email (RFC 5322) format */
  134. format = "%a, %d %b %Y %H:%M:%S %z";
  135. break;
  136. case 'u': /* UTC timezone */
  137. uflag++;
  138. setenv("TZ", "UTC", 1);
  139. tzset();
  140. break;
  141. case 'j':
  142. jflag = true;
  143. break;
  144. case ':':
  145. fprintf(stderr, "%s: error: Missing operand for option: '-%c'\n", argv0, optopt);
  146. usage();
  147. return 1;
  148. case '?':
  149. fprintf(stderr, "%s: error: Unrecognised option: '-%c'\n", argv0, optopt);
  150. usage();
  151. return 1;
  152. }
  153. }
  154. argc -= optind;
  155. argv += optind;
  156. if(uflag)
  157. {
  158. if(gmtime_r(&tp.tv_sec, &tm) == NULL)
  159. {
  160. fprintf(stderr, "%s: error: gmtime_r: %s\n", argv0, strerror(errno));
  161. exit(EXIT_FAILURE);
  162. }
  163. }
  164. else
  165. {
  166. if(localtime_r(&tp.tv_sec, &tm) == NULL)
  167. {
  168. fprintf(stderr, "%s: error: localtime_r: %s\n", argv0, strerror(errno));
  169. exit(EXIT_FAILURE);
  170. }
  171. }
  172. if(argc > 0 && input_format != NULL)
  173. {
  174. char *res = strptime(argv[0], input_format, &tm);
  175. if(res == NULL)
  176. {
  177. fprintf(stderr,
  178. "%s: error: strptime(\"%s\", \"%s\", …) as passed by '-f' option failed\n",
  179. argv0,
  180. argv[0],
  181. input_format);
  182. exit(EXIT_FAILURE);
  183. }
  184. tp.tv_sec = mktime_tz(&tm);
  185. tp.tv_nsec = 0;
  186. if(tp.tv_sec == (time_t)-1)
  187. {
  188. fprintf(stderr, "%s: error: mktime: %s\n", argv0, strerror(errno));
  189. exit(EXIT_FAILURE);
  190. }
  191. errno = 0;
  192. argv++;
  193. argc--;
  194. }
  195. if(input_format == NULL && argc > 0 && *argv && **argv != '+')
  196. {
  197. const char *fmt = "%m%d%H%M";
  198. char *s = strptime(argv[0], fmt, &tm);
  199. if(s == NULL)
  200. {
  201. fprintf(stderr, "%s: error: strptime(\"%s\", \"%s\", …) returned NULL\n", argv0, *argv, fmt);
  202. exit(EXIT_FAILURE);
  203. }
  204. size_t rem = strlen(s);
  205. if(rem > 0)
  206. {
  207. switch(rem)
  208. {
  209. case 2:
  210. fmt = "%y";
  211. break;
  212. case 4:
  213. fmt = "%Y";
  214. break;
  215. default:
  216. fprintf(stderr,
  217. "%s: error: Got %zu trailing characters after \"%s\" for mmddHHMM\n",
  218. argv0,
  219. rem,
  220. fmt);
  221. exit(EXIT_FAILURE);
  222. }
  223. s = strptime(s, fmt, &tm);
  224. if(s == NULL)
  225. {
  226. fprintf(
  227. stderr, "%s: error: strptime(\"%s\", \"%s\", …) returned NULL\n", argv0, *argv, fmt);
  228. exit(EXIT_FAILURE);
  229. }
  230. }
  231. tp.tv_sec = mktime(&tm);
  232. tp.tv_nsec = 0;
  233. if(tp.tv_sec == (time_t)-1)
  234. {
  235. fprintf(stderr, "%s: error: mktime: %s\n", argv0, strerror(errno));
  236. exit(EXIT_FAILURE);
  237. }
  238. errno = 0;
  239. argv++;
  240. argc--;
  241. settime = true;
  242. }
  243. if(settime && !jflag)
  244. {
  245. if(clock_settime(CLOCK_REALTIME, &tp) != 0)
  246. {
  247. fprintf(stderr,
  248. "%s: error: clock_settime(CLOCK_REALTIME, {%ld, %ld}): %s\n",
  249. argv0,
  250. tp.tv_sec,
  251. tp.tv_nsec,
  252. strerror(errno));
  253. exit(EXIT_FAILURE);
  254. }
  255. }
  256. if(argc > 0 && *argv && **argv == '+')
  257. {
  258. format = *argv + 1;
  259. argv++;
  260. argc--;
  261. }
  262. errno = 0;
  263. if(strftime(outstr, sizeof(outstr), format, &tm) == 0 && errno != 0)
  264. {
  265. fprintf(stderr, "%s: error: Failed formatting time: %s\n", argv0, strerror(errno));
  266. exit(EXIT_FAILURE);
  267. }
  268. if(puts(outstr) < 0)
  269. {
  270. fprintf(stderr, "%s: error: Failed writing time: %s\n", argv0, strerror(errno));
  271. exit(EXIT_FAILURE);
  272. }
  273. return 0;
  274. }