Freitag, 24. Juli 2009

Datumsspielereien (Teil 4) / Date gadgets (Part 4)

Bereits in einigen früheren Postings (Datumsspielereien Teil 1-3) waren Datumswerte ein Thema. In diesem Posting dreht es sich jedoch nicht um Feiertage sondern um die Berechnung spezieller Tage. Insbesondere die aktuelle Woche des Monats, die Kalenderwoche sowie der Montag der Woche. Zur Berechnung des Monatswoche benötigen wir drei Funktionen: CEILING(), DOW() und DAY(). CEILING gibt die nächst höhere Ganzzahl zurück.
STORE 10.1 TO num1
STORE -10.9 TO num2
? CEILING(num1)  && zeigt 11
? CEILING(num2)  && zeigt -10
? CEILING(10.0)  && zeigt 10
? CEILING(-10.0) && zeigt -10
DOW liefert einen numerischen Tag-der-Woche Wert auf Basis eines Datums. Hierbei ist jedoch der 2. Parameter zu beachten, in dem der Erste Tag der Woche definiert wird. Da unsere Woche üblicherweise mit einem Montag beginnt, ist als Wert für den 2. Parameter zwingend eine 2 (-> Montag = Wochenbeginn) zu setzen.
? DOW(DATE(),2)    && zeigt beim 24.07.2009 den Wert 5
DAY wiederum extrahiert von einem Datumwert den Tagesblock.
? DAY(DATE())    && zeigt beim 24.07.2009 den Wert 24
FUNCTION WeekOfMonth as Integer
LPARAMETERS vDate as Date

    * // Woche des Monats bestimmen
    RETURN ( CEILING( ( DAY( m.vDate ) + DOW( ( m.vDate ) - DAY( m.vDate ) + 1, 2 )  - 1 ) / 7 ) )

ENDFUNC
Die Berechnung der Kalenderwoche gestaltet sich da schon um einiges einfacher. Die Wochenbestimmung hängt von zwei Grundeinstellungen ab, die der WEEK()-Funktion als Parameter 2 und 3 übergeben werden. - Parameter 2 definiert die Berechnung der ersten Woche des Jahres - Parameter 3 definiert den Wochenanfang und entspricht dem 2. Parameter von DOW()
FUNCTION WeekOfYear as Integer
LPARAMETERS vDate as Date

    * // Woche des Jahres bestimmen
    * // Parameter 2: Wert = 2
    * //              Die längere Hälfte der ersten Woche
    * //              (4 Tage) definiert die Jahreszugehörigkeit
    * // Parameter 3: Wert = 2
    * //              Montag gilt als der erste Tage der Woche
    RETURN ( WEEK( m.vDate, 2, 2 ) )
  
ENDFUNC
Zuletzt kommen wir zur Funktion zur Berechnung des Montags einer Woche. Auch in dieser Funktion wird mit DOW() gearbeitet, und wie bei den vorherigen Einsätzen wird der 2. Parameter wieder auf den Wert 2 gesetzt wird.
FUNCTION MondayOfWeek as Date
LPARAMETERS vDate as Date

    * // Montag der Woche des übergebenen Datums                          
    RETURN ( m.vDate - ( DOW( m.vDate, 2 ) - 1) )

ENDFUNC

Mittwoch, 15. Juli 2009

Arbeiten mit Arrays / Working with arrays

Wer mit VFP arbeitet wird irgendwann feststellen, dass nicht immer alle Informationen als Cursor zur Verfügung stehen. Viele Funktionen des Fuxes die u.a. auch auf Objekte und Verzeichnisse zugreifen, stellen ihre gesammelten Informationen in Form eines Arrays bereit.

Unter anderem gibt die Funktion ADIR() bei gezielten Verzeichnisabfragen (bspw. mit ADIR(myArray,[*],[D],1) ) grundsätzlich den aktuellen (.) sowie den übergeordneten (..) relativen Pfadnamen aus. Zu finden im Array an Position 1,x und 2,x. Sollen diese beiden Verzeichnisse in der weiteren Verarbeitung keine Berücksichtigung finden, kann es bspw. sinnvoll sein, diese beiden Arrayblöcke zu entsorgen.

Für die Arbeit mit Arrays stellt uns VFP diverse Funktionen und Befehle zur Verfügung:

DECLARE DIMENSION LOCAL PUBLIC
Deklarieren oder Redimensioneren eine Variable als 1- oder 2-dimensionalen Array

AINS()
Einfügen weiterer Arrayblöcke

ACOPY()
Kopieren von Arrayblöcken an eine andere Position

ADEL()
Löschen von Arrayblöcken

ALEN()
Abprüfen der Dimensionierung eines Arrays

Zunächst deklarieren wir uns einen leeren Array namens laArrayDemo der nur eine einzelne Zelle beinhaltet:

DECLARE laArrayDemo(1)

