Recent

Author Topic: [Solved] S10 Tracking numbers  (Read 1142 times)

Petrus Vorster

  • Full Member
  • ***
  • Posts: 171
[Solved] S10 Tracking numbers
« on: October 14, 2025, 11:07:29 am »
Hi All

I have a process to create and validate S10 tracking numbers.
(They are used worldwide in the Postal system).

My process works, but it is terribly lengthy, make a lot of different functions and Codeconvert just rolls out garbage that doesn't work.

A S10 Tracking number looks like this: 2 Alphabetical letters, 9 numbers followed by 2 alphabetical letters. e.g. RC952800068DE. ALWAYS 13 characters in that format.
The alphabetical numbers are of no importance here, its just a service indicator and a country of origin indicator.
The first 8 numbers is a sequence the operator allocate to a product, the 9th one is a check digit.

Here is the function that returns a check digit if you give it 8 numbers:
Code: Pascal  [Select][+][-]
  1. function tform1.GetCheckDigit(Num: Integer): Integer;
  2. { """Get S10 check digit.""" }
  3. const
  4.   // Weights for the calculation
  5.   Weights: array[0..7] of Integer = (8, 6, 4, 2, 3, 5, 9, 7);
  6. var
  7.   SumValue: Integer; // Variable to hold the weighted sum
  8.   i: Integer;        // Loop counter
  9.   FormattedNum: string; // 8-digit string representation of Num
  10.   DigitChar: Char;   // Current digit character
  11.   DigitValue: Integer; // Integer value of the current digit
  12. begin
  13.   SumValue := 0; // Initialize sum
  14.  
  15.   // Format the number as an 8-digit string with leading zeros
  16.   FormattedNum := Format('%.8d', [Num]); // Equivalent to Python's f"{num:08}"
  17.  
  18.   // Iterate through the digits of the formatted number string
  19.  
  20.   for i := 1 to Length(FormattedNum) do
  21.   begin
  22.     DigitChar := FormattedNum[i]; // Get the character at position i
  23.  
  24.     // Convert the character digit to its integer value
  25.     // Example: '5' becomes 5
  26.     // Using Ord for direct character code arithmetic is common and efficient
  27.     DigitValue := Ord(DigitChar) - Ord('0');
  28.  
  29.     // Add the product of the weight and the digit value to the sum
  30.     // Weights array is 0-based, string index i is 1-based, so use Weights[i-1]
  31.     SumValue := SumValue + Weights[i - 1] * DigitValue;
  32.   end;
  33.  
  34.   // Calculate the intermediate sum based on modulo 11
  35.   SumValue := 11 - (SumValue mod 11);
  36.  
  37.   // Apply the S10 specific rules for results 10 and 11
  38.   if SumValue = 10 then
  39.     SumValue := 0 // If the result is 10, the check digit is 0
  40.   else if SumValue = 11 then
  41.     SumValue := 5; // If the result is 11, the check digit is 5
  42.  
  43.   // Assign the final calculated check digit to the function result
  44.   Result := SumValue;
  45. end;                            

I want to create a single function to receive a full tracking number which the user scans. Then it must just check the 8 numbers in it to verify if the 9th one is valid and return an integer if correct.

I am making way to many actions to get it done.

If someone is up to putting this into  a single function then I will be very happy!

-Peter
« Last Edit: October 14, 2025, 12:41:21 pm by Petrus Vorster »

Josh

  • Hero Member
  • *****
  • Posts: 1425
