1 /* date.c - set/get the date 2 * 3 * Copyright 2012 Andre Renaud <andre (at) bluewatersys.com> 4 * 5 * See http://opengroup.org/onlinepubs/9699919799/utilities/date.html 6 * 7 * Note: setting a 2 year date is 50 years back/forward from today, 8 * not posix's hardwired magic dates. 9 10 USE_DATE(NEWTOY(date, "d:D:r:u[!dr]", TOYFLAG_BIN)) 11 12 config DATE 13 bool "date" 14 default y 15 help 16 usage: date [-u] [-r FILE] [-d DATE] [+DISPLAY_FORMAT] [-D SET_FORMAT] [SET] 17 18 Set/get the current date/time. With no SET shows the current date. 19 20 Default SET format is "MMDDhhmm[[CC]YY][.ss]", that's (2 digits each) 21 month, day, hour (0-23), and minute. Optionally century, year, and second. 22 Also accepts "@UNIXTIME[.FRACTION]" as seconds since midnight Jan 1 1970. 23 24 -d Show DATE instead of current time (convert date format) 25 -D +FORMAT for SET or -d (instead of MMDDhhmm[[CC]YY][.ss]) 26 -r Use modification time of FILE instead of current date 27 -u Use UTC instead of current timezone 28 29 +FORMAT specifies display format string using these escapes: 30 31 %% literal % %n newline %t tab 32 %S seconds (00-60) %M minute (00-59) %m month (01-12) 33 %H hour (0-23) %I hour (01-12) %p AM/PM 34 %y short year (00-99) %Y year %C century 35 %a short weekday name %A weekday name %u day of week (1-7, 1=mon) 36 %b short month name %B month name %Z timezone name 37 %j day of year (001-366) %d day of month (01-31) %e day of month ( 1-31) 38 %s seconds past the Epoch 39 40 %U Week of year (0-53 start sunday) %W Week of year (0-53 start monday) 41 %V Week of year (1-53 start monday, week < 4 days not part of this year) 42 43 %D = "%m/%d/%y" %r = "%I : %M : %S %p" %T = "%H:%M:%S" %h = "%b" 44 %x locale date %X locale time %c locale date/time 45 */ 46 47 #define FOR_date 48 #include "toys.h" 49 50 GLOBALS( 51 char *file; 52 char *setfmt; 53 char *showdate; 54 55 char *tz; 56 unsigned nano; 57 ) 58 59 // mktime(3) normalizes the struct tm fields, but date(1) shouldn't. 60 static time_t chkmktime(struct tm *tm, const char *str, const char* fmt) 61 { 62 struct tm tm0 = *tm; 63 struct tm tm1; 64 time_t t = mktime(tm); 65 66 if (t == -1 || !localtime_r(&t, &tm1) || 67 tm0.tm_sec != tm1.tm_sec || tm0.tm_min != tm1.tm_min || 68 tm0.tm_hour != tm1.tm_hour || tm0.tm_mday != tm1.tm_mday || 69 tm0.tm_mon != tm1.tm_mon) { 70 int len; 71 72 strftime(toybuf, sizeof(toybuf), fmt, &tm0); 73 len = strlen(toybuf) + 1; 74 strftime(toybuf + len, sizeof(toybuf) - len, fmt, &tm1); 75 error_exit("bad date '%s'; %s != %s", str, toybuf, toybuf + len); 76 } 77 return t; 78 } 79 80 static void utzset(void) 81 { 82 if (!(TT.tz = getenv("TZ"))) TT.tz = (char *)1; 83 setenv("TZ", "UTC", 1); 84 tzset(); 85 } 86 87 static void utzreset(void) 88 { 89 if (TT.tz) { 90 if (TT.tz != (char *)1) setenv("TZ", TT.tz, 1); 91 else unsetenv("TZ"); 92 tzset(); 93 } 94 } 95 96 // Handle default posix date format (mmddhhmm[[cc]yy]) or @UNIX[.FRAC] 97 // returns 0 success, nonzero for error 98 static int parse_default(char *str, struct tm *tm) 99 { 100 int len = 0; 101 102 // Parse @UNIXTIME[.FRACTION] 103 if (*str == '@') { 104 long long ll; 105 time_t tt; 106 107 // Collect seconds and nanoseconds 108 // Note: struct tm hasn't got a fractional seconds field, thus strptime() 109 // doesn't support it, so store nanoseconds out of band (in globals). 110 // tt and ll are separate because we can't guarantee time_t is 64 bit (yet). 111 sscanf(str, "@%lld%n", &ll, &len); 112 if (str[len]=='.') { 113 str += len+1; 114 for (len = 0; len<9; len++) { 115 TT.nano *= 10; 116 if (isdigit(str[len])) TT.nano += str[len]-'0'; 117 } 118 } 119 if (str[len]) return 1; 120 tt = ll; 121 gmtime_r(&tt, tm); 122 123 return 0; 124 } 125 126 // Posix format 127 sscanf(str, "%2u%2u%2u%2u%n", &tm->tm_mon, &tm->tm_mday, &tm->tm_hour, 128 &tm->tm_min, &len); 129 if (len != 8) return 1; 130 str += len; 131 tm->tm_mon--; 132 133 // If year specified, overwrite one we fetched earlier 134 if (*str && *str != '.') { 135 unsigned year; 136 137 len = 0; 138 sscanf(str, "%u%n", &year, &len); 139 if (len == 4) tm->tm_year = year - 1900; 140 else if (len != 2) return 1; 141 str += len; 142 143 // 2 digit years, next 50 years are "future", last 50 years are "past". 144 // A "future" date in past is a century ahead. 145 // A non-future date in the future is a century behind. 146 if (len == 2) { 147 unsigned r1 = tm->tm_year % 100, r2 = (tm->tm_year + 50) % 100, 148 century = tm->tm_year - r1; 149 150 if ((r1 < r2) ? (r1 < year && year < r2) : (year < r1 || year > r2)) { 151 if (year < r1) year += 100; 152 } else if (year > r1) year -= 100; 153 tm->tm_year = year + century; 154 } 155 } 156 if (*str == '.') { 157 len = 0; 158 sscanf(str, ".%u%n", &tm->tm_sec, &len); 159 str += len; 160 } else tm->tm_sec = 0; 161 162 return *str; 163 } 164 165 void date_main(void) 166 { 167 char *setdate = *toys.optargs, *format_string = "%a %b %e %H:%M:%S %Z %Y"; 168 struct tm tm; 169 170 memset(&tm, 0, sizeof(struct tm)); 171 172 // We can't just pass a timezone to mktime because posix. 173 if (toys.optflags & FLAG_u) utzset(); 174 175 if (TT.showdate) { 176 if (TT.setfmt) { 177 char *s = strptime(TT.showdate, TT.setfmt+(*TT.setfmt=='+'), &tm); 178 179 if (!s || *s) goto bad_showdate; 180 } else if (parse_default(TT.showdate, &tm)) goto bad_showdate; 181 } else { 182 time_t now; 183 184 if (TT.file) { 185 struct stat st; 186 187 xstat(TT.file, &st); 188 now = st.st_mtim.tv_sec; 189 } else now = time(0); 190 191 ((toys.optflags & FLAG_u) ? gmtime_r : localtime_r)(&now, &tm); 192 } 193 194 // Fall through if no arguments 195 if (!setdate); 196 // Display the date? 197 else if (*setdate == '+') { 198 format_string = toys.optargs[0]+1; 199 setdate = toys.optargs[1]; 200 201 // Set the date 202 } else if (setdate) { 203 struct timeval tv; 204 205 if (parse_default(setdate, &tm)) error_exit("bad date '%s'", setdate); 206 207 if (toys.optflags & FLAG_u) { 208 // We can't just pass a timezone to mktime because posix. 209 utzset(); 210 tv.tv_sec = chkmktime(&tm, setdate, format_string); 211 utzreset(); 212 } else tv.tv_sec = chkmktime(&tm, setdate, format_string); 213 214 tv.tv_usec = TT.nano/1000; 215 if (settimeofday(&tv, NULL) < 0) perror_msg("cannot set date"); 216 } 217 218 utzreset(); 219 if (!strftime(toybuf, sizeof(toybuf), format_string, &tm)) 220 perror_exit("bad format '%s'", format_string); 221 puts(toybuf); 222 223 return; 224 225 bad_showdate: 226 error_exit("bad date '%s'", TT.showdate); 227 } 228