diff options
author | Martin Burnicki <martin.burnicki@meinberg.de> | 2021-11-11 12:16:04 +0100 |
---|---|---|
committer | Martin Burnicki <martin.burnicki@meinberg.de> | 2022-07-08 17:21:00 +0200 |
commit | 5aad422e486b8ed4b513569d672fd75addb752a7 (patch) | |
tree | 5866155eb1b09052bf91b7d084f9d9dcd0dc789a | |
parent | e5ee94dce7833995f72247cf59301030aae0f8b7 (diff) | |
download | mbgtools-win-5aad422e486b8ed4b513569d672fd75addb752a7.tar.gz mbgtools-win-5aad422e486b8ed4b513569d672fd75addb752a7.zip |
Add some more mbglib files
-rw-r--r-- | mbglib/common/mbgtime.c | 660 | ||||
-rw-r--r-- | mbglib/common/mbgtimex.c | 1282 | ||||
-rw-r--r-- | mbglib/common/mbgtimex.h | 929 |
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 */ |