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 final int day = (int) (secondsInWeek.getSeconds() / TimeOffset.DAY.getSeconds());
184 final TimeOffset secondsInDay = new TimeOffset(secondsInWeek.getSeconds() % TimeOffset.DAY.getSeconds(),
185 secondsInWeek.getAttoSeconds());
186
187 int w = weekNumber;
188 DateComponents dc = new DateComponents(getWeekReferenceDateComponents(system), weekNumber * 7 + day);
189 final int cycleW = GNSSDateType.getRollOverWeek(system);
190 if (weekNumber < cycleW) {
191
192 DateComponents reference = ROLLOVER_REFERENCE.get();
193 if (reference == null) {
194 // lazy setting of a default reference, using end of EOP entries
195 final UT1Scale ut1 = timeScales.getUT1(IERSConventions.IERS_2010, true);
196 final List<EOPEntry> eop = ut1.getEOPHistory().getEntries();
197 final int lastMJD = eop.getLast().getMjd();
198 reference = new DateComponents(DateComponents.MODIFIED_JULIAN_EPOCH, lastMJD);
199 ROLLOVER_REFERENCE.compareAndSet(null, reference);
200 }
201
202 // fix GNSS week rollover
203 final int cycleD = WEEK_D * cycleW;
204 while (dc.getJ2000Day() < reference.getJ2000Day() - cycleD / 2) {
205 dc = new DateComponents(dc, cycleD);
206 w += cycleW;
207 }
208
209 }
210
211 this.weekNumber = w;
212 this.secondsInWeek = secondsInWeek;
213 this.system = system;
214
215 date = new AbsoluteDate(dc, new TimeComponents(secondsInDay), getTimeScale(system, timeScales));
216
217 }
218
219 /**
220 * Build an instance corresponding to a GNSS date.
221 * <p>
222 * GNSS dates are provided as a week number starting at the GNSS reference epoch and
223 * as a number of seconds since week start.
224 * </p>
225 *
226 * @param weekNumber week number
227 * @param secondsInWeek number of seconds since week start
228 * @param system satellite system to consider
229 * @param reference reference date for rollover, the generated date will be less
230 * than one half cycle from this date
231 * @param timeScales the set of time scales. Used to retrieve the appropriate time
232 * scale for the given {@code system}.
233 * @since 12.0
234 */
235 public GNSSDate(final int weekNumber, final double secondsInWeek,
236 final SatelliteSystem system, final DateComponents reference,
237 final TimeScales timeScales) {
238 this(weekNumber, new TimeOffset(secondsInWeek), system, reference, timeScales);
239 }
240
241 /**
242 * Build an instance corresponding to a GNSS date.
243 * <p>
244 * GNSS dates are provided as a week number starting at the GNSS reference epoch and
245 * as a number of seconds since week start.
246 * </p>
247 *
248 * @param weekNumber week number
249 * @param secondsInWeek number of seconds since week start
250 * @param system satellite system to consider
251 * @param reference reference date for rollover, the generated date will be less
252 * than one half cycle from this date
253 * @param timeScales the set of time scales. Used to retrieve the appropriate time
254 * scale for the given {@code system}.
255 * @since 13.0
256 */
257 public GNSSDate(final int weekNumber, final TimeOffset secondsInWeek,
258 final SatelliteSystem system, final DateComponents reference,
259 final TimeScales timeScales) {
260
261 final int day = (int) (secondsInWeek.getSeconds() / TimeOffset.DAY.getSeconds());
262 final TimeOffset secondsInDay = new TimeOffset(secondsInWeek.getSeconds() % TimeOffset.DAY.getSeconds(),
263 secondsInWeek.getAttoSeconds());
264
265 int w = weekNumber;
266 DateComponents dc = new DateComponents(getWeekReferenceDateComponents(system), weekNumber * 7 + day);
267 final int cycleW = GNSSDateType.getRollOverWeek(system);
268 if (weekNumber < cycleW) {
269
270 // fix GNSS week rollover
271 final int cycleD = WEEK_D * cycleW;
272 while (dc.getJ2000Day() < reference.getJ2000Day() - cycleD / 2) {
273 dc = new DateComponents(dc, cycleD);
274 w += cycleW;
275 }
276
277 }
278
279 this.weekNumber = w;
280 this.secondsInWeek = secondsInWeek;
281 this.system = system;
282
283 date = new AbsoluteDate(dc, new TimeComponents(secondsInDay), getTimeScale(system, timeScales));
284
285 }
286
287 /** Build an instance from an absolute date.
288 *
289 * <p>This method uses the {@link DataContext#getDefault() default data context}.
290 *
291 * @param date absolute date to consider
292 * @param system satellite system to consider
293 * @see #GNSSDate(AbsoluteDate, SatelliteSystem, TimeScales)
294 */
295 @DefaultDataContext
296 public GNSSDate(final AbsoluteDate date, final SatelliteSystem system) {
297 this(date, system, DataContext.getDefault().getTimeScales());
298 }
299
300 /**
301 * Build an instance from an absolute date.
302 *
303 * @param date absolute date to consider
304 * @param system satellite system to consider
305 * @param timeScales the set of time scales. Used to retrieve the appropriate time
306 * scale for the given {@code system}.
307 * @since 10.1
308 */
309 public GNSSDate(final AbsoluteDate date,
310 final SatelliteSystem system,
311 final TimeScales timeScales) {
312
313 final AbsoluteDate epoch = getWeekReferenceAbsoluteDate(system, timeScales);
314 this.weekNumber = (int) FastMath.floor(date.durationFrom(epoch) / WEEK_S);
315 final AbsoluteDate weekStart = new AbsoluteDate(epoch, WEEK_S * weekNumber);
316 this.secondsInWeek = date.accurateDurationFrom(weekStart);
317 this.system = system;
318 this.date = date;
319
320 }
321
322 /** Get satellite system.
323 * @return satellite system
324 * @since 14.0
325 */
326 public SatelliteSystem getSystem() {
327 return system;
328 }
329
330 /** Set a reference date for ensuring continuity across GNSS week rollover.
331 * <p>
332 * Instance created using the {@link #GNSSDate(int, double, SatelliteSystem) GNSSDate(weekNumber, secondsInWeek, system)}
333 * constructor and with a week number between 0 and the constellation week cycle (cycleW) after this method has been called will
334 * fix the week number to ensure they correspond to dates between {@code reference - cycleW / 2 weeks}
335 * and {@code reference + cycleW / 2 weeks}.
336 * </p>
337 * <p>
338 * If this method is never called, a default reference date for rollover will be set using
339 * the date of the last known EOP entry retrieved from {@link UT1Scale#getEOPHistory() UT1}
340 * time scale.
341 * </p>
342 * @param reference reference date for GNSS week rollover
343 * @see #getRolloverReference()
344 * @see #GNSSDate(int, double, SatelliteSystem)
345 * @since 9.3.1
346 */
347 public static void setRolloverReference(final DateComponents reference) {
348 ROLLOVER_REFERENCE.set(reference);
349 }
350
351 /** Get the reference date ensuring continuity across GNSS week rollover.
352 * @return reference reference date for GNSS week rollover
353 * @see #setRolloverReference(DateComponents)
354 * @see #GNSSDate(int, double, SatelliteSystem)
355 * @since 9.3.1
356 */
357 public static DateComponents getRolloverReference() {
358 return ROLLOVER_REFERENCE.get();
359 }
360
361 /** Get the week number since the GNSS reference epoch.
362 * <p>
363 * The week number returned here has been fixed for GNSS week rollover, i.e.
364 * it may be larger than the corresponding week cycle of the constellation.
365 * </p>
366 * @return week number since the GNSS reference epoch
367 */
368 public int getWeekNumber() {
369 return weekNumber;
370 }
371
372 /** Get the number of milliseconds since week start.
373 * @return number of milliseconds since week start
374 */
375 public double getMilliInWeek() {
376 return getSecondsInWeek() * 1000.0;
377 }
378
379 /** Get the number of seconds since week start.
380 * @return number of seconds since week start
381 * @since 12.0
382 */
383 public double getSecondsInWeek() {
384 return getSplitSecondsInWeek().toDouble();
385 }
386
387 /** Get the number of seconds since week start.
388 * @return number of seconds since week start
389 * @since 13.0
390 */
391 public TimeOffset getSplitSecondsInWeek() {
392 return secondsInWeek;
393 }
394
395 /** {@inheritDoc} */
396 @Override
397 public AbsoluteDate getDate() {
398 return date;
399 }
400
401 /** Get the time scale related to the given satellite system.
402 * @param satellite satellite system
403 * @param timeScales set of time scales.
404 * @return the time scale
405 */
406 private TimeScale getTimeScale(final SatelliteSystem satellite,
407 final TimeScales timeScales) {
408 switch (satellite) {
409 case GPS : return timeScales.getGPS();
410 case GALILEO : return timeScales.getGST();
411 case QZSS : return timeScales.getQZSS();
412 case BEIDOU : return timeScales.getBDT();
413 case NAVIC : return timeScales.getNavIC();
414 case SBAS : return timeScales.getGPS();
415 default : throw new OrekitException(OrekitMessages.INVALID_SATELLITE_SYSTEM, satellite);
416 }
417 }
418
419 /** Get the reference epoch of the week number for the given satellite system.
420 * <p> Returned parameter is an AbsoluteDate. </p>
421 * @param satellite satellite system
422 * @param timeScales set of time scales.
423 * @return the reference epoch
424 */
425 private AbsoluteDate getWeekReferenceAbsoluteDate(final SatelliteSystem satellite,
426 final TimeScales timeScales) {
427 switch (satellite) {
428 case GPS : return timeScales.getGpsEpoch();
429 case GALILEO : return timeScales.getGalileoEpoch();
430 case QZSS : return timeScales.getQzssEpoch();
431 case BEIDOU : return timeScales.getBeidouEpoch();
432 case NAVIC : return timeScales.getNavicEpoch();
433 case SBAS : return timeScales.getGpsEpoch();
434 default : throw new OrekitException(OrekitMessages.INVALID_SATELLITE_SYSTEM, satellite);
435 }
436 }
437
438 /** Get the reference epoch of the week number for the given satellite system.
439 * <p> Returned parameter is a DateComponents. </p>
440 * @param satellite satellite system
441 * @return the reference epoch
442 */
443 private DateComponents getWeekReferenceDateComponents(final SatelliteSystem satellite) {
444 switch (satellite) {
445 case GPS : return DateComponents.GPS_EPOCH;
446 case GALILEO : return DateComponents.GALILEO_EPOCH;
447 case QZSS : return DateComponents.QZSS_EPOCH;
448 case BEIDOU : return DateComponents.BEIDOU_EPOCH;
449 case NAVIC : return DateComponents.NAVIC_EPOCH;
450 case SBAS : return DateComponents.GPS_EPOCH;
451 default : throw new OrekitException(OrekitMessages.INVALID_SATELLITE_SYSTEM, satellite);
452 }
453 }
454
455 /** Enumerate for GNSS data. */
456 public enum GNSSDateType {
457
458 /** GPS. */
459 GPS(SatelliteSystem.GPS, 1024),
460
461 /** Galileo. */
462 GALILEO(SatelliteSystem.GALILEO, 4096),
463
464 /** QZSS. */
465 QZSS(SatelliteSystem.QZSS, 1024),
466
467 /** BeiDou. */
468 BEIDOU(SatelliteSystem.BEIDOU, 8192),
469
470 /** NavIC. */
471 NAVIC(SatelliteSystem.NAVIC, 1024),
472
473 /** SBAS. */
474 SBAS(SatelliteSystem.SBAS, 1024);
475
476 /** Map for the number of week in one GNSS rollover cycle. */
477 private static final Map<SatelliteSystem, Integer> CYCLE_MAP = new HashMap<>();
478 static {
479 for (final GNSSDateType type : values()) {
480 final int val = type.getRollOverCycle();
481 final SatelliteSystem satellite = type.getSatelliteSystem();
482 CYCLE_MAP.put(satellite, val);
483 }
484 }
485
486 /** Number of week in one rollover cycle. */
487 private final int numberOfWeek;
488
489 /** Satellite system. */
490 private final SatelliteSystem satelliteSystem;
491
492 /**
493 * Build a new instance.
494 *
495 * @param system satellite system
496 * @param rollover number of week in one rollover cycle
497 */
498 GNSSDateType(final SatelliteSystem system, final int rollover) {
499 this.satelliteSystem = system;
500 this.numberOfWeek = rollover;
501 }
502
503 /** Get the number of week in one rollover cycle.
504 * @return the number of week in one rollover cycle
505 */
506 public int getRollOverCycle() {
507 return numberOfWeek;
508 }
509
510 /** Get the satellite system.
511 * @return the satellite system
512 */
513 public SatelliteSystem getSatelliteSystem() {
514 return satelliteSystem;
515 }
516
517 /** Get the number of week in one rollover cycle for the given satellite system.
518 *
519 * @param satellite satellite system
520 * @return the number of week in one rollover cycle for the given satellite system
521 */
522 public static int getRollOverWeek(final SatelliteSystem satellite) {
523 return CYCLE_MAP.get(satellite);
524 }
525
526 }
527 }