Coverage for python / lsst / summit / utils / dateTime.py: 44%
52 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-05 08:54 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-05 08:54 +0000
1# This file is part of summit_utils.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <https://www.gnu.org/licenses/>.
21from __future__ import annotations
23import datetime
24from typing import TYPE_CHECKING
26import astropy
27from astropy import units as u
28from astropy.time import Time, TimeDelta
29from dateutil.tz import gettz
31if TYPE_CHECKING:
32 from lsst.daf.butler import DimensionRecord
35__all__ = [
36 "expRecordToTimespan",
37 "efdTimestampToAstropy",
38 "astropyToEfdTimestamp",
39 "offsetDayObs",
40 "calcNextDay",
41 "calcPreviousDay",
42 "calcDayOffset",
43 "getDayObsStartTime",
44 "getDayObsEndTime",
45 "getDayObsForTime",
46 "dayObsIntToString",
47 "getCurrentDayObsDatetime",
48 "getCurrentDayObsInt",
49 "getCurrentDayObsHumanStr",
50 "getExpRecordAge",
51]
54def expRecordToTimespan(expRecord: DimensionRecord) -> dict:
55 """Get the timespan from an exposure record.
57 Returns the timespan in a format where it can be used to directly unpack
58 into a efdClient.select_time_series() call.
60 Parameters
61 ----------
62 expRecord : `lsst.daf.butler.DimensionRecord`
63 The exposure record.
65 Returns
66 -------
67 timespanDict : `dict`
68 The timespan in a format that can be used to directly unpack into a
69 efdClient.select_time_series() call.
70 """
71 return {
72 "begin": expRecord.timespan.begin.utc,
73 "end": expRecord.timespan.end.utc,
74 }
77def efdTimestampToAstropy(timestamp: float) -> Time:
78 """Get an efd timestamp as an astropy.time.Time object.
80 Parameters
81 ----------
82 timestamp : `float`
83 The timestamp, as a float.
85 Returns
86 -------
87 time : `astropy.time.Time`
88 The timestamp as an astropy.time.Time object.
89 """
90 return Time(timestamp, format="unix")
93def astropyToEfdTimestamp(time: Time) -> float:
94 """Get astropy Time object as an efd timestamp
96 Parameters
97 ----------
98 time : `astropy.time.Time`
99 The time as an astropy.time.Time object.
101 Returns
102 -------
103 timestamp : `float`
104 The timestamp, in UTC, in unix seconds.
105 """
107 return time.utc.unix
110def offsetDayObs(dayObs: int, nDays: int) -> int:
111 """Offset a dayObs by a given number of days.
113 Parameters
114 ----------
115 dayObs : `int`
116 The dayObs, as an integer, e.g. 20231225
117 nDays : `int`
118 The number of days to offset the dayObs by.
120 Returns
121 -------
122 newDayObs : `int`
123 The new dayObs, as an integer, e.g. 20231225
124 """
125 d1 = datetime.datetime.strptime(str(dayObs), "%Y%m%d")
126 oneDay = datetime.timedelta(days=nDays)
127 return int((d1 + oneDay).strftime("%Y%m%d"))
130def calcNextDay(dayObs: int) -> int:
131 """Given an integer dayObs, calculate the next integer dayObs.
133 Integers are used for dayObs, but dayObs values are therefore not
134 contiguous due to month/year ends etc, so this utility provides a robust
135 way to get the integer dayObs which follows the one specified.
137 Parameters
138 ----------
139 dayObs : `int`
140 The dayObs, as an integer, e.g. 20231231
142 Returns
143 -------
144 nextDayObs : `int`
145 The next dayObs, as an integer, e.g. 20240101
146 """
147 return offsetDayObs(dayObs, 1)
150def calcPreviousDay(dayObs: int) -> int:
151 """Given an integer dayObs, calculate the next integer dayObs.
153 Integers are used for dayObs, but dayObs values are therefore not
154 contiguous due to month/year ends etc, so this utility provides a robust
155 way to get the integer dayObs which follows the one specified.
157 Parameters
158 ----------
159 dayObs : `int`
160 The dayObs, as an integer, e.g. 20231231
162 Returns
163 -------
164 nextDayObs : `int`
165 The next dayObs, as an integer, e.g. 20240101
166 """
167 return offsetDayObs(dayObs, -1)
170def calcDayOffset(startDay: int, endDay: int) -> int:
171 """Calculate the number of days between two dayObs values.
173 Positive if endDay is after startDay, negative if before, zero if equal.
175 Parameters
176 ----------
177 startDay : `int`
178 The starting dayObs, e.g. 20231225.
179 endDay : `int`
180 The ending dayObs, e.g. 20240101.
182 Returns
183 -------
184 offset : `int`
185 The number of days from startDay to endDay.
186 """
187 dStart = datetime.datetime.strptime(str(startDay), "%Y%m%d")
188 dEnd = datetime.datetime.strptime(str(endDay), "%Y%m%d")
189 return (dEnd - dStart).days
192def getDayObsStartTime(dayObs: int) -> astropy.time.Time:
193 """Get the start of the given dayObs as an astropy.time.Time object.
195 The observatory rolls the date over at UTC-12.
197 Parameters
198 ----------
199 dayObs : `int`
200 The dayObs, as an integer, e.g. 20231225
202 Returns
203 -------
204 time : `astropy.time.Time`
205 The start of the dayObs as an astropy.time.Time object.
206 """
207 pythonDateTime = datetime.datetime.strptime(str(dayObs), "%Y%m%d")
208 return Time(pythonDateTime) + 12 * u.hour
211def getDayObsEndTime(dayObs: int) -> Time:
212 """Get the end of the given dayObs as an astropy.time.Time object.
214 Parameters
215 ----------
216 dayObs : `int`
217 The dayObs, as an integer, e.g. 20231225
219 Returns
220 -------
221 time : `astropy.time.Time`
222 The end of the dayObs as an astropy.time.Time object.
223 """
224 return getDayObsStartTime(dayObs) + 24 * u.hour
227def getDayObsForTime(time: Time) -> int:
228 """Get the dayObs in which an astropy.time.Time object falls.
230 Parameters
231 ----------
232 time : `astropy.time.Time`
233 The time.
235 Returns
236 -------
237 dayObs : `int`
238 The dayObs, as an integer, e.g. 20231225
239 """
240 twelveHours = datetime.timedelta(hours=-12)
241 offset = TimeDelta(twelveHours, format="datetime")
242 return int((time + offset).utc.isot[:10].replace("-", ""))
245def dayObsIntToString(dayObs: int) -> str:
246 """Convert an integer dayObs to a dash-delimited string.
248 e.g. convert the hard to read 20210101 to 2021-01-01
250 Parameters
251 ----------
252 dayObs : `int`
253 The dayObs.
255 Returns
256 -------
257 dayObs : `str`
258 The dayObs as a string.
259 """
260 assert isinstance(dayObs, int)
261 dStr = str(dayObs)
262 assert len(dStr) == 8
263 return "-".join([dStr[0:4], dStr[4:6], dStr[6:8]])
266def getCurrentDayObsDatetime() -> datetime.date:
267 """Get the current day_obs - the observatory rolls the date over at UTC-12
269 Returned as datetime.date(2022, 4, 28)
270 """
271 utc = gettz("UTC")
272 nowUtc = datetime.datetime.now().astimezone(utc)
273 offset = datetime.timedelta(hours=-12)
274 dayObs = (nowUtc + offset).date()
275 return dayObs
278def getCurrentDayObsInt() -> int:
279 """Return the current dayObs as an int in the form 20220428"""
280 return int(getCurrentDayObsDatetime().strftime("%Y%m%d"))
283def getCurrentDayObsHumanStr() -> str:
284 """Return the current dayObs as a string in the form '2022-04-28'"""
285 return dayObsIntToString(getCurrentDayObsInt())
288def getExpRecordAge(expRecord: DimensionRecord) -> float:
289 """Get the time, in seconds, since the end of exposure.
291 Parameters
292 ----------
293 expRecord : `lsst.daf.butler.DimensionRecord`
294 The exposure record.
296 Returns
297 -------
298 age : `float`
299 The age of the exposure, in seconds.
300 """
301 return -1 * (expRecord.timespan.end - Time.now()).sec