From dd0245cbf8d32342fcedab8bfa446f586e405191 Mon Sep 17 00:00:00 2001 From: Don Cross Date: Mon, 28 Aug 2023 12:24:20 -0400 Subject: [PATCH] C: Astronomy_GetCurrentTime supports microsecond resolution. This is a follow-up to work provided by: Eric Wheeler, KJ7LNW Before now, the C function Astronomy_GetCurrentTime returned the current time from the system clock, but only with whole second resolution. Now it supports microsecond resolution on Linux/Unix, Mac OS, and Windows. For unsupported platforms, a compiler error will occur to indicate that microsecond resolution is not available. However, it is possible to define one of the following two preprocessor symbols to work around the compiler error: 1. ASTRONOMY_ENGINE_NO_CURRENT_TIME Excludes the function Astronomy_CurrentTime from the build. If your project does not need to obtain the current time, or your hardware platform does not provide current date and time in the first place, this is likely the better option. 2. ASTRONOMY_ENGINE_WHOLE_SECOND If your project does need to use the current date and time for astronomy calculations, and it can tolerate whole second resolution, this option provides a version of Astronomy_CurrentTime that uses a call to `time(NULL)`. Notes: - Added unit test to confirm at least millisecond resolution. Because these tests have to run on GitHub Actions cloud platform, and those systems can be heavily CPU-loaded, I want to be tolerant of resolution and avoid false failures. - Added detection of Mac platform. - Added preprocessor options documented above. - On Windows, use ULARGE_INTEGER and eliminated one integer division. - Added comments and developer documentation. - Converted tabs to spaces in astronomy.c, for consistent code format. --- generate/ctest.c | 24 ++++++++++++++-- generate/template/astronomy.c | 54 ++++++++++++++++++++--------------- source/c/README.md | 6 +++- source/c/astronomy.c | 54 ++++++++++++++++++++--------------- source/c/astronomy.h | 2 ++ 5 files changed, 90 insertions(+), 50 deletions(-) diff --git a/generate/ctest.c b/generate/ctest.c index 10201d1a..ac8827f5 100644 --- a/generate/ctest.c +++ b/generate/ctest.c @@ -478,11 +478,11 @@ fail: static int Test_AstroTime(void) { int error = 1; - astro_time_t time; + astro_time_t time, time2; const double expected_ut = 6910.270978506945; const double expected_tt = 6910.271800214368; double diff; - int year; + int year, count; time = Astronomy_MakeTime(2018, 12, 2, 18, 30, 12.543); FDEBUG("ut=%0.12lf, tt=%0.12lf\n", time.ut, time.tt); @@ -522,7 +522,25 @@ static int Test_AstroTime(void) time = Astronomy_MakeTime(+999999, 11, 30, 8, 15, 45.0); CHECK(CheckTimeFormat(time, TIME_FORMAT_MILLI, ASTRO_SUCCESS, "+999999-11-30T08:15:44.999Z")); - FPASS(); + /* Verify that the realtime clock supports fine-grained time resolution. */ + time = Astronomy_CurrentTime(); + count = 0; + do + { + /* Keep looping until the time changes. */ + time2 = Astronomy_CurrentTime(); + + /* Prevent infinite looping, in case something goes bonkers. */ + if (++count > 1000) + FFAIL("TAKING TOO LONG for Astronomy_CurrentTime() to change.\n"); + } while (time2.ut == time.ut); + diff = V((time2.ut - time.ut)*SECONDS_PER_DAY); + if (diff <= 0.0) + FFAIL("IMPOSSIBLE realtime increment = %0.4lg seconds after %d iterations.\n", diff, count); + if (diff > 0.001) + FFAIL("EXCESSIVE realtime increment = %0.4lg seconds after %d iterations.\n", diff, count); + + FPASSA("realtime increment = %0.4lg seconds after %d iterations.\n", diff, count); fail: return error; } diff --git a/generate/template/astronomy.c b/generate/template/astronomy.c index cf00f06a..4769e513 100644 --- a/generate/template/astronomy.c +++ b/generate/template/astronomy.c @@ -31,7 +31,8 @@ #include #include -#if defined(__unix__) +#if !defined(ASTRONOMY_ENGINE_NO_CURRENT_TIME) +#if defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__)) #include #elif defined(_WIN32) #define WIN32_LEAN_AND_MEAN @@ -39,6 +40,7 @@ #else #include #endif +#endif #include "astronomy.h" @@ -972,50 +974,56 @@ astro_time_t Astronomy_TerrestrialTime(double tt) } } - +#if !defined(ASTRONOMY_ENGINE_NO_CURRENT_TIME) /** * @brief Returns the computer's current date and time in the form of an #astro_time_t. * - * Uses the computer's system clock to find the current UTC date and time with 1-second granularity. + * Uses the computer's system clock to find the current UTC date and time. * Converts that date and time to an #astro_time_t value and returns the result. * Callers can pass this value to other Astronomy Engine functions to calculate * current observational conditions. + * + * On supported platforms (Linux/Unix, Mac, Windows), the time is measured with + * microsecond resolution. + * + * On unsupported platforms, a compiler error will occur due to lack of + * microsecond resolution support. However, if whole second resolution is good + * enough for your application, you can define the preprocessor symbol + * `ASTRONOMY_ENGINE_WHOLE_SECOND` to use the portable function `time(NULL)`. + * Alternatively, if you do not need to use `Astronomy_CurrentTime`, you can + * define the preprocessor symbol `ASTRONOMY_ENGINE_NO_CURRENT_TIME` to + * exclude this function from your code. */ astro_time_t Astronomy_CurrentTime(void) { astro_time_t t; + double sec; /* Seconds since midnight January 1, 1970. */ - double sec = 0; - - -#if defined(__unix__) +#if defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__)) struct timeval tv; - gettimeofday(&tv, NULL); - sec = (double)tv.tv_sec + 1e-6*tv.tv_usec; + sec = (double)tv.tv_sec + tv.tv_usec/1.0e+6; #elif defined(_WIN32) - FILETIME ft; - GetSystemTimeAsFileTime(&ft); - unsigned long long tt = ft.dwHighDateTime; - tt <<=32; - tt |= ft.dwLowDateTime; - tt /= 10; - tt -= 11644473600000000ULL; - - sec = (double)tt / 1e6; + FILETIME ft; + ULARGE_INTEGER large; + /* Get time in 100-nanosecond units from January 1, 1601. */ + GetSystemTimeAsFileTime(&ft); + large.u.LowPart = ft.dwLowDateTime; + large.u.HighPart = ft.dwHighDateTime; + sec = (large.QuadPart - 116444736000000000ULL) / 1.0e+7; +#elif defined(ASTRONOMY_ENGINE_WHOLE_SECOND) + sec = time(NULL); #else - #warning microsecond-resolution time function not found: using 1-second resolution. - sec = time(NULL); + #error Microsecond time resolution is not supported on this platform. Define ASTRONOMY_ENGINE_WHOLE_SECOND to use second resolution instead. #endif - /* Get seconds since midnight January 1, 1970, divide to convert to days, */ - /* then subtract to get days since noon on January 1, 2000. */ - + /* Convert seconds to days, then subtract to get days since noon on January 1, 2000. */ t.ut = (sec / SECONDS_PER_DAY) - 10957.5; t.tt = TerrestrialTime(t.ut); t.psi = t.eps = t.st = NAN; return t; } +#endif /** * @brief Creates an #astro_time_t value from a given calendar date and time. diff --git a/source/c/README.md b/source/c/README.md index b1de2560..1c3acb1b 100644 --- a/source/c/README.md +++ b/source/c/README.md @@ -508,7 +508,11 @@ For geocentric calculations, [`Astronomy_GeoVector`](#Astronomy_GeoVector) also -Uses the computer's system clock to find the current UTC date and time with 1-second granularity. Converts that date and time to an [`astro_time_t`](#astro_time_t) value and returns the result. Callers can pass this value to other Astronomy Engine functions to calculate current observational conditions. +Uses the computer's system clock to find the current UTC date and time. Converts that date and time to an [`astro_time_t`](#astro_time_t) value and returns the result. Callers can pass this value to other Astronomy Engine functions to calculate current observational conditions. + +On supported platforms (Linux/Unix, Mac, Windows), the time is measured with microsecond resolution. + +On unsupported platforms, a compiler error will occur due to lack of microsecond resolution support. However, if whole second resolution is good enough for your application, you can define the preprocessor symbol `ASTRONOMY_ENGINE_WHOLE_SECOND` to use the portable function `time(NULL)`. Alternatively, if you do not need to use `Astronomy_CurrentTime`, you can define the preprocessor symbol `ASTRONOMY_ENGINE_NO_CURRENT_TIME` to exclude this function from your code. --- diff --git a/source/c/astronomy.c b/source/c/astronomy.c index e53d32a6..6676074b 100644 --- a/source/c/astronomy.c +++ b/source/c/astronomy.c @@ -31,7 +31,8 @@ #include #include -#if defined(__unix__) +#if !defined(ASTRONOMY_ENGINE_NO_CURRENT_TIME) +#if defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__)) #include #elif defined(_WIN32) #define WIN32_LEAN_AND_MEAN @@ -39,6 +40,7 @@ #else #include #endif +#endif #include "astronomy.h" @@ -978,50 +980,56 @@ astro_time_t Astronomy_TerrestrialTime(double tt) } } - +#if !defined(ASTRONOMY_ENGINE_NO_CURRENT_TIME) /** * @brief Returns the computer's current date and time in the form of an #astro_time_t. * - * Uses the computer's system clock to find the current UTC date and time with 1-second granularity. + * Uses the computer's system clock to find the current UTC date and time. * Converts that date and time to an #astro_time_t value and returns the result. * Callers can pass this value to other Astronomy Engine functions to calculate * current observational conditions. + * + * On supported platforms (Linux/Unix, Mac, Windows), the time is measured with + * microsecond resolution. + * + * On unsupported platforms, a compiler error will occur due to lack of + * microsecond resolution support. However, if whole second resolution is good + * enough for your application, you can define the preprocessor symbol + * `ASTRONOMY_ENGINE_WHOLE_SECOND` to use the portable function `time(NULL)`. + * Alternatively, if you do not need to use `Astronomy_CurrentTime`, you can + * define the preprocessor symbol `ASTRONOMY_ENGINE_NO_CURRENT_TIME` to + * exclude this function from your code. */ astro_time_t Astronomy_CurrentTime(void) { astro_time_t t; + double sec; /* Seconds since midnight January 1, 1970. */ - double sec = 0; - - -#if defined(__unix__) +#if defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__)) struct timeval tv; - gettimeofday(&tv, NULL); - sec = (double)tv.tv_sec + 1e-6*tv.tv_usec; + sec = (double)tv.tv_sec + tv.tv_usec/1.0e+6; #elif defined(_WIN32) - FILETIME ft; - GetSystemTimeAsFileTime(&ft); - unsigned long long tt = ft.dwHighDateTime; - tt <<=32; - tt |= ft.dwLowDateTime; - tt /= 10; - tt -= 11644473600000000ULL; - - sec = (double)tt / 1e6; + FILETIME ft; + ULARGE_INTEGER large; + /* Get time in 100-nanosecond units from January 1, 1601. */ + GetSystemTimeAsFileTime(&ft); + large.u.LowPart = ft.dwLowDateTime; + large.u.HighPart = ft.dwHighDateTime; + sec = (large.QuadPart - 116444736000000000ULL) / 1.0e+7; +#elif defined(ASTRONOMY_ENGINE_WHOLE_SECOND) + sec = time(NULL); #else - #warning microsecond-resolution time function not found: using 1-second resolution. - sec = time(NULL); + #error Microsecond time resolution is not supported on this platform. Define ASTRONOMY_ENGINE_WHOLE_SECOND to use second resolution instead. #endif - /* Get seconds since midnight January 1, 1970, divide to convert to days, */ - /* then subtract to get days since noon on January 1, 2000. */ - + /* Convert seconds to days, then subtract to get days since noon on January 1, 2000. */ t.ut = (sec / SECONDS_PER_DAY) - 10957.5; t.tt = TerrestrialTime(t.ut); t.psi = t.eps = t.st = NAN; return t; } +#endif /** * @brief Creates an #astro_time_t value from a given calendar date and time. diff --git a/source/c/astronomy.h b/source/c/astronomy.h index f8cbac95..3013632e 100644 --- a/source/c/astronomy.h +++ b/source/c/astronomy.h @@ -1164,7 +1164,9 @@ astro_angle_result_t Astronomy_AngleBetween(astro_vector_t a, astro_vector_t b); const char *Astronomy_BodyName(astro_body_t body); astro_body_t Astronomy_BodyCode(const char *name); astro_observer_t Astronomy_MakeObserver(double latitude, double longitude, double height); +#if !defined(ASTRONOMY_ENGINE_NO_CURRENT_TIME) astro_time_t Astronomy_CurrentTime(void); +#endif astro_time_t Astronomy_MakeTime(int year, int month, int day, int hour, int minute, double second); astro_time_t Astronomy_TimeFromUtc(astro_utc_t utc); astro_utc_t Astronomy_UtcFromTime(astro_time_t time);