Standardmäßig enthält die Zelle 1 den boolschen Wert FALSE. Die Ergänzung der Deklaration um einen Datentyp zeigt nur Auswirkung im Bereich von COM-Objekten. Zudem besteht keine Verpflichtung, jeder Zelle Werte identischer Datentypen zuzuordnen. Letztlich ist ein Array nur einen Art Collection.

Wollen wir den o.a. Array um einige Zellen erweitern, so geschieht dies durch eine erneute Deklaration des Arrays mit der entsprechend erhöhten (oder auch reduzierten) Anzahl an Zellen.

DECLARE laArrayDemo(5)

Wissen wir nicht genau wie die aktuelle Dimensionierung aussieht und wollen nur sicherstellen, dass die bestehende um x Zellen erweitert wird, dann müssen wir die Funktion ALEN() zu Hilfe nehmen.

Bei unserem ein-dimensionalen Array stellen wir die Anzahl der Zellen fest und erhöhen das gelieferte Ergebnis um den gewünschten Wert.

DECLARE laArrayDemo(ALEN(laArrayDemo,1) + 4)

Etwas 'leserlicher' sähe das so aus:

* // Arbeitsvariablen definieren
LOCAL liDim1 as Integer, liZuwachs as Integer
* // aktuelle Dimensionierung feststellen
liDim1 = ALEN(laArrayDemo,1)
* // Anzahl der neuen Zellen definieren
liZuwachs = 4
* // Redimensionierung durchführen
DECLARE laArrayDemo(liDim1 + liZuwachs)
* // Arbeitsvariablen freigeben
RELEASE liDim1, liZuwachs

Sind nun alle fünf Positionen mit Werten gefüllt ...

laArrayDemo(1) = [Wert in Zelle 1]
laArrayDemo(2) = [Wert in Zelle 2]
laArrayDemo(3) = [Wert in Zelle 3]
laArrayDemo(4) = [Wert in Zelle 4]
laArrayDemo(5) = [Wert in Zelle 5]

... und wir benötigen an Stelle 2 einen zusätzlichen Eintrag (weitere Zelle), dann nehmen wir die Funktion AINS() zu Hilfe. Wichtig bei dieser Funktion ist, dass sie keine Redimensionierung des Array vornimmt. Die verschiebt ab der definierten Position alle Einträge um eine Position nach hinten. Hierbei fällt u.U. die letzte Zelle raus.

AINS(laArrayDemo,2)

Wichtig ist also, dass wir zuvor eine Redimensionierung durchführen müssen, damit der Inhalt von Zelle 5 nicht verloren geht. Korrekterweise müsste der Code folglich so lauten:

* // Redimensionierung +1 durchführen
DECLARE laArrayDemo(ALEN(laArrayDemo,1) + 1)
* // ab (inklusive) Zelle 2 alle Zellen um
* // eine Position nach hinten verschieben
AINS(laArrayDemo,2)

Wollen wir den Inhalt der Zellen 1 und 2 ans Ende des Arrays verschieben und die Zellen 3-5 auf die Positionen 1-3, dann könnte dies bspw. wie folgt durchgeführt werden:

* / Deklarieren der Arbeitsvariablen
LOCAL liAnzahl as Integer, liQuellPosition as Integer, liZielPosition as Integer
liAnzahl        = 2
liQuellPosition = 1
liZielPosition  = ALEN(laArrayDemo,1) + 1
* // Redimensionierung des Array um die
* // Anzahl der zu verschiebenden Zellen
DECLARE laArrayDemo(ALEN(laArrayDemo,1) + liAnzahl)
* // Kopieren der Zelleninhalte in die
* // neuen angehängten leeren Zellen
ACOPY(laArrayDemo,laArrayDemo,liQuellPosition,liAnzahl,liZielPosition)
* // Löschen der nun nicht mehr benötigten
* // ersten beiden Zellen des Arrays
ADEL(laArrayDemo,1)
ADEL(laArrayDemo,1)
* // Redimensionieren des Arrays auf die
* // ursprüngliche Größe vor dem Kopieren
DECLARE gaArryDemo(ALEN(laArrayDemo,1) - liAnzahl)
* // Freigeben der Arbeitsvariablen
RELEASE liAnzahl, liQuellPosition, liZielPosition

Eine einfachere Vorgehensweise wäre der Einsatz eines 'on the fly' generierten temporären Arrays, wie es von ACOPY() automatisch erzeugt werden kann:

