diff --git a/utm/conversion.py b/utm/conversion.py
index ec30333..33fbfe9 100644
--- a/utm/conversion.py
+++ b/utm/conversion.py
@@ -176,7 +176,7 @@ def latitude_to_zone_letter(latitude):
 
 
 def latlon_to_zone_number(latitude, longitude):
-    if 56 <= latitude <= 64 and 3 <= longitude <= 12:
+    if 56 <= latitude < 64 and 3 <= longitude < 12:
         return 32
 
     if 72 <= latitude <= 84 and longitude >= 0:

Borders of zones 31V and 32V 
In `latlon_to_zone_number` you handle the zones 31V and 32V different from all the other zones.

Usually, you compute the zone number `[left..right)` and the zone letter `[bottom..top)`. But for the corrections of 31V and 32V you use `[left..right]` and `[bottom..top]`.

This leads to the following behavior:
* for 52°N 12°E, 33U is returned. (which, in my opinion is correct)
* for 60°N 12°E, 32V is returned. Shouldn't it be 33V?
* for 64°N 5°E, 32W is returned. Shouldn't it be 31W?

I would replace line 179 by
```python
if 56 <= latitude < 64 and 3 <= longitude < 12:
```

I didn't check your code for the corrections in the X-band in detail. (I'm not interrested in places this far north...) But I believe, we have the same issue there.

diff --git a/test/test_utm.py b/test/test_utm.py
index 1ffcab2..5cbabe8 100644
--- a/test/test_utm.py
+++ b/test/test_utm.py
@@ -173,5 +173,57 @@ class BadInput(UTMTestCase):
         self.assertRaises(
             UTM.OutOfRangeError, UTM.to_latlon, 500000, 5000000, 32, 'Z')
 
+
+class Zone32V(unittest.TestCase):
+
+    def assert_zone_equal(self, result, expected_number, expected_letter):
+        self.assertEqual(result[2], expected_number)
+        self.assertEqual(result[3].upper(), expected_letter.upper())
+
+    def test_inside(self):
+        self.assert_zone_equal(UTM.from_latlon(56, 3), 32, 'V')
+        self.assert_zone_equal(UTM.from_latlon(56, 6), 32, 'V')
+        self.assert_zone_equal(UTM.from_latlon(56, 9), 32, 'V')
+        self.assert_zone_equal(UTM.from_latlon(56, 11.999999), 32, 'V')
+
+        self.assert_zone_equal(UTM.from_latlon(60, 3), 32, 'V')
+        self.assert_zone_equal(UTM.from_latlon(60, 6), 32, 'V')
+        self.assert_zone_equal(UTM.from_latlon(60, 9), 32, 'V')
+        self.assert_zone_equal(UTM.from_latlon(60, 11.999999), 32, 'V')
+
+        self.assert_zone_equal(UTM.from_latlon(63.999999, 3), 32, 'V')
+        self.assert_zone_equal(UTM.from_latlon(63.999999, 6), 32, 'V')
+        self.assert_zone_equal(UTM.from_latlon(63.999999, 9), 32, 'V')
+        self.assert_zone_equal(UTM.from_latlon(63.999999, 11.999999), 32, 'V')
+
+    def test_left_of(self):
+        self.assert_zone_equal(UTM.from_latlon(55.999999, 2.999999), 31, 'U')
+        self.assert_zone_equal(UTM.from_latlon(56, 2.999999), 31, 'V')
+        self.assert_zone_equal(UTM.from_latlon(60, 2.999999), 31, 'V')
+        self.assert_zone_equal(UTM.from_latlon(63.999999, 2.999999), 31, 'V')
+        self.assert_zone_equal(UTM.from_latlon(64, 2.999999), 31, 'W')
+
+    def test_right_of(self):
+        self.assert_zone_equal(UTM.from_latlon(55.999999, 12), 33, 'U')
+        self.assert_zone_equal(UTM.from_latlon(56, 12), 33, 'V')
+        self.assert_zone_equal(UTM.from_latlon(60, 12), 33, 'V')
+        self.assert_zone_equal(UTM.from_latlon(63.999999, 12), 33, 'V')
+        self.assert_zone_equal(UTM.from_latlon(64, 12), 33, 'W')
+
+    def test_below(self):
+        self.assert_zone_equal(UTM.from_latlon(55.999999, 3), 31, 'U')
+        self.assert_zone_equal(UTM.from_latlon(55.999999, 6), 32, 'U')
+        self.assert_zone_equal(UTM.from_latlon(55.999999, 9), 32, 'U')
+        self.assert_zone_equal(UTM.from_latlon(55.999999, 11.999999), 32, 'U')
+        self.assert_zone_equal(UTM.from_latlon(55.999999, 12), 33, 'U')
+
+    def test_above(self):
+        self.assert_zone_equal(UTM.from_latlon(64, 3), 31, 'W')
+        self.assert_zone_equal(UTM.from_latlon(64, 6), 32, 'W')
+        self.assert_zone_equal(UTM.from_latlon(64, 9), 32, 'W')
+        self.assert_zone_equal(UTM.from_latlon(64, 11.999999), 32, 'W')
+        self.assert_zone_equal(UTM.from_latlon(64, 12), 33, 'W')
+
+
 if __name__ == '__main__':
     unittest.main()

pip install -e .
parse_log_pytest
requirements.txt
pytest
apt-get update apt-get install -y gcc
3.9
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
exceptiongroup==1.2.2
iniconfig==2.1.0
packaging==24.2
pluggy==1.5.0
pytest==8.3.5
tomli==2.2.1
-e git+https://github.com/Turbo87/utm.git@a121973a02f2e24cfc9d45a24cd37b7c0bef3af9#egg=utm

name: utm
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
  - 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
  - pip=25.0=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
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - exceptiongroup==1.2.2
      - iniconfig==2.1.0
      - packaging==24.2
      - pluggy==1.5.0
      - pytest==8.3.5
      - tomli==2.2.1
prefix: /opt/conda/envs/utm

test/test_utm.py::Zone32V::test_above test/test_utm.py::Zone32V::test_right_of
test/test_utm.py::KnownValues::test_from_latlon test/test_utm.py::KnownValues::test_to_latlon test/test_utm.py::BadInput::test_from_latlon_range_checks test/test_utm.py::BadInput::test_to_latlon_range_checks test/test_utm.py::Zone32V::test_below test/test_utm.py::Zone32V::test_inside test/test_utm.py::Zone32V::test_left_of
utm/conversion.py
utm/conversion.py