С февраля 2014 в России вводится новый Универсальный Идентификатор начислений (УИН) http://savepayment.ru/soft/53/uin-v-platezhnom-poruchenii. Это 20-ти значная строка, которая содержит цифры и русские и латинские буквы. И если ранее штраф идентифицировался по номеру и дате платежного постановления, то теперь будут УИНы. В принципе, можно осуществить конвертацию УИН в номер дату платежного постановления и обратно. Алгоритм формирования УИН для ГИБДД описан тут (осторожно, множество опечаток в тестовых данных): Письмо МВД России от 9 августа 2013 г. № 13/9-4902 "Об информировании кредитных организаций о правилах формирования уникального идентификатора начислений"
UPDATE: с какого-то момента 2014 года опять все поменялось, и УИН ГИБДД теперь содержит только цифры, без букв, и в нем уже не закодирован номер и дата постановления. http://savepayment.ru/recommendation/51/kvitantsiya-gibdd-izmenilas из такого УИНа нельзя достать номер и дату постановления!
Непосредственно реализация конвертации УИН старого образца в пару номер/дата постановления
public class UIN {
private static final char[] map = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'А', 'Б', 'В', 'Г', 'Д', 'Е', 'Ж', 'З', 'И', 'К', 'Л', 'М', 'Н', 'О', 'П', 'Р', 'С', 'Т', 'У', 'Ф', 'Х', 'Ц', 'Ч', 'Ш', 'Ь', 'Э', 'Ю', 'Я'};
private static final int[] weights = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
1, 3, 17, 29, 6, 30, 31, 13, 32, 33, 10, 34, 12, 35, 14, 16, 36, 37, 38, 18, 39, 40, 41, 21, 19, 42,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 42, 26, 27, 28 };
public static boolean checkCRC(String uin){
if(uin.length() != 20){
throw new RuntimeException("incorrect uin length");
}
String finalNumber = uin.substring(0, 19);
int crc = calculateCRC(finalNumber, 1);
if(crc % 11 == 10){
crc = calculateCRC(finalNumber, 3);
if(crc %11 == 10){
crc = 0;
}
}
crc = crc%11;
return uin.charAt(19) == Character.forDigit(crc, 10);
}
public static String getFineNumber(String uin){
if(uin.length() != 20){
throw new RuntimeException("incorrect uin length");
}
return uin.substring(7,19).replaceAll("(Z){1,12}$", "");
}
public static Date getFineDate(String uin){
if(uin.length() != 20){
throw new RuntimeException("incorrect uin length");
}
int res = 0;
String dateStr = uin.substring(5,7);
for (int i = dateStr.length(); i > 0; i--) {
int index = lookupIndex(dateStr.charAt(dateStr.length() - i));
res += index * Math.pow(64, i - 1);
}
Calendar c = Calendar.getInstance();
int year = c.get(Calendar.YEAR);
if (year % 10 == 0 && res % 10 != 0) { // в 2020 году могут оплатить штраф за 2019-й
c.set(Calendar.YEAR, ((year / 10) - 1) * 10 + res % 10);
} else {
c.set(Calendar.YEAR, (year / 10) * 10 + res % 10);
}
c.set(Calendar.DAY_OF_YEAR, res / 10); // важно сначала установить год, а потом день года
return c.getTime();
}
private static int lookupIndex(char ch) {
for (int i = 0; i < map.length; i++) {
if (map[i] == ch) {
return i;
}
}
throw new RuntimeException("Invalid character: '" + ch + "'");
}
private static int calculateCRC(String finalNumber, int startIndex) {
int crc = 0;
int i = startIndex;
for(char ch : finalNumber.toCharArray()){
crc += i++ * (weights[lookupIndex(ch)]%10);
if(i==11){
i=1;
}
}
return crc;
}
}
юнит-тест, использующий исправленные входные данные отсюда: http://www.garant.ru/products/ipo/prime/doc/70339846
@Test
public void testUIN() throws Exception {
SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy");
Assert.assertTrue(UIN.checkCRC("18810R564BK229683ZZ8"));
Assert.assertTrue(UIN.checkCRC("18810ЗД57KT002522ZZ2"));
Assert.assertTrue(UIN.checkCRC("18810HФ50АО309188ZZ5"));
Assert.assertEquals("64BK229683", UIN.getFineNumber("18810R564BK229683ZZ8"));
Assert.assertEquals("57KT002522", UIN.getFineNumber("18810ЗД57KT002522ZZ2"));
Assert.assertEquals("50АО309188", UIN.getFineNumber("18810HФ50АО309188ZZ5"));
Assert.assertEquals("22.06.2013", sdf.format(UIN.getFineDate("18810R564BK229683ZZ8")));
Assert.assertEquals("24.04.2013", sdf.format(UIN.getFineDate("18810HФ50АО309188ZZ5")));
Assert.assertEquals("05.10.2012", sdf.format(UIN.getFineDate("18810ЗД57KT002522ZZ2")));
}
и небольшой юнит-тест, который использует те-же функции, для обратной конвертации. Он не оформлен как класс и написан непонятно зачем:
@Test
public void testPackUinDate() throws Exception {
String number ="64BK229683";
String dateStr = "22.06.2013";
Date d = new SimpleDateFormat("dd.MM.yyyy").parse(dateStr);
Calendar c = Calendar.getInstance();
c.setTime(d);
int tmp = (c.get(Calendar.DAY_OF_YEAR)) * 10 + (c.get(Calendar.YEAR) % 10);
String result = "";
while (tmp > 64) {
result += map[tmp % 64];
tmp = tmp / 64;
}
result += map[tmp];
Assert.assertEquals("R5", StringUtils.reverse(result));
String finalNumber = "18810" + StringUtils.reverse(result) + StringUtils.rightPad(number, 12, 'Z');
Assert.assertEquals("18810R564BK229683ZZ", finalNumber);
int crc = calculateCRC(finalNumber, 1);
if(crc % 11 == 10){
crc = calculateCRC(finalNumber, 3);
if(crc %11 == 10){
crc = 0;
}
}
crc = crc%11;
Assert.assertEquals(8, crc);
Assert.assertEquals("18810R564BK229683ZZ8", finalNumber+crc);
}
Комментариев нет:
Отправить комментарий