From c8020f6c8eb617fd21b45483f0381f99ac985579 Mon Sep 17 00:00:00 2001 From: Colin S <3526918+cbs228@users.noreply.github.com> Date: Thu, 6 Nov 2025 21:34:19 -0600 Subject: [PATCH] grib: fix NullPointerException for legacy NOAA RAP models MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Attempting to load a [legacy] NOAA Rapid Refresh GRIB2 file fails with a NullPointerException. java -jar netcdfAll-5.10.0-SNAPSHOT.jar \ rap_130_20170720_0000_000.grb2 This file loads correctly in netcdf-java 4.6, but it has a quirk: it contains at least one PDS that does not define a time range. grib_dump -O -p section_4 \ -w section4Length=58,numberOfTimeRange=0 \ rap_130_20170720_0000_000.grb2 6bef2fb9f5 (Fix for issue #606, version 5., 2021-02-26) corrects time calculations for GRIB2 PDS which have incompatible units for **time intervals** and related time quantities. The above patch introduces a `TimeIntervalAndUnits` parser for PDS times. If a PDS does not define *any* time intervals, the previous version of Grib2Tables.getForecastTimeInterval(Grib2Record) returns a duration of zero—i.e., no elapsed time. This is likely the correct interpretation if no time intervals are present. After 6bef2fb9f5, this method raises a NullPointerException instead. This is because the inner function Grib2Tables.getForecastTimeInterval(Grib2Pds.PdsInterval) returns an invalid `TimeIntervalAndUnits` when it ought to return a zero duration instead. The calling functions do not treat this data type as fallible and may use it in conversions that return null. If the PDS does not contain any time intervals, return a "zero hour" duration instead. This restores the previous behavior. * In legacy and modern RAP models, PDS which *do* define time intervals define them as zero. The interpretation of the "quirky" PDS remains consistent with its brethren. * This quirk is not present in more [recent] RAP model runs, so this bug is unlikely to affect current RAP data. [legacy]: https://www.ncei.noaa.gov/oa/prod-model/rapid-refresh/access/historical/analysis/201707/20170720/rap_130_20170720_0000_000.grb2 [recent]: https://www.ncei.noaa.gov/oa/prod-model/rapid-refresh/access/rap-130-13km/analysis/202504/20250401/rap_130_20250401_0000_000.grb2 --- .../nc2/grib/grib2/table/Grib2Tables.java | 4 + .../index/example_pds_8_quirks.grib2.gbx9 | Bin 0 -> 376 bytes .../java/ucar/nc2/grib/grib2/TestPds8.java | 186 ++++++++++++++++++ 3 files changed, 190 insertions(+) create mode 100644 grib/src/test/data/index/example_pds_8_quirks.grib2.gbx9 create mode 100644 grib/src/test/java/ucar/nc2/grib/grib2/TestPds8.java diff --git a/grib/src/main/java/ucar/nc2/grib/grib2/table/Grib2Tables.java b/grib/src/main/java/ucar/nc2/grib/grib2/table/Grib2Tables.java index 63afb2e4fe..2e2d46aae7 100644 --- a/grib/src/main/java/ucar/nc2/grib/grib2/table/Grib2Tables.java +++ b/grib/src/main/java/ucar/nc2/grib/grib2/table/Grib2Tables.java @@ -607,6 +607,10 @@ private TimeIntervalAndUnits getForecastTimeInterval(Grib2Pds.PdsInterval pdsInt range += ti.timeIncrement; } } + if (timeUnitIntv < 0) { + range = 0; + timeUnitIntv = 1; /* hours */ + } return new TimeIntervalAndUnits(timeUnitIntv, range); } diff --git a/grib/src/test/data/index/example_pds_8_quirks.grib2.gbx9 b/grib/src/test/data/index/example_pds_8_quirks.grib2.gbx9 new file mode 100644 index 0000000000000000000000000000000000000000..a501fe6c3eed35c3e983105e7b2c45a1331ec580 GIT binary patch literal 376 zcmZ=S%1koy%u7kFV0+EPC6!u{m|KvO8efo79B&a{SejXsU96W5QX>>B6v)895Xj8H zz!bO{$dY3N5~zUjFarZ)Aj6N`6}%sc3oICGD;)SwPjO&4w@w3$9Y7)=4AUl6&BY)v zk6nU6Nt}aSfI)&ufl-6ei2Wfyy9iK;1%m^l0fUtlkZHvNBsdslnHVyG3`QV^5Dfo8 zOa}IcU=3h4BNJE}Ea9+$(L*C(^J313example_pds_8_quirks.grib2 + * + * # index + * java -jar netcdfAll-5.10.0-SNAPSHOT.jar \ + * example_pds_8_quirks.grib2 + * + * # generates example_pds_8_quirks.grib2.gbx9 + */