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