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("1212121")     === "Väärä erotinmerkki";
44       * hetut.tarkista("12121212")    === "Väärä erotinmerkki";
45       * hetut.tarkista("121212-")     === "Yksilöosa liian lyhyt";
46       * hetut.tarkista("121212-12345")=== "Hetu liian pitkä";
47       * hetut.tarkista("121212-222S") === "Tarkistusmerkin kuuluisi olla N";
48       * hetut.tarkista("121212-222N") === null;
49       * hetut.tarkista("121212-231Y") === null;
50       * hetut.tarkista("311212-2317") === null;
51       * </pre>
52       */  
53      @Override
54      public String tarkista(String hetu) {
55          int pituus = hetu.length();
56          if ( pituus < 6 ) return "Hetu liian lyhyt";
57          String pvm = hetu.substring(0,6);
58          if ( !onkoVain(pvm,NUMEROT)) return "Alkuosassa saa olla vain numeroita"; 
59          int pv = Integer.parseInt(pvm.substring(0,2));
60          int kk = Integer.parseInt(pvm.substring(2,4));
61          // int vv = Integer.parseInt(pvm.substring(4,6)); TODO vielä tarkempi pvm tarkistus
62          if ( kk < 1 )  return "Liian pieni kuukausi";
63          if ( 12 < kk ) return "Liian suuri kuukausi";
64          int pvmkk = KUUKAUDET[kk-1];
65          if ( pv < 1 )  return "Liian pieni päivämäärä";
66          if ( pvmkk < pv ) return "Liian suuri päivämäärä";
67          if ( pituus == 6 ) return null;   // pelkkä syntymäaika kelpaa
68          String erotin = hetu.substring(6,7);
69          if ( !onkoVain(erotin,"+-A")) return "Väärä erotinmerkki";
70          if ( pituus < 11 ) return "Yksilöosa liian lyhyt";
71          if ( pituus > 11 ) return "Hetu liian pitkä";
72          String yksilo = hetu.substring(7,10);
73          if ( !onkoVain(yksilo,NUMEROT)) return "Yksilöosassa kirjaimia";
74          char merkki = hetunTarkistusMerkki(hetu);
75          if ( hetu.charAt(10) != merkki ) return "Tarkistusmerkin kuuluisi olla " + merkki;
76          return null;
77      }
78  
79      
80      /**
81       * Palauttaa mikä olisi hetun tarkistumerkki. Tuotava parametrinä
82       * laillista muotoa oleva hetu, josta mahdollisesti tarkistumerkki 
83       * puuttuu.
84       * @param hetu tutkittava hetu
85       * @return hetun tarkistusmerkki
86       * @example
87       * <pre name="test">
88       *    hetunTarkistusMerkki("121212-222")    === 'N';
89       *    hetunTarkistusMerkki("121212-222S")   === 'N';
90       *    hetunTarkistusMerkki("121212-222N")   === 'N';
91       *    hetunTarkistusMerkki("121212-231Y")   === 'Y';
92       *    hetunTarkistusMerkki("311212-2317")   === '7';
93       *    hetunTarkistusMerkki("311212-2317XY") === '7'; // vaikka on liikaa merkkejä
94       *    hetunTarkistusMerkki("999999-9999XY") === 'F'; // vaikka on pvm väärin
95       *    hetunTarkistusMerkki("12121A-222S")   === 'N'; #THROWS NumberFormatException
96       *    hetunTarkistusMerkki("12121A-22")     === 'N'; #THROWS StringIndexOutOfBoundsException
97       *    hetunTarkistusMerkki("121")           === 'N'; #THROWS StringIndexOutOfBoundsException
98       * </pre>
99       */
100     public static char hetunTarkistusMerkki(String hetu) {
101         String pvm = hetu.substring(0,6);
102         String yksilo = hetu.substring(7,10);
103         long luku = Long.parseLong(pvm+yksilo);
104         int jakojaannos = (int)(luku % 31L);
105         return TARKISTUSMERKIT.charAt(jakojaannos);
106     }
107     
108    
109     /**
110      * Arvotaan satunnainen kokonaisluku välille [ala,yla]
111      * @param ala arvonnan alaraja
112      * @param yla arvonnan yläraja
113      * @return satunnainen luku väliltä [ala,yla]
114      */
115     public static int rand(int ala, int yla) {
116       double n = (yla-ala)*Math.random() + ala;
117       return (int)Math.round(n);
118     }
119 
120     
121     /**
122      * Arvotaan satunnainen henkilötunnus, joka täyttää hetun ehdot    
123      * @return satunnainen laillinen henkilötunnus
124      */
125     public static String arvoHetu() {
126         String apuhetu = String.format("%02d",rand(1,28))   +
127         String.format("%02d",rand(1,12))   +
128         String.format("%02d",rand(1,99))   + "-" +
129         String.format("%03d",rand(1,1000));
130         return apuhetu + hetunTarkistusMerkki(apuhetu);        
131     }
132     
133 }
134