logo

utils-std

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

date.c (6412B)


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