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