Skip to content

Commit 3e062a5

Browse files
authored
Merge pull request #3 from ANDREWNGT/Add_time_utils
Add time utils
2 parents 1fbdf54 + 5e060d4 commit 3e062a5

3 files changed

Lines changed: 139 additions & 2 deletions

File tree

kessler/cdm.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
import datetime
1515
import copy
1616
import pandas as pd
17-
1817
from . import util
1918

2019

@@ -191,11 +190,15 @@ def __eq__(self, other):
191190
if isinstance(other, ConjunctionDataMessage):
192191
return hash(self) == hash(other)
193192
return False
194-
193+
195194
def set_header(self, key, value):
196195
if key in self._keys_header:
197196
if key in self._keys_with_dates:
198197
# We have a field with a date string as the value. Check if the string is in the format needed by the CCSDS 508.0-B-1 standard
198+
time_format = util.get_ccsds_time_format(value)
199+
idx = time_format.find('DDD')
200+
if idx!=-1:
201+
value = util.doy_2_date(value, value[idx:idx+3], value[0:4], idx)
199202
try:
200203
_ = datetime.datetime.strptime(value, '%Y-%m-%dT%H:%M:%S.%f')
201204
except Exception as e:

kessler/util.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,3 +349,93 @@ def progress_bar_end(message=None):
349349
print()
350350
if message is not None:
351351
print(message)
352+
353+
def get_ccsds_time_format(time_string):
354+
'''
355+
Adapted by Andrew Ng, 18/3/2022.
356+
Original MATLAB source code found at: https://github.com/nasa/CARA_Analysis_Tools/blob/master/two-dimension_Pc/Main/TransformationCode/TimeTransformations/getCcsdsTimeFormat.m
357+
get_ccsds_time_format - process and outputs the format of the time string extracted from the CDM.
358+
The CCSDS time format is required to be of the general form
359+
yyyy-[mm-dd|ddd]THH:MM:SS[.F*][Z]
360+
(1) The date and time fields are separated by a "T".
361+
(2) The date field has a four digit year followed by either a two digit
362+
month and two digit day, or a three digit day-of-year.
363+
(3) The year, month, day, and day-of-year fields are separated by a dash.
364+
(4) The hours, minutes and seconds fields are each two digits separated
365+
by colons.
366+
(5) The fraction of seconds is optional and can have any number of
367+
digits.
368+
(6) If a fraction of seconds is provided, it is separated from the two
369+
digit seconds by a period.
370+
(7) The time string can end with an optional "Z" time zone indicator
371+
372+
Args:
373+
- time_string(``str``): Original time string stored in CDM.
374+
Returns:
375+
- time_format(``str``): Outputs the format of the time string. It must be of the form yyyy-[mm-dd|ddd]THH:MM:SS[.F*][Z], otherwise it is invalid and a RuntimeError is raised.
376+
377+
'''
378+
time_format = []
379+
numT = time_string.count('T')
380+
if numT == -1:
381+
# Case when this is 'T' does not exist in time_string
382+
raise RuntimeError(f"*** Error -- Invalid CCSDS time string: {time_string}\nNo 'T' separator found between date and time portions of the string")
383+
elif numT > 1:
384+
raise RuntimeError(f"*** Error -- Invalid CCSDS time string: {time_string} \nMore than one 'T' separator found between date and time portions of the string")
385+
idx_T = time_string.find('T')
386+
if idx_T ==10:
387+
time_format = "yyyy-mm-ddTHH:MM:SS"
388+
elif idx_T ==8:
389+
time_format = "yyyy-DDDTHH:MM:SS"
390+
else:
391+
raise RuntimeError(f"*** Error -- Invalid CCSDS time string: {time_string} \nDate format not one of yyyy-mm-dd or yyyy-DDD.\n")
392+
# % Check if 'Z' time zone indicator appended to the string
393+
if time_string[-1]=='Z':
394+
z_opt = True
395+
else:
396+
z_opt = False
397+
# % Find location of the fraction of seconds decimal separator
398+
num_decimal = time_string.count('.')
399+
if num_decimal > 1:
400+
#time_format = []
401+
raise RuntimeError(f"*** Error -- Invalid CCSDS time string: {time_string}\nMore than one fraction of seconds decimal separator ('.') found.\n")
402+
idx_decimal = time_string.find('.')
403+
nfrac = 0
404+
if num_decimal != 0:
405+
if z_opt:
406+
nfrac = len(time_string) - 1 - idx_decimal -1
407+
else:
408+
nfrac = len(time_string) - 1 - idx_decimal
409+
if nfrac > 0:
410+
frac_str = '.' + ('F'*nfrac)
411+
else:
412+
frac_str = ""
413+
if z_opt:
414+
frac_str = frac_str+'Z'
415+
time_format = time_format + frac_str
416+
return time_format
417+
418+
def doy_2_date(value, doy, year, idx):
419+
'''
420+
Written by Andrew Ng, 18/03/2022,
421+
Based on source code @ https://github.com/nasa/CARA_Analysis_Tools/blob/master/two-dimension_Pc/Main/TransformationCode/TimeTransformations/DOY2Date.m
422+
Use the datetime python package.
423+
doy_2_date - Converts Day of Year (DOY) date format to date format.
424+
425+
Args:
426+
- value(``str``): Original date time string with day of year format "YYYY-DDDTHH:MM:SS.ff"
427+
- doy (``str``): The day of year in the DOY format.
428+
- year (``str``): The year.
429+
- idx (``int``): Index of the start of the original "value" string at which characters 'DDD' are found.
430+
Returns:
431+
-value (``str``): Transformed date in traditional date format. i.e.: "YYYY-mm-ddTHH:MM:SS.ff"
432+
433+
'''
434+
# Calculate datetime format
435+
date_num = datetime.datetime(int(year), 1, 1) + datetime.timedelta(int(doy) - 1)
436+
437+
# Split datetime object into a date list
438+
date_vec = [date_num.year, date_num.month, date_num.day, date_num.hour, date_num.minute]
439+
# Extract final date string. Use zfill() to pad year, month and day fields with zeroes if not filling up sufficient spaces.
440+
value = str(date_vec[0]).zfill(4) +'-' + str(date_vec[1]).zfill(2) + '-' + str(date_vec[2]).zfill(2) + 'T' + value[idx+4:-1]
441+
return value

