1 /*
2  * Copyright (c) 2020 Nick Ward
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #undef _POSIX_C_SOURCE
8 #define _POSIX_C_SOURCE 200809L
9 #include <stdlib.h>
10 #include <zephyr/shell/shell.h>
11 #include <zephyr/init.h>
12 #include <string.h>
13 
14 #include <zephyr/sys/timeutil.h>
15 
16 #if defined(CONFIG_ARCH_POSIX) && defined(CONFIG_EXTERNAL_LIBC)
17 #include <time.h>
18 #else
19 #include <zephyr/posix/time.h>
20 #endif
21 
22 #define HELP_NONE      "[none]"
23 #define HELP_DATE_SET  "[Y-m-d] <H:M:S>"
24 
date_print(const struct shell * sh,struct tm * t)25 static void date_print(const struct shell *sh, struct tm *t)
26 {
27 	shell_print(sh,
28 		    "%d-%02u-%02u "
29 		    "%02u:%02u:%02u UTC",
30 		    t->tm_year + 1900,
31 		    t->tm_mon + 1,
32 		    t->tm_mday,
33 		    t->tm_hour,
34 		    t->tm_min,
35 		    t->tm_sec);
36 }
37 
get_y_m_d(const struct shell * sh,struct tm * t,char * date_str)38 static int get_y_m_d(const struct shell *sh, struct tm *t, char *date_str)
39 {
40 	int year;
41 	int month;
42 	int day;
43 	char *endptr;
44 
45 	year = strtol(date_str, &endptr, 10);
46 	if ((endptr == date_str) || (*endptr != '-')) {
47 		return -EINVAL;
48 	}
49 
50 	date_str = endptr + 1;
51 
52 	month = strtol(date_str, &endptr, 10);
53 	if ((endptr == date_str) || (*endptr != '-')) {
54 		return -EINVAL;
55 	}
56 
57 	if ((month < 1) || (month > 12)) {
58 		shell_error(sh, "Invalid month");
59 		return -EINVAL;
60 	}
61 
62 	date_str = endptr + 1;
63 
64 	day = strtol(date_str, &endptr, 10);
65 	if ((endptr == date_str) || (*endptr != '\0')) {
66 		return -EINVAL;
67 	}
68 
69 	/* Check day against maximum month length */
70 	if ((day < 1) || (day > 31)) {
71 		shell_error(sh, "Invalid day");
72 		return -EINVAL;
73 	}
74 
75 	t->tm_year = year - 1900;
76 	t->tm_mon = month - 1;
77 	t->tm_mday = day;
78 
79 	return 0;
80 }
81 
82 /*
83  * For user convenience of small adjustments to time the time argument will
84  * accept H:M:S, :M:S or ::S where the missing field(s) will be filled in by
85  * the previous time state.
86  */
get_h_m_s(const struct shell * sh,struct tm * t,char * time_str)87 static int get_h_m_s(const struct shell *sh, struct tm *t, char *time_str)
88 {
89 	char *endptr;
90 
91 	if (*time_str == ':') {
92 		time_str++;
93 	} else {
94 		t->tm_hour = strtol(time_str, &endptr, 10);
95 		if (endptr == time_str) {
96 			return -EINVAL;
97 		} else if (*endptr == ':') {
98 			if ((t->tm_hour < 0) || (t->tm_hour > 23)) {
99 				shell_error(sh, "Invalid hour");
100 				return -EINVAL;
101 			}
102 
103 			time_str = endptr + 1;
104 		} else {
105 			return -EINVAL;
106 		}
107 	}
108 
109 	if (*time_str == ':') {
110 		time_str++;
111 	} else {
112 		t->tm_min = strtol(time_str, &endptr, 10);
113 		if (endptr == time_str) {
114 			return -EINVAL;
115 		} else if (*endptr == ':') {
116 			if ((t->tm_min < 0) || (t->tm_min > 59)) {
117 				shell_error(sh, "Invalid minute");
118 				return -EINVAL;
119 			}
120 
121 			time_str = endptr + 1;
122 		} else {
123 			return -EINVAL;
124 		}
125 	}
126 
127 	t->tm_sec = strtol(time_str, &endptr, 10);
128 	if ((endptr == time_str) || (*endptr != '\0')) {
129 		return -EINVAL;
130 	}
131 
132 	/* Note range allows for a leap second */
133 	if ((t->tm_sec < 0) || (t->tm_sec > 60)) {
134 		shell_error(sh, "Invalid second");
135 		return -EINVAL;
136 	}
137 
138 	return 0;
139 }
140 
cmd_date_set(const struct shell * sh,size_t argc,char ** argv)141 static int cmd_date_set(const struct shell *sh, size_t argc, char **argv)
142 {
143 	struct timespec tp;
144 	struct tm tm;
145 	int ret;
146 
147 	clock_gettime(CLOCK_REALTIME, &tp);
148 
149 	gmtime_r(&tp.tv_sec, &tm);
150 
151 	if (argc == 3) {
152 		ret = get_y_m_d(sh, &tm, argv[1]);
153 		if (ret != 0) {
154 			shell_help(sh);
155 			return -EINVAL;
156 		}
157 		ret = get_h_m_s(sh, &tm, argv[2]);
158 		if (ret != 0) {
159 			shell_help(sh);
160 			return -EINVAL;
161 		}
162 	} else if (argc == 2) {
163 		ret = get_h_m_s(sh, &tm, argv[1]);
164 		if (ret != 0) {
165 			shell_help(sh);
166 			return -EINVAL;
167 		}
168 	} else {
169 		shell_help(sh);
170 		return -EINVAL;
171 	}
172 
173 	tp.tv_sec = timeutil_timegm(&tm);
174 	if (tp.tv_sec == -1) {
175 		shell_error(sh, "Failed to calculate seconds since Epoch");
176 		return -EINVAL;
177 	}
178 	tp.tv_nsec = 0;
179 
180 	ret = clock_settime(CLOCK_REALTIME, &tp);
181 	if (ret != 0) {
182 		shell_error(sh, "Could not set date %d", ret);
183 		return -EINVAL;
184 	}
185 
186 	date_print(sh, &tm);
187 
188 	return 0;
189 }
190 
cmd_date_get(const struct shell * sh,size_t argc,char ** argv)191 static int cmd_date_get(const struct shell *sh, size_t argc, char **argv)
192 {
193 	struct timespec tp;
194 	struct tm tm;
195 
196 	clock_gettime(CLOCK_REALTIME, &tp);
197 
198 	gmtime_r(&tp.tv_sec, &tm);
199 
200 	date_print(sh, &tm);
201 
202 	return 0;
203 }
204 
205 SHELL_STATIC_SUBCMD_SET_CREATE(sub_date,
206 	SHELL_CMD(set, NULL, HELP_DATE_SET, cmd_date_set),
207 	SHELL_CMD(get, NULL, HELP_NONE, cmd_date_get),
208 	SHELL_SUBCMD_SET_END /* Array terminated. */
209 );
210 
211 SHELL_CMD_REGISTER(date, &sub_date, "Date commands", NULL);
212