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 strftime(3) syntax: 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 %N nanosec (output only) 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 unsigned nano; 56 ) 57 58 // Handle default posix date format (mmddhhmm[[cc]yy]) or @UNIX[.FRAC] 59 // returns 0 success, nonzero for error 60 static int parse_default(char *str, struct tm *tm) 61 { 62 int len = 0; 63 64 // Parse @UNIXTIME[.FRACTION] 65 if (*str == '@') { 66 long long ll; 67 time_t tt; 68 69 // Collect seconds and nanoseconds 70 // Note: struct tm hasn't got a fractional seconds field, thus strptime() 71 // doesn't support it, so store nanoseconds out of band (in globals). 72 // tt and ll are separate because we can't guarantee time_t is 64 bit (yet). 73 sscanf(str, "@%lld%n", &ll, &len); 74 if (str[len]=='.') { 75 str += len+1; 76 for (len = 0; len<9; len++) { 77 TT.nano *= 10; 78 if (isdigit(str[len])) TT.nano += str[len]-'0'; 79 } 80 } 81 if (str[len]) return 1; 82 tt = ll; 83 gmtime_r(&tt, tm); 84 85 return 0; 86 } 87 88 // Posix format 89 sscanf(str, "%2u%2u%2u%2u%n", &tm->tm_mon, &tm->tm_mday, &tm->tm_hour, 90 &tm->tm_min, &len); 91 if (len != 8) return 1; 92 str += len; 93 tm->tm_mon--; 94 95 // If year specified, overwrite one we fetched earlier 96 if (*str && *str != '.') { 97 unsigned year; 98 99 len = 0; 100 sscanf(str, "%u%n", &year, &len); 101 if (len == 4) tm->tm_year = year - 1900; 102 else if (len != 2) return 1; 103 str += len; 104 105 // 2 digit years, next 50 years are "future", last 50 years are "past". 106 // A "future" date in past is a century ahead. 107 // A non-future date in the future is a century behind. 108 if (len == 2) { 109 unsigned r1 = tm->tm_year % 100, r2 = (tm->tm_year + 50) % 100, 110 century = tm->tm_year - r1; 111 112 if ((r1 < r2) ? (r1 < year && year < r2) : (year < r1 || year > r2)) { 113 if (year < r1) year += 100; 114 } else if (year > r1) year -= 100; 115 tm->tm_year = year + century; 116 } 117 } 118 if (*str == '.') { 119 len = 0; 120 sscanf(str, ".%u%n", &tm->tm_sec, &len); 121 str += len; 122 } else tm->tm_sec = 0; 123 124 return *str; 125 } 126 127 static void check_range(int a, int low, int high) 128 { 129 if (a<low) error_exit("%d<%d", a, low); 130 if (a>high) error_exit("%d>%d", a, high); 131 } 132 133 // Print strftime plus %N escape(s). note: modifies fmt for %N 134 static void puts_time(char *fmt, struct tm *tm) 135 { 136 char *s, *snap; 137 long width = width; 138 139 for (s = fmt;;s++) { 140 141 // Find next %N or end 142 if (*(snap = s) == '%') { 143 width = isdigit(*++s) ? *(s++)-'0' : 9; 144 if (*s && *s != 'N') continue; 145 } else if (*s) continue; 146 147 // Don't modify input string if no %N (default format is constant string). 148 if (*s) *snap = 0; 149 if (!strftime(toybuf, sizeof(toybuf)-10, fmt, tm)) 150 perror_exit("bad format '%s'", fmt); 151 if (*s) { 152 snap = toybuf+strlen(toybuf); 153 sprintf(snap, "%09u", TT.nano); 154 snap[width] = 0; 155 } 156 fputs(toybuf, stdout); 157 if (!*s || !*(fmt = s+1)) break; 158 } 159 xputc('\n'); 160 } 161 162 void date_main(void) 163 { 164 char *setdate = *toys.optargs, *format_string = "%a %b %e %H:%M:%S %Z %Y"; 165 struct tm tm; 166 167 memset(&tm, 0, sizeof(struct tm)); 168 169 if (TT.showdate) { 170 if (TT.setfmt) { 171 char *s = strptime(TT.showdate, TT.setfmt+(*TT.setfmt=='+'), &tm); 172 173 if (!s || *s) goto bad_showdate; 174 } else if (parse_default(TT.showdate, &tm)) goto bad_showdate; 175 } else { 176 struct timespec ts; 177 struct stat st; 178 179 if (TT.file) { 180 xstat(TT.file, &st); 181 ts = st.st_mtim; 182 } else clock_gettime(CLOCK_REALTIME, &ts); 183 184 ((toys.optflags & FLAG_u) ? gmtime_r : localtime_r)(&ts.tv_sec, &tm); 185 TT.nano = ts.tv_nsec; 186 } 187 188 // Fall through if no arguments 189 if (!setdate); 190 // Display the date? 191 else if (*setdate == '+') { 192 format_string = toys.optargs[0]+1; 193 setdate = toys.optargs[1]; 194 195 // Set the date 196 } else if (setdate) { 197 char *tz; 198 struct timeval tv; 199 int u = toys.optflags & FLAG_u; 200 201 if (parse_default(setdate, &tm)) goto bad_setdate; 202 203 check_range(tm.tm_sec, 0, 60); 204 check_range(tm.tm_min, 0, 59); 205 check_range(tm.tm_hour, 0, 23); 206 check_range(tm.tm_mday, 1, 31); 207 check_range(tm.tm_mon, 0, 11); 208 209 if (u) { 210 tz = getenv("TZ"); 211 setenv("TZ", "UTC", 1); 212 tzset(); 213 } 214 errno = 0; 215 tv.tv_sec = mktime(&tm); 216 if (errno) goto bad_setdate; 217 if (u) { 218 if (tz) setenv("TZ", tz, 1); 219 else unsetenv("TZ"); 220 tzset(); 221 } 222 223 tv.tv_usec = TT.nano/1000; 224 if (settimeofday(&tv, NULL) < 0) perror_msg("cannot set date"); 225 } 226 227 puts_time(format_string, &tm); 228 return; 229 230 bad_showdate: 231 setdate = TT.showdate; 232 bad_setdate: 233 perror_exit("bad date '%s'", setdate); 234 } 235