Re: S10 Tracking numbers
« Reply #1 on: October 14, 2025, 11:48:56 am »
Hi function that should check full s10,
it returns true if valid,
you pass it the string and a var of type s10info; it return true or false, and populates the s10info var passed with extracted information.
hope it helps.
Code: Pascal  [Select][+][-]
  1. const
  2.   Weights:array[0..7]of byte=(8,6,4,2,3,5,9,7);
  3.  
  4. type
  5.   TS10Info=record
  6.     IsValid:boolean;
  7.     ServiceIndicator:string[2];
  8.     SerialNumber:string[8];
  9.     CheckDigit:byte;
  10.     Origin:string[2];
  11.   end;
  12.  
  13. function ParseAndValidateAS10(const ATracking:ShortString; out AInfo:TS10Info):boolean;
  14. var
  15.   i,tot,remainder,expectedCheck,d:integer;
  16.   c:char;
  17. begin
  18.   Result:=False;
  19.   // quick exit length has to be 13
  20.   if Length(ATracking)<>13 then Exit;
  21.   // check letter pattern
  22.   if not((ATracking[1]in['A'..'Z'])and(ATracking[2]in['A'..'Z'])and(ATracking[12]in['A'..'Z'])and(ATracking[13]in['A'..'Z']))then Exit;
  23.   // extract service and origin
  24.   AInfo.ServiceIndicator[1]:=ATracking[1];
  25.   AInfo.ServiceIndicator[2]:=ATracking[2];
  26.   AInfo.Origin[1]:=ATracking[12];
  27.   AInfo.Origin[2]:=ATracking[13];
  28.   // Weighted checksum for digits
  29.   tot:=0;
  30.   for i:=0 to 7 do
  31.   begin
  32.     c:=ATracking[3+i];
  33.     d:=Ord(c)-48;
  34.     if(d<0)or(d>9)then Exit;
  35.     AInfo.SerialNumber[i+1]:=c;
  36.     Inc(tot,d*Weights[i]);
  37.   end;
  38.   // Calc check digit
  39.   remainder:=tot mod 11;
  40.   expectedCheck:=11-remainder;
  41.   case expectedCheck of
  42.     10:expectedCheck:=0;
  43.     11:expectedCheck:=5;
  44.   end;
  45.   //Validate check digit
  46.   c:=ATracking[11];
  47.   d:=Ord(c)-48;
  48.   if(d<0)or(d>9)then Exit;
  49.   AInfo.CheckDigit:=d;
  50.   AInfo.IsValid:=(d=expectedCheck);
  51.   Result:=AInfo.IsValid;
  52. end;

*** added some comments
« Last Edit: October 14, 2025, 11:56:34 am by Josh »
The best way to get accurate information on the forum is to post something wrong and wait for corrections.

Petrus Vorster

  • Full Member
  • ***
  • Posts: 171
Re: S10 Tracking numbers
« Reply #2 on: October 14, 2025, 12:04:12 pm »
That is WAY better than my lengthy process.

Thank you greatly Josh.

There are very few people anywhere that deals with S10 trackers.

Thanks a million

-Peter

Petrus Vorster

  • Full Member
  • ***
  • Posts: 171
Re: S10 Tracking numbers
« Reply #3 on: October 14, 2025, 12:16:31 pm »
Where do I need to place that TYPE record?

It throws an error: Identifier not found TS10info.

-Peter

Zvoni

  • Hero Member
  • *****
  • Posts: 3138
Re: S10 Tracking numbers
« Reply #4 on: October 14, 2025, 12:23:34 pm »
Where do I need to place that TYPE record?

It throws an error: Identifier not found TS10info.

-Peter
As always in Pascal: Before you use it.
Probably at the Top below Interface or Implementation, depending if you are exposing it outside that unit
One System to rule them all, One Code to find them,
One IDE to bring them all, and to the Framework bind them,
in the Land of Redmond, where the Windows lie
---------------------------------------------------------------------
Code is like a joke: If you have to explain it, it's bad

Dzandaa

  • Hero Member
  • *****
  • Posts: 501
  • From C# to Lazarus
Re: S10 Tracking numbers
« Reply #5 on: October 14, 2025, 12:24:25 pm »
Hi,

Here is my version: you must enter the 9 digit number.

