C: Astronomy_GetCurrentTime supports microsecond resolution.

This is a follow-up to work provided by:
Eric Wheeler, KJ7LNW <astronomy-git@z.ewheeler.org>

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.
This commit is contained in:
Don Cross
2023-08-28 12:24:20 -04:00
parent 311a0cca59
commit dd0245cbf8
5 changed files with 90 additions and 50 deletions

View File

@@ -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;
}

View File

@@ -31,7 +31,8 @@
#include <stdlib.h>
#include <string.h>
#if defined(__unix__)
#if !defined(ASTRONOMY_ENGINE_NO_CURRENT_TIME)
#if defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))
#include <sys/time.h>
#elif defined(_WIN32)
#define WIN32_LEAN_AND_MEAN
@@ -39,6 +40,7 @@
#else
#include <time.h>
#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.

View File

@@ -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.
---

View File

@@ -31,7 +31,8 @@
#include <stdlib.h>
#include <string.h>
#if defined(__unix__)
#if !defined(ASTRONOMY_ENGINE_NO_CURRENT_TIME)
#if defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))
#include <sys/time.h>
#elif defined(_WIN32)
#define WIN32_LEAN_AND_MEAN
@@ -39,6 +40,7 @@
#else
#include <time.h>
#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.

View File

@@ -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);