fix(import): a deleted DXCC entity is not re-stamped from cty.dat

Reported with a Logger32 export: the record said <DXCC:3>151 and OpsLog
stored 54.

cty.dat answers one question — where is this callsign TODAY — and that is
the wrong question for a log. R1MVI was Malyj Vysotskij Island, entity
151, until the ARRL deleted it in 2012; the same call resolves to European
Russia today. Forcing the lookup onto a 2004 contact therefore destroys a
credit that can NEVER be worked again, because the place no longer counts.

So the ADIF's number wins whenever it names a deleted entity, and nothing
is corrected on those records at all — the country name and the zones of a
deleted entity are equally beyond what a present-day prefix table knows.
The list is the ADIF specification's own enumeration, sixty-odd entities
that only change when the ARRL deletes another one.

The guard sits in the forced path, so it also covers the grid's 'Update
from cty.dat' on a selection — the other way an operator could have lost
the same credits, one right-click at a time.
This commit is contained in:
2026-08-26 21:59:53 +02:00
parent 4a28b51ade
commit c1cd76bbcf
4 changed files with 162 additions and 2 deletions
+97
View File
@@ -0,0 +1,97 @@
package dxcc
// The DELETED DXCC entities, and why a lookup table needs to know about them.
//
// cty.dat answers one question: what entity is this callsign in TODAY. That is
// the right answer for a spot and the wrong one for a log. R1MVI was Malyj
// Vysotskij Island, entity 151, until the ARRL deleted it in February 2012;
// today the same callsign resolves to European Russia, 54. A 2004 contact
// re-stamped from cty.dat therefore loses the entity it was made with — and
// with it a DXCC credit that cannot be worked again, because the place no
// longer counts.
//
// Reported by an operator importing a Logger32 export: his ADIF said
// <DXCC:3>151 and OpsLog stored 54.
//
// So this list exists to be able to say "that number belongs to something that
// no longer exists, leave it alone". It is the ADIF specification's own
// enumeration of deleted entities, and it does not change except when the ARRL
// deletes another one.
var deletedEntities = map[int]string{
2: "Abu Ail Is.",
8: "Aldabra",
19: "Bajo Nuevo",
23: "Blenheim Reef",
25: "British North Borneo",
26: "British Somaliland",
28: "Canal Zone",
30: "Celebe & Molucca Is.",
39: "Comoros",
42: "Damao, Diu",
44: "Desroches",
55: "Farquhar",
57: "French Equatorial Africa",
58: "French Indo-China",
59: "French West Africa",
67: "French India",
68: "Kuwait/Saudi Arabia Neutral Zone",
81: "Germany",
85: "Bonaire, Curacao",
93: "Geyser Reef",
101: "Goa",
102: "Gold Coast, Togoland",
113: "Ifni",
115: "Italian Somaliland",
119: "Java",
127: "Kamaran Is.",
128: "Karelo-Finnish Republic",
134: "Kingman Reef",
139: "Kuria Muria I.",
151: "Malyj Vysotskij I.",
154: "Yemen Arab Republic",
155: "Malaya",
164: "Manchuria",
178: "Minerva Reef",
183: "Netherlands Borneo",
184: "Netherlands New Guinea",
186: "Newfoundland, Labrador",
193: "Okinawa (Ryukyu Is.)",
194: "Okino Tori-shima",
196: "Palestine",
200: "Portuguese Timor",
208: "Ruanda-Urundi",
210: "Saar",
218: "Czechoslovakia",
220: "Sarawak",
226: "Saudi Arabia/Iraq Neutral Zone",
228: "Serrana Bank & Roncador Cay",
229: "German Democratic Republic",
231: "Sikkim",
243: "People's Democratic Rep. of Yemen",
244: "Southern Sudan",
255: "St. Maarten, Saba, St. Eustatius",
258: "Sumatra",
261: "Swan Is.",
264: "Tangier",
267: "Territory of New Guinea",
268: "Tibet",
271: "Trieste",
307: "Zanzibar",
488: "Walvis Bay",
493: "Penguin Is.",
}
// IsDeleted reports whether a DXCC entity number names an entity the ARRL has
// deleted.
//
// The one thing every caller does with a true answer is the same: stop. A
// deleted entity cannot be looked up from a callsign, cannot be worked again,
// and cannot be corrected by anything that only knows about today.
func IsDeleted(n int) bool {
_, ok := deletedEntities[n]
return ok
}
// DeletedName is what entity n was called, or "" if it is not a deleted entity.
// Used to say WHICH entity was preserved rather than only that one was.
func DeletedName(n int) string { return deletedEntities[n] }
+46
View File
@@ -0,0 +1,46 @@
package dxcc
import "testing"
// The contact that reported this: R1MVI, worked in 2004 on Malyj Vysotskij
// Island, entity 151, deleted in February 2012. cty.dat resolves that callsign
// to European Russia (54) today, so anything that re-stamps an old QSO from a
// callsign has to know to stop here.
func TestMalyjVysotskijIsKnownDeleted(t *testing.T) {
if !IsDeleted(151) {
t.Fatal("151 (Malyj Vysotskij I.) must be known as deleted — the whole guard hangs on it")
}
if got := DeletedName(151); got == "" {
t.Error("a deleted entity should be able to say what it was called")
}
}
// The guard must not fire on entities that are alive, or every log would freeze
// at whatever DXCC number it was imported with — including the wrong ones.
func TestLiveEntitiesAreNotTreatedAsDeleted(t *testing.T) {
live := map[int]string{
54: "European Russia — what R1MVI resolves to today",
227: "France",
291: "United States",
339: "Japan",
1: "Canada",
}
for n, what := range live {
if IsDeleted(n) {
t.Errorf("%d (%s) must not be listed as deleted", n, what)
}
}
if got := DeletedName(54); got != "" {
t.Errorf("DeletedName(54) = %q, want empty", got)
}
}
// A few more from the ADIF enumeration, so a careless edit to the table is
// caught rather than discovered by an operator whose credits moved.
func TestSomeOtherDeletedEntities(t *testing.T) {
for _, n := range []int{218 /* Czechoslovakia */, 229 /* German DR */, 255 /* St. Maarten, Saba, St. Eustatius */, 28 /* Canal Zone */} {
if !IsDeleted(n) {
t.Errorf("%d should be a deleted entity", n)
}
}
}