tests/test_util.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,3 +111,47 @@ def test_from_cartesian_to_rtn_2(self):
111111
state_rtn, _ = kessler.util.from_cartesian_to_rtn(state_xyz)
112112
self.assertAlmostEqual(np.linalg.norm(state_xyz[0]), np.linalg.norm(state_rtn[0]), places=1)
113113
self.assertAlmostEqual(np.linalg.norm(state_xyz[1]), np.linalg.norm(state_rtn[1]), places=1)
114+
115+
def test_get_ccsds_time_format(self):
116+
# This test is written by Andrew Ng, 19/03/22. It makes use of example CDMs provided by the NASA CARA
117+
# analysis repo at https://github.com/nasa/CARA_Analysis_Tools/tree/master/two-dimension_Pc/UnitTest/InputFiles.
118+
test_case1 = "2000-01-01T00:00:00.000" #From AlfanoTestCase11.cdm
119+
test_case2 = "2018-229T13:56:33.000" # From DensityDecorrelationTestCaseCDM.txt
120+
test_case1_correct = "yyyy-mm-ddTHH:MM:SS.FFF"
121+
test_case2_correct = "yyyy-DDDTHH:MM:SS.FFF"
122+
123+
self.assertEqual(kessler.util.get_ccsds_time_format(test_case1), test_case1_correct)
124+
self.assertEqual(kessler.util.get_ccsds_time_format(test_case2), test_case2_correct)
125+
126+
def test_doy_2_date(self):
127+
# This test is written by Andrew Ng, 19/03/22. It makes use of example CDMs provided by the NASA CARA
128+
# analysis repo at https://github.com/nasa/CARA_Analysis_Tools/tree/master/two-dimension_Pc/UnitTest/InputFiles.
129+
example1 = "2010-202T12:25:19.000" # From SingleCovTestCase1-4.cdm
130+
example2 = "2018-229T13:56:33.000" # From DensityDecorrelationTestCaseCDM.txt
131+
example3 = "2010-365T00:00:00.000" # Check that works at the final day of a non leap year
132+
example4 = "2010-001T00:00:00.000" # Check that works at the first day of a year
133+
example5 = "2012-366T00:00:00.000" # Check that works at the final day of a leap year
134+
135+
doy_1 = example1[5:5+3]
136+
year_1= example1[0:4]
137+
doy_2 = example2[5:5+3]
138+
year_2= example2[0:4]
139+
doy_3 = example3[5:5+3]
140+
year_3= example3[0:4]
141+
doy_4 = example4[5:5+3]
142+
year_4= example4[0:4]
143+
doy_5 = example5[5:5+3]
144+
year_5= example5[0:4]
145+
146+
test_case1_correct = "2010-07-21T12:25:19.00"
147+
test_case2_correct = "2018-08-17T13:56:33.00"
148+
test_case3_correct = "2010-12-31T00:00:00.00"
149+
test_case4_correct = "2010-01-01T00:00:00.00"
150+
test_case5_correct = "2012-12-31T00:00:00.00"
151+
152+
self.assertEqual(kessler.util.doy_2_date(example1, doy_1, year_1, 5), test_case1_correct)
153+
self.assertEqual(kessler.util.doy_2_date(example2, doy_2, year_2, 5), test_case2_correct)
154+
self.assertEqual(kessler.util.doy_2_date(example3, doy_3, year_3, 5), test_case3_correct)
155+
self.assertEqual(kessler.util.doy_2_date(example4, doy_4, year_4, 5), test_case4_correct)
156+
self.assertEqual(kessler.util.doy_2_date(example5, doy_5, year_5, 5), test_case5_correct)
157+

0 commit comments

Comments
 (0)