Class speedup

This commit is contained in:
Andreas Kristiansen
2026-01-31 11:46:22 +01:00
parent 0aa7809a59
commit 64ae39cc2f

View File

@@ -6,14 +6,28 @@ class Dxcc {
protected $dxcc = array();
protected $dxccexceptions = array();
protected $csadditions = '/^X$|^D$|^T$|^P$|^R$|^B$|^A$|^M$|^LH$|^L$|^J$|^SK$/';
protected $lidadditions = '/^QRP$|^LGT$|^2K$/';
protected $noneadditions = '/^MM$|^AM$/';
protected $csadditions = '/^(?:X|D|T|P|R|B|A|M|LH|L|J|SK)$/';
protected $lidadditions = '/^(?:QRP|LGT|2K)$/';
protected $noneadditions = '/^(?:MM|AM)$/';
function __construct($date) {
$this->read_data($date);
}
/**
* Helper method to return a standard "NONE" result
*/
private function noneResult() {
return [
'adif' => 0,
'entity' => '- NONE -',
'cqz' => 0,
'long' => '0',
'lat' => '0',
'cont' => null
];
}
/**
* Helper method to log DXCC-related errors
*/
@@ -21,25 +35,26 @@ class Dxcc {
log_message("Error", "DXCC Error: " . $message . (empty($context) ? '' : ' - Context: ' . json_encode($context)));
}
/**
* Helper method to check if a date falls within a date range
*/
private function isDateInRange($date, $startDate, $endDate) {
if ($startDate === null && $endDate === null) return true;
if ($startDate === null) return $date <= $endDate;
if ($endDate === null) return $date >= $startDate;
return $date >= $startDate && $date <= $endDate;
}
public function dxcc_lookup($call, $date) {
// Ensure callsign is uppercase for pattern matching
$call = strtoupper($call);
if (array_key_exists($call, $this->dxccexceptions)) {
if (isset($this->dxccexceptions[$call])) {
$exceptions = $this->dxccexceptions[$call];
// Loop through all exceptions for this call
foreach ($exceptions as $exception) {
$startDate = !empty($exception['start']) ? $exception['start'] : null;
$endDate = !empty($exception['end']) ? $exception['end'] : null;
if ($startDate == null && $endDate == null)
return $exception;
if ($date <= $endDate && $date >= $startDate)
return $exception;
if ($endDate == null && $date >= $startDate)
return $exception;
if ($date <= $endDate && $startDate == null)
if ($this->isDateInRange($date, $exception['start'], $exception['end']))
return $exception;
}
}
@@ -88,15 +103,9 @@ class Dxcc {
$call = $callsign;
}
} else {
$result = $this->wpx($call, 1); # use the wpx prefix instead
$result = $this->wpx($prefix, $callsign, $suffix, 1, $call); # use the wpx prefix instead
if ($result == '') {
$row['adif'] = 0;
$row['entity'] = '- NONE -';
$row['cqz'] = 0;
$row['long'] = '0';
$row['lat'] = '0';
$row['cont'] = null;
return $row;
return $this->noneResult();
} else {
$call = $result . "AA";
}
@@ -108,251 +117,205 @@ class Dxcc {
// query the table, removing a character from the right until a match
for ($i = $len; $i > 0; $i--){
$result = '';
$prefix = substr($call, 0, $i);
if (array_key_exists(substr($call, 0, $i), $this->dxcc)) {
$arraykey = substr($call, 0, $i);
$dxccEntries = $this->dxcc[substr($call, 0, $i)];
if (isset($this->dxcc[$prefix])) {
$dxccEntries = $this->dxcc[$prefix];
// Loop through all entries for this call prefix
foreach ($dxccEntries as $dxccEntry) {
$startDate = !empty($dxccEntry['start']) ? $dxccEntry['start'] : null;
$endDate = !empty($dxccEntry['end']) ? $dxccEntry['end'] : null;
if ($startDate == null && $endDate == null)
return $dxccEntry;
if ($date <= $endDate && $date >= $startDate)
return $dxccEntry;
if ($endDate == null && $date >= $startDate)
return $dxccEntry;
if ($date <= $endDate && $startDate == null)
if ($this->isDateInRange($date, $dxccEntry['start'], $dxccEntry['end']))
return $dxccEntry;
}
}
}
return array(
'adif' => 0,
'entity' => '- NONE ',
'cqz' => '0',
'long' => '0',
'lat' => '0',
'cont' => null
);
return $this->noneResult();
}
function wpx($testcall, $i) {
$prefix = '';
$a = '';
$b = '';
$c = '';
function wpx($prefix, $callsign, $suffix, $i, $testcall) {
$pfx = '';
# First check if the call is in the proper format, A/B/C where A and C
# are optional (prefix of guest country and P, MM, AM etc) and B is the
# callsign. Only letters, figures and "/" is accepted, no further check if the
# callsign "makes sense".
# 23.Apr.06: Added another "/X" to the regex, for calls like RV0AL/0/P
# as used by RDA-DXpeditions....
$a = $prefix;
$b = $callsign;
$c = $suffix ?? '';
if (preg_match_all('/^((\d|[A-Z])+\/)?((\d|[A-Z]){3,})(\/(\d|[A-Z])+)?(\/(\d|[A-Z])+)?$/', $testcall, $matches)) {
# Now $1 holds A (incl /), $3 holds the callsign B and $5 has C
# We save them to $a, $b and $c respectively to ensure they won't get
# lost in further Regex evaluations.
$a = $matches[1][0];
$b = $matches[3][0];
$c = $matches[5][0];
if ($a) {
$a = substr($a, 0, -1); # Remove the / at the end
# In some cases when there is no part A but B and C, and C is longer than 2
# letters, it happens that $a and $b get the values that $b and $c should
# have. This often happens with liddish callsign-additions like /QRP and
# /LGT, but also with calls like DJ1YFK/KP5. ~/.yfklog has a line called
# "lidadditions", which has QRP and LGT as defaults. This sorts out half of
# the problem, but not calls like DJ1YFK/KH5. This is tested in a second
# try: $a looks like a call (.\d[A-Z]) and $b doesn't (.\d), they are
# swapped. This still does not properly handle calls like DJ1YFK/KH7K where
# only the OP's experience says that it's DJ1YFK on KH7K.
if (!$c && $a && $b) { # $a and $b exist, no $c
if (preg_match($this->lidadditions, $b) || preg_match('/^[0-9]+$/', $b)) { # check if $b is a lid-addition
$b = $a;
$a = null; # $a goes to $b, delete lid-add
} elseif ((preg_match('/\d[A-Z]+$/', $a)) && (preg_match('/\d$/', $b) || preg_match('/^[A-Z]\d[A-Z]$/', $b) || preg_match('/^\d[A-Z]+$/', $b))) {
$temp = $b;
$b = $a;
$a = $temp;
}
if ($c) {
$c = substr($c, 1); # Remove the / at the beginning
};
# Additional check: if $a looks like a full callsign (longer than typical prefix)
# and $b looks like a country prefix (short, with digit), swap them
# This handles cases like JA0JHQ/VK9X where VK9X should be the prefix
elseif (strlen($a) >= 5 && preg_match('/^\d?[A-Z]+\d[A-Z]+$/', $a) && strlen($b) <= 5 && preg_match('/^[A-Z]+\d[A-Z]*$/', $b)) {
$temp = $b;
$b = $a;
$a = $temp;
}
}
# In some cases when there is no part A but B and C, and C is longer than 2
# letters, it happens that $a and $b get the values that $b and $c should
# have. This often happens with liddish callsign-additions like /QRP and
# /LGT, but also with calls like DJ1YFK/KP5. ~/.yfklog has a line called
# "lidadditions", which has QRP and LGT as defaults. This sorts out half of
# the problem, but not calls like DJ1YFK/KH5. This is tested in a second
# try: $a looks like a call (.\d[A-Z]) and $b doesn't (.\d), they are
# swapped. This still does not properly handle calls like DJ1YFK/KH7K where
# only the OP's experience says that it's DJ1YFK on KH7K.
if (!$c && $a && $b) { # $a and $b exist, no $c
if (preg_match($this->lidadditions, $b) || preg_match('/^[0-9]+$/', $b)) { # check if $b is a lid-addition
$b = $a;
$a = null; # $a goes to $b, delete lid-add
} elseif ((preg_match('/\d[A-Z]+$/', $a)) && (preg_match('/\d$/', $b) || preg_match('/^[A-Z]\d[A-Z]$/', $b) || preg_match('/^\d[A-Z]+$/', $b))) {
$temp = $b;
$b = $a;
$a = $temp;
}
# Additional check: if $a looks like a full callsign (longer than typical prefix)
# and $b looks like a country prefix (short, with digit), swap them
# This handles cases like JA0JHQ/VK9X where VK9X should be the prefix
elseif (strlen($a) >= 5 && preg_match('/^\d?[A-Z]+\d[A-Z]+$/', $a) && strlen($b) <= 5 && preg_match('/^[A-Z]+\d[A-Z]*$/', $b)) {
$temp = $b;
$b = $a;
$a = $temp;
if (preg_match('/^[0-9]+$/', $b)) { # Callsign only consists of numbers. Bad!
return null;
}
if (preg_match('/^[0-9]{2,}$/', $c)) { # If suffix consists of two or more digits -> ignore suffix, To catch callsigns like VP8ADR/40
$c = null;
}
if (preg_match('/^[A-Z]{1}$/', $c ?? '')) { # If suffix consists of exactly one letter -> ignore suffix, To catch callsigns like LU7CC/E
$c = null;
}
# Depending on these values we have to determine the prefix.
# Following cases are possible:
#
# 1. $a and $c undef --> only callsign, subcases
# 1.1 $b contains a number -> everything from start to number
# 1.2 $b contains no number -> first two letters plus 0
# 2. $a undef, subcases:
# 2.1 $c is only a number -> $a with changed number
# 2.2 $c is /P,/M,/MM,/AM -> 1.
# 2.3 $c is something else and will be interpreted as a Prefix
# 3. $a is defined, will be taken as PFX, regardless of $c
if (($a == null) && ($c == null)) { # Case 1
if (preg_match('/\d/', $b)) { # Case 1.1, contains number
if (!preg_match('/(.+\d)[A-Z]*/', $b, $matches)) {
$this->logError('preg_match failed to extract prefix from callsign', [
'testcall' => $testcall,
'b' => $b
]);
return '';
}
$pfx = $matches[1]; # Letters
} else { # Case 1.2, no number
$pfx = substr($b, 0, 2) . "0"; # first two + 0
}
# *** Added later *** The check didn't make sure that the callsign
# contains a letter. there are letter-only callsigns like RAEM, but not
# figure-only calls.
if (preg_match('/^[0-9]+$/', $b)) { # Callsign only consists of numbers. Bad!
return null; # exit, undef
}
if (preg_match('/^[0-9]{2,}$/', $c ?? '')) { # If suffix consists of two or more digits -> ignore suffix, To catch callsigns like VP8ADR/40
$c = null;
}
if (preg_match('/^[A-Z]{1}$/', ($c ?? ''))) { # If suffix consists of exactly one letter -> ignore suffix, To catch callsigns like LU7CC/E
$c = null;
}
# Depending on these values we have to determine the prefix.
# Following cases are possible:
#
# 1. $a and $c undef --> only callsign, subcases
# 1.1 $b contains a number -> everything from start to number
# 1.2 $b contains no number -> first two letters plus 0
# 2. $a undef, subcases:
# 2.1 $c is only a number -> $a with changed number
# 2.2 $c is /P,/M,/MM,/AM -> 1.
# 2.3 $c is something else and will be interpreted as a Prefix
# 3. $a is defined, will be taken as PFX, regardless of $c
if (($a == null) && ($c == null)) { # Case 1
if (preg_match('/\d/', $b)) { # Case 1.1, contains number
if (!preg_match('/(.+\d)[A-Z]*/', $b, $matches)) {
$this->logError('preg_match failed to extract prefix from callsign', [
} elseif (($a == null) && ($c != null && $c != '')) { # Case 2, CALL/X
if (preg_match($this->lidadditions, $c)) { # check if $b is a lid-addition
$pfx = $b;
} else if (preg_match('/^(\d)/', $c)) { # Case 2.1, starts with digit
# Check if $c is a full country prefix (like 6YA, 6Y, etc.) not just a number
# A country prefix has the pattern: digit + letters (like 6YA, 6Y)
# NOT just a single digit
if (strlen($c) > 1 && preg_match('/^\d[A-Z]+$/', $c)) {
# This is a country prefix starting with a digit (like 6YA, 6Y)
# Use it directly - it's already a valid prefix
$pfx = $c;
} elseif (strlen($c) > 1 && preg_match('/^[A-Z]+\d[A-Z]*$/', $c)) {
# Country prefix starting with letters (like W1, K2, etc.)
$pfx = $c;
} else {
# Single digit, replace the digit in the base call # Case 2.1, starts with digit
preg_match('/(.+\d)[A-Z]*/', $b, $matches); # regular Prefix in $1
# Here we need to find out how many digits there are in the
# prefix, because for example A45XR/0 is A40. If there are 2
# numbers, the first is not deleted. If course in exotic cases
# like N66A/7 -> N7 this brings the wrong result of N67, but I
# think that's rather irrelevant cos such calls rarely appear
# and if they do, it's very unlikely for them to have a number
# attached. You can still edit it by hand anyway..
if (!isset($matches[1]) || $matches[1] === null) {
$this->logError('preg_match failed to capture prefix in $b', [
'testcall' => $testcall,
'b' => $b
'b' => $b,
'c' => $c,
'matches' => $matches
]);
return '';
}
$prefix = $matches[1]; # Letters
} else { # Case 1.2, no number
$prefix = substr($b, 0, 2) . "0"; # first two + 0
}
} elseif (($a == null) && ($c != null && $c != '')) { # Case 2, CALL/X
if (preg_match($this->lidadditions, $c)) { # check if $b is a lid-addition
$prefix = $b;
} else if (preg_match('/^(\d)/', $c)) { # Case 2.1, starts with digit
# Check if $c is a full country prefix (like 6YA, 6Y, etc.) not just a number
# A country prefix has the pattern: digit + letters (like 6YA, 6Y)
# NOT just a single digit
if (strlen($c) > 1 && preg_match('/^\d[A-Z]+$/', $c)) {
# This is a country prefix starting with a digit (like 6YA, 6Y)
# Use it directly - it's already a valid prefix
$prefix = $c;
} elseif (strlen($c) > 1 && preg_match('/^[A-Z]+\d[A-Z]*$/', $c)) {
# Country prefix starting with letters (like W1, K2, etc.)
$prefix = $c;
} else {
# Single digit, replace the digit in the base call # Case 2.1, starts with digit
preg_match('/(.+\d)[A-Z]*/', $b, $matches); # regular Prefix in $1
# Here we need to find out how many digits there are in the
# prefix, because for example A45XR/0 is A40. If there are 2
# numbers, the first is not deleted. If course in exotic cases
# like N66A/7 -> N7 this brings the wrong result of N67, but I
# think that's rather irrelevant cos such calls rarely appear
# and if they do, it's very unlikely for them to have a number
# attached. You can still edit it by hand anyway..
if (!isset($matches[1]) || $matches[1] === null) {
$this->logError('preg_match failed to capture prefix in $b', [
if (preg_match('/^([A-Z]\d{2,})$/', $matches[1])) { # e.g. A45 $c = 0
$pfx = $matches[1] . $c; # -> A40
} else { # Otherwise cut all numbers
if (!preg_match('/(.*[A-Z])\d+/', $matches[1], $match)) {
$this->logError('preg_match failed to extract prefix without number', [
'testcall' => $testcall,
'matches1' => $matches[1],
'b' => $b,
'c' => $c,
'matches' => $matches
'c' => $c
]);
return '';
}
if (preg_match('/^([A-Z]\d{2,})$/', $matches[1])) { # e.g. A45 $c = 0
$prefix = $matches[1] . $c; # -> A40
} else { # Otherwise cut all numbers
if (!preg_match('/(.*[A-Z])\d+/', $matches[1], $match)) {
$this->logError('preg_match failed to extract prefix without number', [
'testcall' => $testcall,
'matches1' => $matches[1],
'b' => $b,
'c' => $c
]);
return '';
}
$prefix = $match[1] . $c; # Add attached number
}
}
} elseif (preg_match($this->csadditions, $c)) {
if (!preg_match('/(.+\d)[A-Z]*/', $b, $matches)) {
$this->logError('preg_match failed for csadditions case', [
'testcall' => $testcall,
'b' => $b,
'c' => $c
]);
return '';
}
$prefix = $matches[1];
} elseif (preg_match($this->noneadditions, $c)) {
return '';
} elseif (preg_match('/^\d\d+$/', $c)) { # more than 2 numbers -> ignore
if (!preg_match('/(.+\d)[A-Z]*/', $b, $matches)) {
$this->logError('preg_match failed for multi-digit case', [
'testcall' => $testcall,
'b' => $b,
'c' => $c
]);
return '';
}
$prefix = $matches[1][0];
} else { # Must be a Prefix!
# Check if $c looks like a country prefix
# Pattern: starts with digit followed by letters (6YA, 6Y) OR has digit in it
if (preg_match('/^\d[A-Z]+$/', $c)) { # Starts with digit, has letters after (6YA, 6Y, etc.)
$prefix = $c; # Already a valid prefix
} elseif (preg_match('/\d$/', $c)) { # ends in number -> good prefix
$prefix = $c;
} elseif (preg_match('/\d/', $c)) { # contains digit but doesn't end with one
$prefix = $c . "0"; # Add zero at end
} else { # No digit, add zero
$prefix = $c . "0";
$pfx = $match[1] . $c; # Add attached number
}
}
} elseif (($a) && (preg_match($this->noneadditions, ($c ?? '')))) { # Case 2.1, X/CALL/X ie TF/DL2NWK/MM - DXCC none
return '';
} elseif ($a) {
# $a contains the prefix we want
if (preg_match('/\d$/', $a)) { # ends in number -> good prefix
$prefix = $a;
} else {
$prefix = $a . "0";
}
}
# In very rare cases (right now I can only think of KH5K and KH7K and FRxG/T
# etc), the prefix is wrong, for example KH5K/DJ1YFK would be KH5K0. In this
# case, the superfluous part will be cropped. Since this, however, changes the
# DXCC of the prefix, this will NOT happen when invoked from with an
# extra parameter $_[1]; this will happen when invoking it from &dxcc.
if (preg_match('/(\w+\d)[A-Z]+\d/', $prefix, $matches) && $i == null) {
if (!isset($matches[1][0])) {
$this->logError('preg_match failed to extract prefix in rare case', [
} elseif (preg_match($this->csadditions, $c)) {
if (!preg_match('/(.+\d)[A-Z]*/', $b, $matches)) {
$this->logError('preg_match failed for csadditions case', [
'testcall' => $testcall,
'prefix' => $prefix,
'matches' => $matches
'b' => $b,
'c' => $c
]);
} else {
$prefix = $matches[1][0];
return '';
}
$pfx = $matches[1];
} elseif (preg_match($this->noneadditions, $c)) {
return '';
} elseif (preg_match('/^\d\d+$/', $c)) { # more than 2 numbers -> ignore
if (!preg_match('/(.+\d)[A-Z]*/', $b, $matches)) {
$this->logError('preg_match failed for multi-digit case', [
'testcall' => $testcall,
'b' => $b,
'c' => $c
]);
return '';
}
$pfx = $matches[1][0];
} else { # Must be a Prefix!
# Check if $c looks like a country prefix
# Pattern: starts with digit followed by letters (6YA, 6Y) OR has digit in it
if (preg_match('/^\d[A-Z]+$/', $c)) { # Starts with digit, has letters after (6YA, 6Y, etc.)
$pfx = $c; # Already a valid prefix
} elseif (preg_match('/\d$/', $c)) { # ends in number -> good prefix
$pfx = $c;
} elseif (preg_match('/\d/', $c)) { # contains digit but doesn't end with one
$pfx = $c . "0"; # Add zero at end
} else { # No digit, add zero
$pfx = $c . "0";
}
}
return $prefix;
} else {
} elseif (($a) && (preg_match($this->noneadditions, $c ?? ''))) { # Case 2.1, X/CALL/X ie TF/DL2NWK/MM - DXCC none
return '';
} elseif ($a) {
# $a contains the prefix we want
if (preg_match('/\d$/', $a)) { # ends in number -> good prefix
$pfx = $a;
} else {
$pfx = $a . "0";
}
}
# In very rare cases (right now I can only think of KH5K and KH7K and FRxG/T
# etc), the prefix is wrong, for example KH5K/DJ1YFK would be KH5K0. In this
# case, the superfluous part will be cropped. Since this, however, changes the
# DXCC of the prefix, this will NOT happen when invoked from with an
# extra parameter $_[1]; this will happen when invoking it from &dxcc.
if (preg_match('/(\w+\d)[A-Z]+\d/', $pfx, $matches) && $i == null) {
if (!isset($matches[1][0])) {
$this->logError('preg_match failed to extract prefix in rare case', [
'testcall' => $testcall,
'prefix' => $pfx,
'matches' => $matches
]);
} else {
$pfx = $matches[1][0];
}
}
return $pfx;
}
/*
@@ -386,8 +349,8 @@ class Dxcc {
'cont' => $dxcce->cont,
'entity' => $dxcce->entity,
'cqz' => $dxcce->cqz,
'start' => $dxcce->start,
'end' => $dxcce->end,
'start' => $dxcce->start ?? null,
'end' => $dxcce->end ?? null,
'long' => $dxcce->long,
'lat' => $dxcce->lat
];
@@ -419,8 +382,8 @@ class Dxcc {
'cont' => $dx->cont,
'entity' => $dx->entity,
'cqz' => $dx->cqz,
'start' => $dx->start,
'end' => $dx->end,
'start' => $dx->start ?? null,
'end' => $dx->end ?? null,
'long' => $dx->long,
'lat' => $dx->lat
];