Skip to content

Commit e91f9f3

Browse files
authored
Merge pull request #63 from ESGF/fix/check_time_squareness
[TIME001] Adjust check_time_squareness to follow sampling times guidance
2 parents cecbe5f + 55c4cc7 commit e91f9f3

1 file changed

Lines changed: 57 additions & 9 deletions

File tree

checks/time_checks/check_time_squareness.py

Lines changed: 57 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -294,15 +294,63 @@ def check_time_squareness(
294294
a_t = _round(actual, NDECIMALS)
295295
t_t = _round(theo, NDECIMALS)
296296

297-
bad = np.where(a_t != t_t)[0]
298-
if bad.size:
299-
i = int(bad[0])
300-
ctx.add_failure(
301-
f"Mismatch at index {i}: expected {t_t[i]:.{NDECIMALS}f}, got {a_t[i]:.{NDECIMALS}f}. "
302-
f"(table_id={table_id}, frequency={freq_id}, var={target}, midpoint={use_midpoint})"
303-
)
297+
# For monthly instantaneous data, allow three valid timestamp conventions,
298+
# but require consistency across the whole file (single convention per file):
299+
# - first day of each month at 00:00
300+
# - 15th day of each month at 00:00
301+
# - exact calendar-aware midpoint of each month interval
302+
allow_mon_point_midmonth = (
303+
instantaneous
304+
and (freq_id in {"mon", "monPt"})
305+
and (inc_unit == "months")
306+
and (inc_val == 1)
307+
)
308+
309+
if allow_mon_point_midmonth:
310+
theo_mid = np.zeros(actual.size, dtype=float)
311+
theo_center = np.zeros(actual.size, dtype=float)
312+
cur = start_boundary
313+
for i in range(actual.size):
314+
nxt = _add_time_increment(cur, inc_val, inc_unit, cal)
315+
mid = cftime.datetime(cur.year, cur.month, 15, 0, 0, 0, calendar=cal)
316+
theo_mid[i] = float(cftime.date2num(mid, units=units, calendar=cal))
317+
theo_center[i] = _midpoint_num(cur, nxt, units, cal)
318+
cur = nxt
319+
320+
t_mid = _round(theo_mid, NDECIMALS)
321+
t_center = _round(theo_center, NDECIMALS)
322+
323+
# A file passes only if the full axis matches one convention end-to-end.
324+
full_match_start = np.array_equal(a_t, t_t)
325+
full_match_mid = np.array_equal(a_t, t_mid)
326+
full_match_center = np.array_equal(a_t, t_center)
327+
328+
if not (full_match_start or full_match_mid or full_match_center):
329+
bad = np.where((a_t != t_t) & (a_t != t_mid) & (a_t != t_center))[0]
330+
if bad.size:
331+
i = int(bad[0])
332+
else:
333+
# Mixed-convention axis: every point matches at least one candidate,
334+
# but no single candidate matches the entire file.
335+
first_start = np.where(a_t != t_t)[0]
336+
i = int(first_start[0]) if first_start.size else 0
337+
ctx.add_failure(
338+
f"Mismatch at index {i}: expected {t_t[i]:.{NDECIMALS}f} (month-start) "
339+
f"or {t_mid[i]:.{NDECIMALS}f} (day-15) "
340+
f"or {t_center[i]:.{NDECIMALS}f} (exact center), got {a_t[i]:.{NDECIMALS}f}. "
341+
"The full file must consistently follow one of these conventions. "
342+
f"(table_id={table_id}, frequency={freq_id}, var={target}, midpoint={use_midpoint})"
343+
)
304344
else:
305-
if not ctx.messages:
306-
ctx.add_pass()
345+
bad = np.where(a_t != t_t)[0]
346+
if bad.size:
347+
i = int(bad[0])
348+
ctx.add_failure(
349+
f"Mismatch at index {i}: expected {t_t[i]:.{NDECIMALS}f}, got {a_t[i]:.{NDECIMALS}f}. "
350+
f"(table_id={table_id}, frequency={freq_id}, var={target}, midpoint={use_midpoint})"
351+
)
352+
353+
if not ctx.messages:
354+
ctx.add_pass()
307355

308356
return [ctx.to_result()]

0 commit comments

Comments
 (0)