1 /* Copyright 2002-2026 CS GROUP
2 * Licensed to CS GROUP (CS) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * CS licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.orekit.time;
18
19 import java.io.Serial;
20 import java.io.Serializable;
21 import java.util.HashMap;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.concurrent.atomic.AtomicReference;
25
26 import org.hipparchus.util.FastMath;
27 import org.orekit.annotation.DefaultDataContext;
28 import org.orekit.data.DataContext;
29 import org.orekit.errors.OrekitException;
30 import org.orekit.errors.OrekitMessages;
31 import org.orekit.frames.EOPEntry;
32 import org.orekit.gnss.SatelliteSystem;
33 import org.orekit.utils.Constants;
34 import org.orekit.utils.IERSConventions;
35
36 /** Container for date in GNSS form.
37 * <p> This class can be used to handle {@link SatelliteSystem#GPS GPS},
38 * {@link SatelliteSystem#GALILEO Galileo}, {@link SatelliteSystem#BEIDOU BeiDou}
39 * and {@link SatelliteSystem#QZSS QZSS} dates. </p>
40 * @author Luc Maisonobe (original code)
41 * @author Bryan Cazabonne (generalization to all GNSS constellations)
42 * @see AbsoluteDate
43 */
44 public class GNSSDate implements Serializable, TimeStamped {
45
46 /** Serializable UID. */
47 @Serial
48 private static final long serialVersionUID = 20221228L;
49
50 /** Duration of a week in days. */
51 private static final int WEEK_D = 7;
52
53 /** Duration of a week in seconds. */
54 private static final double WEEK_S = WEEK_D * Constants.JULIAN_DAY;
55
56 /** Reference date for ensuring continuity across GNSS week rollover.
57 * @since 9.3.1
58 */
59 private static final AtomicReference<DateComponents> ROLLOVER_REFERENCE = new AtomicReference<>(null);
60
61 /** Week number since the GNSS reference epoch. */
62 private final int weekNumber;
63
64 /** Number of seconds since week start. */
65 private final TimeOffset secondsInWeek;
66
67 /** Satellite system.
68 * @since 14.0
69 */
70 private final SatelliteSystem system;
71
72 /** Corresponding date. */
73 private final transient AbsoluteDate date;
74
75 /** Build an instance corresponding to a GNSS date.
76 * <p>
77 * GNSS dates are provided as a week number starting at
78 * the GNSS reference epoch and as a number of seconds
79 * since week start.
80 * </p>
81 * <p>
82 * Many interfaces provide week number modulo the constellation week cycle. In order to cope with
83 * this, when the week number is smaller than the week cycle, this constructor assumes a modulo operation
84 * has been performed and it will fix the week number according to the reference date set up for
85 * handling rollover (see {@link #setRolloverReference(DateComponents) setRolloverReference(reference)}).
86 * If the week number is equal to the week cycle or larger, it will be used without any correction.
87 * </p>
88 *
89 * <p>This method uses the {@link DataContext#getDefault() default data context}.
90 *
91 * @param weekNumber week number
92 * @param secondsInWeek number of seconds since week start
93 * @param system satellite system to consider
94 * @see #GNSSDate(int, double, SatelliteSystem, TimeScales)
95 * @since 12.0
96 */
97 @DefaultDataContext
98 public GNSSDate(final int weekNumber, final double secondsInWeek, final SatelliteSystem system) {
99 this(weekNumber, new TimeOffset(secondsInWeek), system, DataContext.getDefault().getTimeScales());
100 }
101
102 /** Build an instance corresponding to a GNSS date.
103 * <p>
104 * GNSS dates are provided as a week number starting at
105 * the GNSS reference epoch and as a number of seconds
106 * since week start.
107 * </p>
108 * <p>
109 * Many interfaces provide week number modulo the constellation week cycle. In order to cope with
110 * this, when the week number is smaller than the week cycle, this constructor assumes a modulo operation
111 * has been performed and it will fix the week number according to the reference date set up for
112 * handling rollover (see {@link #setRolloverReference(DateComponents) setRolloverReference(reference)}).
113 * If the week number is equal to the week cycle or larger, it will be used without any correction.
114 * </p>
115 *
116 * <p>This method uses the {@link DataContext#getDefault() default data context}.
117 *
118 * @param weekNumber week number
119 * @param secondsInWeek number of seconds since week start
120 * @param system satellite system to consider
121 * @see #GNSSDate(int, double, SatelliteSystem, TimeScales)
122 * @since 13.0
123 */
124 @DefaultDataContext
125 public GNSSDate(final int weekNumber, final TimeOffset secondsInWeek, final SatelliteSystem system) {
126 this(weekNumber, secondsInWeek, system, DataContext.getDefault().getTimeScales());
127 }
128
129 /**
130 * Build an instance corresponding to a GNSS date.
131 * <p>
132 * GNSS dates are provided as a week number starting at the GNSS reference epoch and
133 * as a number of seconds since week start.
134 * </p>
135 * <p>
136 * Many interfaces provide week number modulo the constellation week cycle. In order
137 * to cope with this, when the week number is smaller than the week cycle, this
138 * constructor assumes a modulo operation has been performed and it will fix the week
139 * number according to the reference date set up for handling rollover (see {@link
140 * #setRolloverReference(DateComponents) setRolloverReference(reference)}). If the
141 * week number is equal to the week cycle or larger, it will be used without any
142 * correction.
143 * </p>
144 *
145 * @param weekNumber week number
146 * @param secondsInWeek number of seconds since week start
147 * @param system satellite system to consider
148 * @param timeScales the set of time scales. Used to retrieve the appropriate time
149 * scale for the given {@code system}.
150 * @since 12.0
151 */
152 public GNSSDate(final int weekNumber, final double secondsInWeek,
153 final SatelliteSystem system, final TimeScales timeScales) {
154 this(weekNumber, new TimeOffset(secondsInWeek), system, timeScales);
155 }
156
157 /**
158 * Build an instance corresponding to a GNSS date.
159 * <p>
160 * GNSS dates are provided as a week number starting at the GNSS reference epoch and
161 * as a number of seconds since week start.
162 * </p>
163 * <p>
164 * Many interfaces provide week number modulo the constellation week cycle. In order
165 * to cope with this, when the week number is smaller than the week cycle, this
166 * constructor assumes a modulo operation has been performed and it will fix the week
167 * number according to the reference date set up for handling rollover (see {@link
168 * #setRolloverReference(DateComponents) setRolloverReference(reference)}). If the
169 * week number is equal to the week cycle or larger, it will be used without any
170 * correction.
171 * </p>
172 *
173 * @param weekNumber week number
174 * @param secondsInWeek number of seconds since week start
175 * @param system satellite system to consider
176 * @param timeScales the set of time scales. Used to retrieve the appropriate time
177 * scale for the given {@code system}.
178 * @since 13.0
179 */
180 public GNSSDate(final int weekNumber, final TimeOffset secondsInWeek,
181 final SatelliteSystem system, final TimeScales timeScales) {
182
183 int day = (int) (secondsInWeek.getSeconds() / TimeOffset.DAY.getSeconds());
184 TimeOffset secondsInDay = new TimeOffset(secondsInWeek.getSeconds() % TimeOffset.DAY.getSeconds(),
185 secondsInWeek.getAttoSeconds());
186 while (secondsInDay.compareTo(TimeOffset.ZERO) < 0) {
187 // manage negative secondsInWeek
188 // (this happens for example in some GPS navigation messages, where secondsInWeek = -60)
189 --day;
190 secondsInDay = secondsInDay.add(TimeOffset.DAY);
191 }
192
193 int w = weekNumber;
194 DateComponents dc = new DateComponents(getWeekReferenceDateComponents(system), weekNumber * 7 + day);
195 final int cycleW = GNSSDateType.getRollOverWeek(system);
196 if (weekNumber < cycleW) {
197
198 DateComponents reference = ROLLOVER_REFERENCE.get();
199 if (reference == null) {
200 // lazy setting of a default reference, using end of EOP entries
201 final UT1Scale ut1 = timeScales.getUT1(IERSConventions.IERS_2010, true);
202 final List<EOPEntry> eop = ut1.getEOPHistory().getEntries();
203 final int lastMJD = eop.getLast().getMjd();
204 reference = new DateComponents(DateComponents.MODIFIED_JULIAN_EPOCH, lastMJD);
205 ROLLOVER_REFERENCE.compareAndSet(null, reference);
206 }
207
208 // fix GNSS week rollover
209 final int cycleD = WEEK_D * cycleW;
210 while (dc.getJ2000Day() < reference.getJ2000Day() - cycleD / 2) {
211 dc = new DateComponents(dc, cycleD);
212 w += cycleW;
213 }
214
215 }
216
217 this.weekNumber = w;
218 this.secondsInWeek = secondsInWeek;
219 this.system = system;
220
221 date = new AbsoluteDate(dc, new TimeComponents(secondsInDay), getTimeScale(system, timeScales));
222
223 }
224
225 /**
226 * Build an instance corresponding to a GNSS date.
227 * <p>
228 * GNSS dates are provided as a week number starting at the GNSS reference epoch and
229 * as a number of seconds since week start.
230 * </p>
231 *
232 * @param weekNumber week number
233 * @param secondsInWeek number of seconds since week start
234 * @param system satellite system to consider
235 * @param reference reference date for rollover, the generated date will be less
236 * than one half cycle from this date
237 * @param timeScales the set of time scales. Used to retrieve the appropriate time
238 * scale for the given {@code system}.
239 * @since 12.0
240 */
241 public GNSSDate(final int weekNumber, final double secondsInWeek,
242 final SatelliteSystem system, final DateComponents reference,
243 final TimeScales timeScales) {
244 this(weekNumber, new TimeOffset(secondsInWeek), system, reference, timeScales);
245 }
246
247 /**
248 * Build an instance corresponding to a GNSS date.
249 * <p>
250 * GNSS dates are provided as a week number starting at the GNSS reference epoch and
251 * as a number of seconds since week start.
252 * </p>
253 *
254 * @param weekNumber week number
255 * @param secondsInWeek number of seconds since week start
256 * @param system satellite system to consider
257 * @param reference reference date for rollover, the generated date will be less
258 * than one half cycle from this date
259 * @param timeScales the set of time scales. Used to retrieve the appropriate time
260 * scale for the given {@code system}.
261 * @since 13.0
262 */
263 public GNSSDate(final int weekNumber, final TimeOffset secondsInWeek,
264 final SatelliteSystem system, final DateComponents reference,
265 final TimeScales timeScales) {
266
267 final int day = (int) (secondsInWeek.getSeconds() / TimeOffset.DAY.getSeconds());
268 final TimeOffset secondsInDay = new TimeOffset(secondsInWeek.getSeconds() % TimeOffset.DAY.getSeconds(),
269 secondsInWeek.getAttoSeconds());
270
271 int w = weekNumber;
272 DateComponents dc = new DateComponents(getWeekReferenceDateComponents(system), weekNumber * 7 + day);
273 final int cycleW = GNSSDateType.getRollOverWeek(system);
274 if (weekNumber < cycleW) {
275
276 // fix GNSS week rollover
277 final int cycleD = WEEK_D * cycleW;
278 while (dc.getJ2000Day() < reference.getJ2000Day() - cycleD / 2) {
279 dc = new DateComponents(dc, cycleD);
280 w += cycleW;
281 }
282
283 }
284
285 this.weekNumber = w;
286 this.secondsInWeek = secondsInWeek;
287 this.system = system;
288
289 date = new AbsoluteDate(dc, new TimeComponents(secondsInDay), getTimeScale(system, timeScales));
290
291 }
292
293 /** Build an instance from an absolute date.
294 *
295 * <p>This method uses the {@link DataContext#getDefault() default data context}.
296 *
297 * @param date absolute date to consider
298 * @param system satellite system to consider
299 * @see #GNSSDate(AbsoluteDate, SatelliteSystem, TimeScales)
300 */
301 @DefaultDataContext
302 public GNSSDate(final AbsoluteDate date, final SatelliteSystem system) {
303 this(date, system, DataContext.getDefault().getTimeScales());
304 }
305
306 /**
307 * Build an instance from an absolute date.
308 *
309 * @param date absolute date to consider
310 * @param system satellite system to consider
311 * @param timeScales the set of time scales. Used to retrieve the appropriate time
312 * scale for the given {@code system}.
313 * @since 10.1
314 */
315 public GNSSDate(final AbsoluteDate date,
316 final SatelliteSystem system,
317 final TimeScales timeScales) {
318
319 final AbsoluteDate epoch = getWeekReferenceAbsoluteDate(system, timeScales);
320 this.weekNumber = (int) FastMath.floor(date.durationFrom(epoch) / WEEK_S);
321 final AbsoluteDate weekStart = new AbsoluteDate(epoch, WEEK_S * weekNumber);
322 this.secondsInWeek = date.accurateDurationFrom(weekStart);
323 this.system = system;
324 this.date = date;
325
326 }
327
328 /** Get satellite system.
329 * @return satellite system
330 * @since 14.0
331 */
332 public SatelliteSystem getSystem() {
333 return system;
334 }
335
336 /** Set a reference date for ensuring continuity across GNSS week rollover.
337 * <p>
338 * Instance created using the {@link #GNSSDate(int, double, SatelliteSystem) GNSSDate(weekNumber, secondsInWeek, system)}
339 * constructor and with a week number between 0 and the constellation week cycle (cycleW) after this method has been called will
340 * fix the week number to ensure they correspond to dates between {@code reference - cycleW / 2 weeks}
341 * and {@code reference + cycleW / 2 weeks}.
342 * </p>
343 * <p>
344 * If this method is never called, a default reference date for rollover will be set using
345 * the date of the last known EOP entry retrieved from {@link UT1Scale#getEOPHistory() UT1}
346 * time scale.
347 * </p>
348 * @param reference reference date for GNSS week rollover
349 * @see #getRolloverReference()
350 * @see #GNSSDate(int, double, SatelliteSystem)
351 * @since 9.3.1
352 */
353 public static void setRolloverReference(final DateComponents reference) {
354 ROLLOVER_REFERENCE.set(reference);
355 }
356
357 /** Get the reference date ensuring continuity across GNSS week rollover.
358 * @return reference reference date for GNSS week rollover
359 * @see #setRolloverReference(DateComponents)
360 * @see #GNSSDate(int, double, SatelliteSystem)
361 * @since 9.3.1
362 */
363 public static DateComponents getRolloverReference() {
364 return ROLLOVER_REFERENCE.get();
365 }
366
367 /** Get the week number since the GNSS reference epoch.
368 * <p>
369 * The week number returned here has been fixed for GNSS week rollover, i.e.
370 * it may be larger than the corresponding week cycle of the constellation.
371 * </p>
372 * @return week number since the GNSS reference epoch
373 */
374 public int getWeekNumber() {
375 return weekNumber;
376 }
377
378 /** Get the number of milliseconds since week start.
379 * @return number of milliseconds since week start
380 */
381 public double getMilliInWeek() {
382 return getSecondsInWeek() * 1000.0;
383 }
384
385 /** Get the number of seconds since week start.
386 * @return number of seconds since week start
387 * @since 12.0
388 */
389 public double getSecondsInWeek() {
390 return getSplitSecondsInWeek().toDouble();
391 }
392
393 /** Get the number of seconds since week start.
394 * @return number of seconds since week start
395 * @since 13.0
396 */
397 public TimeOffset getSplitSecondsInWeek() {
398 return secondsInWeek;
399 }
400
401 /** {@inheritDoc} */
402 @Override
403 public AbsoluteDate getDate() {
404 return date;
405 }
406
407 /** Get the time scale related to the given satellite system.
408 * @param satellite satellite system
409 * @param timeScales set of time scales.
410 * @return the time scale
411 */
412 private TimeScale getTimeScale(final SatelliteSystem satellite,
413 final TimeScales timeScales) {
414 switch (satellite) {
415 case GPS : return timeScales.getGPS();
416 case GALILEO : return timeScales.getGST();
417 case QZSS : return timeScales.getQZSS();
418 case BEIDOU : return timeScales.getBDT();
419 case NAVIC : return timeScales.getNavIC();
420 case SBAS : return timeScales.getGPS();
421 default : throw new OrekitException(OrekitMessages.INVALID_SATELLITE_SYSTEM, satellite);
422 }
423 }
424
425 /** Get the reference epoch of the week number for the given satellite system.
426 * <p> Returned parameter is an AbsoluteDate. </p>
427 * @param satellite satellite system
428 * @param timeScales set of time scales.
429 * @return the reference epoch
430 */
431 private AbsoluteDate getWeekReferenceAbsoluteDate(final SatelliteSystem satellite,
432 final TimeScales timeScales) {
433 switch (satellite) {
434 case GPS : return timeScales.getGpsEpoch();
435 case GALILEO : return timeScales.getGalileoEpoch();
436 case QZSS : return timeScales.getQzssEpoch();
437 case BEIDOU : return timeScales.getBeidouEpoch();
438 case NAVIC : return timeScales.getNavicEpoch();
439 case SBAS : return timeScales.getGpsEpoch();
440 default : throw new OrekitException(OrekitMessages.INVALID_SATELLITE_SYSTEM, satellite);
441 }
442 }
443
444 /** Get the reference epoch of the week number for the given satellite system.
445 * <p> Returned parameter is a DateComponents. </p>
446 * @param satellite satellite system
447 * @return the reference epoch
448 */
449 private DateComponents getWeekReferenceDateComponents(final SatelliteSystem satellite) {
450 switch (satellite) {
451 case GPS : return DateComponents.GPS_EPOCH;
452 case GALILEO : return DateComponents.GALILEO_EPOCH;
453 case QZSS : return DateComponents.QZSS_EPOCH;
454 case BEIDOU : return DateComponents.BEIDOU_EPOCH;
455 case NAVIC : return DateComponents.NAVIC_EPOCH;
456 case SBAS : return DateComponents.GPS_EPOCH;
457 default : throw new OrekitException(OrekitMessages.INVALID_SATELLITE_SYSTEM, satellite);
458 }
459 }
460
461 /** Enumerate for GNSS data. */
462 public enum GNSSDateType {
463
464 /** GPS. */
465 GPS(SatelliteSystem.GPS, 1024),
466
467 /** Galileo. */
468 GALILEO(SatelliteSystem.GALILEO, 4096),
469
470 /** QZSS. */
471 QZSS(SatelliteSystem.QZSS, 1024),
472
473 /** BeiDou. */
474 BEIDOU(SatelliteSystem.BEIDOU, 8192),
475
476 /** NavIC. */
477 NAVIC(SatelliteSystem.NAVIC, 1024),
478
479 /** SBAS. */
480 SBAS(SatelliteSystem.SBAS, 1024);
481
482 /** Map for the number of week in one GNSS rollover cycle. */
483 private static final Map<SatelliteSystem, Integer> CYCLE_MAP = new HashMap<>();
484 static {
485 for (final GNSSDateType type : values()) {
486 final int val = type.getRollOverCycle();
487 final SatelliteSystem satellite = type.getSatelliteSystem();
488 CYCLE_MAP.put(satellite, val);
489 }
490 }
491
492 /** Number of week in one rollover cycle. */
493 private final int numberOfWeek;
494
495 /** Satellite system. */
496 private final SatelliteSystem satelliteSystem;
497
498 /**
499 * Build a new instance.
500 *
501 * @param system satellite system
502 * @param rollover number of week in one rollover cycle
503 */
504 GNSSDateType(final SatelliteSystem system, final int rollover) {
505 this.satelliteSystem = system;
506 this.numberOfWeek = rollover;
507 }
508
509 /** Get the number of week in one rollover cycle.
510 * @return the number of week in one rollover cycle
511 */
512 public int getRollOverCycle() {
513 return numberOfWeek;
514 }
515
516 /** Get the satellite system.
517 * @return the satellite system
518 */
519 public SatelliteSystem getSatelliteSystem() {
520 return satelliteSystem;
521 }
522
523 /** Get the number of week in one rollover cycle for the given satellite system.
524 *
525 * @param satellite satellite system
526 * @return the number of week in one rollover cycle for the given satellite system
527 */
528 public static int getRollOverWeek(final SatelliteSystem satellite) {
529 return CYCLE_MAP.get(satellite);
530 }
531
532 }
533 }