* // Deklarieren der Arbeitsvariablen
LOCAL liAnzahl as Integer, liQuellPos as Integer, liZielPos as Integer
liAnzahl    = 2
liQuellPos    = 1
liZielPos    = (ALEN(laArrayDemo,1) + 1) - liAnzahl
* // Kopieren der Zellen in den temporären Array
* // Der neue Array wird automatisch erzeugt und
* // verfügt über die selbe Struktur wie der
* // Quellarray. Allerdings sind nur die kopierten
* // Zellen gefüllt. Alle anderen stehen auf .F.
ACOPY(laArrayDemo,laArrayTemp,liQuellPos,liAnzahl,liQuellPos)
* // Nun werden die nachfolgenden Zellen
* // nach oben kopiert
ACOPY(laArrayDemo,laArrayDemo,liQuellPos + liAnzahl,liZielPos - liAnzahl,liQuellPos)
* // Jetzt kommen die Zellen des TEMP-Arrays
* // an ihre neue Position im Basis Array
ACOPY(laArrayTemp,laArrayDemo,liQuellPos,liAnzahl,liZielPos)
* // Arbeitsvariablen freigeben und hierbei
* // den temporären Array nicht vergessen!!
RELEASE liAnzahl, liQuellPos, liZielPos, laArrayTemp

Dieser Code lässt sich als Funktion wie folgt umsetzen:

AMOVE(@pSourceArrayName, nErstesQuellElement, nAnzahlElemente, nErstesZielElement)

Wichtig hierbei ist, dass der Array als Referenz übergeben wird. Hierdurch sparen wir es uns, diesen als Rückgabewert zu liefern und die Funktion kann direkt mit den 'echten' Werten arbeiten. Aufruf:

AMOVE(@laArrayDemo,1,2,4)

Funktion:

FUNCTION AMOVE
LPARAMETERS paArrayRef, piQuellPos as Integer, piAnz as Integer, piZielPos as Integer
  LOCAL llReturn as Boolean
  llReturn = .T.
  TRY
    * // Funktion für 1-dimensionale Arrays
    ACOPY( pbArrayRef  , laArrayTemp , piQuellPos , piAnz , piQuellPos )
    ACOPY( paArrayRef  , paArrayRef  , piQuellPos + piAnz , piZielPos - piAnz,piQuellPos )
    ACOPY( laArrayTemp , paArrayRef  , piQuellPos , piAnz , piZielPos )
    RELEASE laArrayTemp
  CATCH
    llReturn = .F.
  ENDTRY
  RETURN llReturn
ENDFUNC

Nachteil dieser Funktion ist, dass nur eindimensionale Arrays verarbeitet werden können. Also ergänzen wir diese Funktion zum Einen um das automatische Erkennen der Dimensionalität sowie einer Trennung in eine 1- bzw. 2-Dimensionale Verarbeitung. Anschliessend sieht die Funktion wie folgt aus:

FUNCTION AMOVE
LPARAMETERS paArrayRef, piQuellPos as Integer, piAnz as Integer, piZielPos as Integer
  * // Parameterwerte:                                               
  * // paArrayRef    = Referenz auf Array                               
  * // piQuellPos    = Startposition (Row) ab der verschoben werden soll   
  * // piAnz        = Anzahl der Rows die verschoben werden sollen       
  * // piZielPos    = Zielposition (Row) an die verschoben werden soll   
   
  LOCAL llReturn as Boolean
  llReturn = .T.

  IF ALEN(paArrayRef,2) = 0
    TRY
      * // Block für 1-dimensionale Arrays
      ACOPY( paArrayRef  , laArrayTemp , piQuellPos , piAnz , piQuellPos )
      ACOPY( paArrayRef  , paArrayRef  , piQuellPos + piAnz , piZielPos - piAnz, piQuellPos )
      ACOPY( laArrayTemp , paArrayRef  , piQuellPos , piAnz , piZielPos  )
      RELEASE laArrayTemp
    CATCH
      * // Irgendetwas ging schief
      llReturn = .F.
    ENDTRY
  ELSE
    TRY
      * // Block für 2-dimensionale Arrays
      LOCAL liColumns as Integer, liRows as Integer
      * // Zur besseren Übersicht die Array Dimensionen zwischenspeichern
      liColumns  = ALEN(paArrayRef,2)
      liRows     = ALEN(paArrayRef,1)
      * // Berechnen der tatsächlichen Offsets auf Basis der vorhandenen Columns
      piQuellPos = IIF(piQuellPos > 1, piQuellPos * liColumns - liColumns + 1, piQuellPos)
      piZielPos  = IIF(piZielPos > 1,  piZielPos  * liColumns - liColumns + 1, piZielPos )
      piAnz      = piAnz * liColumns
      * // Jetzt kann's los gehen
      ACOPY( paArrayRef  , laArrayTemp , piQuellPos , piAnz , piQuellPos )
      ACOPY( paArrayRef  , paArrayRef  , piQuellPos + piAnz , liRows - piAnz,piQuellPos )
      ACOPY( laArrayTemp , paArrayRef  , piQuellPos, piAnz ,  piZielpos )
      RELEASE laArrayTemp, liColumns, liRows
    CATCH
      * // Irgendetwas ging schief
      llReturn = .F.
    ENDTRY
  ENDIF
  RETURN llReturn
ENDFUNC