Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1from builtins import object 

2import warnings 

3import numpy as np 

4import copy 

5 

6from astropy.time import Time 

7from astropy.utils.iers.iers import IERSRangeError 

8 

9__all__ = ["ModifiedJulianDate", "MJDWarning", "UTCtoUT1Warning"] 

10 

11 

12# Filter out ERFA's complaints that we are simulating dates which 

13# are in the future 

14warnings.filterwarnings("ignore", 

15 message='.*taiutc.*dubious.year.*') 

16 

17 

18class MJDWarning(Warning): 

19 """ 

20 A sub-class of Warning. All of the warnings raised by ModifiedJulianDate 

21 will be of this class (or its sub-classes), so that users can filter them 

22 out by creating a simple filter targeted at category=MJDWarning. 

23 """ 

24 pass 

25 

26 

27class UTCtoUT1Warning(MJDWarning): 

28 """ 

29 A sub-class of MJDWarning meant for use when astropy.Time cannot interpolate 

30 UT1-UTC as a function of UTC because UTC is out of bounds of the data. 

31 This class exists so that users can filter these warnings out by creating 

32 a simple filter targeted at category=UTCtoUT1Warning. 

33 """ 

34 pass 

35 

36 

37class ModifiedJulianDate(object): 

38 

39 @classmethod 

40 def _get_ut1_from_utc(cls, UTC): 

41 """ 

42 Take a numpy array of UTC values and return a numpy array of UT1 and dut1 values 

43 """ 

44 

45 time_list = Time(UTC, scale='utc', format='mjd') 

46 

47 try: 

48 dut1_out = time_list.delta_ut1_utc 

49 ut1_out = time_list.ut1.mjd 

50 except IERSRangeError: 

51 ut1_out = np.copy(UTC) 

52 dut1_out = np.zeros(len(UTC)) 

53 warnings.warn("ModifiedJulianData.get_list() was given date values that are outside " 

54 "astropy's range of interpolation for converting from UTC to UT1. " 

55 "We will treat UT1=UTC for those dates, lacking a better alternative.", 

56 category=UTCtoUT1Warning) 

57 from astropy.utils.iers import TIME_BEFORE_IERS_RANGE, TIME_BEYOND_IERS_RANGE 

58 dut1_test, status = time_list.get_delta_ut1_utc(return_status=True) 

59 good_dexes = np.where(np.logical_and(status != TIME_BEFORE_IERS_RANGE, 

60 status != TIME_BEYOND_IERS_RANGE)) 

61 

62 if len(good_dexes[0]) > 0: 

63 time_good = Time(UTC[good_dexes], scale='utc', format='mjd') 

64 dut1_good = time_good.delta_ut1_utc 

65 ut1_good = time_good.ut1.mjd 

66 

67 ut1_out[good_dexes] = ut1_good 

68 dut1_out[good_dexes] = dut1_good 

69 

70 return ut1_out, dut1_out 

71 

72 @classmethod 

73 def get_list(cls, TAI=None, UTC=None): 

74 """ 

75 Instantiate a list of ModifiedJulianDates from a numpy array of either TAI 

76 or UTC values. 

77 

78 @param[in] TAI (optional) a numpy array of MJD' in TAI 

79 

80 @param[in] UTC (optional) a numpy array of MJDs in UTC 

81 

82 @param[out] a list of ModifiedJulianDate instantiations with all of their 

83 properties already set (so the code does not waste time converting from TAI 

84 to TT, TDB, etc. when those time scales are called for). 

85 """ 

86 

87 if TAI is None and UTC is None: 

88 return None 

89 

90 if TAI is not None and UTC is not None: 

91 raise RuntimeError("You should not specify both TAI and UTC in ModifiedJulianDate.get_list()") 

92 

93 if TAI is not None: 

94 time_list = Time(TAI, scale='tai', format='mjd') 

95 tai_list = TAI 

96 utc_list = time_list.utc.mjd 

97 elif UTC is not None: 

98 time_list = Time(UTC, scale='utc', format='mjd') 

99 utc_list = UTC 

100 tai_list = time_list.tai.mjd 

101 

102 tt_list = time_list.tt.mjd 

103 tdb_list = time_list.tdb.mjd 

104 

105 ut1_list, dut1_list = cls._get_ut1_from_utc(utc_list) 

106 

107 values = np.array([tai_list, utc_list, tt_list, tdb_list, 

108 ut1_list, dut1_list]).transpose() 

109 

110 output = [] 

111 for vv in values: 

112 mjd = ModifiedJulianDate(TAI=40000.0) 

113 mjd._force_values(vv) 

114 output.append(mjd) 

115 

116 return output 

117 

118 def __init__(self, TAI=None, UTC=None): 

