Source code for gdt.missions.fermi.time

# CONTAINS TECHNICAL DATA/COMPUTER SOFTWARE DELIVERED TO THE U.S. GOVERNMENT WITH UNLIMITED RIGHTS
#
# Contract No.: CA 80MSFC17M0022
# Contractor Name: Universities Space Research Association
# Contractor Address: 7178 Columbia Gateway Drive, Columbia, MD 21046
#
# Copyright 2017-2022 by Universities Space Research Association (USRA). All rights reserved.
#
# Developed by: William Cleveland and Adam Goldstein
#               Universities Space Research Association
#               Science and Technology Institute
#               https://sti.usra.edu
#
# Developed by: Daniel Kocevski
#               National Aeronautics and Space Administration (NASA)
#               Marshall Space Flight Center
#               Astrophysics Branch (ST-12)
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
# in compliance with the License. You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License
# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied. See the License for the specific language governing permissions and limitations under the
# License.
#

import datetime
import re

import erfa
import numpy as np
from astropy.time import TimeFromEpoch, TimeUnique, ScaleValueError, Time
from astropy.time.utils import day_frac

__all__ = ['FermiSecTime', 'GbmBurstNumber', 'Time']


[docs]class FermiSecTime(TimeFromEpoch): """Represents the number of seconds elapsed since Jan 1, 2001, 00:00:00 UTC, including leap seconds """ name = 'fermi' """(str): Name of the mission""" unit = 1.0 / 86400 """(float): unit in days""" epoch_val = '2001-01-01 00:01:04.184' """(str): The epoch in Terrestrial Time""" epoch_val2 = None epoch_scale = 'tt' """(str): The scale of :attr:`epoch_val`""" epoch_format = 'iso' """(str): Format of :attr:`epoch_val`"""
[docs]class GbmBurstNumber(TimeUnique): """Represent date as Fermi GBM burst number""" name = 'gbm_bn' """(str): The name of the time format""" _bn_pattern = re.compile(r'^(?P<year>\d\d)(?P<month>\d\d)(?P<day>\d\d)(?P<frac>\d\d\d)$', re.I | re.S) _second_corrections = [ # 2008-01-01 00:00:00.000 UTC < x < 2009-01-01 00:00:00.000 UTC adjust seconds by 1 (datetime.datetime(2008, 1, 1), datetime.datetime(2009, 1, 1), 1), # 2009-01-01 00:00:00.000 UTC < met <= 2009-01-13 00:00:00.000 UTC adjust seconds by 2 (datetime.datetime(2009, 1, 1), datetime.datetime(2009, 1, 13), 2) ] def _check_val_type(self, val1, val2): if not all(self._bn_pattern.match(val) is not None for val in val1.flat): raise TypeError('Input values for {} class must be ' 'burst numbers'.format(self.name)) if val2 is not None: raise ValueError( f'{self.name} objects do not accept a val2 but you provided {val2}') return val1, None
[docs] def set_jds(self, val1, val2): """Convert burst number contained in val1 to jd1, jd2""" # Iterate through the datetime objects, getting year, month, etc. iterator = np.nditer([val1, None, None, None, None, None, None], flags=['refs_ok', 'zerosize_ok'], op_dtypes=[None] + 5 * [np.intc] + [np.double]) for val, iy, im, iday, ihr, imin, dsec in iterator: m = self._bn_pattern.match(str(val)) if not m: raise ValueError('burst number was not the correct format.') bn = m.groupdict() iy[...] = int(bn['year']) + 2000 im[...] = int(bn['month']) iday[...] = int(bn['day']) secs_of_day = int(bn['frac']) * 86.400 ihr[...] = secs_of_day // 3600 secs_of_day %= 3600 imin[...] = secs_of_day // 60 dsec[...] = secs_of_day % 60 jd1, jd2 = erfa.dtf2d(self.scale.upper().encode('ascii'), *iterator.operands[1:]) self.jd1, self.jd2 = day_frac(jd1, jd2)
[docs] def to_value(self, parent=None, out_subfmt=None): """Convert to string representation of GBM burst number""" if out_subfmt is not None: # Out_subfmt not allowed for this format, so raise the standard # exception by trying to validate the value. self._select_subfmts(out_subfmt) # GBM Burst Numbers are in the UTC scale, so make sure that we use UTC if self.scale != 'utc': if parent is None: raise ValueError('cannot compute value without parent Time object') try: tm = getattr(parent, 'utc') except Exception as err: raise ScaleValueError("Cannot convert from '{}' to UTC, got error:\n{}" .format(self.name, self.scale, err)) from err jd1, jd2 = tm._time.jd1, tm._time.jd2 else: jd1, jd2 = self.jd1, self.jd2 # Rather than define a value property directly, we have a function, # since we want to be able to pass in timezone information. scale = self.scale.upper().encode('ascii') iys, ims, ids, ihmsfs = erfa.d2dtf(scale, 6, # 6 for microsec jd1, jd2) ihrs = ihmsfs['h'] imins = ihmsfs['m'] isecs = ihmsfs['s'] ifracs = ihmsfs['f'] iterator = np.nditer([iys, ims, ids, ihrs, imins, isecs, ifracs, None], flags=['refs_ok', 'zerosize_ok'], op_dtypes=7 * [None] + [object]) for iy, im, iday, ihr, imin, isec, ifracsec, out in iterator: day_secs = ihr * 3600 + imin * 60 + isec # Adjust results to match the burst numbers issued early in the mission dt = datetime.datetime(iy, im, iday, ihr, imin, isec) for beg_dt, end_dt, adj_secs in self._second_corrections: if beg_dt < dt <= end_dt: # print(f'{beg_dt} < {dt} <= {end_dt}') day_secs += adj_secs frac = min(int(round(day_secs / 86400 * 1000)), 999) out[...] = f'{iy - 2000:02d}{im:02d}{iday:02d}{frac:03d}' return iterator.operands[-1]
value = property(to_value)