// // wwvb2.c - Generate & display WWVB subcode // // - This version takes command line date/time parameters. // // 04-Feb-2005 /tvb // #include #include #include #include // Define basic types typedef unsigned char byte; typedef unsigned int uint; typedef unsigned long ulong; // Define time code elements struct timecode { uint min; uint hour; uint days; uint year; uint leapyear; uint dst; int ut1; uint leapsec; } tco; // Define forward referenced function prototypes void tick_set (time_t t, struct timecode *tc); void tick_show (struct timecode *tc); void tick_tock (struct timecode *tc); int wwvb_telegram (struct timecode *tc, byte code[]); int GetOptions (int argc, char *argv[]); // Define usage message char Usage[] = "Usage: wwvb2 [options] [max minutes]\n" "\n" "Options:\n" " /year:# - set year (0 to 99)\n" " /days:# - set day of year (1 to 365/366)\n" " /hour:# - set hour (0 to 23)\n" " /min:# - set minute (0 to 59)\n" " /ut1:# - set ut1 offset (-900 to +900)\n" " /dst:# - set both dst bits (0 to 3)\n" " /ly:# - set leap year bit (0/1)\n" " /ls:# - set leap second warning bit (0/1)\n" "\n" "Examples:\n" " - what WWVB will look like for the next half hour:\n" " wwvb2 30\n" "\n" " - what WWVB looked like during the great Y2K event:\n" " wwvb2 /year:1999 /days:365 /hour:23 /min:55 10\n" "\n" " - WWVB looked like this today at UTC midnight:\n" " wwvb2 /hour:0 /min:0 15\n" "\n" " - watch what the December 1998 leap second looked like:\n" " wwvb2 /year:98 /days:365 /hour:23 /min:56 /ut1:-300 /ls:1 7\n" "\n" "Questions or comments:\n" " See www.LeapSecond.com for contact info\n" ; int main (int argc, char *argv[]) { int i, n; long minutes; time_t now; if (argc == 1) { fputs(Usage, stderr); exit(0); } // Set default WWVB time from PC time time(&now); tick_set(now, &tco); // Then allow command line options to override the time fields n = GetOptions(argc, argv); if (n == 0) { fprintf(stderr, "** No timecode options specified (defaulting to PC time)\n"); } argc -= n; argv += n; // Get number of minutes if (argc > 1) { minutes = atol(argv[1]); if (minutes <= 0) { fprintf(stderr, "Invalid Number of minutes: %s\n", argv[1]); exit(1); } } else { fprintf(stderr, "** Number of minutes not specified (defaulting to 10)\n"); minutes = 10; } if (argc > 2) { fprintf(stderr, "Unexpected argument: %s\n", argv[2]); exit(1); } // Generate time code, minute by minute fprintf(stderr, "\nNIST WWVB subcode test generator\n\n"); fprintf(stderr, "PC local time: %s", ctime(&now)); printf("WWVB timecode: "); tick_show(&tco); do { byte bits[61]; printf("'%.2d+%.3d %.2d:%.2d ", tco.year, tco.days, tco.hour, tco.min); n = wwvb_telegram(&tco, bits); for (i = 0; i < n; i += 1) { printf("%d", bits[i]); } printf("\n"); tick_tock(&tco); } while (--minutes > 0) ; return 0; } // Extract one binary coded decimal bit int BCD (int n, int d) { if (n < 0) { n = -n; } while (d >= 10) { n /= 10; d /= 10; } return ((n % 10) & d) ? 1 : 0; } // Convert date and time to 60 bit WWVB time code int wwvb_telegram (struct timecode *tc, byte code[]) { int n; code[0] = 2; code[1] = BCD(tc->min, 40); code[2] = BCD(tc->min, 20); code[3] = BCD(tc->min, 10); code[4] = 0; code[5] = BCD(tc->min, 8); code[6] = BCD(tc->min, 4); code[7] = BCD(tc->min, 2); code[8] = BCD(tc->min, 1); code[9] = 2; code[10] = 0; code[11] = 0; code[12] = BCD(tc->hour, 20); code[13] = BCD(tc->hour, 10); code[14] = 0; code[15] = BCD(tc->hour, 8); code[16] = BCD(tc->hour, 4); code[17] = BCD(tc->hour, 2); code[18] = BCD(tc->hour, 1); code[19] = 2; code[20] = 0; code[21] = 0; code[22] = BCD(tc->days, 200); code[23] = BCD(tc->days, 100); code[24] = 0; code[25] = BCD(tc->days, 80); code[26] = BCD(tc->days, 40); code[27] = BCD(tc->days, 20); code[28] = BCD(tc->days, 10); code[29] = 2; code[30] = BCD(tc->days, 8); code[31] = BCD(tc->days, 4); code[32] = BCD(tc->days, 2); code[33] = BCD(tc->days, 1); code[34] = 0; code[35] = 0; code[36] = tc->ut1 >= 0; code[37] = !code[36]; code[38] = code[36]; code[39] = 2; code[40] = BCD(tc->ut1, 800); code[41] = BCD(tc->ut1, 400); code[42] = BCD(tc->ut1, 200); code[43] = BCD(tc->ut1, 100); code[44] = 0; code[45] = BCD(tc->year, 80); code[46] = BCD(tc->year, 40); code[47] = BCD(tc->year, 20); code[48] = BCD(tc->year, 10); code[49] = 2; code[50] = BCD(tc->year, 8); code[51] = BCD(tc->year, 4); code[52] = BCD(tc->year, 2); code[53] = BCD(tc->year, 1); code[54] = 0; code[55] = tc->leapyear; code[56] = tc->leapsec; code[57] = (tc->dst & 2) == 2; code[58] = (tc->dst & 1) == 1; code[59] = 2; // Handle positive or negative leap second exception n = 60; if (tc->leapsec != 0 && tc->hour == 23 && tc->min == 59) { if (tc->ut1 > 0) { tc->ut1 -= 1000; n = 59; } else if (tc->ut1 < 0) { tc->ut1 += 1000; n = 61; code[60] = 2; } tc->leapsec = 0; } return n; } // Set timecode from PC clock void tick_set (time_t t, struct timecode *tc) { struct tm *tm; tm = gmtime(&t); tc->min = tm->tm_min; tc->hour = tm->tm_hour; tc->days = 1 + tm->tm_yday; tc->year = tm->tm_year % 100; tc->leapyear = (tc->year % 4) == 0; tc->dst = 0; // normal tc->ut1 = 0; // place holder tc->leapsec = 0; // normal return; } // Display timecode values void tick_show (struct timecode *tc) { printf("year=%.2d days=%.3d hour=%.2d min=%.2d ", tc->year, tc->days, tc->hour, tc->min); printf("dst=%d ut1=%d ly=%d ls=%d\n", tc->dst, tc->ut1, tc->leapyear, tc->leapsec); return; } // Increment timecode by one minute void tick_tock (struct timecode *tc) { tc->min += 1; if (tc->min >= 60) { tc->min = 0; tc->hour += 1; if (tc->hour >= 24) { tc->hour = 0; tc->days += 1; if (tc->days >= 365 + tc->leapyear) { tc->days = 1; tc->year += 1; tc->year %= 100; tc->leapyear = (tc->year % 4) == 0; } } } return; } // Parse and validate options. int GetOptions (int argc, char *argv[]) { int argn, i; argn = 0; while (argc > 1 && argv[1][0] == '/') { if (strcmp(argv[1], "/help") == 0 || argv[1][1] == '?') { fputs(Usage, stderr); exit(0); } else if (strncmp(argv[1], "/year:", i = 6) == 0) { tco.year = atoi(&argv[1][i]); if (tco.year > 1000) { fprintf(stderr, "** Truncating 4-digit year to 2-digits\n"); tco.year %= 100; } if (tco.year > 99) { fprintf(stderr, "Invalid year (0 to 99): %s\n", argv[1]); exit(1); } } else if (strncmp(argv[1], "/days:", i = 6) == 0) { tco.days = atoi(&argv[1][i]); if (tco.days < 1 || tco.days > 366) { fprintf(stderr, "Invalid days (1 to 366): %s\n", argv[1]); exit(1); } } else if (strncmp(argv[1], "/hour:", i = 6) == 0) { tco.hour = atoi(&argv[1][i]); if (tco.hour > 23) { fprintf(stderr, "Invalid hour (0 to 23): %s\n", argv[1]); exit(1); } } else if (strncmp(argv[1], "/min:", i = 5) == 0) { tco.min = atoi(&argv[1][i]); if (tco.min > 59) { fprintf(stderr, "Invalid minute (0 to 59): %s\n", argv[1]); exit(1); } } else if (strncmp(argv[1], "/ut1:", i = 5) == 0) { tco.ut1 = atoi(&argv[1][i]); if ((tco.ut1 % 100) != 0 || tco.ut1 < -900 || tco.ut1 > 900) { fprintf(stderr, "Invalid ut1 (-900 to +900): %s\n", argv[1]); exit(1); } } else if (strncmp(argv[1], "/dst:", i = 5) == 0) { tco.dst = atoi(&argv[1][i]); if (tco.dst > 3) { fprintf(stderr, "Invalid dst (0 to 3): %s\n", argv[1]); exit(1); } } else if (strncmp(argv[1], "/ly:", i = 4) == 0) { tco.leapyear = atoi(&argv[1][i]); if (tco.leapyear > 1) { fprintf(stderr, "Invalid leap year (0 or 1): %s\n", argv[1]); exit(1); } } else if (strncmp(argv[1], "/ls:", i = 4) == 0) { tco.leapsec = atoi(&argv[1][i]); if (tco.leapsec > 1) { fprintf(stderr, "Invalid leap second (0 or 1): %s\n", argv[1]); exit(1); } } else { fprintf(stderr, "Unknown option: %s\n", argv[1]); exit(1); } argn += 1; argc -= 1; argv += 1; } if (tco.leapsec && (tco.ut1 == 0)) { fprintf(stderr, "Invalid leap second and ut1 combination\n"); exit(1); } if (tco.leapyear && (tco.year & 0x3)) { fprintf(stderr, "Invalid leap year and year combination\n"); exit(1); } return argn; }