// // daytime -- TCP/IP Daytime Protocol (RFC-867) // // http://www.boulder.nist.gov/timefreq/service/its.htm // // Get PC time, get NIST time, and get PC time again. // Perform error checking, convert to fractional MJD, // and display difference. If requested set PC clock. // // Note: MJD is Modified Julian Date, an integer; fmjd // is fractional MJD which includes fractions of a day. // // 18-May-2005 Tom Van Baak (tvb) www.LeapSecond.com/tools // #include #include #include #include #include #define WIN32_LEAN_AND_MEAN #include #include char *daytime (double *offset); char *parse (char *s, double *fmjd); double current_mjd (); long date_to_mjd (long year, long month, long day); char *trim (char *s); int add_seconds (double seconds, char *note); int set = 0; char *a0; int main (int argc, char *argv[]) { WSADATA WSAData; WSAStartup(MAKEWORD(1,1), &WSAData); a0 = argv[0]; if (argc > 1 && strcmp(argv[1], "/set") == 0) { set = 1; argc -= 1; argv += 1; } else { fprintf(stderr, "Usage: %s [/set] [tries:3] [delay:1.5]\n", argv[0]); fprintf(stderr, "Usage: %s [/set] [tries] [delay] [batches] [delay]\n", argv[0]); fprintf(stderr, "\n"); } { int i, count, j, batch, batch_delay; long errors, total; double delay; double dt, sum; time_t now; char *status; count = (argc > 1) ? atoi(argv[1]) : (set ? 1 : 3); delay = (argc > 2) ? atof(argv[2]) : 1.5; batch = (argc > 3) ? atoi(argv[3]) : 1; batch_delay = (argc > 4) ? atoi(argv[4]) : 0; errors = total = 0; // Get a few NIST timestamps with short delay between calls. for (j = 0; j < batch; j += 1) { sum = 0.0; time(&now); printf("PC= %s", ctime(&now)); for (i = 0; i < count; i += 1) { status = daytime(&dt); if (status != NULL) { printf("%.6lf error: %s\n", current_mjd(), status); if (++errors == 10) { exit(1); } } sum += dt; if (i + 1 < count) { _sleep((int)(delay * 1e3)); } } if (count > 1) { printf("mean PC time offset %.3lf s\n", sum / count); } if (j + 1 < batch) { printf("\n"); _sleep((int)(batch_delay * 1e3)); } } } WSACleanup(); return 0; } // Get timestamp from NIST using daytime (port 13) protocol. char *daytime (double *offset) { char buffer[100]; double mjd_before, mjd_after, mjd_pc, mjd_nist, elapsed_seconds; int n; char *status; SOCKET sock; struct sockaddr_in sin; // Open socket sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); if (sock < 0) { return "socket failed"; } status = NULL; do { // IP address of time-a.timefreq.bldrdoc.gov is 132.163.4.101 memset((char *)&sin, 0x0, sizeof sin); sin.sin_family = AF_INET; sin.sin_port = htons(13); sin.sin_addr.s_addr = inet_addr("132.163.4.101"); n = connect(sock, (struct sockaddr *)&sin, sizeof sin ); if (n < 0) { status = "connect failed"; break; } // Read NIST timestamp bracketed by PC timestamps mjd_before = current_mjd(); n = recv(sock, buffer, sizeof buffer - 1, 0); if (n < 0) { status = "recv failed"; break; } buffer[n] = '\0'; mjd_after = current_mjd(); status = parse(buffer, &mjd_nist); if (status != NULL) { // printf("error %s: %s\n", status, trim(buffer)); break; } // Elapsed time of call to receive NIST packet. elapsed_seconds = (mjd_after - mjd_before) * 86400.0; if (elapsed_seconds >= 0.5) { status = "pc too slow"; set = 0; break; } // Get average PC time of NIST packet received. mjd_pc = (mjd_before + mjd_after) / 2.0; // Compute PC - NIST offset in seconds. *offset = (mjd_pc - mjd_nist) * 86400.0; printf("%.6lf \"%s\" PC= %s%.3lf s\n", mjd_nist, trim(buffer), (*offset > 0) ? "+" : "", *offset); if (set) { char msg[1000]; if (add_seconds(-(*offset), msg) == 0) { set = 0; } fprintf(stderr, "%s: %s\n", a0, msg); { FILE *f = fopen("/tmp/daytime.log", "a"); time_t t = time(NULL); fprintf(f, "%.24s %s\n", ctime(&t), msg); fclose(f); } } } while (0) ; closesocket(sock); return status; } // // Sample response: // // 53388 05-01-18 05:21:13 00 0 0 799.0 UTC(NIST) * // char *parse (char *s, double *fmjd) { int n; int mjd; int month, day, year; int hour, minute, second; int dst, leap, health; float ms_adv; char host[100]; double seconds; if (s[0] == '\0') { return "null reply"; } n = sscanf(s, "%d %d-%d-%d %d:%d:%d %d %d %d %f %s", &mjd, &year, &month, &day, &hour, &minute, &second, &dst, &leap, &health, &ms_adv, host); if (n != 12) { fprintf(stderr, "NIST reply: \"%s\" n=%d\n", s, n); return "bad format"; } if (health != 0) { fprintf(stderr, "NIST reply: \"%s\" n=%d\n", s); return "ill health"; } if (mjd == 99999) { fprintf(stderr, "NIST reply: \"%s\" n=%d\n", s); return "ignored"; } if (ms_adv < 0.0 || ms_adv > 1000.0) { fprintf(stderr, "NIST reply: \"%s\" n=%d\n", s); return "bad ms_adv"; } year += 2000; if (mjd != date_to_mjd (year, month, day)) { fprintf(stderr, "NIST reply: \"%s\" n=%d\n", s); return "bad date"; } seconds = (((hour * 60) + minute) * 60) + second; if (seconds < 0.0 || seconds > 86400.0) { fprintf(stderr, "NIST reply: \"%s\" n=%d\n", s); return "bad time"; } seconds -= ms_adv / 1e3; *fmjd = mjd + seconds / 86400.0; return NULL; } // // Get PC time in fractional MJD format. // double current_mjd () { long mjd; double seconds; SYSTEMTIME St; GetSystemTime(&St); mjd = date_to_mjd(St.wYear, St.wMonth, St.wDay); seconds = (((St.wHour * 60) + St.wMinute) * 60) + St.wSecond; seconds += St.wMilliseconds / 1e3; return mjd + seconds / 86400.0; } // // Return Modified Julian Day (MJD) given // calendar year, month (1-12), and day (1-31). // - Valid for Gregorian dates from 17-Nov-1858. // - Adapted from sci.astro FAQ. // long date_to_mjd (long year, long month, long day) { return 367 * year - 7 * (year + (month + 9) / 12) / 4 - 3 * ((year + (month - 9) / 7) / 100 + 1) / 4 + 275 * month / 9 + day + 1721028 - 2400000; } int white (char c) { return (c == '\r') || (c == '\n') || (c == ' ') || (c == '\t'); } char *trim (char *s) { char *start, *end; int first = 1; start = end = s; for (; *s; s++) { if (!white(*s)) { end = s; if (first) { first = 0; start = s; } } } *end = '\0'; return start; } // // Simple way to jam set PC clock ahead/behind by N seconds. // #define SPRINTF if (note != NULL) sprintf #define NO "[NO set] PC time" #define OK "[set OK] PC time" int add_seconds (double seconds, char *note) { SYSTEMTIME st; // Check for too little or too much time change. if (fabs(seconds) < 0.050) { SPRINTF(note, "%s already close enough (%.0lf ms).", NO, fabs(seconds) * 1e3); return 1; } if (fabs(seconds) > 90) { SPRINTF(note, "%s unusually wrong (%.0lf s) -- set PC clock manually.", NO, fabs(seconds)); return 0; } // Get time, adjust time (plus/minus), set time. GetSystemTime(&st); { unsigned short h = st.wHour; unsigned short m = st.wMinute; unsigned short s = st.wSecond; unsigned short ms = st.wMilliseconds; long ms_delta = (long) (seconds * 1000.0); long mstod = (((h * 60 + m) * 60 + s) * 1000 + ms) + ms_delta; long stod = mstod / 1000; if (mstod < 0 || stod >= 86400) { SPRINTF(note, "%s adjustment would cross day boundary -- try again.", NO); return 0; } st.wHour = stod / 3600; st.wMinute = (stod / 60) % 60; st.wSecond = stod % 60; st.wMilliseconds = mstod % 1000; } SetSystemTime(&st); SPRINTF(note, "%s adjusted by %.3lf seconds.", OK, seconds); return 1; }