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