File: countdown.c 1 /* 2 The MIT License (MIT) 3 4 Copyright © 2020-2025 pacman64 5 6 Permission is hereby granted, free of charge, to any person obtaining a copy of 7 this software and associated documentation files (the “Software”), to deal 8 in the Software without restriction, including without limitation the rights to 9 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 10 of the Software, and to permit persons to whom the Software is furnished to do 11 so, subject to the following conditions: 12 13 The above copyright notice and this permission notice shall be included in all 14 copies or substantial portions of the Software. 15 16 THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 SOFTWARE. 23 */ 24 25 /* 26 You can build this command-line app by running 27 28 cc -Wall -s -O3 -flto -o ./countdown ./countdown.c 29 */ 30 31 #include <stdbool.h> 32 #include <stddef.h> 33 #include <stdint.h> 34 #include <stdio.h> 35 #include <stdlib.h> 36 #include <string.h> 37 #include <time.h> 38 #include <unistd.h> 39 40 #ifdef _WIN32 41 #include <fcntl.h> 42 #include <windows.h> 43 #endif 44 45 #ifdef RED_ERRORS 46 #define ERROR_STYLE "\x1b[38;2;204;0;0m" 47 #ifdef __APPLE__ 48 #define ERROR_STYLE "\x1b[31m" 49 #endif 50 #define RESET_STYLE "\x1b[0m" 51 #else 52 #define ERROR_STYLE 53 #define RESET_STYLE 54 #endif 55 56 #define ERROR_LINE(MSG) (ERROR_STYLE MSG RESET_STYLE "\n") 57 58 const char* info = "" 59 "countdown [options...] [seconds/duration...]\n" 60 "\n" 61 "\n" 62 "Count-down the number of seconds given, or 60 seconds by default.\n" 63 "\n" 64 "The duration can be either a number (of seconds) or a string with no\n" 65 "spaces and numbered units `h`, `m`, `s`, which can be mixed together.\n" 66 "\n" 67 "Decimal dots and negative numbers aren't allowed." 68 "\n" 69 "\n" 70 "Options, all of which can start with either 1 or 2 dashes:\n" 71 "\n" 72 "\n" 73 " -h show this help message\n" 74 " -help show this help message\n" 75 "\n" 76 "\n" 77 "Examples" 78 "\n" 79 "\n" 80 " countdown 33 # 33 seconds\n" 81 " countdown 3h45m # 3 hours and 45 minutes\n" 82 " countdown 3h45s # 3 hours and 45 seconds\n" 83 " countdown 3h4m5s # 3 hours 4 minutes and 5 seconds\n" 84 ""; 85 86 void countdown(int seconds) { 87 struct timespec now; 88 clock_gettime(CLOCK_REALTIME, &now); 89 const int64_t ns2sec = 1e9; 90 const int64_t end = ns2sec * (now.tv_sec + seconds) + now.tv_nsec; 91 92 struct timespec delay; 93 delay.tv_sec = 0; 94 delay.tv_nsec = 100 * 1e6; 95 96 int64_t t = ns2sec * now.tv_sec + now.tv_nsec; 97 while (t < end) { 98 const int64_t diff = end - t; 99 const int64_t round_up = (diff % ns2sec) > (ns2sec / 5); 100 const int64_t left = diff / ns2sec + round_up; 101 const int h = left / 3600; 102 const int m = (left / 60) % 60; 103 const int s = left % 60; 104 fprintf(stderr, "\r%02d:%02d:%02d", h, m, s); 105 106 nanosleep(&delay, NULL); 107 clock_gettime(CLOCK_REALTIME, &now); 108 t = ns2sec * now.tv_sec + now.tv_nsec; 109 } 110 111 fputs("\r \r", stderr); 112 } 113 114 // is_help_option simplifies control-flow for func main 115 bool is_help_option(const char* s) { 116 return (s[0] == '-') && ( 117 strcmp(s, "-h") == 0 || 118 strcmp(s, "-help") == 0 || 119 strcmp(s, "--h") == 0 || 120 strcmp(s, "--help") == 0 121 ); 122 } 123 124 // bool parse_duration(char* s, int* seconds) { 125 // char* end; 126 // int v = strtol(argv[1], &end, 10); 127 // if (*end == 0 && argv[1] != end) { 128 // *seconds = v; 129 // return true; 130 // } 131 // return false; 132 // } 133 134 bool parse_colon_style_duration(char* s, int* seconds) { 135 int accum = 0; 136 int digits = 0; 137 138 int parts[4] = {0, 0, 0, 0}; 139 const int n = sizeof(parts) / sizeof(parts[0]); 140 const int multipliers[4] = {1, 60, 60 * 60, 24 * 60 * 60}; 141 int which = 0; 142 143 for (; *s != 0; s++) { 144 const int b = *s; 145 146 if (b == ':') { 147 if (digits < 1) { 148 return false; 149 } 150 151 parts[which] = accum; 152 accum = 0; 153 digits = 0; 154 155 which++; 156 if (which >= n) { 157 return false; 158 } 159 160 continue; 161 } 162 163 if ('0' <= b && b <= '9') { 164 digits++; 165 accum *= 10; 166 accum += b - '0'; 167 continue; 168 } 169 170 return false; 171 } 172 173 if (digits < 1) { 174 return false; 175 } 176 177 parts[which] = accum; 178 179 int total = 0; 180 for (int i = which, j = 0; i >= 0; i--, j++) { 181 total += multipliers[j] * parts[i]; 182 } 183 *seconds = total; 184 return true; 185 } 186 187 bool parse_duration(char* s, int* seconds) { 188 int total = 0; 189 int accum = 0; 190 191 if (parse_colon_style_duration(s, seconds)) { 192 return true; 193 } 194 195 for (; *s != 0; s++) { 196 const int b = *s; 197 198 if ('0' <= b && b <= '9') { 199 accum *= 10; 200 accum += b - '0'; 201 continue; 202 } 203 204 switch (b) { 205 case 'h': 206 accum *= 3600; 207 total += accum; 208 accum = 0; 209 break; 210 211 case 'm': 212 accum *= 60; 213 total += accum; 214 accum = 0; 215 break; 216 217 case 's': 218 total += accum; 219 accum = 0; 220 break; 221 222 default: 223 return false; 224 } 225 } 226 227 total += accum; 228 *seconds = total; 229 return true; 230 } 231 232 int main(int argc, char** argv) { 233 #ifdef _WIN32 234 setmode(fileno(stdin), O_BINARY); 235 // ensure output lines end in LF instead of CRLF on windows 236 setmode(fileno(stdout), O_BINARY); 237 setmode(fileno(stderr), O_BINARY); 238 #endif 239 240 // handle any of the help options, if given 241 if (argc > 1 && is_help_option(argv[1])) { 242 printf("%s", info); 243 return 0; 244 } 245 246 int seconds = 60; 247 if (argc > 1) { 248 if (!parse_duration(argv[1], &seconds)) { 249 const char* fmt = ERROR_LINE("invalid duration '%s'"); 250 fprintf(stderr, fmt, argv[1]); 251 return 1; 252 } 253 } 254 255 if (seconds > 0) { 256 countdown(seconds); 257 } 258 259 printf("done waiting for %d seconds\n", seconds); 260 return 0; 261 }