Skip to content

Commit 7a579cd

Browse files
authored
Merge pull request #87 from GeoscienceAustralia/NPI-3945-fix-gpsdate-to-datetime
NPI-3945 Further refinements to GPSDate
2 parents 9b8e6bd + 0970ba1 commit 7a579cd

5 files changed

Lines changed: 140 additions & 58 deletions

File tree

gnssanalysis/gn_datetime.py

Lines changed: 70 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
logger = logging.getLogger(__name__)
1818

1919

20-
def gpsweekD(yr, doy, wkday_suff=False):
20+
def derive_gps_week(year: Union[int, str], day_of_year: Union[int, str], weekday_suffix: bool = False) -> str:
2121
"""
2222
Convert year, day-of-year to GPS week format: WWWWD or WWWW
2323
Based on code from Kristine Larson's gps.py
@@ -32,30 +32,37 @@ def gpsweekD(yr, doy, wkday_suff=False):
3232
"""
3333

3434
# Set up the date and time variables
35-
yr = int(yr)
36-
doy = int(doy)
37-
dt = _datetime.strptime(f"{yr}-{doy:03d} 01", "%Y-%j %H")
35+
year = int(year)
36+
day_of_year = int(day_of_year)
37+
dt: _datetime = _datetime.strptime(f"{year}-{day_of_year:03d} 01", "%Y-%j %H")
3838

3939
wkday = dt.weekday() + 1
4040

4141
if wkday == 7:
4242
wkday = 0
4343

44-
mn, dy, hr = dt.month, dt.day, dt.hour
44+
month, day, hour = dt.month, dt.day, dt.hour
4545

46-
if mn <= 2:
47-
yr = yr - 1
48-
mn = mn + 12
46+
if month <= 2:
47+
year = year - 1
48+
month = month + 12
4949

50-
JD = _np.floor(365.25 * yr) + _np.floor(30.6001 * (mn + 1)) + dy + hr / 24.0 + 1720981.5
51-
GPS_wk = int(_np.floor((JD - 2444244.5) / 7.0))
50+
julian_day: float = _np.floor(365.25 * year) + _np.floor(30.6001 * (month + 1)) + day + hour / 24.0 + 1720981.5
51+
GPS_wk = int(_np.floor((julian_day - 2444244.5) / 7.0))
5252

53-
if wkday_suff:
53+
if weekday_suffix:
5454
return str(GPS_wk) + str(wkday)
5555
else:
5656
return str(GPS_wk)
5757

5858

