string last: fix segfault with invalid index
[jimtcl.git] / jim-clock.c
blobcc24319fd3b35e452dd8b755bea04ef996a6107b
1 /*
2 * jim-clock.c
4 * Implements the clock command
5 */
7 #include "jimautoconf.h"
9 /* For strptime() - currently nothing sets this */
10 #ifdef STRPTIME_NEEDS_XOPEN_SOURCE
11 #ifndef _XOPEN_SOURCE
12 #define _XOPEN_SOURCE 500
13 #endif
14 #endif
16 /* For timegm() */
17 #ifndef _GNU_SOURCE
18 #define _GNU_SOURCE
19 #endif
21 #include <stdlib.h>
22 #include <string.h>
23 #include <stdio.h>
24 #include <time.h>
26 #include <jim-subcmd.h>
28 #ifdef HAVE_SYS_TIME_H
29 #include <sys/time.h>
30 #endif
32 struct clock_options {
33 int gmt;
34 const char *format;
37 /* Parses the options ?-format string? ?-gmt boolean? and fills in *opts.
38 * Any options not present are not set.
39 * argc must be even.
41 * Returns JIM_OK or JIM_ERR and sets an error result.
43 static int parse_clock_options(Jim_Interp *interp, int argc, Jim_Obj *const *argv, struct clock_options *opts)
45 static const char * const options[] = { "-gmt", "-format", NULL };
46 enum { OPT_GMT, OPT_FORMAT, };
47 int i;
49 for (i = 0; i < argc; i += 2) {
50 int option;
51 if (Jim_GetEnum(interp, argv[i], options, &option, NULL, JIM_ERRMSG | JIM_ENUM_ABBREV) != JIM_OK) {
52 return JIM_ERR;
54 switch (option) {
55 case OPT_GMT:
56 if (Jim_GetBoolean(interp, argv[i + 1], &opts->gmt) != JIM_OK) {
57 return JIM_ERR;
59 break;
60 case OPT_FORMAT:
61 opts->format = Jim_String(argv[i + 1]);
62 break;
65 return JIM_OK;
68 static int clock_cmd_format(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
70 /* How big is big enough? */
71 char buf[100];
72 time_t t;
73 jim_wide seconds;
74 struct clock_options options = { 0, "%a %b %d %H:%M:%S %Z %Y" };
75 struct tm *tm;
77 if (Jim_GetWide(interp, argv[0], &seconds) != JIM_OK) {
78 return JIM_ERR;
80 if (argc % 2 == 0) {
81 return -1;
83 if (parse_clock_options(interp, argc - 1, argv + 1, &options) == JIM_ERR) {
84 return JIM_ERR;
87 t = seconds;
88 tm = options.gmt ? gmtime(&t) : localtime(&t);
90 if (tm == NULL || strftime(buf, sizeof(buf), options.format, tm) == 0) {
91 Jim_SetResultString(interp, "format string too long or invalid time", -1);
92 return JIM_ERR;
95 Jim_SetResultString(interp, buf, -1);
97 return JIM_OK;
100 #ifdef HAVE_STRPTIME
101 /* Implement timegm() that doesn't require messing with timezone
102 * Based on: http://howardhinnant.github.io/date_algorithms.html#days_from_civil
104 static time_t jim_timegm(const struct tm *tm)
106 int m = tm->tm_mon + 1;
107 int y = 1900 + tm->tm_year - (m <= 2);
108 int era = (y >= 0 ? y : y - 399) / 400;
109 unsigned yoe = (unsigned)(y - era * 400);
110 unsigned doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + tm->tm_mday - 1;
111 unsigned doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
112 long days = (era * 146097 + (int)doe - 719468);
113 int secs = tm->tm_hour * 3600 + tm->tm_min * 60 + tm->tm_sec;
115 return days * 24 * 60 * 60 + secs;
118 static int clock_cmd_scan(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
120 char *pt;
121 struct tm tm;
122 time_t now = time(NULL);
123 /* No default format */
124 struct clock_options options = { 0, NULL };
126 if (argc % 2 == 0) {
127 return -1;
130 if (parse_clock_options(interp, argc - 1, argv + 1, &options) == JIM_ERR) {
131 return JIM_ERR;
133 if (options.format == NULL) {
134 return -1;
137 localtime_r(&now, &tm);
139 pt = strptime(Jim_String(argv[0]), options.format, &tm);
140 if (pt == 0 || *pt != 0) {
141 Jim_SetResultString(interp, "Failed to parse time according to format", -1);
142 return JIM_ERR;
145 /* Now convert into a time_t */
146 Jim_SetResultInt(interp, options.gmt ? jim_timegm(&tm) : mktime(&tm));
148 return JIM_OK;
150 #endif
152 static int clock_cmd_seconds(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
154 Jim_SetResultInt(interp, time(NULL));
156 return JIM_OK;
159 static int clock_cmd_micros(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
161 struct timeval tv;
163 gettimeofday(&tv, NULL);
165 Jim_SetResultInt(interp, (jim_wide) tv.tv_sec * 1000000 + tv.tv_usec);
167 return JIM_OK;
170 static int clock_cmd_millis(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
172 struct timeval tv;
174 gettimeofday(&tv, NULL);
176 Jim_SetResultInt(interp, (jim_wide) tv.tv_sec * 1000 + tv.tv_usec / 1000);
178 return JIM_OK;
181 static const jim_subcmd_type clock_command_table[] = {
182 { "clicks",
183 NULL,
184 clock_cmd_micros,
187 /* Description: Returns the current time in 'clicks' */
189 { "format",
190 "seconds ?-format string? ?-gmt boolean?",
191 clock_cmd_format,
194 /* Description: Format the given time */
196 { "microseconds",
197 NULL,
198 clock_cmd_micros,
201 /* Description: Returns the current time in microseconds */
203 { "milliseconds",
204 NULL,
205 clock_cmd_millis,
208 /* Description: Returns the current time in milliseconds */
210 #ifdef HAVE_STRPTIME
211 { "scan",
212 "str -format format ?-gmt boolean?",
213 clock_cmd_scan,
216 /* Description: Determine the time according to the given format */
218 #endif
219 { "seconds",
220 NULL,
221 clock_cmd_seconds,
224 /* Description: Returns the current time as seconds since the epoch */
226 { NULL }
229 int Jim_clockInit(Jim_Interp *interp)
231 if (Jim_PackageProvide(interp, "clock", "1.0", JIM_ERRMSG))
232 return JIM_ERR;
234 Jim_CreateCommand(interp, "clock", Jim_SubCmdProc, (void *)clock_command_table, NULL);
235 return JIM_OK;