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