1   package kanta;
2   
3   import static kanta.SisaltaaTarkistaja.*;
4   
5   /**
6    * Luokka henkilötunnuksen tarkistamiseksi
7    * @author vesal
8    * @version 9.1.2011
9    *
10   */
11  public class HetuTarkistus implements Tarkistaja {
12      /** Hetuun kelpaavat tarkistusmerkit järjestyksessä */
13      //                                            0123456789012345678901234567890
14      public static final String TARKISTUSMERKIT = "0123456789ABCDEFHJKLMNPRSTUVWXY";
15  
16      /** Kuukausien maksimipituudet */
17      //                                1  2  3  4  5  6  7  8  9 10 11 12   
18      public static int[] KUUKAUDET = {31,29,31,30,31,30,31,31,30,31,30,31};
19  
20  
21      /**
22       * Tarkistetaan hetu.  Sallitaan myös muoto jossa vain syntymäaika.
23       * @param hetu joka tutkitaan.
24       * @return null jos oikein, muuten virhettä kuvaava teksti
25       * TODO tarkistukset kuntoon myös karkausvuodesta.
26       * @example
27       * <pre name="test">
28       * #PACKAGEIMPORT
29       * HetuTarkistus hetut = new HetuTarkistus();
30       * hetut.tarkista("12121")       === "Hetu liian lyhyt";
31       * hetut.tarkista("k")           === "Hetu liian lyhyt";
32       * hetut.tarkista("12121k")      === "Alkuosassa saa olla vain numeroita";
33       * hetut.tarkista("121212")      === null;   // sallitaan pelkkä syntymäaika
34       * hetut.tarkista("001212")      === "Liian pieni päivämäärä";
35       * hetut.tarkista("321212")      === "Liian suuri päivämäärä";
36       * hetut.tarkista("300212")      === "Liian suuri päivämäärä";
37       * hetut.tarkista("310412")      === "Liian suuri päivämäärä";
38       * hetut.tarkista("121312")      === "Liian suuri kuukausi";
39       * hetut.tarkista("120012")      === "Liian pieni kuukausi";
40       * hetut.tarkista("121212B222Q") === "Väärä erotinmerkki";
41       * hetut.tarkista("121212-2k2Q") === "Yksilöosassa kirjaimia";
42       * hetut.tarkista("121212-2")    === "Yksilöosa liian lyhyt";
43       * hetut.tarkista("121212-")     === "Yksilöosa liian lyhyt";
44       * hetut.tarkista("121212-12345")=== "Hetu liian pitkä";
45       * hetut.tarkista("121212-222S") === "Tarkistusmerkin kuuluisi olla N";
46       * hetut.tarkista("121212-222N") === null;
47       * hetut.tarkista("121212-231Y") === null;
48       * hetut.tarkista("311212-2317") === null;
49       * </pre>
50       */  
51      @Override
52      public String tarkista(String hetu) {
53          int pituus = hetu.length();
54          if ( pituus < 6 ) return "Hetu liian lyhyt";
55          String pvm = hetu.substring(0,6);
56          if ( !onkoVain(pvm,NUMEROT)) return "Alkuosassa saa olla vain numeroita"; 
57          int pv = Integer.parseInt(pvm.substring(0,2));
58          int kk = Integer.parseInt(pvm.substring(2,4));
59          // int vv = Integer.parseInt(pvm.substring(4,6)); TODO vielä tarkempi pvm tarkistus
60          if ( kk < 1 )  return "Liian pieni kuukausi";
61          if ( 12 < kk ) return "Liian suuri kuukausi";
62          int pvmkk = KUUKAUDET[kk-1];
63          if ( pv < 1 )  return "Liian pieni päivämäärä";
64          if ( pvmkk < pv ) return "Liian suuri päivämäärä";
65          if ( pituus == 6 ) return null;   // pelkkä syntymäaika kelpaa
66          if ( pituus < 11 ) return "Yksilöosa liian lyhyt";
67          if ( pituus > 11 ) return "Hetu liian pitkä";
68          String erotin = hetu.substring(6,7);
69          if ( !onkoVain(erotin,"+-A")) return "Väärä erotinmerkki";
70          String yksilo = hetu.substring(7,10);
71          if ( !onkoVain(yksilo,NUMEROT)) return "Yksilöosassa kirjaimia";
72          char merkki = hetunTarkistusMerkki(hetu);
73          if ( hetu.charAt(10) != merkki ) return "Tarkistusmerkin kuuluisi olla " + merkki;
74          return null;
75      }
76  
77      
78      /**
79       * Palauttaa mikä olisi hetun tarkistumerkki. Tuotava parametrinä
80       * laillista muotoa oleva hetu, josta mahdollisesti tarkistumerkki 
81       * puuttuu.
82       * @param hetu tutkittava hetu
83       * @return hetun tarkistusmerkki
84       * @example
85       * <pre name="test">
86       *    hetunTarkistusMerkki("121212-222")    === 'N';
87       *    hetunTarkistusMerkki("121212-222S")   === 'N';
88       *    hetunTarkistusMerkki("121212-222N")   === 'N';
89       *    hetunTarkistusMerkki("121212-231Y")   === 'Y';
90       *    hetunTarkistusMerkki("311212-2317")   === '7';
91       *    hetunTarkistusMerkki("311212-2317XY") === '7'; // vaikka on liikaa merkkejä
92       *    hetunTarkistusMerkki("999999-9999XY") === 'F'; // vaikka on pvm väärin
93       *    hetunTarkistusMerkki("12121A-222S")   === 'N'; #THROWS NumberFormatException
94       *    hetunTarkistusMerkki("12121A-22")     === 'N'; #THROWS StringIndexOutOfBoundsException
95       *    hetunTarkistusMerkki("121")           === 'N'; #THROWS StringIndexOutOfBoundsException
96       * </pre>
97       */
98      public static char hetunTarkistusMerkki(String hetu) {
99          String pvm = hetu.substring(0,6);
100         String yksilo = hetu.substring(7,10);
101         long luku = Long.parseLong(pvm+yksilo);
102         int jakojaannos = (int)(luku % 31L);
103         return TARKISTUSMERKIT.charAt(jakojaannos);
104     }
105     
106    
107     /**
108      * Arvotaan satunnainen kokonaisluku välille [ala,yla]
109      * @param ala arvonnan alaraja
110      * @param yla arvonnan yläraja
111      * @return satunnainen luku väliltä [ala,yla]
112      */
113     public static int rand(int ala, int yla) {
114       double n = (yla-ala)*Math.random() + ala;
115       return (int)Math.round(n);
116     }
117 
118     
119     /**
120      * Arvotaan satunnainen henkilötunnus, joka täyttää hetun ehdot    
121      * @return satunnainen laillinen henkilötunnus
122      */
123     public static String arvoHetu() {
124         String apuhetu = String.format("%02d",rand(1,28))   +
125         String.format("%02d",rand(1,12))   +
126         String.format("%02d",rand(1,99))   + "-" +
127         String.format("%03d",rand(1,1000));
128         return apuhetu + hetunTarkistusMerkki(apuhetu);        
129     }
130     
131 }
132