Code: Pascal  [Select][+][-]
  1. function TCheckSumForm.GetCheckDigit(Num: Integer): Integer; // Enter the 9 Digit integer
  2. const
  3.  // Weights for the calculation
  4.  Weights: array[0..7] of Integer = (8, 6, 4, 2, 3, 5, 9, 7);
  5. var
  6.  SumValue, Cnt, Value, ValTmp, Check: Integer;
  7. begin
  8.  Check := Num Mod 10; // If you want the CheckSum
  9.  Value := Num div 10;
  10.  
  11.  SumValue := 0;
  12.  for cnt := 0 to 7 do
  13.  begin
  14.   ValTmp := Value Mod 10;
  15.   Value := Value div 10;
  16.   SumValue := SumValue + Weights[7-cnt] * ValTmp;
  17.  end;
  18.  SumValue := 11 - (SumValue mod 11);
  19.  case SumValue of
  20.   10:
  21.    SumValue := 0;
  22.   11:
  23.    SumValue := 5;
  24.  end;
  25.  Result := SumValue;
  26. end;  
  27.  
  28.  

Just tested with your number.

B->
Regards,
Dzandaa

Josh

  • Hero Member
  • *****
  • Posts: 1425
Re: S10 Tracking numbers
« Reply #6 on: October 14, 2025, 12:25:45 pm »
normally at the top of your program unit, with the other type definitions,aswell as the weights array.

ie.

Code: Pascal  [Select][+][-]
  1. program S10Test;
  2.  
  3. const
  4.   Weights:array[0..7]of byte=(8,6,4,2,3,5,9,7);        
  5.  
  6. ..........
  7.  
  8. type
  9.   TAS10Info = record
  10.     IsValid: boolean;
  11.     ServiceIndicator: string[2];
  12.     SerialNumber: string[8];
  13.     CheckDigit: byte;
  14.     Origin: string[2];
  15.   end;
  16.  
  17. ..............
  18.  
  19. function ParseAndValidateAS10(const ATracking: string; out AInfo: TAS10Info): boolean;
The best way to get accurate information on the forum is to post something wrong and wait for corrections.

Petrus Vorster

  • Full Member
  • ***
  • Posts: 171
Re: S10 Tracking numbers
« Reply #7 on: October 14, 2025, 12:27:20 pm »
Hmmm, that is where i have it.

Petrus Vorster

  • Full Member
  • ***
  • Posts: 171
Re: S10 Tracking numbers
« Reply #8 on: October 14, 2025, 12:40:51 pm »
Dzandaa : Yours is more or less what I have come up with. Thank you.

Josh: This is the first time I encounter OUT in the function line.
I must go and read up on that!

Thank you all.

-Peter

Josh

  • Hero Member
  • *****
  • Posts: 1425
Re: S10 Tracking numbers
« Reply #9 on: October 14, 2025, 12:41:34 pm »
then its out of scope if it cant find it.

r u sure its at the top,maybe you have it as a type within a  class ie a tform.

you can do this, but you will have to create a function within the public/private sectionof the tform

Code: Pascal  [Select][+][-]
  1. TForm1 = class(TForm)
  2.   private
  3.   function ParseAndValidateAS10(const ATracking:ShortString; out AInfo:TS10Info):boolean;

then where you defined the function link it to the tform class
Code: Pascal  [Select][+][-]
  1.  function TForm1.ParseAndValidateAS10(const ATracking:ShortString; out AInfo:TS10Info):boolean;


then the function will only be available to the form.

the original way, the function is available to anywhere in your program
The best way to get accurate information on the forum is to post something wrong and wait for corrections.

Josh

  • Hero Member
  • *****
  • Posts: 1425
Re: [Solved] S10 Tracking numbers
« Reply #10 on: October 14, 2025, 12:56:49 pm »
out if your not fimiliar with it think of it as a normal var

I optimized it to be fast,out will give slightly better performance, but mainly it help you remember that it contains an output with data you may want. There is no string copying in routine, just direct indexing,speed things up.


The best way to get accurate information on the forum is to post something wrong and wait for corrections.

