VIN.cs 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. using System.Diagnostics.Metrics;
  2. using System.Runtime.CompilerServices;
  3. using System.Text.RegularExpressions;
  4. [assembly: InternalsVisibleTo("VINlibr.test")]
  5. namespace VINlib
  6. {
  7. //Vehicle identification number
  8. public static class VIN
  9. {
  10. internal static int NumberEquivalent(char c)
  11. {
  12. c = Char.ToUpper(c);
  13. if (c >= 65 && c <= 90)
  14. {
  15. if (c >= 83) ++c;//s == 2, but why
  16. return (c - 65) % 9 + 1;
  17. }
  18. else if (c >= 48 && c <= 57) return c - 48;
  19. else throw new ArgumentException("Invalid sign of VIN", nameof(c));
  20. }
  21. internal static int IndexWeight(int index)
  22. {
  23. if (index >= 0 && index <= 6) return 8 - index;
  24. else if (index == 7) return 10;
  25. else if (index == 8) return 0;//sum
  26. else if (index >= 9 && index <= 16) return 9 - index % 9;
  27. else throw new IndexOutOfRangeException($"The index must be between 0 and 16, but it is {index}");
  28. }
  29. internal static bool ValidityCheck(string identNumber)
  30. {
  31. Regex regex = new Regex("^[A-HJ-NPR-Z0-9]{17}$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
  32. return regex.IsMatch(identNumber);
  33. }
  34. //returns true if verification is seccessful
  35. public static bool ChecksumVerification(string identNumber)
  36. {
  37. if (ValidityCheck(identNumber))
  38. {
  39. if (identNumber[8] != 'x' && identNumber[8] != 'X' && !(identNumber[8] >= 48 && identNumber[8] <= 57)) return false;
  40. int checkSum = 0;
  41. for (int i = 0; i < identNumber.Length; ++i)
  42. checkSum += NumberEquivalent(identNumber[i]) * IndexWeight(i);
  43. int expectedDifference = identNumber[8] == 'X' || identNumber[8] == 'x' ? 10 : NumberEquivalent(identNumber[8]);
  44. if (checkSum - checkSum / 11 * 11 == expectedDifference) return true;
  45. }
  46. return false;
  47. }
  48. static public VINinfo DecodeVIN(string identNumber)
  49. {
  50. if(ChecksumVerification(identNumber)) return new VINinfo(identNumber);
  51. throw new InvalidOperationException($"VIN {identNumber} doesnt exist");
  52. }
  53. }
  54. readonly public struct VINinfo
  55. {
  56. readonly public string identNumber;
  57. readonly public string geographicalArea;
  58. readonly public string country;
  59. readonly public bool isLargeManufacturer;
  60. readonly public int productionYear;
  61. public VINinfo(string identNumber)
  62. {
  63. this.identNumber = identNumber.ToUpper();
  64. //WMI
  65. geographicalArea = GetGeographicalArea(identNumber[0]);
  66. country = GetCountry(identNumber[0].ToString() + identNumber[1].ToString());
  67. isLargeManufacturer = identNumber[2] != 9;
  68. //VIS
  69. productionYear = GetProductionYear(identNumber[9]);
  70. }
  71. internal static string GetGeographicalArea(char c)
  72. {
  73. if (c >= 'A' && c <= 'Z' || c >= '0' && c <= '9')
  74. {
  75. char[] startRange = { 'A', 'J', 'S', '1', '6', '8' };
  76. char[] endRange = { 'H', 'R', 'Z', '5', '7', '9' };
  77. string[] areas = { "Африка", "Азия", "Европа", "Северная Америка", "Океания", "Южная Америка" };
  78. for (int i = 0; i < 6; ++i)
  79. if (c >= startRange[i] && c <= endRange[i]) return areas[i];
  80. }
  81. throw new IndexOutOfRangeException($"Acceptable symbols: A-Z, 0-9. '{c}' is not included.");
  82. }
  83. internal static bool CharIsIncluded(char c, char start, char end, int num = 0)
  84. {//is number
  85. if (c == '0') c = (char)100;
  86. else if (c <= 57) c += (char)42;//numbers now - 91:100, letters - 65:90
  87. if (start == '0') start = (char)100;
  88. else if (start <= 57) start += (char)42;
  89. if (end == '0') end = (char)100;
  90. else if (end <= 57) end += (char)42;
  91. if (c >= start && c <= end) return true;
  92. else if (c >= end && c <= 100) return false;
  93. throw new IndexOutOfRangeException($"'{c}' don't using to WMI {num} position");
  94. }
  95. internal static string GetCountry(string cc)
  96. {
  97. string[] startRange = {"AA", "AJ", "BA", "BF", "BL", "CA", "CF", "CL", "DA", "DF", "DL", "EA",
  98. "EF", "FA", "FF", "JA", "KA", "KF", "KL", "KS", "LA", "MA", "MF", "ML", "MS", "NA",
  99. "NF", "NL", "PF", "PL", "RA", "RL", "RS", "SA", "SN", "SU", "S1", "TA", "TJ", "TR",
  100. "TW", "UU", "U5", "V3", "V6", "WA", "XF", "XS", "XX", "X3", "YA", "YF", "YL", "YS",
  101. "YX", "Y3", "Y6", "ZA", "ZX", "Z3", "Z6", "1A", "2A", "3A", "3X", "38", "4A", "5A",
  102. "6A", "7A", "8A", "8F", "8L", "8S", "8X", "9A", "9F", "9L", "9S", "9X", "93"};
  103. string[] endRange = {"AH", "AN", "BE", "BK", "BR", "CE", "CK", "CR", "DE", "DK", "DR", "EE", "EK",
  104. "FE", "FK", "JT", "KE", "KK", "KR", "K0", "L0", "ME", "MK", "MR", "M0", "NE", "NK",
  105. "NR", "PK", "PR", "RE", "RR", "R0", "SM", "ST", "SZ", "S4", "TH", "TP", "TV", "T1",
  106. "UZ", "U7", "V5", "V0", "W0", "XK", "XW", "X2", "X0", "YE", "YK", "YR", "YW", "Y2",
  107. "Y5", "Y0", "ZR", "Z2", "Z5", "Z0", "10", "20", "3W", "37", "30", "40", "50", "6W",
  108. "7E", "8E", "8K", "8R", "8W", "82", "9E", "9K", "9R", "9W", "92", "99"};
  109. string[] countries = {"ЮАР", "Кот-д’Ивуар", "Ангола", "Кения", "Танзания", "Бенин", "Мадагаскар", "Тунис",
  110. "Египет", "Марокко", "Замбия", "Эфиопия", "Мозамбик", "Гана", "Нигерия", "Япония", "Шри Ланка",
  111. "Израиль", "Южная Корея", "Казахстан", "Китай", "Индия", "Индонезия", "Таиланд", "Мьянма",
  112. "Иран", "Пакистан", "Турция", "Сингапур", "Малайзия", "ОАЭ", "Вьетнам", "Саудовская Аравия",
  113. "Великобритания", "Восточная Германия", "Польша", "Латвия", "Швейцария", "Чехия", "Венгрия",
  114. "Португалия", "Румыния", "Словакия", "Хорватия", "Эстония", "Германия", "Греция", "СССР/СНГ",
  115. "Люксембург", "Россия", "Бельгия", "Финляндия", "Мальта", "Швеция", "Норвегия", "Беларусь",
  116. "Украина", "Италия", "Словения", "Литва", "Россия", "США", "Канада", "Мексика", "Коста Рика",
  117. "Каймановы острова", "США", "США", "Австралия", "Новая Зеландия", "Аргентина", "Чили",
  118. "Эквадор", "Перу", "Венесуэла", "Бразилия", "Колумбия", "Парагвай", "Уругвай", "Тринидад и Тобаго",
  119. "Бразилия"};
  120. for (int i = 0; i < countries.Length; ++i)
  121. {
  122. if (CharIsIncluded(cc[0], startRange[i][0], endRange[i][0], 0))
  123. if (CharIsIncluded(cc[1], startRange[i][1], endRange[i][1], 1)) return countries[i];
  124. }
  125. throw new IndexOutOfRangeException($"'{cc}' don't using to WMI");
  126. }
  127. internal static int GetProductionYear(char c)
  128. {
  129. Regex regex = new Regex("^[A-HJ-NPR-TV-Y1-9]{1}$", RegexOptions.Compiled);
  130. if (regex.IsMatch(c.ToString()))
  131. {
  132. string yearChars = "Y123456789ABCDEFGHJKLMNPRSTVWX";
  133. Dictionary<char, int> CharsAndYears = new Dictionary<char, int>();
  134. int cur_year = 2000;
  135. foreach (char yearChar in yearChars)
  136. {
  137. CharsAndYears[yearChar] = cur_year;
  138. ++cur_year;
  139. }
  140. return CharsAndYears[c];
  141. }
  142. else throw new IndexOutOfRangeException($"'{c}'should be in A-Y, 1-9");
  143. }
  144. public override string ToString() =>
  145. $"VIN: {identNumber}\n"
  146. + $"WMI: {identNumber.Substring(0, 3)}\n"
  147. + $"VDS: {identNumber.Substring(3, 6)}\n"
  148. + $"VIS: {identNumber.Substring(9)}\n\n"
  149. + $"Country: {country}\n"
  150. + $"Geographical area: {geographicalArea}\n"
  151. + $"Production year: ({productionYear - 30}|{productionYear}|{productionYear + 30})\n"
  152. + "Manufacturer produces "
  153. + (isLargeManufacturer ? "more" : "less")
  154. + " than 500 road vehicles per year\n";
  155. }
  156. }