59+
def gpsweekD(yr, doy, wkday_suff=False) -> str:
60+
"""
61+
TODO DEPRECATED. Legacy wrapper for the above
62+
"""
63+
return derive_gps_week(yr, doy, wkday_suff)
64+
65+
5966
class GPSDate:
6067
"""
6168
Representation of datetime that provides easy access to
@@ -94,24 +101,49 @@ def as_datetime(self) -> _datetime:
94101
return _pd.Timestamp(self._internal_dt64).to_pydatetime()
95102

96103
@property
97-
def yr(self) -> str:
104+
def as_date(self) -> _date:
105+
"""Convert to Python `date` object."""
106+
return self._internal_dt64.astype(_date)
107+
108+
@property
109+
def year(self) -> str:
98110
"""Year"""
99111
return self.as_datetime.strftime("%Y")
100112

113+
@property # For backwards compatibility
114+
def yr(self) -> str:
115+
"""Year DEPRECATED, use GPSDate.year"""
116+
return self.year
117+
101118
@property
102-
def dy(self) -> str:
119+
def day_of_year(self) -> str:
103120
"""Day of year"""
104121
return self.as_datetime.strftime("%j")
105122

123+
@property # For backwards compatibility
124+
def dy(self) -> str:
125+
"""Day of year. DEPRECATED, use GPSDate.day_of_year"""
126+
return self.day_of_year
127+
106128
@property
107-
def gpswk(self) -> str:
129+
def gps_week(self) -> str:
108130
"""GPS week"""
109-
return gpsweekD(self.yr, self.dy, wkday_suff=False)
131+
return derive_gps_week(self.yr, self.dy, weekday_suffix=False)
132+
133+
@property # For backwards compatibility
134+
def gpswk(self) -> str:
135+
"""GPS week. DEPRECATED, use GPSDate.gps_week"""
136+
return self.gps_week
110137

111138
@property
112-
def gpswkD(self) -> str:
139+
def gps_week_and_day_of_week(self) -> str:
113140
"""GPS week with weekday suffix"""
114-
return gpsweekD(self.yr, self.dy, wkday_suff=True)
141+
return derive_gps_week(self.yr, self.dy, weekday_suffix=True)
142+
143+
@property # For backwards compatibility
144+
def gpswkD(self) -> str:
145+
"""GPS week with weekday suffix. DEPRECATED, use GPSDate.gps_week_and_day_of_week"""
146+
return self.gps_week_and_day_of_week
115147

116148
@property
117149
def next(self):
@@ -128,24 +160,27 @@ def __str__(self) -> str:
128160
return str(self._internal_dt64)
129161

130162

131-
def dt2gpswk(dt, wkday_suff=False, both=False) -> Union[str, tuple[str, str]]:
163+
def datetime_to_gps_week(dt: _datetime, wkday_suff: bool = False) -> str:
132164
"""
133-
Convert the given datetime object to a GPS week (option to include day suffix)
165+
Convert given datetime object to GPS week e.g. '2350', optionally including day suffix e.g. '23500' (Sunday)
166+
167+
:param datetime dt: datetime for which to calculate a GPS week (and optionally day)
168+
:returns str: intput datetime expressed as a GPS week (wwww) or GPS week with day (wwwwd)
134169
"""
135170
yr = dt.strftime("%Y")
136171
doy = dt.strftime("%j")
137-
if not both:
138-
return gpsweekD(yr, doy, wkday_suff=wkday_suff)
139-
else:
140-
return gpsweekD(yr, doy, wkday_suff=False), gpsweekD(yr, doy, wkday_suff=True)
172+
return derive_gps_week(yr, doy, weekday_suffix=wkday_suff)
141173

142174

143-
# TODO DEPRECATED
144-
def gpswkD2dt(gpswkD: str) -> _datetime:
175+
def dt2gpswk(dt: _datetime, wkday_suff: bool = False, both: bool = False) -> Union[str, tuple[str, str]]:
145176
"""
146-
DEPRECATED. This is a compatibility wrapper. Use gps_week_day_to_datetime() instead.
177+
TODO DEPRECATED. Please use datetime_to_gps_week()
147178
"""
148-
return gps_week_day_to_datetime(gpswkD)
179+
180+
if both:
181+
return (datetime_to_gps_week(dt, wkday_suff=False), datetime_to_gps_week(dt, wkday_suff=True))
182+
else:
183+
return datetime_to_gps_week(dt, wkday_suff=wkday_suff)
149184

150185

151186
def gps_week_day_to_datetime(gps_week_and_weekday: str) -> _datetime:
@@ -178,6 +213,14 @@ def gps_week_day_to_datetime(gps_week_and_weekday: str) -> _datetime:
178213
return dt_64.astype(_datetime)
179214

180215

216+
# TODO DEPRECATED
217+
def gpswkD2dt(gpswkD: str) -> _datetime:
218+
"""
219+
DEPRECATED. This is a compatibility wrapper. Use gps_week_day_to_datetime() instead.
220+
"""
221+
return gps_week_day_to_datetime(gpswkD)
222+
223+
181224
def yydoysec2datetime(
182225
arr: Union[_np.ndarray, _pd.Series, list], recenter: bool = False, as_j2000: bool = True, delimiter: str = ":"
183226
) -> _np.ndarray:

gnssanalysis/gn_download.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ def generate_long_filename(
336336
start_epoch: _datetime.datetime, # YYYYDDDHHMM
337337
end_epoch: _datetime.datetime = None,
338338
timespan: _datetime.timedelta = None, # LEN
339-
solution_type: str = "", # TTT
339+
solution_type: str = "", # TTT # TODO look at updating to formalised SolutionType
340340
sampling_rate: str = "15M", # SMP
341341
version: str = "0", # V
342342
project: str = "EXP", # PPP, e.g. EXP, OPS
@@ -436,8 +436,11 @@ def generate_product_filename(
436436
else:
437437
hour = f"{reference_start.hour:02}"
438438
prefix = "igs" if solution_type == "FIN" else "igr" if solution_type == "RAP" else "igu"
439-
product_filename = f"{prefix}{gps_date.gpswkD}_{hour}.{file_ext}.Z" if solution_type == "ULT" else \
440-
f"{prefix}{gps_date.gpswkD}.{file_ext}.Z"
439+
product_filename = (
440+
f"{prefix}{gps_date.gps_week_and_day_of_week}_{hour}.{file_ext}.Z"
441+
if solution_type == "ULT"
442+
else f"{prefix}{gps_date.gps_week_and_day_of_week}.{file_ext}.Z"
443+
)
441444
return product_filename, gps_date, reference_start
442445

443446

@@ -865,7 +868,7 @@ def download_product_from_cddis(
865868
project=project_type,
866869
)
867870
logging.debug(
868-
f"Generated filename: {product_filename}, with GPS Date: {gps_date.gpswkD} and reference: {reference_start}"
871+
f"Generated filename: {product_filename}, with GPS Date: {gps_date.gps_week_and_day_of_week} and reference: {reference_start}"
869872
)
870873

871874
ensure_folders([download_dir])

gnssanalysis/gn_frame.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,9 @@ def get_frame_of_day(
5454
elif isinstance(itrf_path_or_df, str):
5555
output = _gn_io.sinex._get_snx_vector_gzchunks(filename=itrf_path_or_df, block_name="SOLUTION/ESTIMATE")
5656
if output is None:
57-
raise Exception(f"Output from _get_snx_vector_gzchunks() was None! Filepath: {itrf_path_or_df}")
57+
raise Exception(
58+
f"Failed to extract SOLUTION/ESTIMATE from Sinex. Check that file was valid: {itrf_path_or_df}"
59+
)
5860
else:
5961
raise ValueError(f"itrf_path_or_df must be a pandas DataFrame or str, got: {type(itrf_path_or_df)}")
6062

gnssanalysis/gn_io/erp.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -594,7 +594,9 @@ def erp_outfile(datetime_epoch: datetime.datetime, output_dir: pathlib.Path):
594594
# Restrict the amount data we output
595595
resampled_df = resampled_df[resampled_df["MJD"] > mjd - 3]
596596

597-
gps_date = _gn_datetime.gpsweekD(datetime_epoch.strftime("%Y"), datetime_epoch.strftime("%j"), wkday_suff=True)
597+
gps_date = _gn_datetime.derive_gps_week(
598+
datetime_epoch.strftime("%Y"), datetime_epoch.strftime("%j"), weekday_suffix=True
599+
)
598600
file_suffix = f'_{int(int(str(mjd).split(".")[1].ljust(2,"0"))*0.24):02}'
599601
file_name = f"igu{gps_date}{file_suffix}.erp"
600602

tests/test_datetime.py

Lines changed: 57 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -43,22 +43,45 @@ def test_gps_week_day_to_datetime(self):
4343
with self.assertRaises(TypeError):
4444
gn_datetime.gps_week_day_to_datetime(2173) # Not a string!
4545

46+
def test_datetime_to_gps_week(self):
47+
48+
# Day of year 244. GPS week and weekday: 2173-3, Wednesday.
49+
self.assertEqual(gn_datetime.datetime_to_gps_week(_datetime(year=2021, month=9, day=1)), "2173")
50+
51+
# With day of week:
52+
self.assertEqual(
53+
gn_datetime.datetime_to_gps_week(_datetime(year=2021, month=9, day=1), wkday_suff=True), "21733"
54+
)
55+
56+
# Legacy version:
57+
self.assertEqual(gn_datetime.dt2gpswk(_datetime(year=2021, month=9, day=1)), "2173")
58+
self.assertEqual(gn_datetime.dt2gpswk(_datetime(year=2021, month=9, day=1), wkday_suff=True), "21733")
59+
self.assertEqual(gn_datetime.dt2gpswk(_datetime(year=2021, month=9, day=1), both=True), ("2173", "21733"))
60+
4661

4762
class TestGPSDate(unittest.TestCase):
4863
def test_gpsdate(self):
49-
date: gn_datetime.GPSDate = gn_datetime.GPSDate(np.datetime64("2021-08-31"))
50-
self.assertEqual(int(date.yr), 2021)
51-
self.assertEqual(int(date.dy), 243)
52-
self.assertEqual(int(date.gpswk), 2173)
53-
self.assertEqual(int(date.gpswkD[-1]), 2)
54-
self.assertEqual(str(date), "2021-08-31")
55-
56-
date = date.next
57-
self.assertEqual(int(date.yr), 2021)
58-
self.assertEqual(int(date.dy), 244)
59-
self.assertEqual(int(date.gpswk), 2173)
60-
self.assertEqual(int(date.gpswkD[-1]), 3)
61-
self.assertEqual(str(date), "2021-09-01")
64+
gps_date: gn_datetime.GPSDate = gn_datetime.GPSDate(np.datetime64("2021-08-31"))
65+
self.assertEqual(int(gps_date.yr), 2021) # Deprecated
66+
self.assertEqual(int(gps_date.year), 2021)
67+
self.assertEqual(int(gps_date.dy), 243) # Deprecated
68+
self.assertEqual(int(gps_date.day_of_year), 243)
69+
self.assertEqual(int(gps_date.gpswk), 2173) # Deprecated
70+
self.assertEqual(int(gps_date.gps_week), 2173)
71+
self.assertEqual(int(gps_date.gpswkD[-1]), 2) # Deprecated
72+
self.assertEqual(int(gps_date.gps_week_and_day_of_week[-1]), 2)
73+
self.assertEqual(str(gps_date), "2021-08-31")
74+
75+
gps_date = gps_date.next
76+
self.assertEqual(int(gps_date.yr), 2021) # Deprecated
77+
self.assertEqual(int(gps_date.year), 2021)
78+
self.assertEqual(int(gps_date.dy), 244) # Deprecated
79+
self.assertEqual(int(gps_date.day_of_year), 244)
80+
self.assertEqual(int(gps_date.gpswk), 2173) # Deprecated
81+
self.assertEqual(int(gps_date.gps_week), 2173)
82+
self.assertEqual(int(gps_date.gpswkD[-1]), 3) # Deprecated
83+
self.assertEqual(int(gps_date.gps_week_and_day_of_week[-1]), 3)
84+
self.assertEqual(str(gps_date), "2021-09-01")
6285

6386
yds = np.asarray(["00:000:00000"])
6487
yds_j2000 = gn_datetime.yydoysec2datetime(yds, recenter=False, as_j2000=True)
@@ -70,25 +93,34 @@ def test_gpsdate(self):
7093

7194
# Test as_datetime property
7295
# Expect 2021-09-01 as a python datetime, not a date or a Numpy datetime64
73-
self.assertEqual(date.as_datetime, _datetime(year=2021, month=9, day=1))
74-
self.assertTrue(isinstance(date.as_datetime, _datetime))
96+
self.assertEqual(gps_date.as_datetime, _datetime(year=2021, month=9, day=1))
97+
self.assertTrue(isinstance(gps_date.as_datetime, _datetime))
98+
99+
# Test as_date
100+
self.assertEqual(gps_date.as_date, _date(year=2021, month=9, day=1))
101+
self.assertTrue(isinstance(gps_date.as_date, _date))
102+
103+
# Check accessor works
104+
self.assertEqual(gps_date.get_datetime64, np.datetime64("2021-09-01"))
105+
# Check legacy version
106+
self.assertEqual(gps_date.ts, np.datetime64("2021-09-01"))
75107

76108
# Test construction from other types
77-
date = gn_datetime.GPSDate(_datetime(year=2021, month=9, day=5))
78-
self.assertEqual(date.dy, "248")
79-
self.assertEqual(date.as_datetime, _datetime(year=2021, month=9, day=5))
109+
gps_date = gn_datetime.GPSDate(_datetime(year=2021, month=9, day=5))
110+
self.assertEqual(gps_date.day_of_year, "248")
111+
self.assertEqual(gps_date.as_datetime, _datetime(year=2021, month=9, day=5))
80112

81-
date = gn_datetime.GPSDate(_date(year=2021, month=9, day=5))
82-
self.assertEqual(date.dy, "248")
83-
self.assertEqual(date.as_datetime, _datetime(year=2021, month=9, day=5))
113+
gps_date = gn_datetime.GPSDate(_date(year=2021, month=9, day=5))
114+
self.assertEqual(gps_date.day_of_year, "248")
115+
self.assertEqual(gps_date.as_datetime, _datetime(year=2021, month=9, day=5))
84116

85-
date = gn_datetime.GPSDate("2021-09-05")
86-
self.assertEqual(date.dy, "248")
87-
self.assertEqual(date.as_datetime, _datetime(year=2021, month=9, day=5))
117+
gps_date = gn_datetime.GPSDate("2021-09-05")
118+
self.assertEqual(gps_date.day_of_year, "248")
119+
self.assertEqual(gps_date.as_datetime, _datetime(year=2021, month=9, day=5))
88120

89121
# And exceptions on trying to construct from incorrect types or date string formats
90122
with self.assertRaises(TypeError):
91-
gn_datetime.GPSDate(5) # An int isn't one of the supported date types
123+
gn_datetime.GPSDate(5) # type: ignore # An int isn't one of the supported date types
92124

93125
with self.assertRaises(ValueError):
94126
gn_datetime.GPSDate("123-12-123") # A string, but not a valid date format

0 commit comments

Comments
 (0)