Zvoni

  • Hero Member
  • *****
  • Posts: 3138
Re: S10 Tracking numbers
« Reply #11 on: October 14, 2025, 01:01:37 pm »
FWIW, my Approach using Regex.
The general calculation of the Checkdigit is easy to adapt to create your own SerialNumbers resp. your own complete TrackingNumber
Code: Pascal  [Select][+][-]
  1. program Project1;
  2. {$mode objfpc}{$H+}
  3. Uses Classes, Sysutils, regexpr;
  4.  
  5. Const
  6.   S10:String='RC473124829DE';
  7.   Pattern:String='(\w{2})(\d{8})(\d{1})(\w{2})';
  8.   Weights:array[0..7]of byte=(8,6,4,2,3,5,9,7);
  9.  
  10. Var
  11.   SV,SN,CO:String;
  12.   CD:Integer;
  13.   IsValid:Boolean;
  14.  
  15. Function CheckS10(Const AS10:String;var AService:String;var ASerialNo:String;Var ACheckDigit:Integer;var ACountry:String):Boolean;
  16. Var
  17.   regex:TRegExpr;
  18.   b:Boolean;
  19.   i:Integer;
  20.   s:Integer;
  21.   cd:Integer;
  22. Begin
  23.   Result:=False;
  24.   s:=0;
  25.   If Length(AS10)<>13 Then Exit;
  26.   regex:=TRegexpr.Create;
  27.   regex.ModifierI:=True;
  28.   regex.ModifierG:=True;
  29.   regex.Expression:=Pattern;
  30.   regex.InputString:=AS10;
  31.   b:=regex.Exec;
  32.   If Not b Then Exit; //Not a valid S10-Format
  33.   If regex.SubExprMatchCount<>4 Then Exit;
  34.   AService:=Regex.Match[1];
  35.   ASerialNo:=regex.Match[2];
  36.   ACheckDigit:=regex.Match[3].ToInteger;
  37.   ACountry:=regex.Match[4];
  38.   For i:=0 To 7 Do s:=s+StrToInt(ASerialNo[i+1])*Weights[i];
  39.   cd:=11-(s mod 11);
  40.   Case cd of
  41.     10: cd:=0;
  42.     11: cd:=5;
  43.   End;
  44.   Result:=(ACheckDigit=cd);
  45.   regex.Free;
  46. End;
  47.  
  48. begin
  49.   IsValid:=CheckS10(S10,SV,SN,CD,CO);
  50.   Writeln(IsValid);
  51.   Readln;
  52. end.
« Last Edit: October 14, 2025, 01:03:57 pm by Zvoni »
One System to rule them all, One Code to find them,
One IDE to bring them all, and to the Framework bind them,
in the Land of Redmond, where the Windows lie
---------------------------------------------------------------------
Code is like a joke: If you have to explain it, it's bad

Petrus Vorster

  • Full Member
  • ***
  • Posts: 171
Re: [Solved] S10 Tracking numbers
« Reply #12 on: October 14, 2025, 01:08:15 pm »
Wow, you all should contract for the Postal Industry!

This help of today also showed me one of my barcode label generators had a miscalculation on one of the check digits. Would have made a complete fool of myself if i started using that.

That REGX example is WAY over my head.  :)

Thanks a million everyone.

-Peter
« Last Edit: October 14, 2025, 01:14:15 pm by Petrus Vorster »

Zvoni

  • Hero Member
  • *****
  • Posts: 3138
Re: [Solved] S10 Tracking numbers
« Reply #13 on: October 14, 2025, 01:38:34 pm »
Wow, you all should contract for the Postal Industry!

This help of today also showed me one of my barcode label generators had a miscalculation on one of the check digits. Would have made a complete fool of myself if i started using that.

That REGX example is WAY over my head.  :)

Thanks a million everyone.

-Peter
It's actually pretty easy
Code: Pascal  [Select][+][-]
  1. Pattern:String='(\w{2})(\d{8})(\d{1})(\w{2})';
