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