// Aufgabe 1106 Maskdate: Datum formatieren // Teil der ersten Übungsrunde // "AU Einführung in das Programmieren" an der TU-Wien // Dokumentation siehe 'maskdate.txt' // Autor: Starzinger Michael, <e0306126@student.tuwien.ac.at> // Datum: Dienstag, 21. Oktober 2003 import eprog.EprogException; import eprog.EprogIO; import java.lang.Math; import java.text.NumberFormat; class DateFormatExt { static boolean checkDateStr(String date, String pattern) { boolean bValid = false; //nur überprüfen wenn datum und pattern gleich lang //ansonsten ist das datum ungültig if (date.length() == pattern.length()) { bValid = true; //zeichenweises vergleichen der beiden strings for (int i=0; i<date.length(); i++) { char chD = date.charAt(i); char chP = pattern.charAt(i); //entweder zeichen sind gleich oder im pattern ist ein 'x' als escape verzeichnet bValid &= (chP == 'x' || chP == chD); } } return bValid; } static boolean checkDate(int dateD, int dateM, int dateY) { boolean bValid; //grobe beschränkung aller werte bValid = (dateD <= 31 && dateD > 0) && (dateM <= 12 && dateM > 0) && (dateY >= 0); //genau berücksichtigung des schaltjahres beim februar vornehmen //entweder es ist nicht februar oder <28 oder schaltjahr und <29 bValid &= (dateM != 2) || (dateD <= 28) || (dateD <= 29 && isLeap(dateY)); //genaue beschränkung des tages in den einzelnen monaten mit nur 30 tagen switch (dateM) { case 4: case 6: case 9: case 11: bValid &= (dateD <= 30); } return bValid; } static boolean isLeap(int dateY) { //schaltjahre sind jedes vierte jahr, alle hundert jahre nicht und alle 400 dann wieder schon //das heisst das jahr muss auf jeden fall durch vier teilbar sein und nicht durch hundert teilbar //ausser es ist dann wieder durch 400 teilbar return ((dateY % 4 == 0) && ((dateY % 100 != 0) || (dateY % 400 == 0))); } static String getDayName(int dateDOW) { //array mit allen namen beginnen bei 0 //dateDOW darf den rahmen des arrays unter keinen umständen sprengen, deshalb mod 7 String DayNames[] = {"SUNDAY", "MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY"}; return DayNames[dateDOW % 7]; } static String getMonthName(int dateM) //detto! { String MonthNames[] = {"JANUARY", "FEBRUARY", "MARCH", "APRIL", "MAY", "JUNE", "JULY", "AUGUST", "SEPTEMBER", "OCTOBER", "NOVEMBER", "DECEMBER"}; return MonthNames[dateM % 12]; } static String getMonthNameShort(int dateM) //um nicht alle monatsnamen noch einmal eintippen zu müssen werden hier einfach //die ersten drei buchstaben des langen namens extrahiert { return getMonthName(dateM).substring(0,3); } static byte getDayOfWeek(int d, int m, int y) { //berechnung des wochentages! int days = 0; //zunächst werden die tage gezählt die im eingegebenen jahr bereits vergangen sind //dazu werden rückwärts für jedes volendete monat die tage addiert switch (m) { case 12: days+=30; case 11: days+=31; case 10: days+=30; case 9: days+=31; case 8: days+=31; case 7: days+=30; case 6: days+=31; case 5: days+=30; case 4: days+=31; case 3: days+=isLeap(y)?29:28; case 2: days+=31; //...zum schluss werden noch die tage des gerade laufenden monats addiert //wobei zu beachten ist, dass hier auch von null zu zählen begonnen werden muss default: days+=d-1; } //...wir haben nun die anzahl der tage die in diesem jahr vergangen sind. //wenn wir nun noch die verschiebung des ersten wochentages addieren, so ist es //möglich danach auf den gewünschten wochentag zu schließen indem wir durch 7 modulieren days += (y + Math.floor((y-1)/4) - Math.floor((y-1)/100) + Math.floor((y-1)/400)); return (byte)(days % 7); //...voila! } static String getFormattedNumeric(int numeric, int count) { //zum formatieren der einzelnen zahlen benutzen wir hier java.text.NumberFormat NumberFormat nf = NumberFormat.getNumberInstance(); //sowohl ober- als auch untergrenze der ziffernanzahl werden auf den gewünschten wert gesetzt nf.setMinimumIntegerDigits(count); nf.setMaximumIntegerDigits(count); //nun muss noch verhindert werden, dass nach der dritten stelle ein punkt folgt, //dies wäre bei der vierstelligen ausgabe des jahres fatal! nf.setGroupingUsed(false); return nf.format(numeric); } static String getFormattedDate(String date, String format) throws EprogException { //das kernstück der vorliegenden klasse short dateD, dateM, dateY; String strResult = ""; //als erstes muss überprüft werden ob das eingegebenen datum einem der gewünschten //formate entspricht. wenn nicht, haben wir einen grund uns beim benutzer zu beschweren. //die methode terminiert in diesem fall mit einer exception welche im hauptprogramm //wieder abgefangen wird. if (!(checkDateStr(date, "xx-xx-xxxx") || checkDateStr(date, "xx/xx/xxxx"))) { throw new EprogException("incorrect date string"); } try { //nun werden die zahlen an den erwarteten positionen eingelesen und in numerische //werte konvertiert. dateD = Short.parseShort(date.substring(0,2)); dateM = Short.parseShort(date.substring(3,5)); dateY = Short.parseShort(date.substring(6,10)); //zur sicherheit fangen wir hier alle ausnahmen auf, die mir einem fehler in der //formatierung zu tun haben (zb: zeichen die keine ziffern darstellen!) } catch(java.lang.NumberFormatException e) { //und lösen gegebenenfalls unsere eigene exception aus throw new EprogException("incorrect number format"); } //sind die eingegebenen werte plausibel??? -> nein -> beschweren! if (!checkDate(dateD, dateM, dateY)) { throw new EprogException("incorrect date"); } //zeichenweises abarbeiten des format-strings! for (int i=0; i<format.length(); i++) { char ch; switch (ch = format.charAt(i)) { //sollte eines der gültigen trennzeichen gefunden werden, so übernehmen wir es an //dieser stelle direkt in unser ergebnis. ein '_' wird auch gleich zu einem ' ' case '_': ch = ' '; case '/': case '.': case '-': strResult += ch; break; //ansonsten muss es sich wohl um eines der definierten schlüsselwörter handeln default: //...das schlüsselwort wird extrahiert... String strToken = format.substring(i, format.length()); //...und der reihe nach verglichen. hierbei ist zu beachten dass vergleiche mit längeren //schlüssel wörter (zB. YYYY) vor deren küzeren versionen (zB YY) gemacht werden müssen. //würde dies nicht geschehen dann würde anstatt einmal dem vierstelligen jahr, zweimal das //zweistellige jahr im ergebniss erscheinen. //ausserdem wir bei einem gefunden schlüssel mit dem vergeichen abgebrochen und der index //an das letzte zeichen im schlüsselwort verschoben! if (strToken.startsWith("DD")) {strResult += getFormattedNumeric(dateD,2); i+=1;} else if (strToken.startsWith("MM")) {strResult += getFormattedNumeric(dateM,2); i+=1;} else if (strToken.startsWith("YYYY")) {strResult += getFormattedNumeric(dateY,4); i+=3;} else if (strToken.startsWith("YY")) {strResult += getFormattedNumeric(dateY,2); i+=1;} else if (strToken.startsWith("MONTH")) {strResult += getMonthName(dateM-1); i+=4;} else if (strToken.startsWith("MON")) {strResult += getMonthNameShort(dateM-1); i+=2;} else if (strToken.startsWith("DAY")) {strResult += getDayName(getDayOfWeek(dateD,dateM,dateY)); i+=2;} //ist der schlüssel nicht definiert, liegt ein fehler in der eingabe vor //wir haben wieder einen grund uns zu beschweren! else {throw new EprogException("incorrect format string");} } } return strResult; } }; class Maskdate { //in dieser klasse liegt unser hauptprogramm, sonst nichts interessantes. public static void main(String[] args) { EprogIO IO = new EprogIO(); DateFormatExt df = new DateFormatExt(); String strDate, strFormat; String strResult = ""; //als erstes müssen zwei strings eingelesen werden strDate = IO.readWord(); strFormat = IO.readWord(); try { //...danach wird versucht das datum mit hilfe einer instanz der DateFormatExt klasse zu formatieren strResult = df.getFormattedDate(strDate, strFormat); } catch (EprogException e) { //...tritt innerhalb dieser instanz ein fehler auf werden wir mittels EprogException //darüber in kenntnis gesetzt und ändern die ausgabe definitionsgemäß ab! strResult = "FALSCHE EINGABE"; //eine ausgabe der fehlerbeschreibung kann zu debug-zweichen nützlich sein //IO.println("---> Error-Message: " + e.getMessage()); } //in jedem fall erfolgt eine ausgabe! IO.println(strResult); } }