119 """ 

120 Must specify either: 

121 

122 @param [in] TAI = the International Atomic Time as an MJD 

123 

124 or 

125 

126 @param [in] UTC = Universal Coordinate Time as an MJD 

127 """ 

128 

129 if TAI is None and UTC is None: 

130 raise RuntimeError("You must specify either TAI or UTC to " 

131 "instantiate ModifiedJulianDate") 

132 

133 if TAI is not None: 

134 self._time = Time(TAI, scale='tai', format='mjd') 

135 self._tai = TAI 

136 self._utc = None 

137 self._initialized_with = 'TAI' 

138 else: 

139 self._time = Time(UTC, scale='utc', format='mjd') 

140 self._utc = UTC 

141 self._tai = None 

142 self._initialized_with = 'UTC' 

143 

144 self._tt = None 

145 self._tdb = None 

146 self._ut1 = None 

147 self._dut1 = None 

148 

149 def _force_values(self, values): 

150 """ 

151 Force the properties of this ModifiedJulianDate to have specific values. 

152 

153 values is a list of [TAI, UTC, TT, TDB, UT1, UT1-UTC] values. 

154 

155 This method exists so that, when instantiating lists of ModifiedJulianDates, 

156 we can use astropy.time.Time's vectorized methods to quickly perform many 

157 conversions at once. Users should not try to use this method by hand. 

158 """ 

159 self._tai = values[0] 

160 self._utc = values[1] 

161 self._tt = values[2] 

162 self._tdb = values[3] 

163 self._ut1 = values[4] 

164 self._dut1 = values[5] 

165 

166 def __eq__(self, other): 

167 return self._time == other._time 

168 

169 def __ne__(self, other): 

170 return not self.__eq__(other) 

171 

172 def __deepcopy__(self, memo): 

173 if self._initialized_with == 'TAI': 

174 new_mjd = ModifiedJulianDate(TAI=self.TAI) 

175 else: 

176 new_mjd = ModifiedJulianDate(UTC=self.UTC) 

177 

178 new_mjd._tai = copy.deepcopy(self._tai, memo) 

179 new_mjd._utc = copy.deepcopy(self._utc, memo) 

180 new_mjd._tt = copy.deepcopy(self._tt, memo) 

181 new_mjd._tdb = copy.deepcopy(self._tdb, memo) 

182 new_mjd._ut1 = copy.deepcopy(self._ut1, memo) 

183 new_mjd._dut1 = copy.deepcopy(self._dut1, memo) 

184 

185 return new_mjd 

186 

187 def _warn_utc_out_of_bounds(self, method_name): 

188 """ 

189 Raise a standard warning if UTC is outside of the range that can 

190 be interpolated on the IERS tables. 

191 

192 method_name is the name of the method that caused this warning. 

193 """ 

194 warnings.warn("UTC is outside of IERS table for UT1-UTC.\n" 

195 "Returning UT1 = UTC for lack of a better idea\n" 

196 "This warning was caused by calling ModifiedJulianDate.%s\n" % method_name, 

197 category=UTCtoUT1Warning) 

198 

199 @property 

200 def TAI(self): 

201 """ 

202 International Atomic Time as an MJD 

203 """ 

204 if self._tai is None: 

205 self._tai = self._time.tai.mjd 

206 

207 return self._tai 

208 

209 @property 

210 def UTC(self): 

211 """ 

212 Universal Coordinate Time as an MJD 

213 """ 

214 if self._utc is None: 

215 self._utc = self._time.utc.mjd 

216 

217 return self._utc 

218 

219 @property 

220 def UT1(self): 

221 """ 

222 Universal Time as an MJD 

223 """ 

224 if self._ut1 is None: 

225 try: 

226 self._ut1 = self._time.ut1.mjd 

227 except IERSRangeError: 

228 self._warn_utc_out_of_bounds('UT1') 

229 self._ut1 = self.UTC 

230 

231 return self._ut1 

232 

233 @property 

234 def dut1(self): 

235 """ 

236 UT1-UTC in seconds 

237 """ 

238 

239 if self._dut1 is None: 

240 try: 

241 self._dut1 = self._time.delta_ut1_utc 

242 except IERSRangeError: 

243 self._warn_utc_out_of_bounds('dut1') 

244 self._dut1 = 0.0 

245 

246 return self._dut1 

247 

248 @property 

249 def TT(self): 

250 """ 

251 Terrestrial Time (aka Terrestrial Dynamical Time) as an MJD 

252 """ 

253 if self._tt is None: 

254 self._tt = self._time.tt.mjd 

255 

256 return self._tt 

257 

258 @property 

259 def TDB(self): 

260 """ 

261 Barycentric Dynamical Time as an MJD 

262 """ 

263 if self._tdb is None: 

264 self._tdb = self._time.tdb.mjd 

265 

266 return self._tdb 

267