Home | History | Annotate | Line # | Download | only in libntp
      1  1.1  christos /*	$NetBSD: ntp_calgps.c,v 1.2 2020/05/25 20:47:24 christos Exp $	*/
      2  1.1  christos 
      3  1.1  christos /*
      4  1.1  christos  * ntp_calgps.c - calendar for GPS/GNSS based clocks
      5  1.1  christos  *
      6  1.1  christos  * Written by Juergen Perlinger (perlinger (at) ntp.org) for the NTP project.
      7  1.1  christos  * The contents of 'html/copyright.html' apply.
      8  1.1  christos  *
      9  1.1  christos  * --------------------------------------------------------------------
     10  1.1  christos  *
     11  1.1  christos  * This module implements stuff often used with GPS/GNSS receivers
     12  1.1  christos  */
     13  1.1  christos 
     14  1.1  christos #include <config.h>
     15  1.1  christos #include <sys/types.h>
     16  1.1  christos 
     17  1.1  christos #include "ntp_types.h"
     18  1.1  christos #include "ntp_calendar.h"
     19  1.1  christos #include "ntp_calgps.h"
     20  1.1  christos #include "ntp_stdlib.h"
     21  1.1  christos #include "ntp_unixtime.h"
     22  1.1  christos 
     23  1.1  christos #include "ntp_fp.h"
     24  1.1  christos #include "ntpd.h"
     25  1.1  christos #include "vint64ops.h"
     26  1.1  christos 
     27  1.1  christos /* ====================================================================
     28  1.1  christos  * misc. helpers -- might go elsewhere sometime?
     29  1.1  christos  * ====================================================================
     30  1.1  christos  */
     31  1.1  christos 
     32  1.1  christos l_fp
     33  1.1  christos ntpfp_with_fudge(
     34  1.1  christos 	l_fp	lfp,
     35  1.1  christos 	double	ofs
     36  1.1  christos 	)
     37  1.1  christos {
     38  1.1  christos 	l_fp	fpo;
     39  1.1  christos 	/* calculate 'lfp - ofs' as '(l_fp)(-ofs) + lfp': negating a
     40  1.1  christos 	 * double is cheap, as it only flips one bit...
     41  1.1  christos 	 */
     42  1.1  christos 	ofs = -ofs;
     43  1.1  christos 	DTOLFP(ofs, &fpo);
     44  1.1  christos 	L_ADD(&fpo, &lfp);
     45  1.1  christos 	return fpo;
     46  1.1  christos }
     47  1.1  christos 
     48  1.1  christos 
     49  1.1  christos /* ====================================================================
     50  1.1  christos  * GPS calendar functions
     51  1.1  christos  * ====================================================================
     52  1.1  christos  */
     53  1.1  christos 
     54  1.1  christos /* --------------------------------------------------------------------
     55  1.1  christos  * normalization functions for day/time and week/time representations.
     56  1.1  christos  * Since we only use moderate offsets (leap second corrections and
     57  1.1  christos  * alike) it does not really pay off to do a floor-corrected division
     58  1.1  christos  * here.  We use compare/decrement/increment loops instead.
     59  1.1  christos  * --------------------------------------------------------------------
     60  1.1  christos  */
     61  1.1  christos static void
     62  1.1  christos _norm_ntp_datum(
     63  1.1  christos 	TNtpDatum *	datum
     64  1.1  christos 	)
     65  1.1  christos {
     66  1.1  christos 	static const int32_t limit = SECSPERDAY;
     67  1.1  christos 
     68  1.1  christos 	if (datum->secs >= limit) {
     69  1.1  christos 		do
     70  1.1  christos 			++datum->days;
     71  1.1  christos 		while ((datum->secs -= limit) >= limit);
     72  1.1  christos 	} else if (datum->secs < 0) {
     73  1.1  christos 		do
     74  1.1  christos 			--datum->days;
     75  1.1  christos 		while ((datum->secs += limit) < 0);
     76  1.1  christos 	}
     77  1.1  christos }
     78  1.1  christos 
     79  1.1  christos static void
     80  1.1  christos _norm_gps_datum(
     81  1.1  christos 	TGpsDatum *	datum
     82  1.1  christos 	)
     83  1.1  christos {
     84  1.1  christos 	static const int32_t limit = 7 * SECSPERDAY;
     85  1.1  christos 
     86  1.1  christos 	if (datum->wsecs >= limit) {
     87  1.1  christos 		do
     88  1.1  christos 			++datum->weeks;
     89  1.1  christos 		while ((datum->wsecs -= limit) >= limit);
     90  1.1  christos 	} else if (datum->wsecs < 0) {
     91  1.1  christos 		do
     92  1.1  christos 			--datum->weeks;
     93  1.1  christos 		while ((datum->wsecs += limit) < 0);
     94  1.1  christos 	}
     95  1.1  christos }
     96  1.1  christos 
     97  1.1  christos /* --------------------------------------------------------------------
     98  1.1  christos  * Add an offset to a day/time and week/time representation.
     99  1.1  christos  *
    100  1.1  christos  * !!Attention!! the offset should be small, compared to the time period
    101  1.1  christos  * (either a day or a week).
    102  1.1  christos  * --------------------------------------------------------------------
    103  1.1  christos  */
    104  1.1  christos void
    105  1.1  christos gpsntp_add_offset(
    106  1.1  christos 	TNtpDatum *	datum,
    107  1.1  christos 	l_fp		offset
    108  1.1  christos 	)
    109  1.1  christos {
    110  1.1  christos 	/* fraction can be added easily */
    111  1.1  christos 	datum->frac += offset.l_uf;
    112  1.1  christos 	datum->secs += (datum->frac < offset.l_uf);
    113  1.1  christos 
    114  1.1  christos 	/* avoid integer overflow on the seconds */
    115  1.1  christos 	if (offset.l_ui >= INT32_MAX)
    116  1.1  christos 		datum->secs -= (int32_t)~offset.l_ui + 1;
    117  1.1  christos 	else
    118  1.1  christos 		datum->secs += (int32_t)offset.l_ui;
    119  1.1  christos 	_norm_ntp_datum(datum);
    120  1.1  christos }
    121  1.1  christos 
    122  1.1  christos void
    123  1.1  christos gpscal_add_offset(
    124  1.1  christos 	TGpsDatum *	datum,
    125  1.1  christos 	l_fp		offset
    126  1.1  christos 	)
    127  1.1  christos {
    128  1.1  christos 	/* fraction can be added easily */
    129  1.1  christos 	datum->frac  += offset.l_uf;
    130  1.1  christos 	datum->wsecs += (datum->frac < offset.l_uf);
    131  1.1  christos 
    132  1.1  christos 
    133  1.1  christos 	/* avoid integer overflow on the seconds */
    134  1.1  christos 	if (offset.l_ui >= INT32_MAX)
    135  1.1  christos 		datum->wsecs -= (int32_t)~offset.l_ui + 1;
    136  1.1  christos 	else
    137  1.1  christos 		datum->wsecs += (int32_t)offset.l_ui;
    138  1.1  christos 	_norm_gps_datum(datum);
    139  1.1  christos }
    140  1.1  christos 
    141  1.1  christos /* -------------------------------------------------------------------
    142  1.1  christos  *	API functions civil calendar and NTP datum
    143  1.1  christos  * -------------------------------------------------------------------
    144  1.1  christos  */
    145  1.1  christos 
    146  1.1  christos static TNtpDatum
    147  1.1  christos _gpsntp_fix_gps_era(
    148  1.1  christos 	TcNtpDatum * in
    149  1.1  christos 	)
    150  1.1  christos {
    151  1.1  christos 	/* force result in basedate era
    152  1.1  christos 	 *
    153  1.1  christos 	 * When calculating this directly in days, we have to execute a
    154  1.1  christos 	 * real modulus calculation, since we're obviously not doing a
    155  1.1  christos 	 * modulus by a power of 2. Executing this as true floor mod
    156  1.1  christos 	 * needs some care and is done under explicit usage of one's
    157  1.1  christos 	 * complement and masking to get mostly branchless code.
    158  1.1  christos 	 */
    159  1.1  christos 	static uint32_t const	clen = 7*1024;
    160  1.1  christos 
    161  1.1  christos 	uint32_t	base, days, sign;
    162  1.1  christos 	TNtpDatum	out = *in;
    163  1.1  christos 
    164  1.1  christos 	/* Get base in NTP day scale. No overflows here. */
    165  1.1  christos 	base = (basedate_get_gpsweek() + GPSNTP_WSHIFT) * 7
    166  1.1  christos 	     - GPSNTP_DSHIFT;
    167  1.1  christos 	days = out.days;
    168  1.1  christos 
    169  1.1  christos 	sign = (uint32_t)-(days < base);
    170  1.1  christos 	days = sign ^ (days - base);
    171  1.1  christos 	days %= clen;
    172  1.1  christos 	days = base + (sign & clen) + (sign ^ days);
    173  1.1  christos 
    174  1.1  christos 	out.days = days;
    175  1.1  christos 	return out;
    176  1.1  christos }
    177  1.1  christos 
    178  1.1  christos TNtpDatum
    179  1.1  christos gpsntp_fix_gps_era(
    180  1.1  christos 	TcNtpDatum * in
    181  1.1  christos 	)
    182  1.1  christos {
    183  1.1  christos 	TNtpDatum out = *in;
    184  1.1  christos 	_norm_ntp_datum(&out);
    185  1.1  christos 	return _gpsntp_fix_gps_era(&out);
    186  1.1  christos }
    187  1.1  christos 
    188  1.1  christos /* ----------------------------------------------------------------- */
    189  1.1  christos static TNtpDatum
    190  1.1  christos _gpsntp_from_daytime(
    191  1.1  christos 	TcCivilDate *	jd,
    192  1.1  christos 	l_fp		fofs,
    193  1.1  christos 	TcNtpDatum *	pivot,
    194  1.1  christos 	int		warp
    195  1.1  christos 	)
    196  1.1  christos {
    197  1.1  christos 	static const int32_t shift = SECSPERDAY / 2;
    198  1.1  christos 
    199  1.1  christos 	TNtpDatum	retv;
    200  1.1  christos 
    201  1.1  christos 	/* set result based on pivot -- ops order is important here */
    202  1.1  christos 	ZERO(retv);
    203  1.1  christos 	retv.secs = ntpcal_date_to_daysec(jd);
    204  1.1  christos 	gpsntp_add_offset(&retv, fofs);	/* result is normalized */
    205  1.1  christos 	retv.days = pivot->days;
    206  1.1  christos 
    207  1.1  christos 	/* Manual periodic extension without division: */
    208  1.1  christos 	if (pivot->secs < shift) {
    209  1.1  christos 		int32_t lim = pivot->secs + shift;
    210  1.1  christos 		retv.days -= (retv.secs > lim ||
    211  1.1  christos 			      (retv.secs == lim && retv.frac >= pivot->frac));
    212  1.1  christos 	} else {
    213  1.1  christos 		int32_t lim = pivot->secs - shift;
    214  1.1  christos 		retv.days += (retv.secs < lim ||
    215  1.1  christos 			      (retv.secs == lim && retv.frac < pivot->frac));
    216  1.1  christos 	}
    217  1.1  christos 	return warp ? _gpsntp_fix_gps_era(&retv) : retv;
    218  1.1  christos }
    219  1.1  christos 
    220  1.1  christos /* -----------------------------------------------------------------
    221  1.1  christos  * Given the time-of-day part of a civil datum and an additional
    222  1.1  christos  * (fractional) offset, calculate a full time stamp around a given pivot
    223  1.1  christos  * time so that the difference between the pivot and the resulting time
    224  1.1  christos  * stamp is less or equal to 12 hours absolute.
    225  1.1  christos  */
    226  1.1  christos TNtpDatum
    227  1.1  christos gpsntp_from_daytime2_ex(
    228  1.1  christos 	TcCivilDate *	jd,
    229  1.1  christos 	l_fp		fofs,
    230  1.1  christos 	TcNtpDatum *	pivot,
    231  1.1  christos 	int/*BOOL*/	warp
    232  1.1  christos 	)
    233  1.1  christos {
    234  1.1  christos 	TNtpDatum	dpiv = *pivot;
    235  1.1  christos 	_norm_ntp_datum(&dpiv);
    236  1.1  christos 	return _gpsntp_from_daytime(jd, fofs, &dpiv, warp);
    237  1.1  christos }
    238  1.1  christos 
    239  1.1  christos /* -----------------------------------------------------------------
    240  1.1  christos  * This works similar to 'gpsntp_from_daytime1()' and actually even uses
    241  1.1  christos  * it, but the pivot is calculated from the pivot given as 'l_fp' in NTP
    242  1.1  christos  * time scale. This is in turn expanded around the current system time,
    243  1.1  christos  * and the resulting absolute pivot is then used to calculate the full
    244  1.1  christos  * NTP time stamp.
    245  1.1  christos  */
    246  1.1  christos TNtpDatum
    247  1.1  christos gpsntp_from_daytime1_ex(
    248  1.1  christos 	TcCivilDate *	jd,
    249  1.1  christos 	l_fp		fofs,
    250  1.1  christos 	l_fp		pivot,
    251  1.1  christos 	int/*BOOL*/	warp
    252  1.1  christos 	)
    253  1.1  christos {
    254  1.1  christos 	vint64		pvi64;
    255  1.1  christos 	TNtpDatum	dpiv;
    256  1.1  christos 	ntpcal_split	split;
    257  1.1  christos 
    258  1.1  christos 	pvi64 = ntpcal_ntp_to_ntp(pivot.l_ui, NULL);
    259  1.1  christos 	split = ntpcal_daysplit(&pvi64);
    260  1.1  christos 	dpiv.days = split.hi;
    261  1.1  christos 	dpiv.secs = split.lo;
    262  1.1  christos 	dpiv.frac = pivot.l_uf;
    263  1.1  christos 	return _gpsntp_from_daytime(jd, fofs, &dpiv, warp);
    264  1.1  christos }
    265  1.1  christos 
    266  1.1  christos /* -----------------------------------------------------------------
    267  1.1  christos  * Given a calendar date, zap it into a GPS time format and then convert
    268  1.1  christos  * that one into the NTP time scale.
    269  1.1  christos  */
    270  1.1  christos TNtpDatum
    271  1.1  christos gpsntp_from_calendar_ex(
    272  1.1  christos 	TcCivilDate *	jd,
    273  1.1  christos 	l_fp		fofs,
    274  1.1  christos 	int/*BOOL*/	warp
    275  1.1  christos 	)
    276  1.1  christos {
    277  1.1  christos 	TGpsDatum	gps;
    278  1.1  christos 	gps = gpscal_from_calendar_ex(jd, fofs, warp);
    279  1.1  christos 	return gpsntp_from_gpscal_ex(&gps, FALSE);
    280  1.1  christos }
    281  1.1  christos 
    282  1.1  christos /* -----------------------------------------------------------------
    283  1.1  christos  * create a civil calendar datum from a NTP date representation
    284  1.1  christos  */
    285  1.1  christos void
    286  1.1  christos gpsntp_to_calendar(
    287  1.1  christos 	TCivilDate * cd,
    288  1.1  christos 	TcNtpDatum * nd
    289  1.1  christos 	)
    290  1.1  christos {
    291  1.1  christos 	memset(cd, 0, sizeof(*cd));
    292  1.1  christos 	ntpcal_rd_to_date(
    293  1.1  christos 		cd,
    294  1.1  christos 		nd->days + DAY_NTP_STARTS + ntpcal_daysec_to_date(
    295  1.1  christos 			cd, nd->secs));
    296  1.1  christos }
    297  1.1  christos 
    298  1.1  christos /* -----------------------------------------------------------------
    299  1.1  christos  * get day/tod representation from week/tow datum
    300  1.1  christos  */
    301  1.1  christos TNtpDatum
    302  1.1  christos gpsntp_from_gpscal_ex(
    303  1.1  christos 	TcGpsDatum *	gd,
    304  1.1  christos     	int/*BOOL*/	warp
    305  1.1  christos 	)
    306  1.1  christos {
    307  1.1  christos 	TNtpDatum	retv;
    308  1.1  christos 	vint64		ts64;
    309  1.1  christos 	ntpcal_split	split;
    310  1.1  christos 	TGpsDatum	date = *gd;
    311  1.1  christos 
    312  1.1  christos 	if (warp) {
    313  1.1  christos 		uint32_t base = basedate_get_gpsweek() + GPSNTP_WSHIFT;
    314  1.1  christos 		_norm_gps_datum(&date);
    315  1.1  christos 		date.weeks = ((date.weeks - base) & 1023u) + base;
    316  1.1  christos 	}
    317  1.1  christos 
    318  1.1  christos 	ts64  = ntpcal_weekjoin(date.weeks, date.wsecs);
    319  1.1  christos 	ts64  = subv64u32(&ts64, (GPSNTP_DSHIFT * SECSPERDAY));
    320  1.1  christos 	split = ntpcal_daysplit(&ts64);
    321  1.1  christos 
    322  1.1  christos 	retv.frac = gd->frac;
    323  1.1  christos 	retv.secs = split.lo;
    324  1.1  christos 	retv.days = split.hi;
    325  1.1  christos 	return retv;
    326  1.1  christos }
    327  1.1  christos 
    328  1.1  christos /* -----------------------------------------------------------------
    329  1.1  christos  * get LFP from ntp datum
    330  1.1  christos  */
    331  1.1  christos l_fp
    332  1.1  christos ntpfp_from_ntpdatum(
    333  1.1  christos 	TcNtpDatum *	nd
    334  1.1  christos 	)
    335  1.1  christos {
    336  1.1  christos 	l_fp retv;
    337  1.1  christos 
    338  1.1  christos 	retv.l_uf = nd->frac;
    339  1.1  christos 	retv.l_ui = nd->days * (uint32_t)SECSPERDAY
    340  1.1  christos 	          + nd->secs;
    341  1.1  christos 	return retv;
    342  1.1  christos }
    343  1.1  christos 
    344  1.1  christos /* -------------------------------------------------------------------
    345  1.1  christos  *	API functions GPS week calendar
    346  1.1  christos  *
    347  1.1  christos  * Here we use a calendar base of 1899-12-31, so the NTP epoch has
    348  1.1  christos  * { 0, 86400.0 } in this representation.
    349  1.1  christos  * -------------------------------------------------------------------
    350  1.1  christos  */
    351  1.1  christos 
    352  1.1  christos static TGpsDatum
    353  1.1  christos _gpscal_fix_gps_era(
    354  1.1  christos 	TcGpsDatum * in
    355  1.1  christos 	)
    356  1.1  christos {
    357  1.1  christos 	/* force result in basedate era
    358  1.1  christos 	 *
    359  1.1  christos 	 * This is based on calculating the modulus to a power of two,
    360  1.1  christos 	 * so signed integer overflow does not affect the result. Which
    361  1.1  christos 	 * in turn makes for a very compact calculation...
    362  1.1  christos 	 */
    363  1.1  christos 	uint32_t	base, week;
    364  1.1  christos 	TGpsDatum	out = *in;
    365  1.1  christos 
    366  1.1  christos 	week = out.weeks;
    367  1.1  christos 	base = basedate_get_gpsweek() + GPSNTP_WSHIFT;
    368  1.1  christos 	week = base + ((week - base) & (GPSWEEKS - 1));
    369  1.1  christos 	out.weeks = week;
    370  1.1  christos 	return out;
    371  1.1  christos }
    372  1.1  christos 
    373  1.1  christos TGpsDatum
    374  1.1  christos gpscal_fix_gps_era(
    375  1.1  christos 	TcGpsDatum * in
    376  1.1  christos 	)
    377  1.1  christos {
    378  1.1  christos 	TGpsDatum out = *in;
    379  1.1  christos 	_norm_gps_datum(&out);
    380  1.1  christos 	return _gpscal_fix_gps_era(&out);
    381  1.1  christos }
    382  1.1  christos 
    383  1.1  christos /* -----------------------------------------------------------------
    384  1.1  christos  * Given a calendar date, zap it into a GPS time format and the do a
    385  1.1  christos  * proper era mapping in the GPS time scale, based on the GPS base date,
    386  1.1  christos  * if so requested.
    387  1.1  christos  *
    388  1.1  christos  * This function also augments the century if just a 2-digit year
    389  1.1  christos  * (0..99) is provided on input.
    390  1.1  christos  *
    391  1.1  christos  * This is a fail-safe against GPS receivers with an unknown starting
    392  1.1  christos  * point for their internal calendar calculation and therefore
    393  1.1  christos  * unpredictable (but reproducible!) rollover behavior. While there
    394  1.1  christos  * *are* receivers that create a full date in the proper way, many
    395  1.1  christos  * others just don't.  The overall damage is minimized by simply not
    396  1.1  christos  * trusting the era mapping of the receiver and doing the era assignment
    397  1.1  christos  * with a configurable base date *inside* ntpd.
    398  1.1  christos  */
    399  1.1  christos TGpsDatum
    400  1.1  christos gpscal_from_calendar_ex(
    401  1.1  christos 	TcCivilDate *	jd,
    402  1.1  christos 	l_fp		fofs,
    403  1.1  christos 	int/*BOOL*/	warp
    404  1.1  christos 	)
    405  1.1  christos {
    406  1.1  christos 	/*  (-DAY_GPS_STARTS) (mod 7*1024) -- complement of cycle shift */
    407  1.1  christos 	static const uint32_t s_compl_shift =
    408  1.1  christos 	    (7 * 1024) - DAY_GPS_STARTS % (7 * 1024);
    409  1.1  christos 
    410  1.1  christos 	TGpsDatum	gps;
    411  1.1  christos 	TCivilDate	cal;
    412  1.1  christos 	int32_t		days, week;
    413  1.1  christos 
    414  1.1  christos 	/* if needed, convert from 2-digit year to full year
    415  1.1  christos 	 * !!NOTE!! works only between 1980 and 2079!
    416  1.1  christos 	 */
    417  1.1  christos 	cal = *jd;
    418  1.1  christos 	if (cal.year < 80)
    419  1.1  christos 		cal.year += 2000;
    420  1.1  christos 	else if (cal.year < 100)
    421  1.1  christos 		cal.year += 1900;
    422  1.1  christos 
    423  1.1  christos 	/* get RDN from date, possibly adjusting the century */
    424  1.1  christos again:	if (cal.month && cal.monthday) {	/* use Y/M/D civil date */
    425  1.1  christos 		days = ntpcal_date_to_rd(&cal);
    426  1.1  christos 	} else {				/* using Y/DoY date */
    427  1.1  christos 		days = ntpcal_year_to_ystart(cal.year)
    428  1.1  christos 		     + (int32_t)cal.yearday
    429  1.1  christos 		     - 1; /* both RDN and yearday start with '1'. */
    430  1.1  christos 	}
    431  1.1  christos 
    432  1.1  christos 	/* Rebase to days after the GPS epoch. 'days' is positive here,
    433  1.1  christos 	 * but it might be less than the GPS epoch start. Depending on
    434  1.1  christos 	 * the input, we have to do different things to get the desired
    435  1.1  christos 	 * result. (Since we want to remap the era anyway, we only have
    436  1.1  christos 	 * to retain congruential identities....)
    437  1.1  christos 	 */
    438  1.1  christos 
    439  1.1  christos 	if (days >= DAY_GPS_STARTS) {
    440  1.1  christos 		/* simply shift to days since GPS epoch */
    441  1.1  christos 		days -= DAY_GPS_STARTS;
    442  1.1  christos 	} else if (jd->year < 100) {
    443  1.1  christos 		/* Two-digit year on input: add another century and
    444  1.1  christos 		 * retry.  This can happen only if the century expansion
    445  1.1  christos 		 * yielded a date between 1980-01-01 and 1980-01-05,
    446  1.1  christos 		 * both inclusive. We have at most one retry here.
    447  1.1  christos 		 */
    448  1.1  christos 		cal.year += 100;
    449  1.1  christos 		goto again;
    450  1.1  christos 	} else {
    451  1.1  christos 		/* A very bad date before the GPS epoch. There's not
    452  1.1  christos 		 * much we can do, except to add the complement of
    453  1.1  christos 		 * DAY_GPS_STARTS % (7 * 1024) here, that is, use a
    454  1.1  christos 		 * congruential identity: Add the complement instead of
    455  1.1  christos 		 * subtracting the value gives a value with the same
    456  1.1  christos 		 * modulus. But of course, now we MUST to go through a
    457  1.1  christos 		 * cycle fix... because the date was obviously wrong!
    458  1.1  christos 		 */
    459  1.1  christos 		warp  = TRUE;
    460  1.1  christos 		days += s_compl_shift;
    461  1.1  christos 	}
    462  1.1  christos 
    463  1.1  christos 	/* Splitting to weeks is simple now: */
    464  1.1  christos 	week  = days / 7;
    465  1.1  christos 	days -= week * 7;
    466  1.1  christos 
    467  1.1  christos 	/* re-base on start of NTP with weeks mapped to 1024 weeks
    468  1.1  christos 	 * starting with the GPS base day set in the calendar.
    469  1.1  christos 	 */
    470  1.1  christos 	gps.weeks = week + GPSNTP_WSHIFT;
    471  1.1  christos 	gps.wsecs = days * SECSPERDAY + ntpcal_date_to_daysec(&cal);
    472  1.1  christos 	gps.frac  = 0;
    473  1.1  christos 	gpscal_add_offset(&gps, fofs);
    474  1.1  christos 	return warp ? _gpscal_fix_gps_era(&gps) : gps;
    475  1.1  christos }
    476  1.1  christos 
    477  1.1  christos /* -----------------------------------------------------------------
    478  1.1  christos  * get civil date from week/tow representation
    479  1.1  christos  */
    480  1.1  christos void
    481  1.1  christos gpscal_to_calendar(
    482  1.1  christos 	TCivilDate * cd,
    483  1.1  christos 	TcGpsDatum * wd
    484  1.1  christos 	)
    485  1.1  christos {
    486  1.1  christos 	TNtpDatum nd;
    487  1.1  christos 
    488  1.1  christos 	memset(cd, 0, sizeof(*cd));
    489  1.1  christos 	nd = gpsntp_from_gpscal_ex(wd, FALSE);
    490  1.1  christos 	gpsntp_to_calendar(cd, &nd);
    491  1.1  christos }
    492  1.1  christos 
    493  1.1  christos /* -----------------------------------------------------------------
    494  1.1  christos  * Given the week and seconds in week, as well as the fraction/offset
    495  1.1  christos  * (which should/could include the leap seconds offset), unfold the
    496  1.1  christos  * weeks (which are assumed to have just 10 bits) into expanded weeks
    497  1.1  christos  * based on the GPS base date derived from the build date (default) or
    498  1.1  christos  * set by the configuration.
    499  1.1  christos  *
    500  1.1  christos  * !NOTE! This function takes RAW GPS weeks, aligned to the GPS start
    501  1.1  christos  * (1980-01-06) on input. The output weeks will be aligned to NTPD's
    502  1.1  christos  * week calendar start (1899-12-31)!
    503  1.1  christos  */
    504  1.1  christos TGpsDatum
    505  1.1  christos gpscal_from_gpsweek(
    506  1.1  christos 	uint16_t	week,
    507  1.1  christos 	int32_t		secs,
    508  1.1  christos 	l_fp		fofs
    509  1.1  christos 	)
    510  1.1  christos {
    511  1.1  christos 	TGpsDatum retv;
    512  1.1  christos 
    513  1.1  christos 	retv.frac  = 0;
    514  1.1  christos 	retv.wsecs = secs;
    515  1.1  christos 	retv.weeks = week + GPSNTP_WSHIFT;
    516  1.1  christos 	gpscal_add_offset(&retv, fofs);
    517  1.1  christos 	return _gpscal_fix_gps_era(&retv);
    518  1.1  christos }
    519  1.1  christos 
    520  1.1  christos /* -----------------------------------------------------------------
    521  1.1  christos  * internal work horse for time-of-week expansion
    522  1.1  christos  */
    523  1.1  christos static TGpsDatum
    524  1.1  christos _gpscal_from_weektime(
    525  1.1  christos 	int32_t		wsecs,
    526  1.1  christos 	l_fp    	fofs,
    527  1.1  christos 	TcGpsDatum *	pivot
    528  1.1  christos 	)
    529  1.1  christos {
    530  1.1  christos 	static const int32_t shift = SECSPERWEEK / 2;
    531  1.1  christos 
    532  1.1  christos 	TGpsDatum	retv;
    533  1.1  christos 
    534  1.1  christos 	/* set result based on pivot -- ops order is important here */
    535  1.1  christos 	ZERO(retv);
    536  1.1  christos 	retv.wsecs = wsecs;
    537  1.1  christos 	gpscal_add_offset(&retv, fofs);	/* result is normalized */
    538  1.1  christos 	retv.weeks = pivot->weeks;
    539  1.1  christos 
    540  1.1  christos 	/* Manual periodic extension without division: */
    541  1.1  christos 	if (pivot->wsecs < shift) {
    542  1.1  christos 		int32_t lim = pivot->wsecs + shift;
    543  1.1  christos 		retv.weeks -= (retv.wsecs > lim ||
    544  1.1  christos 			       (retv.wsecs == lim && retv.frac >= pivot->frac));
    545  1.1  christos 	} else {
    546  1.1  christos 		int32_t lim = pivot->wsecs - shift;
    547  1.1  christos 		retv.weeks += (retv.wsecs < lim ||
    548  1.1  christos 			       (retv.wsecs == lim && retv.frac < pivot->frac));
    549  1.1  christos 	}
    550  1.1  christos 	return _gpscal_fix_gps_era(&retv);
    551  1.1  christos }
    552  1.1  christos 
    553  1.1  christos /* -----------------------------------------------------------------
    554  1.1  christos  * expand a time-of-week around a pivot given as week datum
    555  1.1  christos  */
    556  1.1  christos TGpsDatum
    557  1.1  christos gpscal_from_weektime2(
    558  1.1  christos 	int32_t		wsecs,
    559  1.1  christos 	l_fp    	fofs,
    560  1.1  christos 	TcGpsDatum *	pivot
    561  1.1  christos 	)
    562  1.1  christos {
    563  1.1  christos 	TGpsDatum wpiv = * pivot;
    564  1.1  christos 	_norm_gps_datum(&wpiv);
    565  1.1  christos 	return _gpscal_from_weektime(wsecs, fofs, &wpiv);
    566  1.1  christos }
    567  1.1  christos 
    568  1.1  christos /* -----------------------------------------------------------------
    569  1.1  christos  * epand a time-of-week around an pivot given as LFP, which in turn
    570  1.1  christos  * is expanded around the current system time and then converted
    571  1.1  christos  * into a week datum.
    572  1.1  christos  */
    573  1.1  christos TGpsDatum
    574  1.1  christos gpscal_from_weektime1(
    575  1.1  christos 	int32_t	wsecs,
    576  1.1  christos 	l_fp    fofs,
    577  1.1  christos 	l_fp    pivot
    578  1.1  christos 	)
    579  1.1  christos {
    580  1.1  christos 	vint64		pvi64;
    581  1.1  christos 	TGpsDatum	wpiv;
    582  1.1  christos 	ntpcal_split	split;
    583  1.1  christos 
    584  1.1  christos 	/* get 64-bit pivot in NTP epoch */
    585  1.1  christos 	pvi64 = ntpcal_ntp_to_ntp(pivot.l_ui, NULL);
    586  1.1  christos 
    587  1.1  christos 	/* convert to weeks since 1899-12-31 and seconds in week */
    588  1.1  christos 	pvi64 = addv64u32(&pvi64, (GPSNTP_DSHIFT * SECSPERDAY));
    589  1.1  christos 	split = ntpcal_weeksplit(&pvi64);
    590  1.1  christos 
    591  1.1  christos 	wpiv.weeks = split.hi;
    592  1.1  christos 	wpiv.wsecs = split.lo;
    593  1.1  christos 	wpiv.frac  = pivot.l_uf;
    594  1.1  christos 	return _gpscal_from_weektime(wsecs, fofs, &wpiv);
    595  1.1  christos }
    596  1.1  christos 
    597  1.1  christos /* -----------------------------------------------------------------
    598  1.1  christos  * get week/tow representation from day/tod datum
    599  1.1  christos  */
    600  1.1  christos TGpsDatum
    601  1.1  christos gpscal_from_gpsntp(
    602  1.1  christos 	TcNtpDatum *	gd
    603  1.1  christos 	)
    604  1.1  christos {
    605  1.1  christos 	TGpsDatum	retv;
    606  1.1  christos 	vint64		ts64;
    607  1.1  christos 	ntpcal_split	split;
    608  1.1  christos 
    609  1.1  christos 	ts64  = ntpcal_dayjoin(gd->days, gd->secs);
    610  1.1  christos 	ts64  = addv64u32(&ts64, (GPSNTP_DSHIFT * SECSPERDAY));
    611  1.1  christos 	split = ntpcal_weeksplit(&ts64);
    612  1.1  christos 
    613  1.1  christos 	retv.frac  = gd->frac;
    614  1.1  christos 	retv.wsecs = split.lo;
    615  1.1  christos 	retv.weeks = split.hi;
    616  1.1  christos 	return retv;
    617  1.1  christos }
    618  1.1  christos 
    619  1.1  christos /* -----------------------------------------------------------------
    620  1.1  christos  * convert week/tow to LFP stamp
    621  1.1  christos  */
    622  1.1  christos l_fp
    623  1.1  christos ntpfp_from_gpsdatum(
    624  1.1  christos 	TcGpsDatum *	gd
    625  1.1  christos 	)
    626  1.1  christos {
    627  1.1  christos 	l_fp retv;
    628  1.1  christos 
    629  1.1  christos 	retv.l_uf = gd->frac;
    630  1.1  christos 	retv.l_ui = gd->weeks * (uint32_t)SECSPERWEEK
    631  1.1  christos 	          + (uint32_t)gd->wsecs
    632  1.1  christos 	          - (uint32_t)SECSPERDAY * GPSNTP_DSHIFT;
    633  1.1  christos 	return retv;
    634  1.1  christos }
    635  1.1  christos 
    636  1.1  christos /* -*-EOF-*- */
    637