summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Burnicki <martin.burnicki@meinberg.de>2021-11-11 12:16:04 +0100
committerMartin Burnicki <martin.burnicki@meinberg.de>2022-07-08 17:21:00 +0200
commit5aad422e486b8ed4b513569d672fd75addb752a7 (patch)
tree5866155eb1b09052bf91b7d084f9d9dcd0dc789a
parente5ee94dce7833995f72247cf59301030aae0f8b7 (diff)
downloadmbgtools-win-5aad422e486b8ed4b513569d672fd75addb752a7.tar.gz
mbgtools-win-5aad422e486b8ed4b513569d672fd75addb752a7.zip
Add some more mbglib files
-rw-r--r--mbglib/common/mbgtime.c660
-rw-r--r--mbglib/common/mbgtimex.c1282
-rw-r--r--mbglib/common/mbgtimex.h929
3 files changed, 2871 insertions, 0 deletions
diff --git a/mbglib/common/mbgtime.c b/mbglib/common/mbgtime.c
new file mode 100644
index 0000000..a84ea26
--- /dev/null
+++ b/mbglib/common/mbgtime.c
@@ -0,0 +1,660 @@
+
+/**************************************************************************
+ *
+ * $Id: mbgtime.c 1.14 2019/11/27 11:02:57Z martin REL_M $
+ *
+ * Copyright (c) Meinberg Funkuhren, Bad Pyrmont, Germany
+ *
+ * Description:
+ * Functions related to date and time.
+ *
+ * -----------------------------------------------------------------------
+ * $Log: mbgtime.c $
+ * Revision 1.14 2019/11/27 11:02:57Z martin
+ * Renamed function n_days() to n_days_since_year_0()
+ * to make clearer what the function returns, and provided
+ * a backward-compatible inline function n_days().
+ * Updated some doxygen comments.
+ * Revision 1.13 2019/09/27 14:44:54 martin
+ * New function find_past_gps_wn_lsf_from_table().
+ * Changed parameters of n_days() to int to avoid compiler warnings.
+ * Revision 1.12 2019/09/19 16:35:14 martin
+ * Preliminary changes.
+ * Revision 1.11 2019/07/23 07:28:49 martin
+ * Exclude old sprint_...() functions from build in non-firmware projects.
+ * Revision 1.10 2019/06/26 10:02:12 martin
+ * New function day_of_week_sun06() which returns 0 for Sunday.
+ * Removed duplicate code in day_of_week().
+ * Updated doxygen comments.
+ * Revision 1.9 2018/11/21 12:02:18 martin
+ * Functions nano_time_to_double() and double_to_nano_time()
+ * were moved to module nanotime.c.
+ * Functions err_tm() and tm_to_wsec() now accept a pointer
+ * to a const structure.
+ * Function clear_time() also clears the TM_GPS::frac field.
+ * Added some casts to avoid compiler warnings.
+ * Removed obsolete inclusion of mystd.h.
+ * Added doxygen comments.
+ * Revision 1.8 2009/03/13 09:27:41 martin
+ * Include mystd.h here rather than in mbgtime.h.
+ * Revision 1.7 2006/08/25 09:32:12Z martin
+ * Added new functions nano_time_to_double() and double_to_nano_time().
+ * Revision 1.6 2005/01/03 10:17:40Z martin
+ * Moved functions days_to_years() and n_days() here.
+ * Parameters of n_days() have changed.
+ * Revision 1.5 2002/09/06 07:49:42Z martin
+ * Turned name of included header file to lower case.
+ * New file header.
+ *
+ **************************************************************************/
+
+#define _MBGTIME
+ #include <mbgtime.h>
+#undef _MBGTIME
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <string.h>
+
+#ifdef _C166_FAULT
+ #define MOD_CORR( x ) labs( x )
+#else
+ #define MOD_CORR( x ) x
+#endif
+
+
+
+/*HDR*/
+/**
+ * @brief Set a timeout object to specified interval
+ *
+ * @param[out] t The timeout object
+ * @param[in] clk The current time, in clock_t ticks
+ * @param[in] interval The interval until expiration, in clock_t ticks
+ */
+void set_timeout( TIMEOUT *t, clock_t clk, clock_t interval )
+{
+ t->start = clk;
+ t->stop = ( clk + interval ) & MASK_CLOCK_T;
+ t->is_set = 1;
+
+} // set_timeout
+
+
+
+/*HDR*/
+/**
+ * @brief Stretch a timeout specified in given timeout object
+ *
+ * @param[in,out] t The timeout object
+ * @param[in] interval The interval until expiration, in clock_t ticks
+ */
+void stretch_timeout( TIMEOUT *t, clock_t interval )
+{
+ t->stop = ( t->stop + interval ) & MASK_CLOCK_T;
+
+} // stretch_timeout
+
+
+
+/*HDR*/
+/**
+ * @brief Check if a timeout object has expired
+ *
+ * @param[in] t The timeout object
+ * @param[in] clk The current time, in clock_t ticks
+ *
+ * @return 1 if timeout expired, else 0
+ */
+bit check_timeout( TIMEOUT *t, clock_t clk )
+{
+ static bit b1;
+ static bit b2;
+ static bit b3;
+
+ if ( !t->is_set )
+ return 0;
+
+ b1 = clk > t->stop;
+ b2 = clk < t->start;
+
+ if ( t->stop >= t->start )
+ b3 = b1 | b2;
+ else
+ b3 = b1 & b2;
+
+ if ( b3 )
+ t->is_set = 0;
+
+ return b3;
+
+} // check_timeout
+
+
+
+/*HDR*/
+/**
+ * @brief Check if a ::TM_GPS structure contains a valid date and time
+ *
+ * @param[in] tm The date/time structure to be checked
+ *
+ * @return 0 if date/time is valid, else a negative number indicating
+ * which field was found invalid
+ */
+int err_tm( const TM_GPS *tm )
+{
+ if ( (ushort) tm->sec > 60 ) // also accept leap second
+ return -2;
+
+ if ( (ushort) tm->min > 59 )
+ return -3;
+
+ if ( (ushort) tm->hour > 59 )
+ return -4;
+
+
+ if ( (ushort) tm->wday > 6 )
+ return -5;
+
+ if ( (ushort) tm->yday > 366 ) // also accept leap year
+ return -6;
+
+
+ if ( tm->mday < 1 || tm->mday > 31 )
+ return -7;
+
+ if ( tm->month < 1 || tm->month > 12 )
+ return -8;
+
+ if ( tm->year < 0 || tm->year > 9999 )
+ return -9;
+
+ return 0; // no error
+
+} // err_tm
+
+
+
+/*HDR*/
+/**
+ * @brief Set the time in a ::TM_GPS structure to 00:00:00.000
+ *
+ * @param[in,out] tm The date/time structure to be set
+ *
+ * @return Pointer to the ::TM_GPS structure that has been passed
+ */
+TM_GPS *clear_time( TM_GPS *tm )
+{
+ tm->frac = tm->sec = tm->min = tm->hour = 0;
+
+ return tm;
+
+} // clear_time
+
+
+
+/*HDR*/
+/**
+ * @brief Convert second-of-week to day-of-week and time-of-day
+ *
+ * @param[in] wsec The second-of-week number to be converted.
+ * Must not be negative.
+ * @param[out] tm Address of a ::TM_GPS structure which takes
+ * the computed results. Updates the fields
+ * ::TM_GPS::hour, ::TM_GPS::min, ::TM_GPS::sec,
+ * and ::TM_GPS::wday in the range 0..6, with
+ * 0 = Sunday.
+ *
+ * @return Pointer to the ::TM_GPS structure that has been passed
+ *
+ * @see ::tm_to_wsec
+ * @see ::day_of_week_sun06
+ */
+TM_GPS *wsec_to_tm( long wsec, TM_GPS *tm )
+{
+ ldiv_t ldt;
+ div_t dt;
+
+
+ ldt = ldiv( wsec, SECS_PER_DAY );
+ tm->wday = (int8_t) ldt.quot;
+
+ ldt.rem = MOD_CORR( ldt.rem );
+ ldt = ldiv( ldt.rem, SECS_PER_HOUR );
+ tm->hour = (int8_t) ldt.quot;
+
+ ldt.rem = MOD_CORR( ldt.rem );
+ dt = div( (int) ldt.rem, SECS_PER_MIN );
+ tm->min = (int8_t) dt.quot;
+ tm->sec = (int8_t) dt.rem;
+
+ return tm;
+
+} // wsec_to_tm
+
+
+
+/*HDR*/
+/**
+ * @brief Compute second-of-week from day-of-week and time-of-day
+ *
+ * @todo Specify input / output ranges
+ *
+ * @param[in] tm Address of a ::TM_GPS structure providing day-of-week and time-of-day
+ *
+ * @return The computed second-of-week number
+ *
+ * @see ::wsec_to_tm
+ */
+long tm_to_wsec( const TM_GPS *tm )
+{
+ long l;
+
+ l = (long) tm->wday * SECS_PER_DAY
+ + (long) tm->hour * SECS_PER_HOUR
+ + (long) tm->min * SECS_PER_MIN
+ + (long) tm->sec;
+
+ return l;
+
+} // tm_to_wsec
+
+
+
+/*HDR*/
+/**
+ * @brief Check if a specific year is a leap year
+ *
+ * @param[in] y The full year number
+ *
+ * @return != 0 if the year is a leap year, else 0
+ */
+int is_leap_year( int y )
+{
+ return ( ( ( ( y % 4 ) == 0 ) && ( ( y % 100 ) != 0 ) ) || ( ( y % 400 ) == 0 ) );
+
+} // is_leap_year
+
+
+
+/*HDR*/
+/**
+ * @brief Compute the day-of-year from a given date
+ *
+ * @param[in] day The day-of-month
+ * @param[in] month The month
+ * @param[in] year The full year number
+ *
+ * @return The computed day-of-year
+ */
+int day_of_year( int day, int month, int year )
+{
+ register const char *t;
+
+ t = days_of_month[ is_leap_year( year ) ];
+
+ while ( --month )
+ day += *t++;
+
+ return day;
+
+} // day_of_year
+
+
+
+/*HDR*/
+/**
+ * @brief Compute a date from a given year and day-of-year
+ *
+ * @param[in] year The full year number
+ * @param[in] day_num Number of days from the beginning of that year, may be negative
+ * @param[out] tm Address of a ::TM_GPS structure which takes the computed results
+ */
+void date_of_year( int year, int day_num, TM_GPS *tm )
+{
+ register int i;
+ register const char *t;
+
+ while ( day_num < 1 )
+ day_num += day_of_year( 31, 12, --year );
+
+ t = days_of_month[is_leap_year( year )];
+
+
+ for ( i = 1; i < 13 && day_num > *t ; i++ )
+ day_num -= *t++;
+
+ if ( i > 12 )
+ date_of_year( ++year, day_num, tm );
+ else
+ {
+ tm->mday = (int8_t) day_num;
+ tm->month = (int8_t) i;
+ tm->year = (int16_t) year;
+ }
+
+} // date_of_year
+
+
+
+/*HDR*/
+/**
+ * @brief Compute day-of-week for a given date.
+ *
+ * ATTENTION: The computed day-of-week is in the range 0..6,
+ * with 0 = Monday (!).
+ *
+ * In most cases the function ::day_of_week_sun06 is
+ * more suitable for applications.
+ *
+ * @param[in] day The day-of-month, 0..31
+ * @param[in] month The month, 1..12
+ * @param[in] year The full year number
+ *
+ * @return The computed day-of-week, 0..6, 0 = Monday (!)
+ *
+ * @see ::day_of_week_sun06
+ * @see ::n_days_since_year_0
+ */
+int day_of_week( int day, int month, int year )
+{
+ long l = n_days_since_year_0( day, month, year );
+
+ return (int) ( l % DAYS_PER_WEEK );
+
+} // day_of_week
+
+
+
+/*HDR*/
+/**
+ * @brief Compute day-of-week for a given date.
+ *
+ * The computed day-of-week is in the range 0..6,
+ * with 0 = Sunday, as expected by most applications.
+ *
+ * @param[in] day The day-of-month, 0..31
+ * @param[in] month The month, 1..12
+ * @param[in] year The full year number
+ *
+ * @return The computed day-of-week, 0..6, with 0 = Sunday.
+ *
+ * @see ::n_days_since_year_0
+ * @see ::wsec_to_tm
+ */
+int day_of_week_sun06( int day, int month, int year )
+{
+ long l = n_days_since_year_0( day, month, year ) + 1;
+
+ return (int) ( l % DAYS_PER_WEEK );
+
+} // day_of_week_sun06
+
+
+
+/*HDR*/
+/**
+ * @brief Update a year number by a number of days, accounting for leap years
+ *
+ * @param[in] day_num The number of days to evaluate
+ * @param[in] year The year number to start with
+ *
+ * @return The computed year number
+ */
+int days_to_years( long *day_num, int year )
+{
+ for (;;)
+ {
+ int yearday = is_leap_year( year ) ? 366 : 365;
+
+ if ( *day_num < yearday )
+ return year;
+
+ *day_num -= yearday;
+
+ year++;
+ }
+
+} // days_to_years
+
+
+
+/*HDR*/
+/**
+ * @brief Compute number of days after Jan 1, 0000 for a given date
+ *
+ * @param[in] mday The day-of-month, [1..31]
+ * @param[in] month The month, [1..12].
+ * @param[in] year The full year number, starting from year 0.
+ *
+ * @return The computed number of days
+ *
+ * @see ::day_of_week
+ */
+long n_days_since_year_0( int mday, int month, int year )
+{
+ long l;
+ long lyear = year - 1;
+
+ l = lyear * 365L // days until beginning of year, 365 days per year
+ + lyear / 4L // plus number of leap days
+ - lyear / 100L // except years modulo 100 are no leap years
+ + lyear / 400L // but years modulo 400 are leap years anyway
+ + (long) day_of_year( mday, month, year ) - 1L; // plus number of days until specified date
+
+ return l;
+
+} // n_days_since_year_0
+
+
+
+/*HDR*/
+/**
+ * @brief Search a table of known past leap second dates for a specific week and day number.
+ *
+ * Optionally we return the latest week number we
+ * have found in the table, so an application can
+ * start there searching there for future potential
+ * leap second dates.
+ *
+ * @ingroup group_true_gps_wn_fncs
+ */
+int find_past_gps_wn_lsf_from_table( GPS_WNUM *p_wn, GPS_DNUM dn_t, int srch_all, GPS_WNUM *p_wn_last )
+{
+ static const LS_TABLE_ENTRY_GPS tbl[] = KNOWN_LEAP_SECOND_INFO_GPS;
+
+ GPS_WNUM wn_srch;
+ GPS_DNUM dn_srch;
+ const LS_TABLE_ENTRY_GPS *p;
+ int n_found = 0;
+ GPS_WNUM wn_last = 0;
+
+ #if DEBUG_WNLSF
+ printf( "WNlsf %i (0x%04X), DNt %i (tbl):", *p_wn, *p_wn, dn_t );
+ #endif
+
+ // While the table entries are normalized (like 1930|0),
+ // the wn|dn pair to be looked up may not be normalized
+ // (like 1929|7), so we might miss a matching table entry
+ // unless we also normalize the search pattern.
+ wn_srch = *p_wn;
+ dn_srch = dn_t;
+ normalize_gps_wn_dn( &wn_srch, &dn_srch );
+
+ // We only use the 8 bit truncated week number
+ // to search the table.
+ wn_srch &= 0xFF;
+
+ for ( p = tbl; p->wn || p->dn || p->gps_tai_offs; p++ )
+ {
+ wn_last = p->wn;
+
+ #if DEBUG_WNLSF
+ printf( "\n wn %i (0x%04X), dn %i", p->wn, p->wn, p->dn );
+ #endif
+
+ if ( p->dn != dn_srch )
+ continue;
+
+ if ( ( p->wn & 0xFF ) != wn_srch )
+ continue;
+
+ // Found a matching wn|dn pair.
+
+ #if DEBUG_WNLSF
+ printf( " MATCH" );
+ #endif
+
+ n_found++;
+
+ // The day number is explicit, so we only have to
+ // update the week number provided by the caller.
+
+ // If the caller has searched for day number 7,
+ // we have to adjust the full week number
+ // from the table accordingly.
+ *p_wn = ( dn_t == 7 ) ? ( p->wn - 1 ) : p->wn;
+
+ // If the first search result is sufficient
+ // for the application, we can stop searching.
+ if ( !srch_all )
+ break;
+ }
+
+ #if DEBUG_WNLSF
+ printf( "\n" );
+ printf( " found %i, last wn %i (0x%04X)\n", n_found, wn_last, wn_last );
+ #endif
+
+ // Optionally we return the latest week number we
+ // have found in the table, so an application can
+ // start there searching for future potential
+ // leap second dates.
+ if ( p_wn_last )
+ *p_wn_last = wn_last;
+
+ return n_found;
+
+} // find_past_gps_wn_lsf_from_table
+
+
+
+#if _IS_MBG_FIRMWARE
+
+// Old sprint_...() functions used by existing firmware.
+// Should be replaced by snprint_...() variants implemented
+// using snprint_safe(), etc.
+// Similar for
+
+/*HDR*/
+/**
+ * @brief Print time with hours, minutes, seconds to a string
+ *
+ * @param[out] s Address of a string buffer to be filled
+ * @param[in] tm Address of a ::TM_GPS structure providing date and time
+ */
+int sprint_time( char *s, const TM_GPS *tm )
+{
+ return sprintf( s, time_fmt, tm->hour, tm->min, tm->sec );
+
+} // sprint_time
+
+
+/*HDR*/
+/**
+ * @brief Print time with hours, minutes to a string
+ *
+ * @param[out] s Address of a string buffer to be filled
+ * @param[in] tm Address of a ::TM_GPS structure providing date and time
+ */
+int sprint_short_time( char *s, const TM_GPS *tm )
+{
+ return sprintf( s, short_time_fmt, tm->hour, tm->min );
+
+} // sprint_short_time
+
+
+/*HDR*/
+/**
+ * @brief Print date to a string
+ *
+ * @param[out] s Address of a string buffer to be filled
+ * @param[in] tm Address of a ::TM_GPS structure providing date and time
+ */
+int sprint_date( char *s, const TM_GPS *tm )
+{
+ return sprintf( s, date_fmt, tm->mday, tm->month, tm->year );
+
+} // sprint_date
+
+
+/*HDR*/
+/**
+ * @brief Print day-of-week and date to a string
+ *
+ * @param[out] s Address of a string buffer to be filled
+ * @param[in] tm Address of a ::TM_GPS structure providing date and time
+ */
+int sprint_day_date( char *s, const TM_GPS *tm )
+{
+ return sprintf( s, day_date_fmt, day_name_eng[(uchar) tm->wday], tm->mday, tm->month, tm->year );
+
+} // sprint_day_date
+
+
+/*HDR*/
+/**
+ * @brief Print day-of-week, date and time to a string
+ *
+ * @param[out] s Address of a string buffer to be filled
+ * @param[in] tm Address of a ::TM_GPS structure providing date and time
+ */
+int sprint_tm( char *s, const TM_GPS *tm )
+{
+ return sprintf( s, "%s, %2i.%02i.%04i %2i:%02i:%02i",
+ day_name_eng[(uchar)tm->wday],
+ tm->mday, tm->month, tm->year,
+ tm->hour, tm->min, tm->sec );
+
+} // sprint_tm
+
+#endif // _IS_MBG_FIRMWARE
+
+
+
+/*HDR*/
+/**
+ * @brief Extract a time from a string
+ *
+ * @param[in] s A time string in format hh:mm:ss
+ * @param[out] tm Address of a ::TM_GPS structure which takes the extracted time
+ */
+void sscan_time( const char *s, TM_GPS *tm )
+{
+ tm->hour = (int8_t) atoi( &s[0] );
+ tm->min = (int8_t) atoi( &s[3] );
+ tm->sec = (int8_t) atoi( &s[6] );
+
+} // sscan_time
+
+
+
+/*HDR*/
+/**
+ * @brief Extract a date from a string
+ *
+ * @param[in] s A date string in format dd.mm. or dd.mm.yyyy
+ * @param[out] tm Address of a ::TM_GPS structure which takes the extracted date
+ */
+void sscan_date( char *s, TM_GPS *tm )
+{
+ tm->mday = (int8_t) atoi( &s[0] );
+ tm->month = (int8_t) atoi( &s[3] );
+
+ if ( _isdigit( s[6] ) )
+ tm->year = (int16_t) atoi( &s[6] );
+
+} // sscan_date
+
+
diff --git a/mbglib/common/mbgtimex.c b/mbglib/common/mbgtimex.c
new file mode 100644
index 0000000..ddae3d6
--- /dev/null
+++ b/mbglib/common/mbgtimex.c
@@ -0,0 +1,1282 @@
+
+/**************************************************************************
+ *
+ * $Id: mbgtimex.c 1.5 2020/02/28 13:13:40Z martin REL_M $
+ *
+ * Copyright (c) Meinberg Funkuhren, Bad Pyrmont, Germany
+ *
+ * Description:
+ * Meinberg extended time conversion functions.
+ *
+ * -----------------------------------------------------------------------
+ * $Log: mbgtimex.c $
+ * Revision 1.5 2020/02/28 13:13:40Z martin
+ * mbg_set_ls_info_from_gps_utc() can now optionally return
+ * the determined true GPS leap second week number.
+ * Revision 1.4 2020/02/27 16:09:41 martin
+ * FIXED A BUG where the DST switching times were not computed
+ * correctly because day_of_week_sun06() was called improperly.
+ * Account for function n_days() renamed to n_days_since_year_0().
+ * Fixed some routines for timezones without DST.
+ * Fixed a bug in mbg_tz_info_to_tai where the standard time offset
+ * was not removed when converting timestamps to TAI.
+ * Added some conditional debug code.
+ * Updated some doxygen comments.
+ * Revision 1.3 2019/09/27 15:07:04 martin
+ * GPS_WNUM, GPS_DNUM, and GPS_WSEC have been changed to signed types now,
+ * so they can now be used consistently.
+ * Functions now used MBG_TIME64_T now by default, but for compatibility
+ * there are inline wrappers functions using time_t.
+ * Some more leap second support stuff.
+ * Leap second dates now always refer to the time immediately *after*
+ * a leap second, i.e. when a new TAI offset comes into effect.
+ * This is more consistent with external leap second tables, and is
+ * appropriate for inserted as well as for deleted leap seconds.
+ * Usage of 64 bit timestamp types is now reflected in the names of
+ * functions and variables.
+ * Updated doxygen comments.
+ * Revision 1.2 2019/08/26 13:24:22 martin
+ * Fixed a compiler warning that was sometimes displayed for no real reason.
+ * Revision 1.1 2019/08/08 13:12:12 martin
+ * Initial revision.
+ *
+ **************************************************************************/
+
+#define _MBGTIMEX
+ #include <mbgtimex.h>
+#undef _MBGTIMEX
+
+#include <timeutil.h>
+#include <mbgmktm.h>
+#include <mbgerror.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+
+#if !defined( DEBUG_WNLSF )
+ #if defined( DEBUG ) && DEBUG
+ #define DEBUG_WNLSF 0 // can be 0 or 1
+ #else
+ #define DEBUG_WNLSF 0 // should always be 0
+ #endif
+#endif
+
+#if !defined( DEBUG_TZI )
+ #if defined( DEBUG ) && DEBUG
+ #define DEBUG_TZI 0 // can be 0 or 1
+ #else
+ #define DEBUG_TZI 0 // should always be 0
+ #endif
+#endif
+
+#if DEBUG_WNLSF || DEBUG_TZI
+ #include <mbg_data.h> // some printing functions
+#endif
+
+
+
+/*HDR*/
+/**
+ * @brief Convert date and time from <em>struct tm</em> to GPS week number and second-of-week.
+ *
+ * Only the data format is converted, the offset between GPS time scale
+ * and %UTC scale is not taken into account.
+ *
+ * The calculated second-of-week is always greater than 0, but the week number
+ * can be less than 0, if the original time is before the GPS time epoch.
+ *
+ * @param[out] p_wn Address of a ::GPS_WNUM variable to take the computed week number.
+ *
+ * @param[out] p_wsec Address of a ::GPS_WSEC variable to take the computed second-of-week.
+ *
+ * @param[in] p_tm Pointer to a <em>struct tm</em> providing the date and time to be converted.
+ *
+ * @return ::MBG_SUCCESS on success, else one of the @ref MBG_ERROR_CODES
+ *
+ * @ingroup mbgtimex_gps_posix_time_cnv_fncs
+ */
+int mbg_struct_tm_to_gps_wn_wsec( GPS_WNUM *p_wn, GPS_WSEC *p_wsec, const struct tm *p_tm )
+{
+ // Compute the number of days since the GPS epoch.
+ long l = n_days_since_year_0( p_tm->tm_mday, p_tm->tm_mon + 1, p_tm->tm_year + 1900 ) - GPS_INITIAL_DAY;
+
+ // Split into number of full weeks and remaining days.
+ ldiv_t ldt = ldiv( l, DAYS_PER_WEEK );
+
+ // Compute GPS week number.
+ *p_wn = (GPS_WNUM) ldt.quot;
+
+ // Compute second-of-week.
+ *p_wsec = ldt.rem * SECS_PER_DAY + p_tm->tm_hour * SECS_PER_HOUR
+ + p_tm->tm_min * SECS_PER_MIN + p_tm->tm_sec;
+
+ normalize_wn_wsec( p_wn, p_wsec );
+
+ return MBG_SUCCESS;
+
+} // mbg_struct_tm_to_gps_wn_wsec
+
+
+
+/*HDR*/
+/**
+ * @brief Convert GPS week number plus second-of-week to ::MBG_TIME64_T.
+ *
+ * Only the data format is converted, the offset between GPS time scale
+ * and %UTC scale is not taken into account.
+ *
+ * @param[out] p_t64 Address of an ::MBG_TIME64_T to take the computed timestamp.
+ *
+ * @param[in] wn A GPS week number as ::GPS_WNUM.
+ *
+ * @param[in] wsec Seconds of the week as ::GPS_WSEC.
+ *
+ * @return ::MBG_SUCCESS on success, else one of the @ref MBG_ERROR_CODES
+ *
+ * @ingroup mbgtimex_gps_posix_time_cnv_fncs
+ */
+int mbg_gps_wn_wsec_to_time64_t( MBG_TIME64_T *p_t64, GPS_WNUM wn, GPS_WSEC wsec )
+{
+ *p_t64 = (MBG_TIME64_T) wn * SECS_PER_WEEK
+ + (MBG_TIME64_T) wsec
+ + GPS_SEC_BIAS;
+
+ return MBG_SUCCESS;
+
+} // mbg_gps_wn_wsec_to_time64_t
+
+
+
+/*HDR*/
+/**
+ * @brief Convert a GPS week number / day-of-week pair to ::MBG_TIME64_T.
+ *
+ * Only the data format is converted, the offset between GPS time scale
+ * and %UTC scale is not taken into account.
+ *
+ * @note If this function is called to compute the leap second date
+ * from the GPS ::UTC parameters, the computed timestamp is associated
+ * with <b>the end of the leap second transition</b>, e.g.
+ * <em>2017-01-01 00:00:00</em> rather than <em>2016-12-31 23:59:59</em>.
+ * Also, first calling the function ::mbg_find_true_gps_wn_lsf may be
+ * required to resolve an ambiguity of the week number.
+ * See @ref group_true_gps_wn_fncs.
+ *
+ * @param[out] p_t64 Address of an ::MBG_TIME64_T to take the computed timestamp.
+ *
+ * @param[in] wn A GPS week number as ::GPS_WNUM, e.g. ::UTC::WNlsf.
+ *
+ * @param[in] dn A day number as ::GPS_DNUM, e.g. ::UTC::DNt.
+ *
+ * @return ::MBG_SUCCESS on success, else one of the @ref MBG_ERROR_CODES
+ *
+ * @ingroup mbgtimex_gps_posix_time_cnv_fncs
+ * @see ::mbg_find_true_gps_wn_lsf
+ * @see @ref group_true_gps_wn_fncs
+ */
+int mbg_gps_wn_dn_to_time64_t( MBG_TIME64_T *p_t64, GPS_WNUM wn, GPS_DNUM dn )
+{
+ return mbg_gps_wn_wsec_to_time64_t( p_t64, wn, (GPS_WSEC) dn * SECS_PER_DAY );
+
+} // mbg_gps_wn_dn_to_time64_t
+
+
+
+/*HDR*/
+/**
+ * @brief Convert an ::MBG_TIME64_T to GPS week number and second-of-week.
+ *
+ * Only the data format is converted, the offset between GPS time scale
+ * and %UTC scale is not taken into account.
+ *
+ * The calculated week number can be negative if @a *p_t64 is before
+ * the GPS epoch. The calculated values are normalized so that the
+ * second-of-week is always non-negative, i.e. contains the seconds
+ * after the beginning of the week, even if the week number is negative.
+ *
+ * @param[out] p_wn Address of a ::GPS_WNUM variable to take the computed week number.
+ *
+ * @param[out] p_wsec Address of a ::GPS_WSEC variable to take the computed second-of-week.
+ *
+ * @param[in] p_t64 Pointer to an ::MBG_TIME64_T timestamp to be converted.
+ *
+ * @return ::MBG_SUCCESS on success, else one of the @ref MBG_ERROR_CODES
+ *
+ * @ingroup mbgtimex_gps_posix_time_cnv_fncs
+ */
+int mbg_time64_t_to_gps_wn_wsec( GPS_WNUM *p_wn, GPS_WSEC *p_wsec, const MBG_TIME64_T *p_t64 )
+{
+ MBG_TIME64_T t64 = *p_t64 - GPS_SEC_BIAS; // Account for the GPS epoch.
+
+ *p_wn = (GPS_WNUM) ( t64 / SECS_PER_WEEK );
+ *p_wsec = (GPS_WSEC) ( t64 % SECS_PER_WEEK );
+
+ normalize_wn_wsec( p_wn, p_wsec );
+
+ return MBG_SUCCESS;
+
+} // mbg_time64_t_to_gps_wn_wsec
+
+
+
+/*HDR*/
+/**
+ * @brief Convert an ::MBG_TIME64_T to ::T_GPS.
+ *
+ * Only the data format is converted, the offset between GPS time scale
+ * and %UTC scale is not taken into account.
+ *
+ * The week number of the calculated ::T_GPS can be negative if @a *p_t64
+ * is before the GPS epoch. The calculated values are normalized so that
+ * the second-of-week is always non-negative, i.e. contains the seconds
+ * after the beginning of the week, even if the week number is negative.
+ *
+ * @param[out] p_t_gps Address of a ::T_GPS variable to take the computed timestamp.
+ *
+ * @param[in] p_t64 Pointer to an ::MBG_TIME64_T timestamp.
+ *
+ * @return ::MBG_SUCCESS on success, else one of the @ref MBG_ERROR_CODES
+ *
+ * @ingroup mbgtimex_gps_posix_time_cnv_fncs
+ * @see ::mbg_t_gps_to_time_t
+ */
+int mbg_time64_t_to_t_gps( T_GPS *p_t_gps, MBG_TIME64_T *p_t64 )
+{
+ memset( p_t_gps, 0, sizeof( *p_t_gps ) );
+
+ return mbg_time64_t_to_gps_wn_wsec( &p_t_gps->wn, &p_t_gps->sec, p_t64 );
+
+} // mbg_time64_t_to_t_gps
+
+
+
+/*HDR*/
+/**
+ * @brief Convert ::T_GPS to ::MBG_TIME64_T.
+ *
+ * Only the data format is converted, the offset between GPS time scale
+ * and %UTC scale is not taken into account.
+ *
+ * @param[out] p_t64 Address of an ::MBG_TIME64_T type to take the computed timestamp.
+ *
+ * @param[in] p_t Pointer to the timestamp to be converted, in ::T_GPS format.
+ *
+ * @return ::MBG_SUCCESS on success, else one of the @ref MBG_ERROR_CODES
+ *
+ * @ingroup mbgtimex_gps_posix_time_cnv_fncs
+ * @see ::mbg_time_t_to_t_gps
+ */
+int mbg_t_gps_to_time64_t( MBG_TIME64_T *p_t64, const T_GPS *p_t )
+{
+ return mbg_gps_wn_wsec_to_time64_t( p_t64, p_t->wn, p_t->sec );
+
+} // mbg_t_gps_to_time64_t
+
+
+
+/*HDR*/
+/**
+ * @brief Check is a <em>struct tm</em> and a ::TM_GPS refer to the same date and time.
+ *
+ * This has to take into account that some fields in a <em>struct tm</em>
+ * have different meanings than the associated fiellds in a ::TM_GPS.
+ *
+ * @param[in] p_tm Pointer to the <em>struct tm</em> to be compared.
+ * @param[in] p_tm_gps Pointer to the ::TM_GPS to be compared.
+ *
+ * @return @a true, if both variables refer to the same date and time, else @a false.
+ */
+bool is_same_tm_tm_gps( const struct tm *p_tm, const TM_GPS *p_tm_gps )
+{
+ bool b;
+
+ // Keep in mind that the fields of 'struct tm' and TM_GPS
+ // have to be interpreted differently.
+ b = ( p_tm->tm_year + 1900 == p_tm_gps->year ) &&
+ ( p_tm->tm_mon + 1 == p_tm_gps->month ) &&
+ ( p_tm->tm_mday == p_tm_gps->mday ) &&
+ ( p_tm->tm_hour == p_tm_gps->hour ) &&
+ ( p_tm->tm_min == p_tm_gps->min ) &&
+ ( p_tm->tm_sec == p_tm_gps->sec ) &&
+ ( p_tm->tm_wday == p_tm_gps->wday );
+
+ // Also the day-of-year fields have different valid ranges.
+ // struct tm::tm_yday counts "days since January 1",
+ // so 0 indicates the first day of a year.
+ // However, for TM_GPS::yday the first day of a year is day 1,
+ // so if it is 0, we just assume it has not been set.
+ if ( p_tm_gps->yday != 0 ) // day-of-year has been set
+ if ( p_tm_gps->yday != p_tm->tm_yday + 1 ) // account for different ranges
+ b = false;
+
+ return b;
+
+} // is_same_tm_tm_gps
+
+
+
+/*HDR*/
+/**
+ * @brief Determine the true week number for an ambiguous ::UTC::WNlsf number.
+ *
+ * See @ref group_true_gps_wn_fncs for details how this is supposed to work.
+ *
+ * This variant only checks calculated potential dates.
+ * See ::mbg_find_true_gps_wn_lsf for a variant which searches a table
+ * of known leaps second dates first, and thus executes faster.
+ *
+ * @param[in,out] p_wn The GPS week number of the leap second, see ::UTC::WNlsf.
+ * Updated if a solution could be found. If @a srch_all
+ * is @a true, the last update is done for the last match
+ * found.
+ *
+ * @param[in] dn_t The day-of-week number at the end of which the
+ * leap second is to be inserted. See ::UTC::DNt,
+ * which is usually in the range 1..7.
+ *
+ * @param[in] srch_all If this flag is @a true then always the full range
+ * is searched. The search is even continued after
+ * a first match has already been found. This takes
+ * longer to execute, but allows detection
+ * of multiple matches, e.g. for testing.
+ *
+ * @param[in] first_wn First GPS week number to start the search.
+ * Can be 0 to search the full range, or the latest
+ * known week number from a leap second table to check
+ * only dates that are after the last week number
+ * present in the table.
+ *
+ * @return The number of matches found. Should be 1 for an unambiguous result,
+ * even if @a srch_all was @a true
+ *
+ * @ingroup group_true_gps_wn_fncs
+ * @see ::mbg_find_true_gps_wn_lsf
+ * @see @ref group_true_gps_wn_fncs
+ * @see ::mbg_gps_wn_dn_to_time_t
+ * @see ::UTC
+ */
+int mbg_find_true_gps_wn_lsf_ex( GPS_WNUM *p_wn, GPS_DNUM dn_t, bool srch_all, GPS_WNUM first_wn )
+{
+ // Total number of matches. More than one means
+ // the result is ambiguous.
+ int n_found = 0;
+
+ GPS_WNUM wn_trunc; // The truncated week number.
+
+ int i;
+
+ #if DEBUG_WNLSF
+ printf( "WNlsf %i (0x%04X), DNt %i (APIex):\n", *p_wn, *p_wn, dn_t );
+ #endif
+
+ wn_trunc = *p_wn & 0xFF;
+
+ for ( i = 0; i < N_GPS_WN_EPOCH; i++ )
+ {
+ GPS_WNUM wn_tmp;
+ MBG_TIME64_T t64_ls;
+ struct tm tm = { 0 };
+ int rc;
+
+ // Use only the 8 LSBs as transmitted by the
+ // satellites, but add an epoch number.
+ wn_tmp = wn_trunc | ( i << 8 );
+
+ if ( wn_tmp < first_wn )
+ {
+ #if DEBUG_WNLSF
+ printf( " 0x%02X: wn 0x%04X < 0x%04X (%i < %i), skipping\n",
+ wn_tmp >> 8, wn_tmp, first_wn, wn_tmp, first_wn );
+ #endif
+ continue;
+ }
+
+ rc = mbg_gps_wn_dn_to_time64_t( &t64_ls, wn_tmp, dn_t );
+
+ #if DEBUG_WNLSF
+ printf( "%02i: wn 0x%04X (%u) --> ", i, wn_tmp, wn_tmp );
+ #endif
+
+ if (mbg_rc_is_success( rc ) )
+ rc = mbg_gmtime64( &tm, &t64_ls );
+ else
+ {
+ #if DEBUG_WNLSF
+ printf( " ** to t64 ERROR: %s\n", mbg_strerror( rc ) );
+ #endif
+ }
+
+ if ( mbg_rc_is_error( rc ) )
+ {
+ #if DEBUG_WNLSF
+ printf( " ** ERROR: %s\n", mbg_strerror( rc ) );
+ #endif
+
+ // If yet a single match has been found, that's OK.
+ if ( ( n_found == 1 ) && !srch_all )
+ break;
+
+ // Otherwise continue.
+ continue;
+ }
+
+ #if DEBUG_WNLSF
+ print_tm_date( &tm );
+ #endif
+
+ // Take care: tm_mon counts from 0!
+ if ( is_valid_leap_second_date_tm( &tm ) )
+ {
+ #if DEBUG_WNLSF
+ printf( " MATCH\n" );
+ #endif
+
+ *p_wn = wn_tmp;
+
+ n_found++;
+
+ if ( !srch_all )
+ break; // Stop searching for additional matches.
+ }
+ #if DEBUG_WNLSF
+ else
+ printf( "\n" );
+ #endif
+ }
+
+ #if DEBUG_WNLSF
+ if ( n_found == 1 )
+ printf( "Success: found single result, wn %i (0x%04X).\n", *p_wn, *p_wn );
+ else
+ printf( "Error: %i match(es) found.\n", n_found );
+
+ printf( "\n" );
+ #endif
+
+ return n_found;
+
+} // mbg_find_true_gps_wn_lsf_ex
+
+
+
+/*HDR*/
+/**
+ * @brief Determine the true week number for an ambiguous ::UTC::WNlsf number.
+ *
+ * See @ref group_true_gps_wn_fncs for details how this is supposed to work.
+ *
+ * To reduce the execution time, this variant uses a table to search past,
+ * known leap second dates, and calls ::mbg_find_true_gps_wn_lsf_ex only
+ * if no result has been found in the table, or the @a srch_all flag is @a true.
+ *
+ * @param[in,out] p_wn The GPS week number of the leap second, see ::UTC::WNlsf.
+ * Updated if a solution could be found. If @a srch_all
+ * is @a true, the last update is done for the last match
+ * found.
+ *
+ * @param[in] dn_t The day-of-week number at the end of which the
+ * leap second is to be inserted. See ::UTC::DNt,
+ * which is usually in the range 1..7.
+ *
+ * @param[in] srch_all If this flag is @a true then always the full range
+ * is searched. The search is even continued after
+ * a first match has already been found. This takes
+ * longer to execute, but allows detection
+ * of multiple matches, e.g. for testing.
+ *
+ * @return The number of matches found. Should be 1 for an unambiguous result,
+ * even if @a srch_all was @a true
+ *
+ * @ingroup group_true_gps_wn_fncs
+ * @see ::mbg_find_true_gps_wn_lsf
+ * @see @ref group_true_gps_wn_fncs
+ * @see ::mbg_gps_wn_dn_to_time_t
+ * @see ::UTC
+ */
+int mbg_find_true_gps_wn_lsf( GPS_WNUM *p_wn, GPS_DNUM dn_t, int srch_all )
+{
+ // Total number of matches. More than one means
+ // the result is ambiguous.
+ int n_found;
+
+ GPS_WNUM wn_last;
+
+ #if DEBUG_WNLSF
+ printf( "WNlsf %i (0x%04X), DNt %i (FW):\n", *p_wn, *p_wn, dn_t );
+ #endif
+
+ // First search the table of known leap second dates.
+ n_found = find_past_gps_wn_lsf_from_table( p_wn, dn_t, srch_all, &wn_last );
+
+ // Compute later dates only if no result has been found, or
+ // the srch_all flag has been set.
+ if ( ( n_found < 1 ) || srch_all )
+ n_found += mbg_find_true_gps_wn_lsf_ex( p_wn, dn_t, srch_all, wn_last );
+
+ #if DEBUG_WNLSF
+ if ( n_found == 0 )
+ printf( "Error: no match found.\n" );
+ else
+ if ( n_found == 1 )
+ printf( "Success: found single result, wn %i (0x%04X).\n", *p_wn, *p_wn );
+ else
+ printf( "Warning: found %i matches.\n", n_found );
+
+ printf( "\n" );
+ #endif
+
+ return n_found;
+
+} // mbg_find_true_gps_wn_lsf
+
+
+
+static /*HDR*/
+/**
+ * @brief Calculate an ::MBG_TIME64_T timestamp for beginning or end of DST.
+ *
+ * @param[out] p_t64 Address of an ::MBG_TIME64_T variable to take the computed
+ * timestamp associated with the switching time.
+ * The rules specified in ::TZDL are specified using local time,
+ * so the computed timestamp is local time, too.
+ *
+ * @param[in] p_tm_gps Pointer to a switching time rule, e.g. ::TZDL::tm_on
+ * or ::TZDL::tm_off. Can either contain an explicit
+ * full date including the calendar year, or a rule for
+ * automatic calculation for any year, in which case a
+ * day-of-week must have been specified at which the
+ * switching occurs. See ::TZDL.
+ *
+ * @param[in] year The calendar year for which to calculate the switching
+ * time in case parameter @a p_tm_gps provides a an automatic
+ * rule that is valid for any year.
+ *
+ * @return ::MBG_SUCCESS on success, else one of the @ref MBG_ERROR_CODES
+ *
+ * @ingroup mbgtimex_tzdl_fncs
+ * @see ::mbg_set_tz_info_for_year
+ */
+int set_tzi_time( MBG_TIME64_T *p_t64, const TM_GPS *p_tm_gps, int year )
+{
+ int rc;
+
+ // If the DST rule is for a specific year only, we use that year number
+ // instead of the year passed as parameter.
+ if ( p_tm_gps->year & DL_AUTO_FLAG )
+ {
+ MBG_TIME64_T t64;
+ int wday;
+ int days;
+
+ #if DEBUG_TZI
+ MBG_TIME64_T t64_raw;
+ int days_raw;
+ #endif
+
+ // Compute a preliminary switching time, which may
+ // already match the correct date, or not.
+ rc = mbg_mktime64( &t64, year - 1900, p_tm_gps->month - 1, p_tm_gps->mday,
+ p_tm_gps->hour, p_tm_gps->min, p_tm_gps->sec );
+ if ( mbg_rc_is_error( rc ) )
+ goto out;
+
+ // For an automatic calculation we have to consider the specified
+ // day-of-week. If the DOW for the date computed above doesn't match
+ // the DOW specified for switching, we have to apply a correction
+ // for a number of days.
+ // For example, if the rule says, "Sunday after March, 25", the
+ // computed date is already correct if March, 25 of the specified
+ // year is a Sunday. However, if the computed date is a Friday then
+ // we have to add 2 days to get the switching time for Sunday.
+ wday = day_of_week_sun06( p_tm_gps->mday, p_tm_gps->month, year );
+
+ days = p_tm_gps->wday - wday;
+
+ #if DEBUG_TZI
+ days_raw = days;
+ t64_raw = t64;
+ #endif
+
+ if ( days < 0 )
+ days += DAYS_PER_WEEK;
+
+ t64 += days * SECS_PER_DAY;
+
+ #if DEBUG_TZI
+ print_wday( p_tm_gps->wday, 0 );
+ printf( " after %02i.%02i.", p_tm_gps->mday, p_tm_gps->month );
+ print_time64_t_and_tm( &t64_raw, ": ", 0 );
+ printf( ": t: %li ", (long) t64 );
+ printf( " -> " );
+ printf( "% 2i % 2i % 2i: ", wday, days_raw, days );
+ print_time64_t_and_tm( &t64, " --> ", 1 );
+ #endif // DEBUG_TZI
+
+ *p_t64 = t64;
+
+ // rc is still MBG_SUCCESS at this point.
+
+ goto out;
+ }
+
+ // The specified switching time rule contains an explicit date
+ // including a year number, so we have to calculate the switching time
+ // using the year number from the rule, and ignore the extra 'year'
+ // parameter and the DOW from the rule, which should be a wildcard.
+ rc = mbg_mktime64_from_tm_gps( p_t64, p_tm_gps );
+
+out:
+ return rc;
+
+} // set_tzi_time
+
+
+
+/*HDR*/
+/**
+ * @brief Set up an ::MBG_TZ_INFO structure for a given year.
+ *
+ * This function should be called whenever @a p_tzdl has been updated,
+ * or the current @a year has changed.
+ *
+ * @param[out] p_tzi Address of an ::MBG_TZ_INFO variable be set up.
+ *
+ * @param[in] p_tzdl Pointer to a ::TZDL structure providing
+ * local time offset and DST rules.
+ *
+ * @param[in] p_t64_std The standard time (%UTC + ::TZDL::offs) for which
+ * to calculate the relevant switching times.
+ *
+ * @param[in] year The calendar year for which to calculate the
+ * switching times.
+ *
+ * @return ::MBG_SUCCESS on success, else one of the @ref MBG_ERROR_CODES
+ *
+ * @ingroup mbgtimex_tzdl_fncs
+ * @see ::mbg_set_tz_info_for_utc_time64_t
+ * @see ::set_tzi_time
+ */
+int mbg_set_tz_info_for_year( MBG_TZ_INFO *p_tzi, const TZDL *p_tzdl,
+ const MBG_TIME64_T *p_t64_std, int year )
+{
+ int valid = 0;
+ int i;
+ int rc = MBG_SUCCESS;
+
+ // If the switching rules for DST on and off are identical, there is
+ // no DST, and thus no need to compute any switching time.
+ if ( memcmp( &p_tzdl->tm_on, &p_tzdl->tm_off, sizeof( p_tzdl->tm_on ) ) == 0 )
+ {
+ // NO DST changes, so set switching times to 0.
+ p_tzi->t_on = 0;
+ p_tzi->t_off = 0;
+ goto out;
+ }
+
+ p_tzi->auto_flag = ( p_tzdl->tm_on.year & DL_AUTO_FLAG ) != 0;
+
+ for ( i = 0; i < 2; i++ ) // Eventually repeat loop once.
+ {
+ // Compute beginning and end of DST for the current year.
+ rc = set_tzi_time( &p_tzi->t_on, &p_tzdl->tm_on, year );
+
+ if ( mbg_rc_is_error( rc ) )
+ goto out;
+
+ rc = set_tzi_time( &p_tzi->t_off, &p_tzdl->tm_off, year );
+
+ if ( mbg_rc_is_error( rc ) )
+ goto out;
+
+ // Adjust t_off to standard time.
+ p_tzi->t_off -= p_tzdl->offs_dl;
+
+ // If switching times are not calculated automatically,
+ // we're done.
+ if ( !p_tzi->auto_flag )
+ break;
+
+ // If at least one switching time is still in the future,
+ // we're done.
+ if ( ( p_tzi->t_on > *p_t64_std ) || ( p_tzi->t_off > *p_t64_std ) )
+ break;
+
+ // Calculate switching times for the next year.
+ year++;
+ }
+
+ // rc is still MBG_SUCCESS at this point.
+
+out:
+ if ( mbg_rc_is_success( rc ) )
+ valid = 1;
+
+ p_tzi->year = year;
+ p_tzi->offs = p_tzdl->offs;
+ p_tzi->offs_dl = p_tzdl->offs_dl;
+ p_tzi->valid = valid;
+
+ return rc;
+
+} // mbg_set_tz_info_for_year
+
+
+
+/*HDR*/
+/**
+ * @brief Set up an ::MBG_TZ_INFO structure for a given %UTC time.
+ *
+ * This function should be called whenever @a p_tzdl has been updated,
+ * or the current time @a t_utc has increased into a different year.
+ *
+ * @param[out] p_tzi Address of an ::MBG_TZ_INFO variable be set up.
+ *
+ * @param[in] p_tzdl Pointer to a ::TZDL structure providing
+ * local time offset and DST rules.
+ *
+ * @param[in] p_t64_utc Pointer to the %UTC time for which to calculate
+ * the relevant switching times.
+ *
+ * @return ::MBG_SUCCESS on success, else one of the @ref MBG_ERROR_CODES
+ *
+ * @ingroup mbgtimex_tzdl_fncs
+ * @see ::mbg_set_tz_info_for_year
+ * @see ::set_tzi_time
+ */
+int mbg_set_tz_info_for_utc_time64_t( MBG_TZ_INFO *p_tzi, const TZDL *p_tzdl,
+ const MBG_TIME64_T *p_t64_utc )
+{
+ struct tm tm = { 0 };
+ MBG_TIME64_T t64_std = *p_t64_utc + p_tzdl->offs;
+
+ // Compute the year number for the specified %UTC time.
+ int rc = mbg_gmtime64( &tm, &t64_std );
+
+ if ( mbg_rc_is_error( rc ) )
+ goto out;
+
+ // Now set up the time zone info for the computed year.
+ rc = mbg_set_tz_info_for_year( p_tzi, p_tzdl, &t64_std, tm.tm_year + 1900 );
+
+out:
+ return rc;
+
+} // mbg_set_tz_info_for_utc_time64_t
+
+
+
+static __mbg_inline /*HDR*/
+MBG_TIME64_T tzi_tstamp_to_tai( MBG_TIME64_T t64_tzi, const MBG_LS_INFO *p_lsi )
+{
+ MBG_TIME64_T t64 = 0;
+
+ if ( t64_tzi )
+ (void) mbg_time64_utc_to_tai( &t64, &t64_tzi, p_lsi );
+
+ return t64;
+
+} // tzi_tstamp_to_tai
+
+
+
+/*HDR*/
+/**
+ * @brief Convert an ::MBG_TZ_INFO to TAI.
+ *
+ * By default, an ::MBG_TZ_INFO structure stores the
+ * switching times for start and end of DST as local
+ * standard time. This function can be used to set up
+ * another structure where the switching times are TAI,
+ * which allows for faster evaluation in some cases.
+ *
+ * The local time zone offset values are left untouched.
+ * The field @a offs_dl anyway only depends on the
+ * selected time zone, and the standard time offset
+ * from TAI may change in the middle of a DST or
+ * non-DST interval, whenever a leap second event
+ * occurs, so the exact %UTC/TAI offset needs to be
+ * determined whenever a local time is to be
+ * derived from TAI.
+ *
+ * This function should be called whenever @a p_tzi
+ * or @a p_lsi have been updated.
+ *
+ * @param[out] p_tzi_tai Address of an ::MBG_TZ_INFO variable to be set up.
+ *
+ * @param[in] p_tzi Pointer to an ::MBG_TZ_INFO variable with the standard settings,
+ * where switching time are local standard times.
+ *
+ * @param[in] p_lsi Pointer to an ::MBG_LS_INFO variable with current %UTC/leap second information.
+ *
+ * @return ::MBG_SUCCESS on success, else one of the @ref MBG_ERROR_CODES
+ *
+ * @ingroup mbgtimex_time_fncs
+ */
+int mbg_tz_info_to_tai( MBG_TZ_INFO *p_tzi_tai, const MBG_TZ_INFO *p_tzi, const MBG_LS_INFO *p_lsi )
+{
+ // By default copy all information.
+ *p_tzi_tai = *p_tzi;
+
+ // The switching times are usually stored as local standard time.
+ // To convert to TAI we have to subtract the local standard time
+ // offset to yield %UTC, and then convert from %UTC to TAI, which
+ // may depend on whether the particular switching time is before
+ // or after a potential leap second event.
+
+ p_tzi_tai->t_on = tzi_tstamp_to_tai( p_tzi->t_on - p_tzi->offs, p_lsi );
+ p_tzi_tai->t_off = tzi_tstamp_to_tai( p_tzi->t_off - p_tzi->offs, p_lsi );
+
+ // We leave the offsets untouched. The field 'offs_dl' anyway only
+ // depends on the selected time zone, and the standard time offset
+ // from TAI may change in the middle of a DST or non-DST interval,
+ // whenever a leap second event occurs.
+
+ return MBG_SUCCESS;
+
+} // mbg_tz_info_to_tai
+
+
+
+/*HDR*/
+/**
+ * @brief Set up an ::MBG_LS_INFO structure from a given GPS ::UTC structure.
+ *
+ * This function should be called whenever @a p_utc has been updated.
+ *
+ * @param[out] p_lsi Address of an ::MBG_LS_INFO variable be set up.
+ *
+ * @param[in] p_utc Pointer to ::UTC structure providing a valid GPS/%UTC
+ * time offset and leap second information in GPS format.
+ *
+ * @param[out] p_gps_wn_ls Optional address of a ::GPS_WNUM variable that can take
+ * the true GPS week number of the leap second as determined
+ * during the conversion, can be NULL.
+ *
+ * @return ::MBG_SUCCESS on success, else one of the @ref MBG_ERROR_CODES
+ *
+ * @ingroup mbgtimex_gps_time_fncs
+ */
+int mbg_set_ls_info_from_gps_utc( MBG_LS_INFO *p_lsi, const UTC *p_utc, GPS_WNUM *p_gps_wn_ls )
+{
+ GPS_WNUM ls_wn = 0;
+ int rc = MBG_SUCCESS;
+
+ memset( p_lsi, 0, sizeof( *p_lsi ) );
+
+ if ( !p_utc->valid )
+ {
+ rc = MBG_ERR_INV_PARM;
+ goto out;
+ }
+
+ // By default use the GPS/UTC offset specified for the time
+ // after the last known leap second.
+ p_lsi->offs_gps_utc = p_utc->delta_tlsf;
+ p_lsi->offs_tai_utc = p_utc->delta_tlsf + GPS_TAI_OFFSET;
+
+ // delta_tls differs from delta_tlsf only if a leap second
+ // is currently being announced by the GPS satellites.
+ // In this case ls_step computed below is != 0. It is > 0
+ // if a leap second is to be inserted (which is the usual case),
+ // and would be < 0 otherwise, which has yet never happened.
+ p_lsi->ls_step = p_utc->delta_tlsf - p_utc->delta_tls;
+
+ ls_wn = p_utc->WNlsf;
+
+ if ( ( p_utc->WNlsf || p_utc->DNt ) && ( p_lsi->ls_step == 0 ) )
+ {
+ // A leap second week number and day number is available, but
+ // no leap second is being announced, so ls_wn can be ambiguous.
+ rc = mbg_find_true_gps_wn_lsf( &ls_wn, p_utc->DNt, 0 );
+
+ if ( mbg_rc_is_error( rc ) )
+ goto out;
+ }
+
+ rc = mbg_gps_wn_dn_to_time64_t( &p_lsi->t64_ls_utc, ls_wn, p_utc->DNt );
+ p_lsi->t64_ls_tai = mbg_rc_is_success( rc ) ? p_lsi->t64_ls_utc + p_lsi->offs_tai_utc : 0;
+
+ p_lsi->valid = 1;
+
+out:
+ if ( p_gps_wn_ls )
+ * p_gps_wn_ls = ls_wn;
+
+ return rc;
+
+} // mbg_set_ls_info_from_gps_utc
+
+
+
+/*HDR*/
+/**
+ * @brief Determine DST status for a given local standard time in ::MBG_TIME64_T format.
+ *
+ * Determine the DST and DST announcement status for a local standard time
+ * in ::MBG_TIME64_T format (i.e. %UTC plus standard time zone offset already applied).
+ * DST is in effect after @a t_on and before @a t_off.
+ *
+ * In the Northern hemisphere @a t_on is usually before @a t_off, so DST is <b>off</b>
+ * at the beginning of the year until @a t_on, then it is <b>on</b> for the interval between
+ * @a t_on and @a t_off, and <b>off</b> again after @a t_off until the end of the year.
+ *
+ * However, in the Southern hemisphere DST switching times are usually reversed:
+ * @a t_off is <b>before</b> @a t_on in a given year, so DST is observed from the start of
+ * the year until @a t_off, and later from @a t_on until the end of the year, but
+ * DST is <b>off</b> in the middle of the year in the interval from @a t_off to @a t_on.
+ *
+ * @param[in] p_t64_std Pointer to an ::MBG_TIME64_T type providing local standard time
+ * (i.e. %UTC plus standard time zone offset already applied) for
+ * which to determine the DST and DST announcement status.
+ *
+ * @param[in] p_tzi Pointer to a valid ::MBG_TZ_INFO structure.
+ *
+ * @param[in] ann_limit_dl The announcement interval before a DST status change, in seconds.
+ * Must be a negative number, see e.g. ::ANN_LIMIT.
+ *
+ * @param[out] p_intv An optional address of a variable into which the time from the
+ * nearest change is saved, or NULL. If the value is negative, the
+ * current timestamp @a *p_t64_std is still <b>before</b> the next change,
+ * e.g. -30 means the next change will occur 30 seconds later.
+ *
+ * @return A status word of combined ::TM_GPS_STATUS_BIT_MASKS flags, namely ::TM_DL_ANN and ::TM_DL_ENB.
+ *
+ * @ingroup mbgtimex_tzdl_fncs
+ */
+TM_GPS_STATUS mbg_time_dst_status( const MBG_TIME64_T *p_t64_std, const MBG_TZ_INFO *p_tzi, long ann_limit_dl, int64_t *p_intv )
+{
+ TM_GPS_STATUS status = 0; ///< status flags, see ::TM_GPS_STATUS_BIT_MASKS
+ int64_t dt;
+
+ if ( !p_tzi->valid ) // DST settings not valid.
+ goto out;
+
+ if ( p_tzi->t_on == p_tzi->t_off ) // DST on/off times are the same
+ goto out;
+
+
+ // Check if DST times are 'on' before 'off', or vice versa.
+ if ( p_tzi->t_on < p_tzi->t_off )
+ {
+ dt = *p_t64_std - p_tzi->t_on;
+
+ // Normal case for Northern hemisphere: t_on is before t_off.
+ // Check if current time is still before t_on.
+ if ( dt < 0 )
+ {
+ // Current time is still before t_on: no DST, yet.
+ // However, check if DST is going to start soon.
+ if ( dt >= ann_limit_dl )
+ status |= TM_DL_ANN;
+ }
+ else
+ {
+ // Current time is after t_on,
+ // so check if it is still before t_off.
+ dt = *p_t64_std - p_tzi->t_off;
+
+ if ( dt < 0 )
+ {
+ // Current time is still before t_off,
+ // so DST is in effect.
+ status |= TM_DL_ENB;
+
+ // Anyway, also check if DST will end soon.
+ if ( dt >= ann_limit_dl )
+ status |= TM_DL_ANN;
+ }
+ }
+ }
+ else
+ {
+ // Normal case for Southern hemisphere: t_off is before t_on.
+ // Check if current time is still before t_off.
+ dt = *p_t64_std - p_tzi->t_off;
+
+ if ( dt < 0 )
+ {
+ // Current time is still before t_off,
+ // so DST is in effect.
+ status |= TM_DL_ENB;
+
+ // Anyway, also check if DST will end soon.
+ if ( dt >= ann_limit_dl )
+ status |= TM_DL_ANN;
+ }
+ else
+ {
+ // Current time is after t_off,
+ // so check if it is still before t_on.
+ dt = *p_t64_std - p_tzi->t_on;
+
+ if ( dt < 0 )
+ {
+ // Current time is still before t_on,
+ // so DST is *NOT* in effect.
+ // Anyway, also check if DST will start soon.
+ if ( dt >= ann_limit_dl )
+ status |= TM_DL_ANN;
+ }
+ else
+ {
+ // Current time is after t_on,
+ // so DST is in effect.
+ status |= TM_DL_ENB;
+ }
+ }
+ }
+
+ if ( p_intv )
+ *p_intv = dt;
+
+out:
+ return status;
+
+} // mbg_time_dst_status
+
+
+
+/*HDR*/
+/**
+ * @brief Convert a %UTC time to local time, and update a status accordingly.
+ *
+ * Conversion to a valid local time can only be done if the @a p_tzi parameter
+ * set is valid. The @a xstatus value is updated accordingly.
+ *
+ * Optionally the local standard time and the offset applied to yield local time
+ * can be returned to the caller.
+ *
+ * @param[out] p_t64_loc Address of an ::MBG_TIME64_T type to take the calculated local timestamp.
+ *
+ * @param[in] p_t64_utc Pointer to an ::MBG_TIME64_T providing the %UTC time to be converted.
+ *
+ * @param[in] p_tzi Pointer to an ::MBG_TZ_INFO variable with the standard settings,
+ * where switching time are local standard times.
+ *
+ * @param[in] ann_limit_dl The announcement interval before a DST status change, in seconds.
+ * Must be a negative number, see e.g. ::ANN_LIMIT.
+ *
+ * @param[in,out] p_status Optional address of a ::TM_GPS_STATUS_EXT variable which has to be set
+ * to 0 or something meaningful before this function is called.
+ * Additional flags will be set as appropriate.
+ * May be @a NULL.
+ *
+ * @param[out] p_t64_std An optional address of an ::MBG_TIME64_T type which is set to
+ * the standard time, i.e. without DST adjustment applied.
+ * May be @a NULL.
+ *
+ * @param[out] p_offs Optional pointer to a variable to take the offset
+ * that has been subtracted from the TAI time stamp.
+ * May be @a NULL.
+ *
+ * @return ::MBG_SUCCESS on success, else one of the @ref MBG_ERROR_CODES
+ *
+ * @ingroup mbgtimex_time_fncs
+ */
+int mbg_utc_to_local_time( MBG_TIME64_T *p_t64_loc, const MBG_TIME64_T *p_t64_utc,
+ const MBG_TZ_INFO *p_tzi, long ann_limit_dl, TM_GPS_STATUS_EXT *p_status,
+ MBG_TIME64_T *p_t64_std, long *p_offs )
+{
+ MBG_TIME64_T t64 = *p_t64_utc;
+ TM_GPS_STATUS_EXT xstatus = 0;
+ long offs = 0;
+
+ if ( !p_tzi->valid ) // TODO || !( *xstatus & TM_UTC ) )
+ {
+ if ( p_t64_std )
+ *p_t64_std = t64;
+
+ // Still set the offset, if required.
+ goto out;
+ }
+
+ // Time zone settings are valid, add offset required
+ // to yield local standard time.
+
+ offs += p_tzi->offs;
+ xstatus |= TM_LOCAL;
+
+ if ( p_t64_std )
+ *p_t64_std = t64 + offs;
+
+ // Now check if DST is active.
+ xstatus |= mbg_time_dst_status( &t64, p_tzi, ann_limit_dl, NULL );
+
+ // If DST is active, add DST offset.
+ if ( xstatus & TM_DL_ENB )
+ offs += p_tzi->offs_dl;
+
+ // Apply offset tzo the original time stamp.
+ t64 += offs;
+
+ if ( p_status )
+ *p_status = xstatus;
+
+out:
+ // Optionally save the offset that has been added.
+ if ( p_offs )
+ *p_offs = offs;
+
+ *p_t64_loc = t64;
+
+ return MBG_SUCCESS;
+
+} // mbg_utc_to_local_time
+
+
+
+/*HDR*/
+/**
+ * @brief Set up ::MBG_LS_INFO and ::MBG_TZ_INFO structures for %UTC.
+ *
+ * The structures are used to store intermediate results, and thus
+ * avoid unnecessary computation when converting between %UTC and
+ * local time.
+ *
+ * This variant expects a %UTC timestamp as input.
+ *
+ * This function should be called after program startup, when valid
+ * ::TZDL and ::UTC data sets are already available, and whenever
+ * the the GPS ::UTC parameters have changed, or the computed DST
+ * switching times are in the past even though automatic DST computation
+ * has been configured.
+ *
+ * @param[out] p_lsi Address of an ::MBG_LS_INFO structure to be set up.
+ *
+ * @param[out] p_tzi Address of an ::MBG_TZ_INFO structure to be set up
+ * where the switching times are %UTC.
+ *
+ * @param[in] p_t64_utc Pointer to the current %UTC time for which to set up @a p_tzi.
+ *
+ * @param[in] p_utc Pointer to a valid GPS ::UTC parameter set.
+ *
+ * @param[in] p_tzdl Pointer to a valid ::TZDL parameter set.
+ *
+ * @return ::MBG_SUCCESS on success, else one of the @ref MBG_ERROR_CODES
+ *
+ * @ingroup mbgtimex_tzdl_fncs
+ * @see mbg_setup_lsi_tzi_for_tai
+ */
+int mbg_setup_lsi_tzi_for_utc( MBG_LS_INFO *p_lsi,
+ MBG_TZ_INFO *p_tzi,
+ const MBG_TIME64_T *p_t64_utc,
+ const UTC *p_utc,
+ const TZDL *p_tzdl )
+{
+ int rc;
+
+ // First set up the MBG_LS_INFO structure referenced by p_lsi.
+ //
+ // Here the necessary information is derived from a GPS UTC
+ // parameter set, but alternatively a different function could
+ // be called which e.g. reads and evaluates an NTP leap second file.
+ rc = mbg_set_ls_info_from_gps_utc( p_lsi, p_utc, NULL );
+
+ if ( mbg_rc_is_error( rc ) )
+ goto out;
+
+ // If p_lsi->ls_step is != 0 at this point, a leap second is being
+ // announced, and p_lsi->t_ls_gps as well as p_lsi->t_ls_tai provide
+ // the point in time for the associated time scale when the leap second
+ // is to be inserted.
+
+ // Now compute the local time parameters and DST switching times.
+ // This has to be repeated whenever the TZDL parameters have changed,
+ // or when both DST start and end times are in the past.
+ rc = mbg_set_tz_info_for_utc_time64_t( p_tzi, p_tzdl, p_t64_utc );
+
+out:
+ return rc;
+
+} // mbg_setup_lsi_tzi_for_utc
+
+
+
+/*HDR*/
+/**
+ * @brief Set up ::MBG_LS_INFO and ::MBG_TZ_INFO structures for TAI.
+ *
+ * The structures are used to store intermediate results, and thus
+ * avoid unnecessary computation when converting between TAI / %UTC,
+ * and local time.
+ *
+ * This variant expects a TAI timestamp as input and also sets up
+ * an ::MBG_TZ_INFO structure where the switching times are TAI.
+ *
+ * This function should be called after program startup, when valid
+ * ::TZDL and ::UTC data sets are already available, and whenever
+ * the the GPS ::UTC parameters have changed, or the computed DST
+ * switching times are in the past even though automatic DST computation
+ * has been configured.
+ *
+ * @param[out] p_lsi Address of an ::MBG_LS_INFO structure to be set up.
+ *
+ * @param[out] p_tzi Address of an ::MBG_TZ_INFO structure to be set up
+ * where the switching times are %UTC.
+ *
+ * @param[out] p_tzi_tai Address of an additional ::MBG_TZ_INFO structure
+ * to be set up, where the switching times are TAI.
+ *
+ * @param[in] p_t64_tai Pointer to the current TAI time for which to
+ * set up @a p_tzi and @a p_tzi_tai.
+ *
+ * @param[in] p_utc Pointer to a valid GPS ::UTC parameter set.
+ *
+ * @param[in] p_tzdl Pointer to a valid ::TZDL parameter set.
+ *
+ * @return ::MBG_SUCCESS on success, else one of the @ref MBG_ERROR_CODES
+ *
+ * @ingroup mbgtimex_tzdl_fncs
+ * @see mbg_setup_lsi_tzi_for_utc
+ */
+int mbg_setup_lsi_tzi_for_tai( MBG_LS_INFO *p_lsi,
+ MBG_TZ_INFO *p_tzi,
+ MBG_TZ_INFO *p_tzi_tai,
+ const MBG_TIME64_T *p_t64_tai,
+ const UTC *p_utc,
+ const TZDL *p_tzdl )
+{
+ MBG_TIME64_T t64_utc;
+ int rc;
+
+ // First set up the MBG_LS_INFO structure referenced by p_lsi.
+ //
+ // Here the necessary information is derived from a GPS UTC
+ // parameter set, but alternatively a different function could
+ // be called which e.g. reads and evaluates an NTP leap second file.
+ rc = mbg_set_ls_info_from_gps_utc( p_lsi, p_utc, NULL );
+
+ if ( mbg_rc_is_error( rc ) )
+ goto out;
+
+ // If p_lsi->ls_step is != 0 at this point, a leap second is being
+ // announced, and p_lsi->t_ls_gps as well as p_lsi->t_ls_tai provide
+ // the point in time for the associated time scale when the leap second
+ // has just finished, i.e. Jan 1 00:00:00 or Jul 1 00:00:00.
+
+ // Computing of local time parameters and DST switching times
+ // is based on UTC, so we have to convert the t_tai parameter
+ // to UTC first, using the leap second info computed above.
+ mbg_time64_tai_to_utc( &t64_utc, p_t64_tai, p_lsi );
+
+ // Now compute the local time parameters and DST switching times.
+ // This has to be repeated whenever the TZDL parameters have changed,
+ // or when both DST start and end times are in the past.
+ rc = mbg_set_tz_info_for_utc_time64_t( p_tzi, p_tzdl, &t64_utc );
+
+ if ( mbg_rc_is_error( rc ) )
+ goto out;
+
+ // Finally convert the timestamps for DST start/end to TAI, and
+ // save them into another structure.
+ mbg_tz_info_to_tai( p_tzi_tai, p_tzi, p_lsi );
+
+out:
+ return rc;
+
+} // mbg_setup_lsi_tzi_for_tai
+
+
+
diff --git a/mbglib/common/mbgtimex.h b/mbglib/common/mbgtimex.h
new file mode 100644
index 0000000..c4ae1cd
--- /dev/null
+++ b/mbglib/common/mbgtimex.h
@@ -0,0 +1,929 @@
+
+/**************************************************************************
+ *
+ * $Id: mbgtimex.h 1.3 2020/02/28 13:13:49Z martin REL_M $
+ *
+ * Copyright (c) Meinberg Funkuhren, Bad Pyrmont, Germany
+ *
+ * Description:
+ * Definitions and prototypes for mbgtimex.c.
+ *
+ * -----------------------------------------------------------------------
+ * $Log: mbgtimex.h $
+ * Revision 1.3 2020/02/28 13:13:49Z martin
+ * Updated function prototypes.
+ * Revision 1.2 2019/09/27 15:09:51 martin
+ * Cleanup.
+ * Updated function names and prototypes.
+ * Revision 1.1 2019/08/08 13:12:14 martin
+ * Initial revision.
+ *
+ **************************************************************************/
+
+#ifndef _MBGTIMEX_H
+#define _MBGTIMEX_H
+
+
+/* Other headers to be included */
+
+#include <mbgtime.h>
+#include <timeutil.h>
+
+#include <time.h>
+
+
+#ifdef _MBGTIMEX
+ #define _ext
+ #define _DO_INIT
+#else
+ #define _ext extern
+#endif
+
+
+
+/* Start of header body */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if !defined( _T_DEPRECATED_BY )
+ #if 0
+ #define _T_DEPRECATED_BY( _x ) _DEPRECATED_BY( _x )
+ #else
+ #define _T_DEPRECATED_BY( _x )
+ #endif
+#endif
+
+
+
+/**
+ * @defgroup mbgtimex_fncs Meinberg extended time conversion functions
+ */
+
+/**
+ * @defgroup mbgtimex_gps_time_fncs Meinberg GPS time functions
+ * @ingroup mbgtimex_fncs
+ *
+ * Functions that deal with Meinberg ::T_GPS, ::TM_GPS, week numbers, etc.
+ *
+ * @see @ref mbgtimex_time_fncs
+ * @see @ref mbgtimex_tzdl_fncs
+ * @see @ref mbgtimex_gps_posix_time_cnv_fncs
+ * @see @ref mbgtimex_fncs
+ * @see @ref group_true_gps_wn_fncs
+ */
+
+/**
+ * @defgroup mbgtimex_time_fncs Meinberg functions that deal with POSIX-like time
+ * @ingroup mbgtimex_fncs
+ *
+ * Convert 'time_t'-like timestamps between %UTC, TAI, and local time,
+ * based on rules stored in Meinberg data structures.
+ *
+ * @see @ref mbgtimex_gps_time_fncs
+ * @see @ref mbgtimex_tzdl_fncs
+ * @see @ref mbgtimex_gps_posix_time_cnv_fncs
+ * @see @ref mbgtimex_fncs
+ */
+
+/**
+ * @defgroup mbgtimex_tzdl_fncs Meinberg functions that evaluate Meinberg time zone and daylight saving data
+ * @ingroup mbgtimex_fncs
+ *
+ * Functions to deal with Meinberg ::T_GPS, ::TM_GPS, week numbers, etc.
+ *
+ * @see The ::TZDL structure.
+ * @see @ref mbgtimex_gps_time_fncs
+ * @see @ref mbgtimex_time_fncs
+ * @see @ref mbgtimex_gps_posix_time_cnv_fncs
+ * @see @ref mbgtimex_fncs
+ */
+
+/**
+ * @defgroup mbgtimex_gps_posix_time_cnv_fncs Meinberg GPS time to POSIX time conversion functions
+ * @ingroup mbgtimex_fncs
+ *
+ * Functions to convert from Meinberg ::T_GPS, ::TM_GPS, etc.,
+ * to POSIX time_t, struct tm, or the Meinberg-specific POSIX-like
+ * ::MBG_TIME64_T type.
+ *
+ * @see @ref mbgtimex_gps_time_fncs
+ * @see @ref mbgtimex_time_fncs
+ * @see @ref mbgtimex_tzdl_fncs
+ * @see @ref mbgtimex_fncs
+ */
+
+
+
+/* function prototypes: */
+
+/* ----- function prototypes begin ----- */
+
+/* This section was generated automatically */
+/* by MAKEHDR, do not remove the comments. */
+
+ /**
+ * @brief Convert date and time from <em>struct tm</em> to GPS week number and second-of-week.
+ *
+ * Only the data format is converted, the offset between GPS time scale
+ * and %UTC scale is not taken into account.
+ *
+ * The calculated second-of-week is always greater than 0, but the week number
+ * can be less than 0, if the original time is before the GPS time epoch.
+ *
+ * @param[out] p_wn Address of a ::GPS_WNUM variable to take the computed week number.
+ *
+ * @param[out] p_wsec Address of a ::GPS_WSEC variable to take the computed second-of-week.
+ *
+ * @param[in] p_tm Pointer to a <em>struct tm</em> providing the date and time to be converted.
+ *
+ * @return ::MBG_SUCCESS on success, else one of the @ref MBG_ERROR_CODES
+ *
+ * @ingroup mbgtimex_gps_posix_time_cnv_fncs
+ */
+ int mbg_struct_tm_to_gps_wn_wsec( GPS_WNUM *p_wn, GPS_WSEC *p_wsec, const struct tm *p_tm ) ;
+
+ /**
+ * @brief Convert GPS week number plus second-of-week to ::MBG_TIME64_T.
+ *
+ * Only the data format is converted, the offset between GPS time scale
+ * and %UTC scale is not taken into account.
+ *
+ * @param[out] p_t64 Address of an ::MBG_TIME64_T to take the computed timestamp.
+ *
+ * @param[in] wn A GPS week number as ::GPS_WNUM.
+ *
+ * @param[in] wsec Seconds of the week as ::GPS_WSEC.
+ *
+ * @return ::MBG_SUCCESS on success, else one of the @ref MBG_ERROR_CODES
+ *
+ * @ingroup mbgtimex_gps_posix_time_cnv_fncs
+ */
+ int mbg_gps_wn_wsec_to_time64_t( MBG_TIME64_T *p_t64, GPS_WNUM wn, GPS_WSEC wsec ) ;
+
+ /**
+ * @brief Convert a GPS week number / day-of-week pair to ::MBG_TIME64_T.
+ *
+ * Only the data format is converted, the offset between GPS time scale
+ * and %UTC scale is not taken into account.
+ *
+ * @note If this function is called to compute the leap second date
+ * from the GPS ::UTC parameters, the computed timestamp is associated
+ * with <b>the end of the leap second transition</b>, e.g.
+ * <em>2017-01-01 00:00:00</em> rather than <em>2016-12-31 23:59:59</em>.
+ * Also, first calling the function ::mbg_find_true_gps_wn_lsf may be
+ * required to resolve an ambiguity of the week number.
+ * See @ref group_true_gps_wn_fncs.
+ *
+ * @param[out] p_t64 Address of an ::MBG_TIME64_T to take the computed timestamp.
+ *
+ * @param[in] wn A GPS week number as ::GPS_WNUM, e.g. ::UTC::WNlsf.
+ *
+ * @param[in] dn A day number as ::GPS_DNUM, e.g. ::UTC::DNt.
+ *
+ * @return ::MBG_SUCCESS on success, else one of the @ref MBG_ERROR_CODES
+ *
+ * @ingroup mbgtimex_gps_posix_time_cnv_fncs
+ * @see ::mbg_find_true_gps_wn_lsf
+ * @see @ref group_true_gps_wn_fncs
+ */
+ int mbg_gps_wn_dn_to_time64_t( MBG_TIME64_T *p_t64, GPS_WNUM wn, GPS_DNUM dn ) ;
+
+ /**
+ * @brief Convert an ::MBG_TIME64_T to GPS week number and second-of-week.
+ *
+ * Only the data format is converted, the offset between GPS time scale
+ * and %UTC scale is not taken into account.
+ *
+ * The calculated week number can be negative if @a *p_t64 is before
+ * the GPS epoch. The calculated values are normalized so that the
+ * second-of-week is always non-negative, i.e. contains the seconds
+ * after the beginning of the week, even if the week number is negative.
+ *
+ * @param[out] p_wn Address of a ::GPS_WNUM variable to take the computed week number.
+ *
+ * @param[out] p_wsec Address of a ::GPS_WSEC variable to take the computed second-of-week.
+ *
+ * @param[in] p_t64 Pointer to an ::MBG_TIME64_T timestamp to be converted.
+ *
+ * @return ::MBG_SUCCESS on success, else one of the @ref MBG_ERROR_CODES
+ *
+ * @ingroup mbgtimex_gps_posix_time_cnv_fncs
+ */
+ int mbg_time64_t_to_gps_wn_wsec( GPS_WNUM *p_wn, GPS_WSEC *p_wsec, const MBG_TIME64_T *p_t64 ) ;
+
+ /**
+ * @brief Convert an ::MBG_TIME64_T to ::T_GPS.
+ *
+ * Only the data format is converted, the offset between GPS time scale
+ * and %UTC scale is not taken into account.
+ *
+ * The week number of the calculated ::T_GPS can be negative if @a *p_t64
+ * is before the GPS epoch. The calculated values are normalized so that
+ * the second-of-week is always non-negative, i.e. contains the seconds
+ * after the beginning of the week, even if the week number is negative.
+ *
+ * @param[out] p_t_gps Address of a ::T_GPS variable to take the computed timestamp.
+ *
+ * @param[in] p_t64 Pointer to an ::MBG_TIME64_T timestamp.
+ *
+ * @return ::MBG_SUCCESS on success, else one of the @ref MBG_ERROR_CODES
+ *
+ * @ingroup mbgtimex_gps_posix_time_cnv_fncs
+ * @see ::mbg_t_gps_to_time_t
+ */
+ int mbg_time64_t_to_t_gps( T_GPS *p_t_gps, MBG_TIME64_T *p_t64 ) ;
+
+ /**
+ * @brief Convert ::T_GPS to ::MBG_TIME64_T.
+ *
+ * Only the data format is converted, the offset between GPS time scale
+ * and %UTC scale is not taken into account.
+ *
+ * @param[out] p_t64 Address of an ::MBG_TIME64_T type to take the computed timestamp.
+ *
+ * @param[in] p_t Pointer to the timestamp to be converted, in ::T_GPS format.
+ *
+ * @return ::MBG_SUCCESS on success, else one of the @ref MBG_ERROR_CODES
+ *
+ * @ingroup mbgtimex_gps_posix_time_cnv_fncs
+ * @see ::mbg_time_t_to_t_gps
+ */
+ int mbg_t_gps_to_time64_t( MBG_TIME64_T *p_t64, const T_GPS *p_t ) ;
+
+ /**
+ * @brief Check is a <em>struct tm</em> and a ::TM_GPS refer to the same date and time.
+ *
+ * This has to take into account that some fields in a <em>struct tm</em>
+ * have different meanings than the associated fiellds in a ::TM_GPS.
+ *
+ * @param[in] p_tm Pointer to the <em>struct tm</em> to be compared.
+ * @param[in] p_tm_gps Pointer to the ::TM_GPS to be compared.
+ *
+ * @return @a true, if both variables refer to the same date and time, else @a false.
+ */
+ bool is_same_tm_tm_gps( const struct tm *p_tm, const TM_GPS *p_tm_gps ) ;
+
+ /**
+ * @brief Determine the true week number for an ambiguous ::UTC::WNlsf number.
+ *
+ * See @ref group_true_gps_wn_fncs for details how this is supposed to work.
+ *
+ * This variant only checks calculated potential dates.
+ * See ::mbg_find_true_gps_wn_lsf for a variant which searches a table
+ * of known leaps second dates first, and thus executes faster.
+ *
+ * @param[in,out] p_wn The GPS week number of the leap second, see ::UTC::WNlsf.
+ * Updated if a solution could be found. If @a srch_all
+ * is @a true, the last update is done for the last match
+ * found.
+ *
+ * @param[in] dn_t The day-of-week number at the end of which the
+ * leap second is to be inserted. See ::UTC::DNt,
+ * which is usually in the range 1..7.
+ *
+ * @param[in] srch_all If this flag is @a true then always the full range
+ * is searched. The search is even continued after
+ * a first match has already been found. This takes
+ * longer to execute, but allows detection
+ * of multiple matches, e.g. for testing.
+ *
+ * @param[in] first_wn First GPS week number to start the search.
+ * Can be 0 to search the full range, or the latest
+ * known week number from a leap second table to check
+ * only dates that are after the last week number
+ * present in the table.
+ *
+ * @return The number of matches found. Should be 1 for an unambiguous result,
+ * even if @a srch_all was @a true
+ *
+ * @ingroup group_true_gps_wn_fncs
+ * @see ::mbg_find_true_gps_wn_lsf
+ * @see @ref group_true_gps_wn_fncs
+ * @see ::mbg_gps_wn_dn_to_time_t
+ * @see ::UTC
+ */
+ int mbg_find_true_gps_wn_lsf_ex( GPS_WNUM *p_wn, GPS_DNUM dn_t, bool srch_all, GPS_WNUM first_wn ) ;
+
+ /**
+ * @brief Determine the true week number for an ambiguous ::UTC::WNlsf number.
+ *
+ * See @ref group_true_gps_wn_fncs for details how this is supposed to work.
+ *
+ * To reduce the execution time, this variant uses a table to search past,
+ * known leap second dates, and calls ::mbg_find_true_gps_wn_lsf_ex only
+ * if no result has been found in the table, or the @a srch_all flag is @a true.
+ *
+ * @param[in,out] p_wn The GPS week number of the leap second, see ::UTC::WNlsf.
+ * Updated if a solution could be found. If @a srch_all
+ * is @a true, the last update is done for the last match
+ * found.
+ *
+ * @param[in] dn_t The day-of-week number at the end of which the
+ * leap second is to be inserted. See ::UTC::DNt,
+ * which is usually in the range 1..7.
+ *
+ * @param[in] srch_all If this flag is @a true then always the full range
+ * is searched. The search is even continued after
+ * a first match has already been found. This takes
+ * longer to execute, but allows detection
+ * of multiple matches, e.g. for testing.
+ *
+ * @return The number of matches found. Should be 1 for an unambiguous result,
+ * even if @a srch_all was @a true
+ *
+ * @ingroup group_true_gps_wn_fncs
+ * @see ::mbg_find_true_gps_wn_lsf
+ * @see @ref group_true_gps_wn_fncs
+ * @see ::mbg_gps_wn_dn_to_time_t
+ * @see ::UTC
+ */
+ int mbg_find_true_gps_wn_lsf( GPS_WNUM *p_wn, GPS_DNUM dn_t, int srch_all ) ;
+
+ /**
+ * @brief Set up an ::MBG_TZ_INFO structure for a given year.
+ *
+ * This function should be called whenever @a p_tzdl has been updated,
+ * or the current @a year has changed.
+ *
+ * @param[out] p_tzi Address of an ::MBG_TZ_INFO variable be set up.
+ *
+ * @param[in] p_tzdl Pointer to a ::TZDL structure providing
+ * local time offset and DST rules.
+ *
+ * @param[in] p_t64_std The standard time (%UTC + ::TZDL::offs) for which
+ * to calculate the relevant switching times.
+ *
+ * @param[in] year The calendar year for which to calculate the
+ * switching times.
+ *
+ * @return ::MBG_SUCCESS on success, else one of the @ref MBG_ERROR_CODES
+ *
+ * @ingroup mbgtimex_tzdl_fncs
+ * @see ::mbg_set_tz_info_for_utc_time64_t
+ * @see ::set_tzi_time
+ */
+ int mbg_set_tz_info_for_year( MBG_TZ_INFO *p_tzi, const TZDL *p_tzdl, const MBG_TIME64_T *p_t64_std, int year ) ;
+
+ /**
+ * @brief Set up an ::MBG_TZ_INFO structure for a given %UTC time.
+ *
+ * This function should be called whenever @a p_tzdl has been updated,
+ * or the current time @a t_utc has increased into a different year.
+ *
+ * @param[out] p_tzi Address of an ::MBG_TZ_INFO variable be set up.
+ *
+ * @param[in] p_tzdl Pointer to a ::TZDL structure providing
+ * local time offset and DST rules.
+ *
+ * @param[in] p_t64_utc Pointer to the %UTC time for which to calculate
+ * the relevant switching times.
+ *
+ * @return ::MBG_SUCCESS on success, else one of the @ref MBG_ERROR_CODES
+ *
+ * @ingroup mbgtimex_tzdl_fncs
+ * @see ::mbg_set_tz_info_for_year
+ * @see ::set_tzi_time
+ */
+ int mbg_set_tz_info_for_utc_time64_t( MBG_TZ_INFO *p_tzi, const TZDL *p_tzdl, const MBG_TIME64_T *p_t64_utc ) ;
+
+ /**
+ * @brief Convert an ::MBG_TZ_INFO to TAI.
+ *
+ * By default, an ::MBG_TZ_INFO structure stores the
+ * switching times for start and end of DST as local
+ * standard time. This function can be used to set up
+ * another structure where the switching times are TAI,
+ * which allows for faster evaluation in some cases.
+ *
+ * The local time zone offset values are left untouched.
+ * The field @a offs_dl anyway only depends on the
+ * selected time zone, and the standard time offset
+ * from TAI may change in the middle of a DST or
+ * non-DST interval, whenever a leap second event
+ * occurs, so the exact %UTC/TAI offset needs to be
+ * determined whenever a local time is to be
+ * derived from TAI.
+ *
+ * This function should be called whenever @a p_tzi
+ * or @a p_lsi have been updated.
+ *
+ * @param[out] p_tzi_tai Address of an ::MBG_TZ_INFO variable to be set up.
+ *
+ * @param[in] p_tzi Pointer to an ::MBG_TZ_INFO variable with the standard settings,
+ * where switching time are local standard times.
+ *
+ * @param[in] p_lsi Pointer to an ::MBG_LS_INFO variable with current %UTC/leap second information.
+ *
+ * @return ::MBG_SUCCESS on success, else one of the @ref MBG_ERROR_CODES
+ *
+ * @ingroup mbgtimex_time_fncs
+ */
+ int mbg_tz_info_to_tai( MBG_TZ_INFO *p_tzi_tai, const MBG_TZ_INFO *p_tzi, const MBG_LS_INFO *p_lsi ) ;
+
+ /**
+ * @brief Set up an ::MBG_LS_INFO structure from a given GPS ::UTC structure.
+ *
+ * This function should be called whenever @a p_utc has been updated.
+ *
+ * @param[out] p_lsi Address of an ::MBG_LS_INFO variable be set up.
+ *
+ * @param[in] p_utc Pointer to ::UTC structure providing a valid GPS/%UTC
+ * time offset and leap second information in GPS format.
+ *
+ * @param[out] p_gps_wn_ls Optional address of a ::GPS_WNUM variable that can take
+ * the true GPS week number of the leap second as determined
+ * during the conversion, can be NULL.
+ *
+ * @return ::MBG_SUCCESS on success, else one of the @ref MBG_ERROR_CODES
+ *
+ * @ingroup mbgtimex_gps_time_fncs
+ */
+ int mbg_set_ls_info_from_gps_utc( MBG_LS_INFO *p_lsi, const UTC *p_utc, GPS_WNUM *p_gps_wn_ls ) ;
+
+ /**
+ * @brief Determine DST status for a given local standard time in ::MBG_TIME64_T format.
+ *
+ * Determine the DST and DST announcement status for a local standard time
+ * in ::MBG_TIME64_T format (i.e. %UTC plus standard time zone offset already applied).
+ * DST is in effect after @a t_on and before @a t_off.
+ *
+ * In the Northern hemisphere @a t_on is usually before @a t_off, so DST is <b>off</b>
+ * at the beginning of the year until @a t_on, then it is <b>on</b> for the interval between
+ * @a t_on and @a t_off, and <b>off</b> again after @a t_off until the end of the year.
+ *
+ * However, in the Southern hemisphere DST switching times are usually reversed:
+ * @a t_off is <b>before</b> @a t_on in a given year, so DST is observed from the start of
+ * the year until @a t_off, and later from @a t_on until the end of the year, but
+ * DST is <b>off</b> in the middle of the year in the interval from @a t_off to @a t_on.
+ *
+ * @param[in] p_t64_std Pointer to an ::MBG_TIME64_T type providing local standard time
+ * (i.e. %UTC plus standard time zone offset already applied) for
+ * which to determine the DST and DST announcement status.
+ *
+ * @param[in] p_tzi Pointer to a valid ::MBG_TZ_INFO structure.
+ *
+ * @param[in] ann_limit_dl The announcement interval before a DST status change, in seconds.
+ * Must be a negative number, see e.g. ::ANN_LIMIT.
+ *
+ * @param[out] p_intv An optional address of a variable into which the time from the
+ * nearest change is saved, or NULL. If the value is negative, the
+ * current timestamp @a *p_t64_std is still <b>before</b> the next change,
+ * e.g. -30 means the next change will occur 30 seconds later.
+ *
+ * @return A status word of combined ::TM_GPS_STATUS_BIT_MASKS flags, namely ::TM_DL_ANN and ::TM_DL_ENB.
+ *
+ * @ingroup mbgtimex_tzdl_fncs
+ */
+ TM_GPS_STATUS mbg_time_dst_status( const MBG_TIME64_T *p_t64_std, const MBG_TZ_INFO *p_tzi, long ann_limit_dl, int64_t *p_intv ) ;
+
+ /**
+ * @brief Convert a %UTC time to local time, and update a status accordingly.
+ *
+ * Conversion to a valid local time can only be done if the @a p_tzi parameter
+ * set is valid. The @a xstatus value is updated accordingly.
+ *
+ * Optionally the local standard time and the offset applied to yield local time
+ * can be returned to the caller.
+ *
+ * @param[out] p_t64_loc Address of an ::MBG_TIME64_T type to take the calculated local timestamp.
+ *
+ * @param[in] p_t64_utc Pointer to an ::MBG_TIME64_T providing the %UTC time to be converted.
+ *
+ * @param[in] p_tzi Pointer to an ::MBG_TZ_INFO variable with the standard settings,
+ * where switching time are local standard times.
+ *
+ * @param[in] ann_limit_dl The announcement interval before a DST status change, in seconds.
+ * Must be a negative number, see e.g. ::ANN_LIMIT.
+ *
+ * @param[in,out] p_status Optional address of a ::TM_GPS_STATUS_EXT variable which has to be set
+ * to 0 or something meaningful before this function is called.
+ * Additional flags will be set as appropriate.
+ * May be @a NULL.
+ *
+ * @param[out] p_t64_std An optional address of an ::MBG_TIME64_T type which is set to
+ * the standard time, i.e. without DST adjustment applied.
+ * May be @a NULL.
+ *
+ * @param[out] p_offs Optional pointer to a variable to take the offset
+ * that has been subtracted from the TAI time stamp.
+ * May be @a NULL.
+ *
+ * @return ::MBG_SUCCESS on success, else one of the @ref MBG_ERROR_CODES
+ *
+ * @ingroup mbgtimex_time_fncs
+ */
+ int mbg_utc_to_local_time( MBG_TIME64_T *p_t64_loc, const MBG_TIME64_T *p_t64_utc, const MBG_TZ_INFO *p_tzi, long ann_limit_dl, TM_GPS_STATUS_EXT *p_status, MBG_TIME64_T *p_t64_std, long *p_offs ) ;
+
+ /**
+ * @brief Set up ::MBG_LS_INFO and ::MBG_TZ_INFO structures for %UTC.
+ *
+ * The structures are used to store intermediate results, and thus
+ * avoid unnecessary computation when converting between %UTC and
+ * local time.
+ *
+ * This variant expects a %UTC timestamp as input.
+ *
+ * This function should be called after program startup, when valid
+ * ::TZDL and ::UTC data sets are already available, and whenever
+ * the the GPS ::UTC parameters have changed, or the computed DST
+ * switching times are in the past even though automatic DST computation
+ * has been configured.
+ *
+ * @param[out] p_lsi Address of an ::MBG_LS_INFO structure to be set up.
+ *
+ * @param[out] p_tzi Address of an ::MBG_TZ_INFO structure to be set up
+ * where the switching times are %UTC.
+ *
+ * @param[in] p_t64_utc Pointer to the current %UTC time for which to set up @a p_tzi.
+ *
+ * @param[in] p_utc Pointer to a valid GPS ::UTC parameter set.
+ *
+ * @param[in] p_tzdl Pointer to a valid ::TZDL parameter set.
+ *
+ * @return ::MBG_SUCCESS on success, else one of the @ref MBG_ERROR_CODES
+ *
+ * @ingroup mbgtimex_tzdl_fncs
+ * @see mbg_setup_lsi_tzi_for_tai
+ */
+ int mbg_setup_lsi_tzi_for_utc( MBG_LS_INFO *p_lsi, MBG_TZ_INFO *p_tzi, const MBG_TIME64_T *p_t64_utc, const UTC *p_utc, const TZDL *p_tzdl ) ;
+
+ /**
+ * @brief Set up ::MBG_LS_INFO and ::MBG_TZ_INFO structures for TAI.
+ *
+ * The structures are used to store intermediate results, and thus
+ * avoid unnecessary computation when converting between TAI / %UTC,
+ * and local time.
+ *
+ * This variant expects a TAI timestamp as input and also sets up
+ * an ::MBG_TZ_INFO structure where the switching times are TAI.
+ *
+ * This function should be called after program startup, when valid
+ * ::TZDL and ::UTC data sets are already available, and whenever
+ * the the GPS ::UTC parameters have changed, or the computed DST
+ * switching times are in the past even though automatic DST computation
+ * has been configured.
+ *
+ * @param[out] p_lsi Address of an ::MBG_LS_INFO structure to be set up.
+ *
+ * @param[out] p_tzi Address of an ::MBG_TZ_INFO structure to be set up
+ * where the switching times are %UTC.
+ *
+ * @param[out] p_tzi_tai Address of an additional ::MBG_TZ_INFO structure
+ * to be set up, where the switching times are TAI.
+ *
+ * @param[in] p_t64_tai Pointer to the current TAI time for which to
+ * set up @a p_tzi and @a p_tzi_tai.
+ *
+ * @param[in] p_utc Pointer to a valid GPS ::UTC parameter set.
+ *
+ * @param[in] p_tzdl Pointer to a valid ::TZDL parameter set.
+ *
+ * @return ::MBG_SUCCESS on success, else one of the @ref MBG_ERROR_CODES
+ *
+ * @ingroup mbgtimex_tzdl_fncs
+ * @see mbg_setup_lsi_tzi_for_utc
+ */
+ int mbg_setup_lsi_tzi_for_tai( MBG_LS_INFO *p_lsi, MBG_TZ_INFO *p_tzi, MBG_TZ_INFO *p_tzi_tai, const MBG_TIME64_T *p_t64_tai, const UTC *p_utc, const TZDL *p_tzdl ) ;
+
+
+/* ----- function prototypes end ----- */
+
+
+
+static __mbg_inline /*HDR*/
+/**
+ * @brief Normalize a GPS week number / second-of-week pair.
+ *
+ * If a GPS week number / second-of-week pair has been computed
+ * for a point in time before the GPS epoch (see ::GPS_INITIAL_DAY),
+ * both the week number and the second-of-week can be negative and should
+ * be normalized so that the second-of-week is always non-negative, i.e.
+ * ***after*** the beginning of the week with the computed number.
+ * This avoids errors in further computations.
+ *
+ * @param[in,out] p_wn Address of a variable containing the week number.
+ * @param[in,out] p_wsec Address of a variable containing the second-of-week.
+ *
+ * @ingroup mbgtimex_gps_time_fncs
+ */
+void normalize_wn_wsec( GPS_WNUM *p_wn, GPS_WSEC *p_wsec )
+{
+ while ( *p_wsec < 0 )
+ {
+ *p_wsec += SECS_PER_WEEK;
+ (*p_wn)--;
+ }
+
+ while ( *p_wsec >= SECS_PER_WEEK )
+ {
+ *p_wsec -= SECS_PER_WEEK;
+ (*p_wn)++;
+ }
+
+} // normalize_wn_wsec
+
+
+
+static __mbg_inline /*HDR*/
+/**
+ * @brief Determine the TAI/%UTC time offset for a given TAI time.
+ *
+ * If the current time @a *p_t64_tai is <b>after</b> the last known leap second,
+ * we don't know if the last leap second was an insertion or deletion, and we
+ * can't be sure if the stored offset before the LS is still the true offset
+ * that was valid before the leap second. It might have been updated to match
+ * the offset after the leap second (as in the GPS navigation message, where
+ * ::UTC::delta_tls switches to the same value as ::UTC::delta_tlsf after the
+ * leap second has passed, so we use the offset after the leap second by default.
+ *
+ * Only if a leap second is currently being announced, and @a *p_t64_tai is still
+ * <b>before</b> the time the leap second occurs, we also apply the step count
+ * to yield the offset that is valid before the leap second.
+ *
+ * @param[in] p_t64_tai Pointer to an ::MBG_TIME64_T type providing the current TAI time.
+
+ * @param[in] p_lsi Pointer to an ::MBG_LS_INFO variable with current
+ * %UTC/leap second information.
+ *
+ * @return The determined offset required to compute %UTC.
+ *
+ * @ingroup mbgtimex_time_cnv_fncs
+ * @see ::mbg_time64_utc_to_tai
+ * @see ::mbg_time64_tai_to_utc
+ */
+long mbg_offs_utc_from_tai( const MBG_TIME64_T *p_t64_tai, const MBG_LS_INFO *p_lsi )
+{
+ long l = p_lsi->offs_tai_utc;
+
+ if ( p_lsi->ls_step && ( *p_t64_tai < p_lsi->t64_ls_tai ) )
+ l -= p_lsi->ls_step;
+
+ return l;
+
+} // mbg_offs_utc_from_tai
+
+
+
+static __mbg_inline /*HDR*/
+/**
+ * @brief Convert a TAI timestamp in ::MBG_TIME64_T format to %UTC.
+ *
+ * TAI is ahead of %UTC by 37 or even more seconds, so the value
+ * of the computed %UTC timestamp is less than the value of the
+ * original TAI timestamp.
+ *
+ * @param[out] p_t64_utc Address of an ::MBG_TIME64_T to take the computed %UTC timestamp.
+ *
+ * @param[in] p_t64_tai Pointer to an ::MBG_TIME64_T providing the TAI timestamp to be converted.
+ *
+ * @param[in] p_lsi Pointer to an ::MBG_LS_INFO variable with current %UTC/leap second information.
+ *
+ * @return ::MBG_SUCCESS on success, else one of the @ref MBG_ERROR_CODES
+ *
+ * @ingroup mbgtimex_time_fncs
+ * @see ::mbg_time64_utc_to_tai
+ * @see ::mbg_offs_utc_from_tai
+ */
+int mbg_time64_tai_to_utc( MBG_TIME64_T *p_t64_utc, const MBG_TIME64_T *p_t64_tai, const MBG_LS_INFO *p_lsi )
+{
+ *p_t64_utc = *p_t64_tai - mbg_offs_utc_from_tai( p_t64_tai, p_lsi );
+
+ return MBG_SUCCESS;
+
+} // mbg_time64_tai_to_utc
+
+
+
+static __mbg_inline /*HDR*/
+/**
+ * @brief Convert a %UTC timestamp in ::MBG_TIME64_T format to TAI.
+ *
+ * NOTE: If a leap second is inserted, the conversion may
+ * be <b>ambiguous during the leap second</b> itself because
+ * the %UTC time is usually simply stepped back by 1 s, which
+ * results in 2 consecutive %UTC timestamps with the same
+ * numeric value.
+ *
+ * @param[out] p_t64_tai Address of an ::MBG_TIME64_T to take the computed TAI timestamp.
+ *
+ * @param[in] p_t64_utc Pointer to a %UTC timestamp in ::MBG_TIME64_T format to be converted.
+ *
+ * @param[in] p_lsi Pointer to an ::MBG_LS_INFO variable with current %UTC/leap second information.
+ *
+ * @return ::MBG_SUCCESS on success, else one of the @ref MBG_ERROR_CODES
+ *
+ * @ingroup mbgtimex_time_fncs
+ * @see ::mbg_time64_tai_to_utc
+ */
+int mbg_time64_utc_to_tai( MBG_TIME64_T *p_t64_tai, const MBG_TIME64_T *p_t64_utc, const MBG_LS_INFO *p_lsi )
+{
+ // First we assume the TAI time to be computed
+ // is *after* the last known leap second.
+ *p_t64_tai = *p_t64_utc + p_lsi->offs_tai_utc;
+
+ // If a leap second is currently being announced,
+ // and the *current time* is still *before* the LS,
+ // we have to subtract the offset that will be
+ // applied when the leap second is handled.
+ // NOTE This is ambiguous during the leap second itself.
+ // TODO Check if in the comparison below '<=' is better
+ // than '<'.
+ if ( p_lsi->ls_step && ( *p_t64_tai < p_lsi->t64_ls_tai ) )
+ *p_t64_tai -= p_lsi->ls_step;
+
+ return MBG_SUCCESS;
+
+} // mbg_time64_utc_to_tai
+
+
+
+static __mbg_inline /*HDR*/
+/**
+ * @brief Convert GPS week number plus second-of-week to to POSIX @a time_t format.
+ *
+ * Only the data format is converted, the offset between
+ * GPS time scale and %UTC scale is not taken into account.
+ *
+ * @deprecated This function is deprecated, use ::mbg_gps_wn_wsec_to_time64_t preferably.
+ *
+ * @param[out] p_t Address of an ::MBG_TIME64_T type to take the computed timestamp.
+ *
+ * @param[in] wn A GPS week number as ::GPS_WNUM.
+ *
+ * @param[in] wsec Seconds of the week as ::GPS_WSEC, >= 0.
+ *
+ * @return ::MBG_SUCCESS on success, else one of the @ref MBG_ERROR_CODES
+ *
+ * @ingroup mbgtimex_gps_posix_time_cnv_fncs
+ */
+int _T_DEPRECATED_BY( "mbg_gps_wn_wsec_to_time64_t" ) mbg_gps_wn_wsec_to_time_t( time_t *p_t, GPS_WNUM wn, GPS_WSEC wsec )
+{
+ MBG_TIME64_T t64;
+ int rc = mbg_gps_wn_wsec_to_time64_t( &t64, wn, wsec );
+
+ // On success we still check if the result is in a valid range
+ // and convert the result, if required.
+ if ( mbg_rc_is_success( rc ) )
+ rc = mbg_trnc_time64_t_to_time_t( p_t, &t64 );
+
+ return rc;
+
+} // mbg_gps_wn_wsec_to_time_t
+
+
+
+static __mbg_inline /*HDR*/
+/**
+ * @brief Convert a GPS week number / day-of-week pair to POSIX @a time_t format.
+ *
+ * Only the data format is converted, the offset between
+ * GPS time scale and %UTC scale is not taken into account.
+ *
+ * @note The week number WNlsf from the ::UTC parameter set contains
+ * the 8 LSBs of the full week number only, covering a +/- ~128 week
+ * range. So if the leap second date is ~128 weeks or more before or
+ * after the time of reception from the satellites, the WNlsf field
+ * is ambiguous, and the ::mbg_find_true_gps_wn_lsf function
+ * can be used to try to solve this ambiguity and return the
+ * true extended week number, if possible.
+ *
+ * @note If this function is called to compute the leap second
+ * date from the GPS ::UTC parameters, the computed timestamp is
+ * associated with <b>the end of the leap second transition</b>, e.g.
+ * <em>2017-01-01 00:00:00</em> rather than <em>2016-12-31 23:59:59</em>.
+ *
+ * @param[out] p_t Address of a POSIX @a time_t type to take the computed timestamp.
+ *
+ * @param[in] wn_lsf The true extended week number in which
+ * a leap second occurs, see ::UTC::WNlsf.
+ *
+ * @param[in] dn_t A day-of-week at the end of which the
+ * leap second occurs, see ::UTC::DNt.
+ *
+ * @return ::MBG_SUCCESS on success, else one of the @ref MBG_ERROR_CODES
+ *
+ * @ingroup mbgtimex_gps_posix_time_cnv_fncs
+ * @see ::mbg_find_true_gps_wn_lsf
+ */
+int _T_DEPRECATED_BY( "mbg_gps_wn_dn_to_time64_t" ) mbg_gps_wn_dn_to_time_t( time_t *p_t, GPS_WNUM wn_lsf, GPS_DNUM dn_t )
+{
+ return mbg_gps_wn_wsec_to_time_t( p_t, wn_lsf, (GPS_WSEC) dn_t * SECS_PER_DAY );
+
+} // mbg_gps_wn_dn_to_time_t
+
+
+
+static __mbg_inline /*HDR*/
+/**
+ * @brief Convert POSIX @a time_t format to GPS week number and second-of-week.
+ *
+ * Only the data format is converted, the offset between
+ * GPS time scale and %UTC scale is not taken into account.
+ *
+ * The computed week number can be negative if @a *p_t is before
+ * the GPS epoch. The computed values are normalized so that
+ * the second-of-week is always non-negative, i.e. contains
+ * the seconds after the beginning of the week, even if
+ * the week number is negative.
+ *
+ * @param[out] p_wn Address of a ::GPS_WNUM variable for the computed week number.
+ *
+ * @param[out] p_wsec Address of a ::GPS_WSEC variable for the computed second-of-week.
+ *
+ * @param[in] p_t Pointer to the original timestamp in POSIX @a time_t format.
+ *
+ * @return ::MBG_SUCCESS on success, else one of the @ref MBG_ERROR_CODES
+ *
+ * @ingroup mbgtimex_gps_posix_time_cnv_fncs
+ */
+int _T_DEPRECATED_BY( mbg_time64_t_to_gps_wn_wsec ) mbg_time_t_to_gps_wn_wsec( GPS_WNUM *p_wn, GPS_WSEC *p_wsec, const time_t *p_t )
+{
+ MBG_TIME64_T t64;
+
+ int rc = mbg_exp_time_t_to_time64_t( &t64, p_t );
+
+ if ( mbg_rc_is_success( rc ) )
+ mbg_time64_t_to_gps_wn_wsec( p_wn, p_wsec, &t64 );
+
+ return rc;
+
+} // mbg_time_t_to_gps_wn_wsec
+
+
+
+static __mbg_inline /*HDR*/
+/**
+ * @brief Convert POSIX @a time_t format to ::T_GPS.
+ *
+ * Only the data format is converted, the offset between
+ * GPS time scale and %UTC scale is not taken into account.
+ *
+ * @param[out] p_t_gps Address of a ::T_GPS variable to take the computed timestamp.
+ *
+ * @param[in] p_t Pointer to the original timestamp in POSIX @a time_t format.
+ *
+ * @return ::MBG_SUCCESS on success, else one of the @ref MBG_ERROR_CODES
+ *
+ * @ingroup mbgtimex_gps_posix_time_cnv_fncs
+ * @see ::mbg_t_gps_to_time_t
+ */
+int _T_DEPRECATED_BY( mbg_time64_t_to_gps_wn_wsec ) mbg_time_t_to_t_gps( T_GPS *p_t_gps, const time_t *p_t )
+{
+ MBG_TIME64_T t64;
+
+ int rc = mbg_exp_time_t_to_time64_t( &t64, p_t );
+
+ if ( mbg_rc_is_success( rc ) )
+ rc = mbg_time64_t_to_t_gps( p_t_gps, &t64 );
+
+ return rc;
+
+} // mbg_time_t_to_t_gps
+
+
+
+static __mbg_inline /*HDR*/
+/**
+ * @brief Convert ::T_GPS to POSIX @a time_t format.
+ *
+ * Only the data format is converted, the offset between
+ * GPS time scale and %UTC scale is not taken into account.
+ *
+ * @param[out] p_t Address of a POSIX @a time_t type to take the computed timestamp.
+ *
+ * @param[in] p_t_gps The timestamp to be converted, in ::T_GPS format.
+ *
+ * @return ::MBG_SUCCESS on success, else one of the @ref MBG_ERROR_CODES
+ *
+ * @ingroup mbgtimex_gps_posix_time_cnv_fncs
+ * @see ::mbg_time_t_to_t_gps
+ */
+int _T_DEPRECATED_BY( mbg_gps_wn_wsec_to_time64_t ) mbg_t_gps_to_time_t( time_t *p_t, const T_GPS *p_t_gps )
+{
+ MBG_TIME64_T t64;
+ int rc = mbg_gps_wn_wsec_to_time64_t( &t64, p_t_gps->wn, p_t_gps->sec );
+
+ // On success we still check if the result is in a valid range
+ // and convert the result, if required.
+ if ( mbg_rc_is_success( rc ) )
+ rc = mbg_trnc_time64_t_to_time_t( p_t, &t64 );
+
+ return rc;
+
+} // mbg_t_gps_to_time_t
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+
+/* End of header body */
+
+#undef _ext
+#undef _DO_INIT
+
+#endif /* _MBGTIMEX_H */