So, hier sind die Perlen:
C#:
int d = (int)DateTime.Now.Day;
int m = (int)DateTime.Now.Month;
int y = (int)DateTime.Now.Year;
// leap year, = 1 if leap yeap, 0 if not; needs y
int ly = (4 - y % 4) / 4 - (100 - y % 100) / 100 + (400 - y % 400) / 400;
// day of week; needs d, m, y; 1=mon ... 7=sun
int dow = ((d + (26 * ((m + 9) % 12 + 1) - 2) / 10 + (y - ((22 - m) / 10 - 1)) % 100 + ((y - ((22 - m) / 10 - 1)) % 100 / 4) + ((y - ((22 - m) / 10 - 1)) / 400) - 2 * ((y - ((22 - m) / 10 - 1)) / 100) - 1) % 7 + 7) % 7 + 1;
// day of year; needs d, m, y; starts with 1 i.e. if 01.01.xxxx then d=1 and m=1 and doy = 1
int doy = ((m - 1) * 1529 - 100 * ((m + 7) / 10) + 25) / 50 + ((4 - y % 4) / 4 - (100 - y % 100) / 100 + (400 - y % 400) / 400) * ((m + 7) / 10) + d;
// easter day of year; returns day of year for easter sunday; respects leap year
int easterdoy = 59 + (21 + ((19 * (y % 19) + (15 + (3 * (y / 100) + 3) / 4 - (8 * (y / 100) + 13) / 25)) % 30) - ((((19 * (y % 19) + (15 + (3 * (y / 100) + 3) / 4 - (8 * (y / 100) + 13) / 25)) % 30) + (y % 19) / 11) / 29)) + (7 - ((21 + ((19 * (y % 19) + (15 + (3 * (y / 100) + 3) / 4 - (8 * (y / 100) + 13) / 25)) % 30) - ((((19 * (y % 19) + (15 + (3 * (y / 100) + 3) / 4 - (8 * (y / 100) + 13) / 25)) % 30) + (y % 19) / 11) / 29)) - (7 - (y + y / 4 + (2 - (3 * (y / 100) + 3) / 4)) % 7)) % 7) + ((4 - y % 4) / 4 - (100 - y % 100) / 100 + (400 - y % 400) / 400);
// prayer - buss und bettag; returns day of year, respects leap year, needs year
int bbdoy = 326 - (4 + ((46 + y % 100 + y % 100 / 4 + y / 400 - 2 * (y / 100) - 1) % 7 + 7) % 7) % 7 + (4 - y % 4) / 4 - (100 - y % 100) / 100 + (400 - y % 400) / 400;
// calc month from doy, needs doy and ly
int doy2m = (((doy - ly) * 50 + 74) / 1529) * (1 - ((366 - doy) / 335)) + 1;
// calc date from doy; respects leap year; needs doy, ly and doy2m; return int in form of <dd><mm>, i.e. 02.04. = 204, 31.12. = 3112
int doy2date = (doy - (12 - ((doy2m + 10) % 12)) / 12 * 31 - (1 - (12 - doy2m) / 10) * ly - (12 - ((doy2m + 9) % 12)) / 12 * 59
- (12 - ((doy2m + 8) % 12)) / 12 * 90 - (12 - ((doy2m + 7) % 12)) / 12 * 120 - (12 - ((doy2m + 6) % 12)) / 12 * 151
- (12 - ((doy2m + 5) % 12)) / 12 * 181 - (12 - ((doy2m + 4) % 12)) / 12 * 212 - (12 - ((doy2m + 3) % 12)) / 12 * 243
- (12 - ((doy2m + 2) % 12)) / 12 * 273 - (12 - ((doy2m + 1) % 12)) / 12 * 304 - (12 - (doy2m % 12)) / 12 * 334) * 100 + doy2m;
string retval = "";
foreach( string holi in PhoneSystem.Root.GetParameterByName(holidayparamname).Value.Split(";"))
{
string dholi = holi.Split(",")[0].Trim();
string dholistr = holi.Substring(holi.IndexOf(",")+1).Trim();
if (dholi.Length > 0 && dholi[0] == 'e')
{
if (dholi.Length == 1 && easterdoy == doy)
{
retval = dholistr;
}
else if (dholi.Length > 2 && dholi[1].Equals('+'))
{
int dholiadd = int.Parse(dholi[2..]);
if (( easterdoy + dholiadd) == doy)
{
retval = dholistr;
}
}
else if (dholi.Length > 2 && dholi[1].Equals('-'))
{
int dholisub = int.Parse(dholi[2..]);
if ((easterdoy - dholisub) == doy)
{
retval = dholistr;
}
}
}
else if (dholi.Length > 0 && dholi[0] == 'b')
{
if (bbdoy == doy)
{
retval = dholistr;
}
}
else if (dholi.Length > 0 )
{
int hd = int.Parse(dholi.Split(".")[0].Replace(" ", ""));
int hm=0;
if (dholi.Split(".").Length > 1)
{
hm = int.Parse(dholi.Split(".")[1].Replace(" ", ""));
}
else if (hd > 100)
{
hm = hd % 100;
hd = hd / 100;
}
int hdoy = ((hm - 1) * 1529 - 100 * ((hm + 7) / 10) + 25) / 50 + ((4 - y % 4) / 4 - (100 - y % 100) / 100 + (400 - y % 400) / 400) * ((hm + 7) / 10) + hd;
if ( hm > 0 && hd > 0 && hdoy == doy)
{
retval = dholistr;
}
}
}
// write to 3cxcallflow.log:
// File.AppendAllText(System.IO.Directory.GetCurrentDirectory()+"/Instance1/Data/Logs/3CXCallFlow.log", "\n##### CFA C# Log: |" + PhoneSystem.Root.GetParameterByName(holidayparamname).Value + "|\n");
return retval;
Der Routine wird ein Parameter mit Namen
holidayparamname
vom Typ String mitgegeben (siehe Beispiel unten). Dieser hält den Namen des globalen Parameter der 3CX in der letztendlich die Daten zur Auswertung stehen. Damit wird das C# Schnipsel variabel nutzbar. Die Auswertung erfolgt mit einfachen Konstrukten (einfache Zeichenkettenoperationen und Rechnungen mit Ganzzahlen), ist also auch auf andere Programmiersprachen übertragbar.
Fazit: wird dieses C# Schnipsel mit den richtigen Daten gefüttert und wird das Ergebnis richtig ausgewertet (siehe Beispiel unten), dann funktioniert das ewig - bis zur nächsten Kalender- oder Feiertagreform.
In dem variablen 3CX Parameter stehen die Feiertagdaten in folgender möglicher Form (aktuell 2 bis 4 mögliche Parameter, bei Bedarf ausbaufähig):
<Datum>,<Bezeichnung>[,Name einer Audio Datei][,Nummer zur Weiterleitung]
Die einzelnen Wert eines Eintrages werden mit einem Komma
,
separiert. Mögliche folgende Feiertag Datensätze werden mit einem Semikolon
;
separiert (siehe Beispiel für
HOLIDAYS_DE_SN
unten). Das heisst: in den sonstigen Nutzdaten sollte kein Komma oder Semikolon verwendet werden. Das C# Schnipsel wertet pro Feiertag Datensatz nur den ersten Parameter (das angegebene Datum) aus, nichts anderes. Wenn das Datum mit dem aktuellen Tag übereinstimmt (es somit ein Feiertag ist), dann werden alle anderen Parameter des Feiertag Datensatzes zurückgegeben - egal welche und wie viele das sind. Die Komma und Angaben zu Name einer Audio Datei und Nummer zur Weiterleitugn kann bei Bedarf entfallen. Es ist dann die Aufgabe der ander CFD Codeblöcke diese auszuwerten, siehe Beispiel unten. Das Datum kann ohne Punkte angegeben werden (z.b. 0104 für den 1.4.), die Jahresangabe wird ignoriert. Wenn das Datum mit einem
e
(für easter, Ostern) beginnt, dann ist das ein gleitender Feiertag und das richtige Datum des jew. Jahres (evtl. ab- oder zuzüglich weiterer Tage) wird berechnet. Wenn das Datum mit einem
b
(für Buß- und Bettag) beginnt, dann ist das auch ein gleitender Feiertag und das richtige Datum des jew. Jahres wird berechnet. Stimmt das aktuelle Datum mit einer der (teils errechneten) Datumangaben im 3CX Parameter überein, so ist es an diesem Tag ein Feiertag und die restlichen Felddaten des Feiertag Eintrages (Bezeichnung, Name einer Audio Datei, Nummer.
Für den Freistaat Sachsen gibt es einen globalen 3CX Parameter
HOLIDAYS_DE_SN
mit dem Inhalt (in diesem Fall hier ohne jegliche Angaben von Audio Dateien oder Nummern für spezielle Weiterleitungen):
Code:
1.1., Neujahr; e-2, Karfreitag; e, Ostersonntag; e+1, Ostermontag; 1.5., Tag der Arbeit; e+39, Christi Himmelfahrt; e+49, Pfingstsonntag; e+50, Pfingstmontag; 3.10., Tag der deutschen Einheit; 31.10., Reformationstag; b, Buß- und Bettag; 25.12., Erster Weihnachtsfeiertag; 26.12., Zweiter Weihnachtsfeiertag
Das komplette Funktionsbeispiel für den 3CX CFD habe ich vorhin extra noch für das Forum hier gebaut. Der hat noch einige wenige Bausteine mehr:
- es wird zuerst der globale 3CX Parameter
HOLIDAYS_DE_SN
als Vorgabe für dieses Beispiel gesetzt (auf das aktuelle Datum, zusammen mit ein klein wenig anderen Daten zur Illustration der Funktion), die C# Feiertagroutine wird somit immer ein Ergebnis liefern
- die oben besprochene Feiertagroutine als C# Code Schnipsel
- eine Auswertung der zurückgegebenen Parameter, hier wird bei Bedarf eine Audio Datei (die in der CFA / der 3CX vorhanden sein muss) abgespielt, der Anruf es wird auf eine optional angegebene Nummer weitergeleitet oder sonstiges
Es können in diesem Beispiel - je nach Feiertag Eintrag im 3CX Parameter - in der Zeichenkette der Rückgabe mehrere Parameter (hier bis zu drei, durch Komma getrennt, ist ausbaufähig) zurückgegeben werden: Bezeichnung, Name einer Audio Datei, Nummer für eine Weiterleitung. Die Auswertung des vom C# Schnipsel übergebenen Rückgabewertes im CFD Block
Condition
erfolgt immer von links nach rechts, first match wins:
- ist die Länge des Rückgabewertes > 0 und es werden drei Parameter zurückgegeben (ist die Anzahl an Kommas
,
gleich 2: mit REPLACE_REG_EXP
werden alle Zeichen außer den Kommas aus dem Rückgabewert entfernt und diese werden danach mit LEN
gezählt), dann hole aus dem Rückgabewert den zweiten Parameter (den Namen der Audio Datei, es wird mittels zweier REPLACE_REG_EXP
der erste und der letzte Parameter entfernt) und spiele diese ab, danach hole aus dem Rückgabewert den dritten Parameter (die Nummer der Weiterleitung, es wird mittels REPLACE_REG_EXP
die ersten beiden Parameter entfernt) und leite den Anruf dort hin weiter
- ist die Länge des Rückgabewertes > 0 und es werden zwei Parameter zurückgegeben (ist die Anzahl an Kommas
,
gleich 1: mit REPLACE_REG_EXP
werden alle Zeichen außer den Kommas aus dem Rückgabewert entfernt und diese werden danach mit LEN
gezählt), dann hole aus dem Rückgabewert den zweiten Parameter (den Namen der Audio Datei, es wird mittels zweier REPLACE_REG_EXP
der erste und der letzte Parameter entfernt) und spiele diese ab, danach leite den Anruf an eine fest vorgegebene Nummer / NSt. weiter
- ist die Länge des Rückgabewertes > 0, so wird nur ein Parameter zurückgegeben (die Bezeichnung des Feiertages, die Prüfung auf zwei oder ein Komma ist ja in den beiden Zweigen zuvor erfolgt), dann leite den Anruf an eine fest vorgegebene Nummer / NSt. weiter
- ist die Länge des Rückgabewertes == 0, dann ist heute kein Feiertag, dann leite den Anruf an eine fest vorgegebene Nummer / NSt. weiter
Die Formel in
dow
(Wochentagberechnung) und die in
easterdoy
(Osterdatumberechnung) habe ich mal irgendwann schamlos aus einem meiner früheren Tafelwerke kopiert und stark angepasst. Das sind vmtl. die Formeln von Zeller und von Gauss (nach dem Gregorianischem / Westkirchen Kalender). Alles andere (
ly
,
doy
,
bbdoy
,
doy2m
und
doy2date
) habe ich mir früher mal ausgedacht. Das geht sicher eleganter (und auch kürzer, insbes. in C#), aber das sind generische und sehr schlichte Formeln, einfache Funktionen, keine Routinen: kein if-then Sprünge, keine Schleifen, keine Tabellen, keine oder nur wenige Variablen in Abhängigkeit von anderen Berechnungen vorab, es werden nur Grundrechenarten (+, -, *, /, %) verwendet und das alles sind durchgängig Ganzzahlberechnungen (keine FPU, keine Rundungsfehler). Damit funktionieren diese Formeln auf allen Plattformen und in allen Programmiersprachen die mind. 16 bit Integer verarbeiten können (für die 8bitter ist eine kleine Anpassung bei der Division nötig): Assembler, Forth, C, C++, Perl, Php, Powershell u.v.a.m - so eben auch in C#. Die Formeln habe ich schon Jahrzehnte, da wird nichts mehr verändert. Die werden nur bei Bedarf in eine andere Programmiersprache kopiert und da läuft es wieder. Die kann sich jeder kopieren so wie er es braucht. Vielleicht hat oder kennt ja einer elegantere generische Lösungen dafür, dann kann er die ja hier drunter schreiben. Ich habe nie danach gesucht.
Suchstichwort: CFD Call Flow Designer App Schnipsel
p.s.: Das Schreiben des Textes hier hat einiges länger gedauert als das zusammenklicken des CFD Beispiels.