codecov-io: # [Codecov](https://codecov.io/gh/python-metar/python-metar/pull/78?src=pr&el=h1) Report
> Merging [#78](https://codecov.io/gh/python-metar/python-metar/pull/78?src=pr&el=desc) into [master](https://codecov.io/gh/python-metar/python-metar/commit/47bde0ea2224b197cfcbcb5d500e8ae761837e87?src=pr&el=desc) will **increase** coverage by `0.65%`.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/python-metar/python-metar/pull/78/graphs/tree.svg?width=650&token=W7CMdTTvRY&height=150&src=pr)](https://codecov.io/gh/python-metar/python-metar/pull/78?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master      #78      +/-   ##
==========================================
+ Coverage   87.32%   87.98%   +0.65%     
==========================================
  Files           4        4              
  Lines        1018     1032      +14     
==========================================
+ Hits          889      908      +19     
+ Misses        129      124       -5
```


| [Impacted Files](https://codecov.io/gh/python-metar/python-metar/pull/78?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [metar/Metar.py](https://codecov.io/gh/python-metar/python-metar/pull/78/diff?src=pr&el=tree#diff-bWV0YXIvTWV0YXIucHk=) | `90.46% <100%> (+0.83%)` | :arrow_up: |
| [metar/Datatypes.py](https://codecov.io/gh/python-metar/python-metar/pull/78/diff?src=pr&el=tree#diff-bWV0YXIvRGF0YXR5cGVzLnB5) | `84.61% <0%> (+0.28%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/python-metar/python-metar/pull/78?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/python-metar/python-metar/pull/78?src=pr&el=footer). Last update [47bde0e...f60cd35](https://codecov.io/gh/python-metar/python-metar/pull/78?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

coveralls: ## Pull Request Test Coverage Report for [Build 85](https://coveralls.io/builds/21067809)

* **14** of **14**   **(100.0%)**  changed or added relevant lines in **1** file are covered.
* **1** unchanged line in **1** file lost coverage.
* Overall coverage increased (+**0.7%**) to **87.984%**

---


|  Files with Coverage Reduction | New Missed Lines | % |
| :-----|--------------|--: |
| [metar/Metar.py](https://coveralls.io/builds/21067809/source?filename=metar%2FMetar.py#L519) | 1 | 90.46% |
<!-- | **Total:** | **1** |  | -->

|  Totals | [![Coverage Status](https://coveralls.io/builds/21067809/badge)](https://coveralls.io/builds/21067809) |
| :-- | --: |
| Change from base [Build 84](https://coveralls.io/builds/19618672): |  0.7% |
| Covered Lines: | 908 |
| Relevant Lines: | 1032 |

---
##### 💛  - [Coveralls](https://coveralls.io)

akrherz: looking at this data more closely, it appears to follow the same precipitation nomenclature such that `000` represents a TRACE value.  Gonna seek some confirmation on this first, so please hold of on merging for the moment.
diff --git a/metar/Datatypes.py b/metar/Datatypes.py
index c9eb438..9f92ced 100644
--- a/metar/Datatypes.py
+++ b/metar/Datatypes.py
@@ -376,8 +376,8 @@ class precipitation(object):
             raise ValueError("unrecognized greater-than/less-than symbol: '"+gtlt+"'")
         self._gtlt = gtlt
         self._value = float(value)
-        # In METAR world, a string of four zeros denotes trace
-        self._istrace = (value == "0000")
+        # In METAR world, a string of just four or three zeros denotes trace
+        self._istrace = (value in ["0000", "000"])
 
     def __str__(self):
         return self.string()
diff --git a/metar/Metar.py b/metar/Metar.py
index c8274e5..7275b2a 100644
--- a/metar/Metar.py
+++ b/metar/Metar.py
@@ -144,6 +144,9 @@ TS_LOC_RE = re.compile(r"""TS(\s+(?P<loc>( OHD | VC | DSNT\s+ | \s+AND\s+ |
                                           ( \s+MOV\s+(?P<dir>[NSEW][EW]?) )?\s+""",
                            re.VERBOSE)
 SNOWDEPTH_RE = re.compile(r"""^4/(?P<snowdepth>\d\d\d)\s+""")
+ICE_ACCRETION_RE = re.compile(
+    r"^I(?P<ice_accretion_hours>[136])(?P<ice_accretion_depth>\d\d\d)\s+"
+)
 
 
 # translation of weather location codes
@@ -345,6 +348,9 @@ class Metar(object):
         self.precip_6hr = None             # precipitation over the last 6 hours
         self.precip_24hr = None            # precipitation over the last 24 hours
         self.snowdepth = None              # snow depth (distance)
+        self.ice_accretion_1hr = None      # ice accretion over the past hour
+        self.ice_accretion_3hr = None      # ice accretion over the past 3 hours
+        self.ice_accretion_6hr = None      # ice accretion over the past 6 hours
         self._trend = False                # trend groups present (bool)
         self._trend_groups = []            # trend forecast groups
         self._remarks = []                 # remarks (list of strings)
@@ -924,6 +930,14 @@ class Metar(object):
         self.snowdepth = distance(float(d['snowdepth']), 'IN')
         self._remarks.append(" snowdepth %s" % (self.snowdepth, ))
 
+    def _handleIceAccretionRemark(self, d):
+        """
+        Parse the I/ group ice accretion report.
+        """
+        myattr = "ice_accretion_%shr" % (d['ice_accretion_hours'], )
+        value = precipitation(float(d['ice_accretion_depth']) / 100., 'IN')
+        setattr(self, myattr, value)
+
     def _unparsedRemark( self, d ):
         """
         Handle otherwise unparseable remark groups.
@@ -974,6 +988,7 @@ class Metar(object):
                         (TEMP_6HR_RE,     _handleTemp6hrRemark),
                         (TEMP_24HR_RE,    _handleTemp24hrRemark),
                         (SNOWDEPTH_RE,    _handleSnowDepthRemark),
+                        (ICE_ACCRETION_RE, _handleIceAccretionRemark),
                         (UNPARSED_RE,     _unparsedRemark) ]
 
     # functions that return text representations of conditions for output
@@ -1026,6 +1041,12 @@ class Metar(object):
             lines.append("6-hour precipitation: %s" % str(self.precip_6hr))
         if self.precip_24hr:
             lines.append("24-hour precipitation: %s" % str(self.precip_24hr))
+        if self.ice_accretion_1hr:
+            lines.append("1-hour Ice Accretion: %s" % str(self.ice_accretion_1hr, ))
+        if self.ice_accretion_3hr:
+            lines.append("3-hour Ice Accretion: %s" % str(self.ice_accretion_3hr, ))
+        if self.ice_accretion_6hr:
+            lines.append("6-hour Ice Accretion: %s" % str(self.ice_accretion_6hr, ))
         if self._remarks:
             lines.append("remarks:")
             lines.append("- "+self.remarks("\n- "))

Add support for Ice Group IXnnn
A NWS collaborator reached out to me wondering if I was parsing the I Group for 1,3 hourly ice accumulation totals.  These are now collected from new sensors in Texas and Oklahoma.  For example:

```
KABI 031752Z 30010KT 6SM BR FEW009 OVC036 02/01 A3003 RMK AO2 SLP176 60001 I6003 T00170006 10017 21006 56017
```

From FMH-1
>Ice Accretion. At designated stations (with SPECI capability freezing rain sensor) ice
accretion shall be reported in METAR and SPECI reports when accretion is occurring or
has occurred during the reporting period.
>1. Hourly Ice Accretion Amount (I1nnn). At designated automated stations, the hourly
ice accretion amount shall be coded in the format I1nnn, where I is the ice accretion
group indicator, 1 indicates an hourly amount, and nnn is the accretion amount since
the last METAR. The amount shall be coded in hundredths of an inch. For example,
"I1004" would indicate 4/100 of an inch of ice accretion in the past hour.
>2. 3- and 6-Hourly Ice Accretion Amount (I3nnn and I6nnn). At designated stations,
the 3- and 6-hourly ice accretion group shall be coded in the format I3nnn and I6nnn,
respectively, where I is the group indicator, 3 and 6 indicate 3- and 6- hourly groups,
respectively, and nnn is the accretion amount. The amount of ice accretion in the past
3 hours shall be reported in the 3-hourly report; the amount of ice accretion in the past
6 hours shall be reported in the 6-hourly report. The amount shall be coded in
hundredths of an inch. For example, I6023 would indicate 23/100 of an inch of ice
accretion in the past 6 hours.

diff --git a/test/test_metar.py b/test/test_metar.py
index 6a0e6ca..0d99861 100644
--- a/test/test_metar.py
+++ b/test/test_metar.py
@@ -39,6 +39,18 @@ def test_module():
     assert hasattr(metar, "__version__")
 
 
+@pytest.mark.parametrize("hours", [1, 3, 6])
+def test_issue77_ice_accretion(hours):
+    """Metar parser supports ice accretion data."""
+    report = Metar.Metar(
+        ("KABI 031752Z 30010KT 6SM BR FEW009 OVC036 02/01 A3003 RMK AO2 "
+         "SLP176 60001 I%i003 T00170006 10017 21006 56017") % (hours, )
+    )
+    myattr = "ice_accretion_%ihr" % (hours, )
+    assert abs(getattr(report, myattr).value('IN') - 0.03) < 0.001
+    assert str(report).find("Ice Accretion") > 0
+
+
 def test_issue64_cloudkeyerror():
     """Lookup on CLOUD_TYPE should not keyerror."""
     report = Metar.Metar(

pip install -e .[dev]
parse_log_pytest
pytest
pytest
3.9
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
exceptiongroup @ file:///croot/exceptiongroup_1706031385326/work
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
-e git+https://github.com/python-metar/python-metar.git@47bde0ea2224b197cfcbcb5d500e8ae761837e87#egg=metar
packaging @ file:///croot/packaging_1734472117206/work
pluggy @ file:///croot/pluggy_1733169602837/work
pytest @ file:///croot/pytest_1738938843180/work
tomli @ file:///opt/conda/conda-bld/tomli_1657175507142/work

name: python-metar
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - exceptiongroup=1.2.0=py39h06a4308_0
  - iniconfig=1.1.1=pyhd3eb1b0_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - packaging=24.2=py39h06a4308_0
  - pip=25.0=py39h06a4308_0
  - pluggy=1.5.0=py39h06a4308_0
  - pytest=8.3.4=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tomli=2.0.1=py39h06a4308_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
prefix: /opt/conda/envs/python-metar

test/test_metar.py::test_issue77_ice_accretion[1] test/test_metar.py::test_issue77_ice_accretion[3] test/test_metar.py::test_issue77_ice_accretion[6]
test/test_metar.py::test_xlate_loc test/test_metar.py::test_module test/test_metar.py::test_issue64_cloudkeyerror test/test_metar.py::test_issue67_precip_text test/test_metar.py::test_issue40_runwayunits test/test_metar.py::test_010_parseType_default test/test_metar.py::test_011_parseType_legal test/test_metar.py::test_020_parseStation_legal test/test_metar.py::test_021_parseStation_illegal test/test_metar.py::test_030_parseTime_legal test/test_metar.py::test_031_parseTime_specify_year test/test_metar.py::test_032_parseTime_specify_month test/test_metar.py::test_033_parseTime_auto_month test/test_metar.py::test_034_parseTime_auto_year test/test_metar.py::test_035_parseTime_suppress_auto_month test/test_metar.py::test_040_parseModifier_default test/test_metar.py::test_041_parseModifier test/test_metar.py::test_042_parseModifier_nonstd test/test_metar.py::test_043_parseModifier_illegal test/test_metar.py::test_140_parseWind test/test_metar.py::test_141_parseWind_nonstd test/test_metar.py::test_issue51_strict test/test_metar.py::test_142_parseWind_illegal test/test_metar.py::test_150_parseVisibility test/test_metar.py::test_151_parseVisibility_direction test/test_metar.py::test_152_parseVisibility_with_following_temperature test/test_metar.py::test_290_ranway_state test/test_metar.py::test_300_parseTrend test/test_metar.py::test_snowdepth test/test_metar.py::test_310_parse_sky_conditions test/test_metar.py::test_not_strict_mode
metar/Metar.py metar/Datatypes.py
metar/Metar.py metar/Datatypes.py