Take the Inputstring and start comparing:
1st Group: (\w{2}) --> \w = A Letter - {2} = 2 times
2nd Group: (\d{8}) --> \d = A Digit - {8} = 8 times
3rd Group: (\d{1}) --> \d = A Digit - {1} = 1 times
4th Group: (\w{2}) --> \w = A Letter - {2} = 2 times
in that order of occurence.
In my lines 33 i check if SubExprMatchCount is 4
A successful Regex returns as follows
If there is a Match, Match[0] is the InputString itself
The following Matches are the SubGroups as defined with "()" (Paranthesis), in this case 4 SubGroups
Lines 34 to 37 i access those Matches and assign it to the Variables
One System to rule them all, One Code to find them,
One IDE to bring them all, and to the Framework bind them,
in the Land of Redmond, where the Windows lie
---------------------------------------------------------------------
Code is like a joke: If you have to explain it, it's bad

BrunoK

  • Hero Member
  • *****
  • Posts: 721
  • Retired programmer
Re: [Solved] S10 Tracking numbers
« Reply #14 on: October 14, 2025, 03:02:16 pm »
Classic style program :
Code: Pascal  [Select][+][-]
  1. program Project1;
  2.   {$mode objfpc}
  3.   {$H+}
  4.  
  5. type
  6.   TS10Info = packed record
  7.     ServiceIndicator: array[0..1] of char; // Country of origin ?
  8.     SerialNumber: array[0..7] of char;
  9.     CheckDigit: char;
  10.     Destination: array[0..1] of char;      // Country of destination
  11.     IsValid: boolean;
  12.   end;
  13.  
  14.   function ParseAndValidateAS10(aTracking: string; out aoInfo: TS10Info): boolean;
  15.   const
  16.     Weights: array[0..7] of byte = (8, 6, 4, 2, 3, 5, 9, 7);
  17.   var
  18.     i, RunSum: integer;
  19.   begin
  20.     Result := False;
  21.     aoInfo := Default(TS10Info);
  22.     // quick exit length has to be 13
  23.     if Length(ATracking) <> 13 then
  24.       Exit;
  25.     system.move(aTracking[1], aoInfo, 13);
  26.     with aoInfo do begin
  27.       RunSum := 0;
  28.       for i := 0 to Length(SerialNumber) - 1 do begin
  29.         // check letter pattern
  30.         if not (SerialNumber[i] in ['0'..'9']) then
  31.           exit;
  32.         Inc(RunSum, Weights[i] * (byte(SerialNumber[i]) - Ord('0')));
  33.       end;
  34.       if 11 - RunSum mod 11 <> byte(CheckDigit) - Ord('0') then
  35.         exit;
  36.       Result := True;
  37.       IsValid := Result;
  38.     end;
  39.   end;
  40.  
  41. var
  42.   vS10Str: string;
  43.   voS10: TS10Info;
  44. begin
  45.   vS10Str := 'RC473124829DE';
  46.   ParseAndValidateAS10(vS10Str, voS10);
  47.   with voS10 do
  48.     WriteLn(vS10Str, #9, ServiceIndicator, #9, SerialNumber, #9, CheckDigit,
  49.       #9, Destination, #9, IsValid);
  50.   vS10Str := 'LW293266916CH';
  51.   ParseAndValidateAS10(vS10Str, voS10);
  52.   with voS10 do
  53.     WriteLn(vS10Str, #9, ServiceIndicator, #9, SerialNumber, #9, CheckDigit,
  54.       #9, Destination, #9, IsValid);
  55.   vS10Str := 'LW193266916CH';
  56.   ParseAndValidateAS10(vS10Str, voS10);
  57.   with voS10 do
  58.     WriteLn(vS10Str, #9, ServiceIndicator, #9, SerialNumber, #9, CheckDigit,
  59.       #9, Destination, #9, IsValid);
  60.   Readln;
  61. end.

 

TinyPortal © 2005-2018