infobase: EDV - MS-Access


Datum & Zeit

allgemein   Quelle: dmt   Datum: 03.2004   nach oben

- Datumsberechnungen in Access z.B. per DatePart (typisch bei KW) nur korrekt, wenn erweiterte Angaben zu landesspezifischen Einstellungen bez. "Erster Tag der Woche" und "Erste Woche des Jahres" gemacht wurden.

Bsp.:

DatePart("ww", vVon, 2, 2)


Anfang/Ende   Quelle: dmt   Datum: 08.2006   nach oben

START- und ENDE-DATUM für einen gegebenen Zeitraum ermitteln:

Die folgende Routine (Access 2.0) berechnet das jeweils korrekte Start- und Ende-Datum für angegebene Zeiträume (Quartale, Monate, Wochen) und übergibt diese in dem Beispiel an zwei unsichtbare Formular-Steuerelemente.

Private Sub Zeitraum_AfterUpdate()

    Dim i As Byte
    Dim DatumVon As Variant, DatumBis As Variant

    If InStr(Me!Zeitraum, "Quartal") > 0 Then
       i = Val(Right$(Me!Zeitraum, 1))
       Select Case i
          Case 1:   DatumVon = CVDate("01.01." & Year(Me!vom))
                    If Month(Me!vom) > 3 Then DatumVon = CVDate("01.01." & Year(Me!bis))
                    DatumBis = CVDate("31.03." & Year(DatumVon))
          Case 2:   DatumVon = CVDate("01.04." & Year(Me!vom))
                    If Month(Me!vom) > 6 Then DatumVon = CVDate("01.01." & Year(Me!bis))
                    DatumBis = CVDate("30.06." & Year(DatumVon))
          Case 3:   DatumVon = CVDate("01.07." & Year(Me!vom))
                    If Month(Me!vom) > 9 Then DatumVon = CVDate("01.01." & Year(Me!bis))
                    DatumBis = CVDate("30.09." & Year(DatumVon))
          Case 4:   DatumVon = CVDate("01.10." & Year(Me!vom))
                    DatumBis = CVDate("31.12." & Year(DatumVon))
       End Select
    ElseIf InStr(Me!Zeitraum, "Monat") > 0 Then
       DatumVon = CVDate("01." & Right$(Me!Zeitraum, 2) & "." & Year(Me!vom))
       If Month(DatumVon) < Month(Me!vom) Then DatumVon = CVDate("01." & Right$(Me!Zeitraum, 2) & "." & Year(Me!bis))
          DatumBis = DateAdd("d", 31, DatumVon)
          If Month(DatumBis) > Month(DatumVon) Then
             Do While Month(DatumBis) > Month(DatumVon)
                DatumBis = DateAdd("d", -1, DatumBis)
             Loop
          Else
             Do While Month(DatumBis) < Month(DatumVon)
                DatumBis = DateAdd("d", -1, DatumBis)
             Loop
          End If
    ElseIf InStr(Me!Zeitraum, "Woche") > 0 Then
       DatumVon = Me!vom
       i = Val(Right$(Me!Zeitraum, 2))
       If Week(DatumVon) > i Then DatumVon = Me!bis

       If i = 1 Then
          DatumVon = CVDate("25.12." & Year(DatumVon) - 1)  ' auf jeden Fall im richtigen Jahr
          Do While Week(DatumVon) = 52                      ' Woche vorwärts überlaufen
             DatumVon = DatumVon + 1
          Loop
          DatumVon = DatumVon + 1
       Else
          DatumVon = CVDate("01.01." & Year(DatumVon))      ' auf jeden Fall im richtigen Jahr
          DatumVon = DateAdd("d", i * 6, DatumVon)          ' und knapp vor der gewünschten Woche
          Do While i > Week(DatumVon)                       ' Woche vorwärts überlaufen
             DatumVon = DatumVon + 1
          Loop
       End If

       DatumBis = DatumVon
       Do While i = Week(DatumBis)                          ' Woche vorwärts überlaufen
          DatumBis = DatumBis + 1
       Loop
       DatumBis = DatumBis - 1                              ' und den letzten Tag der gewünschten Woche einstellen
    End If

    Me!Startdatum = DatumVon
    Me!Endedatum = DatumBis

    BuildSQLKriterium

End Sub


between   Quelle: dmt   Datum: 12.2006   nach oben

DATUMSFELDER per SQL abfragen / BETWEEN:

Um Datensätze, die innerhalb einer gewissen Zeitspanne liegen, abzufragen, kann in Access u.a die between-Anweisung benutzt werden.

Allerdings muss dabei einiges beachtet werden (Beispiel: Daten im Zeitraum 01.09.1997 bis 30.09.1997):

In SQL kann das dann so aussehen:

SELECT *
FROM Termine
WHERE von BETWEEN #09/01/1997# AND #09/30/1997# + 1

Fazit: IMMER ALLES ausprobieren, der Dokumentation nicht über den Weg trauen und NICHTS glauben, was man nicht hoechstpersoenlich SELBST nachgeprueft hat !

Da kommen dann echte Verschwörungsgefühle auf, toll.


Datum   Quelle: dmt   Datum: 12.2006   nach oben

DATUM:

Das Erst-Datum für die Access-eigene serielle Datumsverwaltung ist 30.12.1899 00:00.

* * * *

Ein wunderbares Beispiel für Ärger im Umgang mit MS-ACCESS ist die Übergabe eines Datenherkunft-String's innerhalb einer RecordSet-Anweisung.

Set RS_Termine = DB.OpenRecordset("Termine", DB_OPEN_SNAPSHOT)

weist dem RecordSet RS_Termine alle vorhandenen Termine zu.

Soweit, so gut, aber jetzt kommt's:

Will man die Menge der gefundenen Datensätze sinnvoll eingrenzen (zeige alle Termine eines bestimmten Tages), was innerhalb eines relationalen Datenbanksystems eine ganz normale Sache ist, so muß der Where-Klausel des Herkunft-Strings ein amerikanisches Datums-Literal mit dem Format #mm/dd/yy# übergeben werden. Der gültige String kann z.Bsp. so aussehen:

"SELECT von,bis FROM Termine WHERE Datum=#03/25/95#"

Die an sich ganz ordentliche Dokumentation schweigt sich hierzu recht beharrlich aus.
Durch Zufall konnten unter dem Stichwort 'Filter' folgende Informationen gefunden werden:

" Sie sollten das US-Datumsformat (Monat-Tag-Jahr) verwenden, wenn Sie Felder filtern, die Datumsangaben enthalten. Sie sollten dies auch dann tun, wenn Sie nicht die US-Version von Microsoft Access einsetzen, da sonst die Daten möglicherweise nicht wie erwartet gefiltert werden. Mit der Funktion Format können Sie dies vereinfachen, z.B.: "

DS1.Filter = "Startdatum > #" & Format(Datum1, "m/d/yy") & "#"

Das bloße Tippen solcher Konstrukte empfinde ich als Demütigung, deswegen verwende ich Funktionen, die sich dieses Problemes annehmen.

Nachdem diese jahrelang problemlos im Einsatz waren, konnte im Umfeld komplexer Abfragen doch glatt ein Fehler provoziert werden: Ein als String übergebenes "24.06.2003 20:00:00" wollte sich nicht so recht ins amerikanische Format (Tage/Monat/Jahr) umstellen lassen. Deswegen wurden die entsprechenden Date_US-Funktionen um ein CVDate(Datum) erweitert.

Function Date_US_SQL(Datum As Variant) As Variant

    Dim sD As String

    If IsDate(Datum) Then
    
       Datum = CVDate(Datum)

       sD = "#"                                          ' SQL-Format #mm/dd/yyyy#
       If Month(Datum) < 10 Then sD = sD & "0"
       sD = sD & Month(Datum)
       sD = sD & "/"
       If Day(Datum) < 10 Then sD = sD & "0"
       sD = sD & Day(Datum)
       sD = sD & "/"
       sD = sD & Year(Datum)
       sD = sD & "#"

       Date_US_SQL = sD

    Else

       Beep
       MsgBox "Das Argument '" & Datum & "' ist kein gültiges Datum", 16, "Modul: Tools / Date_US_SQL"

    End If

End Function

Eine modernere, aktualisierte Fassung kann das sogar mit Sekunden-genauen Vergleichen a'la

WHERE Datum > #03/04/2004 19:53:55#
umgehen:

Function Date_exact_US_SQL (Datum As Variant) As String

    On Erro GoTo err_Date_exact_US_SQL

    Dim s As String

    If IsDate(Datum) Then
       s = Format(CVDate(Datum), "mm.dd.yyyy hh:nn:ss")
       s = ReplaceInString(s, ".", "/")
       s = "#" & s & "#"
       Date_exact_US_SQL = s
    Else
       Beep: MsgBox "Das Argument '" & Datum & "' ist kein gültiges Datum", 16, "Modul : Tools / Date_exact_US_SQL"
    End If

    Exit Function


err_Date_exact_US_SQL:

    Fehler "Date_exact_US_SQL"
    Exit Function

End Function

und beinahe banal:

Function Date_US (Datum As Variant) As Variant

    If IsDate(Datum) Then
       Date_US = Format(CDate(Datum), "mm-dd-yyyy")   ' Datum im englischen Format
    Else
       Beep
       MsgBox "Das Argument '" & Datum & "' ist kein gültiges Datum", 16, "Modul : Tools / Date_US_SQL"
    End If

End Function

Aber das ist noch nicht das Ende der Fahnenstange:

Wenn man Datums-Format-Geschichten in Abfragen realisieren möchte, tun sich noch weitere (und weitere und nochmals weitere) Felder verschiedenartiger Ausdrucksnormen vor uns auf:

Beinahe als Klartext erscheint mittlerweile folgende SQL-Anweisung:

'SELECT DISTINCTROW TERM2ASC.*, Format$(TERM2ASC.Datum,"dd\.mm\.yyyy") AS DatumA, Format$(TERM2ASC.Datum,"hh:nn") AS TimeA FROM TERM2ASC;'

Hier wird ein Feld Datum der Tabelle TERM2ASC zwei berechneten Ausdrücken zugeordnet, von denen der eine das reine Datum und der andere die Zeit darstellt ( Das Feld Datum enthält einen vollständigen, seriellen Access-Zeitwert ). Einen
vollständigen Datumsstring innerhalb einer WHERE-Anweisung zu übergeben, wollte bisher nicht gelingen.

Aber den Vogel abscheißenderweise bildet das folgende, angeblich ?DEUTSCHE? Äquivalent den Höhepunkt Programmier-sprachlicher Verunstaltung:

'DatumA: Format$(TERM2ASC.Datum;"tt\.mm\.jjjj")'


Datumsfilter: Jahre, Quartale und Monate   Quelle: dmt   Datum: 08.2006   nach oben

Im Rahmen einer Finanzdaten enthaltenden Anwendung macht es Sinn, dem Anwender Zeitraum-bezogene Filtermöglichkeiten zu geben.

So könnte ein Filter-Klappfeld die in der Datensatzsammlung vertretenen Jahre, ein anderes die enthaltenen Quartale und ein weiteres die vertretene Monate listen (s.a. Rukaber WARU Lagerverwaltung).

Das Jahres-Klappfeld (ComboBox) mit dem Namen jahr kann das per SQL erledigen, indem in der Eigenschaft Datensatzherkunft (RowSource) folgendes SQL-Statement hinterlegt wird: SELECT DISTINCT Year(datum) FROM kassenbuch;

Die Felder quartal und monat sind anfangs noch leer bzw. disabled.

Nachdem der Anwender ein vorhandenes Jahr gewählt hat, können die Felder quartal und monat Werte für vorhandene Daten annehmen. Geschmackvoller Weise kann beim ersten Anzeigen der Datensätze der Wert für jahr gleich auf das aktuelle Jahr gesetzt werden, entsprechend muß die Routine jahr_AfterUpdate angestoßen werden.

Diese Sub-Prozedur leert und deaktiviert bei leerer Jahresangabe die Felder quartal und monat oder weist ihnen per SQL die für das angegebene Jahr vorhandenen Quartals- und Monatswerte zu.

Für die Quartalszahlen-Ermittlung wird hier die Visual-Basic-Funktion DatePart verwendet, die in Access 2.0 leider noch nicht zur Verfügung stand.

Private Sub jahr_AfterUpdate()

    On Error GoTo err_jahr_AfterUpdate

    Me!monat = Null
    Me!quartal = Null

    If IsNull(Me!jahr) Then
       Me!monat.RowSource = ""
       Me!monat.Enabled = False
       Me!quartal.RowSource = ""
       Me!quartal.Enabled = False
    Else
       Me!monat.RowSource = "SELECT DISTINCT Month(datum) FROM kassenbuch WHERE Year(datum) = " & Me!jahr & ";"
       Me!monat.Enabled = True
       Me!quartal.RowSource = "SELECT DISTINCT DatePart('q', datum) FROM kassenbuch WHERE Year(datum) = " & Me!jahr & ";"
       Me!quartal.Enabled = True
    End If

    Auswertungen

    Exit Sub


err_jahr_AfterUpdate:

    Fehler "jahr_AfterUpdate"
    Exit Sub

End Sub

Die Routine Auswertungen wertet die Angaben dieser Felder aus und bastelt diverse SQL-Statements zusammen, um z.B. Summen und ähnliches angeben zu können:

Private Sub Auswertungen()

    On Error GoTo err_Auswertungen

    Dim s As String, sJahr As String, sQuartal As String, sMonat As String, sRecordSource As String

    Const TABELLE = "kassenbuch"
    Const SQL_ORDER = " ORDER BY datum"

    ' **** SQL-Kriterien zusammenbasteln ****

    ' Jahr, Quartal und Monat auswerten

    If IsNull(Me!jahr) Then
       sJahr = ""
    Else
       sJahr = "Year(datum) = " & Me!jahr
    End If

    If IsNull(Me!quartal) Then
       sQuartal = ""
    Else
       sQuartal = "DatePart('q', datum) = " & Me!quartal
    End If

    If IsNull(Me!monat) Then
       sMonat = ""
    Else
       sMonat = "Month(datum) = " & Me!monat
    End If

    ' gültiges SQL-WHERE-Kriterium zusammenbasteln

    If sJahr <> "" And sQuartal <> "" Then
       s = sJahr & " AND " & sQuartal
    Else
       s = sJahr & sQuartal
    End If

    If sJahr <> "" And sMonat <> "" Then
       s = s & " AND " & sMonat
    Else
       s = s & sMonat
    End If

    ' **** Auswertungsfelder ansteuern ****

    If s = "" Then
       sRecordSource = "SELECT * FROM " & TABELLE & SQL_ORDER
    Else
       sRecordSource = "SELECT * FROM " & TABELLE & " WHERE " & s & SQL_ORDER
    End If
    
    Me!UF.Form.RecordSource = sRecordSource

    Me!von = DMin("datum", TABELLE, s)
    Me!bis = DMax("datum", TABELLE, s)

    Me!Summe_Einnahmen = DSum("einnahmen", TABELLE, s)
    Me!Summe_Ausgaben = DSum("ausgaben", TABELLE, s)

    Me!Bestand_Anfang = DLookup("bestand_vorher", TABELLE, "id = " & DMin("id", TABELLE, s))
    Me!Bestand_Ende = DLookup("bestand_nachher", TABELLE, "id = " & DMax("id", TABELLE, s))

    Exit Sub


err_Auswertungen:

    Fehler "Auswertungen"
    Exit Sub

End Sub

Damit es auf der grafischen Benutzeroberfläche (GUI) rund läuft, müssen die Steuerelemente sich gegenseitig halbwegs sinnvoll beeinflussen und sollten bei fehlerhafter oder quatschiger DAU-Bedienung nicht gerade zu kollabierenden Anwendungszuständen führen. Die Klappfelder sollten auf NurListeneinträge (LimitToList) = ja eingestellt sein, damit der Anwender nicht irgendeinen Mist eingeben und dann evtl. die Bildung der SQL-Statements aushebelt. Access97 verhält sich hier sehr wohlwollend: der Wert einer ComboBox kann (ohne Fehlermeldung) geleert werden und durch die Verzahnung der Routinen wird alles brav und korrekt aktualisiert. Unter Access 2.0 war das nach meinen Erfahrungen sehr restriktiv und hat schlichtweg einen Haufen Ärger bereitet.

Ein geänderter Quartalswert sollte das Monatsfeld sicherheitshalber leeren und ihm die Monats-Listenwerte zuweisen, die für das Jahr und das angegebene Quartal vorhanden sind:

Private Sub quartal_AfterUpdate()

    On Error Resume Next

    Me!monat = Null
    Me!monat.RowSource = "SELECT DISTINCT Month(datum) FROM kassenbuch WHERE Year(datum) = " & Me!jahr & " AND DatePart('q', datum) = " & Me!quartal & ";"

    Auswertungen

End Sub

Eine Änderung des Monatsfeldes ist dagegen eher unspektakulär:

Private Sub monat_AfterUpdate()

    On Error Resume Next

    Auswertungen

End Sub


Dauer berechnen   Quelle: dmt   Datum: 03.2004   nach oben

DATUM / ZEITDAUER berechnen:

Ein spätestens seit AOS bestehendes Problem ist das Berechnen der Dauer zweier Zeiteinträge, bei denen Mitternacht zwischen [von] und [bis] liegt.

Je nach gewünschtem Ausgabeformat sieht die entsprechende Formel dann doch ziemlich verschieden aus:

=Wenn([bis]>=[von];([bis]-[von])*24;(-1+[von]-[bis])*-24)

als 'deutscher' Ausdruck in der Eigenschaft Steuerelementinhalt eines Textfeldes, das als Standardzahl definiert ist und z.B. Werte wie 1,5 ausgibt, oder

IIF(Leistungsrapport.bis>=Leistungsrapport.von,Format((Leistungsrapport.bis-Leistungsrapport.von-Abzug),"hh:nn"),
Format(-24+Leistungsrapport.von-Leistungsrapport.bis+Abzug,"hh:nn"))

(
  Oder auch:
    IIF(bis

, das als englischer Ausdruck innerhalb einer SQL-Anweisung einen als Zeitausdruck formatierten String a'la '13:45: ausgibt.

Ein Wermutstropfen bleibt bestehen, wenn man diese Berechnungen nur anhand von Zeitwerten durchführt. Für z.B. von 01.03.1994 13:30 bis 01.03.1994 00:00 werden 10,5h ermittelt, was streng genommen falsch ist, da der zweite Zeitpunkt ja vor dem ersten liegt. Die 10,5h sind bei reiner Betrachtung der Stunden-Zeiten logisch, aber mit Betrachtung des Datums falsch. Zeitberechnungen mit Feldern, die ein richtiges Datum enthalten, können vereinfacht mit einem
=([bis]-[von])*24 durchgeführt werden; daß haut immer hin.

* * * *

Check_Laufzeit_Dauer () ist eine feine Routine, die die Anzahl von Monaten, die zwischen den angegebenen von-bis-Datumswerten liegen, berechnet.

Angestoßen kann sie z.B. in Feld_NachAktualisierung per '=Check_Laufzeit_Dauer()'.
Allerdings sollte dann noch ein Errorhandler eingebaut werden und die Funktion sollte vielleicht sicherheitshalber einen Variant zurückgeben, dann klappt's auch mit den Formular-Steuerelementen.

Private Function Check_Laufzeit_Dauer ()

    Dim iADiff As Integer, iMDiff As Integer

    iADiff = Year(Me!Laufzeit_bis) - Year(Me!Laufzeit_von)

    iMDiff = Month(Me!Laufzeit_bis) - Month(Me!Laufzeit_von)

    Me!Laufzeit_Dauer = (iADiff * 12) + iMDiff

End Function

Schön auch die folgende Routine:

Function RundeZeit (vDatum As Variant, iRundungsMinuten As Integer) As Double

    On Error GoTo err_RundeZeit

    Const MINUTENPROTAG = 1440

    Dim iMinuten As Integer
    Dim lngDatum As Long
    Dim dblDatum As Double, dblMinuten As Double

    If iRundungsMinuten = 0 Then
       Beep
       MsgBox "Ungültiger Parameter iRundungsMinuten=" & iRundungsMinuten & " !", 16, "RundeZeit"
       Exit Function
    End If

    dblDatum = vDatum                                   ' Fließkomma-Zeitwert
    lngDatum = dblDatum                                 ' ganzzahliger Tageswert

    dblMinuten = dblDatum - lngDatum                    ' Nachkomma-Anteil
    iMinuten = dblMinuten * MINUTENPROTAG               ' ganzzahlige Minuten

    iMinuten = iMinuten / iRundungsMinuten              ' durch Teilen und Multiplizieren mit Rundungsfaktor
    iMinuten = iMinuten * iRundungsMinuten              ' sowie Übergabe an Integer wird die Rundung realisiert.

    RundeZeit = lngDatum + (iMinuten / MINUTENPROTAG)   ' gerundetes Datum zusammenbauen

    Exit Function


err_RundeZeit:

    Fehler "RundeZeit"
    Exit Function

End Function

* * * *

Den jeweils ersten und letzten Tag eines Monates ermitteln:

Die Beispiel-Routine nimmt per Parameter 2 Referenzen mit Bezug auf Formular-Steuerelemente sowie den gewünschten Monat entgegen.

Vorausgesetzt wird das aktuelle Jahr.

Mit etwas Umstricken kann das auch an ein Parameter-Jahr angepaßt werden.

Sub Set_von_bis (Cvon As Control, Cbis As Control, Monat As Variant)

    Dim sTag As String, sMonat As String, sJahr As String, vDatum As Variant

    sTag = "01."

    sMonat = Monat

    If Len(sMonat) = 1 Then
       sMonat = "0" & sMonat
    End If

    If CInt(Monat) > CInt(Month(Date)) Then
       sJahr = "." & Year(Date) - 1
    Else
       sJahr = "." & Year(Date)
    End If

    Cvon = sTag & sMonat & sJahr

    Cbis = DateAdd("d", 31, Cvon)

    Do While Month(Cbis) <> Month(Cvon)
       Cbis = DateAdd("d", -1, Cbis)
    Loop

End Sub


default   Quelle: dmt   Datum: 03.2004   nach oben

STANDARDWERT / DEFAULTVALUE eines Datumfeldes:

Häufig wird für ein Datumsfeld der Standardwert '=DATUM()' im Entwurfsfenster der Tabelle oder des Formulares gewählt.

Gegeben sei der Fall, daß ein Mensch, der einer Arbeit nachgeht, eine Software benutzt, um sich selbige zu erleichtern (z.B. eine Termin-Software). Nach Mitternacht vom letzten Termin heimgekommen möchte er die Termine des vergangenen Tages eingeben. Nach dem Speichern des ersten Eintrages wäre es schön, wenn das Programm beim Anlegen eines neuen Termines nicht jedesmal stur das aktuelle Tagesdatum vorschlagen würde, sondern während einer Arbeitssitzung
das Datum des zuletzt angelegten Termines als Standardwert übernehmen würde.

Hier wird der Programmierer veranlaßt, im Formular-Ereignis AfterInsert der Feld-Eigenschaft DefaultValue des Datumsfeldes eine Zuweisung eines neuen Wertes vorzunehmen und muß mit Verwunderung feststellen, daß ALLE Versuche
fehlschlagen. Auch sämtliche Versuche, den zugewiesenen Wert, der selbst mit z.B. '01.01.1997' ein durchaus gültiger Datumswert ist, dem Datumsfeld zu entlocken, wird mit einem beharrlichem "Ungültiges Vorkommen von '!, ., ...' quittiert.

Rien ne vas plus, was so viel heißt wie 'die Pisse rennt wieder mal den Bach runter'.

Nach heftigem Nachdenken kam ich zu der Erkenntnis, daß die Eigenschaft Standardwert selbst nur einen String und keineswegs einen Datumswert enthält. Interessanterweise wird dieser String (z.B. 'Datum()') als Funktion ausgewertet
und deren Rückgabewert erfolgreich zugewiesen. Kombiniert man diese Erkenntnis mit den demutsvollen Verrenkungen der Übergabe von Datumssequenzen an SQL-Where-Klauseln, kann man (muß man aber nicht) zu folgender Lösung kommen:

Me!Datum_von.DefaultValue= "='" & Format$(Me!Datum_von, "dd.mm.yyyy") &"'"

Hier wird dem Feld 'Datum_von' des aktuellen Formulares ein neuer Standardwert zugewiesen. Der Wert lautet INKLUSIVE einfacher Anführungszeichen ='01.01.1997' und wird anstandslos akzeptiert.

Bei mehrmaligem Ändern zur Laufzeit geht's dann auf einmal gar nicht mehr !!
Trotz = NULL und Requery und Arsch und überhaupt !

Schwierig wird es mit einem per Code zusammengebautem Standardwert dann, wenn hartcodierte und variable Teile enthalten sein sollen:

    sDefaultValue = "'" & iPraefix & "' & Month(Date()) & '.' & Year(Date())"

Im Zweifelsfall geeignete Einstellungen im Entwurfsmodus quasideutsch vornehmen und per MsgBox im Code anzeigen lassen, da wird dann einiges klarer.


Eingabeformat   Quelle: dmt   Datum: 03.2004   nach oben

EINGABEFORMAT / DATUM:

Ein ebenfalls sehr schönes Detail ist die unglückliche Verquickung von Eingabeformat (zur Unterstützung des Anwenders, der in ein vorformatiertes Feld nur noch die Ziffernfolge gemäß dd.mm.jj eingeben muß) und dem späteren Editieren eines solchen Datums-Feldes, das den Wert dd.mm.jjjj angenommen hat. Wird auch nur eine einzige Ziffer ausgetauscht, wird das Aktualisieren des Datensatzes mit der Meldung 'ungültiges Datumsformat' quittiert. Erst wenn die ersten beiden Ziffern der Jahreszahl entfernt werden, wird der Feldinhalt fehlerfrei aktualisiert.

Das ständige Stolpern über das Standard-Datums-Eingabeformat '99.99.00', das eine Eingabe a'la '111196' zuläßt, dann auch korrekt '11.11.1996' anzeigt, aber beim Editieren den Wert '11111996' als ungültig moniert, kann mit dem
Eingabe-Format '99.99.9999' verhindert werden. Somit ist es möglich, sowohl '111196' als '11111996' einzugeben, anzuzeigen und zu editieren. Selbst Spar-Eingaben wie '01012' werden elegant als '01.01.1902' erkannt, da die '9'-Ziffer die evtl. Eingabe einer Ziffer bereitstellt, und die '.' für eine korrekte Eingabemaske sorgen, die standardmäßig mit '_' auch ganz gut kommt. Selbst sparsamste Eingaben a'la '011' werden mit dem Eingabeformat '99.99.9999;0;_' z.B. zu '01.01.1998' ergänzt. Das sollte hiermit erledigt sein.

Etwas restriktiver ist '00.00.0099;0;_', aber wenn '99.99.9999;0;_' das Problem geradezu omnipotent erschlägt, dann gut.

Für Jahr-2000-Kompatibilität empfiehlt sich das Eingabeformat '00.00.0000;0;_'; das schränkt zwar den Anwender bevormundend etwas ein, ist aber innerhalb der Grenzen von MS-Access für alle Jahrtausende ok.

* * * *

So wird auch der Versuch, dem Anwender die Eingabe gemäß 'mm.jj' zu ermöglichen ( wenn die Angabe 'Jan.1995' gewünscht wird ) nur von einem Phyrrus-Sieg gekrönt. Über das Eingabeformat "01".00.0000;0;_ wird als Literal der 'dd'-Teil
bereitgestellt, obwohl alle Angaben in der Tabelle auf 'mm.jj' stehen. So funktioniert wenigstens die Eingabe nach dem Schema 'mm.jj'. Wird ein solcher Feldinhalt später editiert, wird Eingabe 'mm.jj' klaglos akzeptiert.

In weniger glücklichen Fällen würde das bedeuten, daß sowohl für das Eingeben von Daten in ein bisher leeres Feld als auch für das Editieren eines bestehenden Feldinhaltes verschiedene Routinen entwickelt werden müssen !

* * * *

Neuester Stand zur 'Jan. 2000'-Thematik:

Feldformat:    'mmm. jjjj'
Eingabeformat: '"01."00.0000;0;_'

Dummerweise verhält sich der Cursor beim navigierenden Eintritt in ein solches Feld derartig Scheiße, daß zwar der korrekte Navigationsmodus erhalten bleibt, aber die erste getippte Taste (erste Monatsstelle) wird verschluckt !
Ein vorhergehendes <F2> würde die Sache ins richtige Lot bringen.

* * * *

Eine Kompromiß-'Lösung' für diese Scheiße bietet sich (Aufwand vertretbar) folgendes an:

Für das Steuerelement gilt:

Format = "m.jj"
Eingabeformat= "99.99.0000;0;_"

Das Format läßt Werte wie "5.1993" zu, soweit in Ordnung.
Das Eingabeformat zeigt die gewohnte Literalkette "__.__.__".

Jegliche Manipulationen am Eingabeformat-String waren ohne verwertbares Ergebnis.

Um den geneigten Anwender eine komfortable Eingabe eines Kurzdatums a'la 'mmjj' zu ermöglichen, wird für das Ereignis Beim Hingehen nachstehender Code ausgeführt:

Sub Datumsfeld_Enter ()

    If IsNull(Me!Datumsfeld) Then
       SendKeys "01"
    End If

End Sub

So wird ein defaultmäßiges "01" in die Literalkette geschrieben und der Anwender muß nur
noch Monat und Jahr gemäß "mmjj" eintippen.

Beim nachträglichen Editieren eines vorhandenen Wertes scheinen mit vertretbaren Aufwand realisierbare Lösungen nicht in Sichtweite zu liegen. Als fauler Kompromiß kann in der Statuszeile mit:

"Datumsfeld:  Neu-Eingabeformat 'mmjj', bei nachträglicher Änderung 'ttmmjj'"

darauf hingewiesen, daß bei Änderung eines bereits vorhandenen Wertes leider das vollständige Datumsformat berücksichtigt werden muß.

Beinahe schien es zu gelingen, über das Formular-Ereignis Bei Fehler den Fehler Steuerelementbezogen zu erkennen und abfangen zu können. Eine hier eingreifende Routine könnte bei der (nachträgliche Eingabe) fehlerhaften Eingabe "0595" künstlich einen gültigen Datumsstring zusammenbauen, doch leider kann dem Steuerelement, das den Fehler ausgelöst hat, kein neuer Wert, der gültig ist, zugewiesen werden, da dies einen erneuten Fehler auslöst. Fuck off !

Trotzdem scheint ein Licht am Ende des Tunnels, das nicht die Beleuchtung der Gates'schen Privat-Lokomotive zu sein scheint:

Im Ereignis 'Bei Taste ab' ist es möglich, auf die Eigenschaft SelLength des Steuerelementes zuzugreifen, bevor die Inhaltsänderung durch Drücken der Taste in Kraft tritt.

Sub Edit_Datum (C As Control)

    If C.SelLength = 10 Then
       C = Null
    End If

End Sub

wird aus Sub Steuerelement_KeyDown heraus aufgerufen, die erkennt, daß eine Taste für ein Datumsfeld gedrückt wurde. Das Format für dieses Feld ist Datum_kurz (01.01.1997).
Da ein nachträgliches Editieren eines solchen Feldes bisher IMMER Probleme bereitete ( die Eingabeformate klappen nur bei neuen Datensätzen, bei denen die Steuerelemente meist NULL enthalten ) sehe ich die letzte Möglichkeit darin, im
Ereignis Key_Down das Editieren eines vollmarkierten Feldes zu erkennen, dieses zu NULLEN und damit das an sich funktionierende Eingabeformat von Access zum Vorschein zu bringen. Eleganterweise wird die gedrückte Taste als Zeichen in das jetzt auftauchende Eingabeformat eingetragen, ohne Extra-Klimmzüge machen zu müssen.

Leider kann damit immer noch verhindert werden, das z.B. ein Hineinklicken in die Feld-Zeichen den kompletten formatlosen Inhalt des Feldes zu Vorschein bringt.


Eingabehilfen   Datum: 03.2004   nach oben

Im Rahmen einer Tastatur-gestützten Eingabe-Optimierung kann das folgende Utility Datums- und Zeitfelder per +/- Tasten die entsprechenden Werte als Tage oder Stunden verändern:

Im Steuerelement-Ereignis Bei Taste steht:

Sub Datum_bis_KeyPress (KeyAscii As Integer)

    KeyAscii = PlusMinusByKeys(KeyAscii)

End Sub

Eine zentrale Funktion verarbeit Datums- und Zeit-Felder:

Private Function PlusMinusByKeys (iKeyAscii As Integer) As Integer

    On Error GoTo err_PlusMinusByKeys

    Dim C As Control, iWert As Integer

    ' +/- Tastatur-Eingaben werden zur Erhöhung/Erniedrigung der Datumswerte benutzt

    Set C = Screen.ActiveControl

    If Not IsNumeric(C) Then Beep: MsgBox C.Name & " ist kein numerisches Feld.", 16, "PlusMinusByKeys": GoSub exit_PlusMinusByKeys
    If Not IsDate(C) Then Beep: MsgBox C.Name & " ist kein Datums- oder Zeit-Feld.", 16, "PlusMinusByKeys": GoSub exit_PlusMinusByKeys

    If iKeyAscii = 43 Then
       iWert = 1
    ElseIf iKeyAscii = 45 Then
       iWert = -1
    Else
       GoSub exit_PlusMinusByKeys   ' Alle anderen Tasten unverändert zulassen.
    End If

    C = C + (iWert * C.Tag / 24)    ' C.Tag ist ein Stunden-bezogener Faktor.

    PlusMinusByKeys = 0             ' Die Rückgabe setzt den KeyAscii-Handler immer auf 0.


exit_PlusMinusByKeys:

    Set C = Nothing
    Exit Function


err_PlusMinusByKeys:

    Fehler "PlusMinusByKeys"
    Resume exit_PlusMinusByKeys

End Function

* * * *

Das kann auch weniger elegant übr Schaltflächen gelöst werden:

Das geht z.B. per Schaltfläche (Mausbedienung), die beim Ereignis "Klicken"

     PlusMinus cPLUS

ausführt, während bei "Maustaste auf"

    If Button = LEFT_BUTTON Then
       PlusMinus cCANCEL
    End If

nötig ist.

und hier die Prozedur PlusMinus:

Private Sub PlusMinus (iModus As Integer)

    On Error GoTo err_PlusMinus

    ' **** Fokus erst wieder abgeben, wenn iModus=cCANCEL ****
    
    If iModus <> cCANCEL Then
       Set cPreviousControl = Screen.PreviousControl
    Else
       Set_Singles
       cPreviousControl.SetFocus
       Exit Sub
    End If

    ' **** manipuliert werden Datumsfelder mit Zahlen-  ****
    ' **** Tags, da hier leider auch Zeitfelder Datums- ****
    ' **** format besitzen.                             ****
    
    If IsDate(cPreviousControl) And IsNumeric(cPreviousControl) Then
       cPreviousControl = cPreviousControl + (iModus * cPreviousControl.Tag / 24)
       Me.Repaint
    End If
    
    Exit Sub


err_PlusMinus:

    ' Bei 'Objekt hat keinen Wert' keine Meldung (z.B. PreviousControl=Schaltfläche)
    
    If Err <> 2427 Then
       Fehler "PlusMinus"
       Exit Sub
    Else
       Exit Sub
    End If

End Sub


Funktionen   Quelle: dmt   Datum: 12.2006   nach oben

DATUMSFUNKTIONEN:

Das Kalender-OLE-Objekt wurde von mir im Formular Kalender-dmt.mdb um eine Kalender-Wochen-Zahl-Anzeige erweitert, die auch die Eigenarten (Darstellungsbugs) des Access-Controls abfängt. Das Zusammenspiel der folgenden Funktionen kann dort bewundert werden. (Funktionen teilweise zusammengefasst in einem Modul Datumsfunktionen z.B. in bestatt.mdb; siehe auch ZEITFUNKTIONEN)

Da unter Win 9.x häufige Schutzverletzungen zu beklagen waren, wurde das Kalender-Utility komplett neu in bravem BASIC geschrieben, siehe ebenfalls dmt.mdb.

In diesem Zusammenhang mußte ich mich näher mit der Kalender-Problematik befassen.

Kurz zusammengefasst (DIN 1355 von 1974, in Kraft getreten am 01.01.1976):

Der Montag ist der erste Tag der Woche
Der 4. Januar liegt immer in der ersten Kalenderwoche.
Der 28. Dezember liegt immer in der letzten Kalenderwoche.

Weitere Informationen zur allgemeinen Kalender-Problematik in info/kalender.txt

* * * *

WEEKDAY:

Die eingebaute Basic-Funktion weekday liefert nur dann den in Deutschland ab dem 10.10.1976 richtigen Wert, wenn ein zweiter Parameter, die Funktion zwingt, als Berechnungsgrundlage auf die Access-Einstellungen (Menü Ansicht/Optionen) zurück zu greifen.

weekday(date,0) liefert dann ein einstellungsbezogenes Ergebnis.

Ohne zweiten Parametern läuft das Ganze dann immer amerikanisch mit Sonntag=1 !

* * * *

DATEPART:

Eine an sich ziemlich mächtige Funktion im Zusammenspiel mit Datumsangaben.
Leider stolpern wir auch hier über die deutschnationale (mittlerweile europäische) Eigenarten zum Thema erster Tag der Woche und erste Woche des Jahres.

Datepart kann hierzu zwei Parameter entgegennehmen, von denen aber nur der zweite die Einstellungen im Menü Ansicht/Optionen-Modul auslesen kann.

Also den Quatsch dann gleich explizit angeben:

datepart (Intervall-String, Datumswert, 2, 2)

zwingt datepart, seine Ergebnisse gemäß "Montag ist 1. Tag der Woche" und "die erste Woche des Jahres ist die, die mind. 4 Tage enthält" anzuzeigen.

* * * *

WEEK:

Eine in Access schmerzlich vermißte Funktion ist week (Open-Access), die jedoch per DatePart nachgebildet werden konnte.

Function Week (vDate As Variant) As Integer

    If IsDate(vDate) Then
       Week = DatePart("ww", vDate, 2, 2)
    Else
       Beep
       MsgBox "Ungültiger Übergabewert vDate='" & vDate & "' !", 16, "Week"
    End If

End Function

In manchen Umgebungen kann es vorkommen, daß die 4-Tages-Regel für die KW 1 nicht richtig erkannt wird (habe ich mindestens einmal im Hause SEL mit Access 2 erlebt, Office 97 war ok, bei mir zuhause war es aber auch mit
Access 2 in Ordnung, seltsam).

Für solche Fälle konnte diese Version helfen:

Function Week (vDate As Variant) As Integer

    Dim iKw As Integer, v As Variant

    If IsDate(vDate) Then
       iKw = DatePart("ww", vDate, 2, 2)
    Else
       Beep
       MsgBox "Ungültiger Übergabewert vDate='" & vDate & "' !", 16, "Week"
       Exit Function
    End If

    If iKw = 53 Then
       ' in Access 2.0 wird die 4-Tages-Regel für die KW 1 nicht richtig erkannt
       v = vDate
       Do
          v = v + 1
       Loop While Year(v) = Year(vDate)
       If DatePart("w", v, 2, 2) <= 4 Then
          iKw = 1
       End If
    End If

    Week = iKw

End Function

* * * *

Bei zum wiederholten Male auftretenden Datums-Berechnungs-Problemen werden jetzt endlich diverse Routinen zur Vereinfachung der Problematik erstellt.

LASTDAYOFMONTH gibt den letzten Tag des Monats des übergebenen Datums als Variant vom Typ Datum zurück.

Function LastDayOfMonth (vDate As Variant) As Variant

    Dim iNextMonth As Integer, iYear As Integer, v As Variant

    If IsDate(vDate) Then

       iYear = Year(vDate)
       iNextMonth = Month(vDate) + 1

       If iNextMonth = 13 Then
          iYear = iYear + 1
          iNextMonth = 1
       End If

       v = CVDate("01." & iNextMonth & "." & iYear)
       v = v - 1

       LastDayOfMonth = v

    Else

       Beep
       MsgBox "Ungültiger Übergabewert vDate='" & vDate & "' !", 16,
"LastDayOfMonth"

    End If

End Function

* * * *

Im Zusammenspiel mit der oben genannten Funktion ergibt sich die Berechnung der Anzahl der Tage eines Monats als eine einfache Sache (von wegen Kapselung und so):

Function DaysPerMonth (vDate As Variant) As Integer

    If IsDate(vDate) Then
       DaysPerMonth = Day(LastDayOfMonth(vDate))
    Else
       Beep
       MsgBox "Ungültiger Übergabewert vDate='" & vDate & "' !", 16, "DaysPerMonth"
    End If

End Function

* * * *

Entsprechend simpel ergibt sich das Berechnen des ersten Tages eines Monats:

Function FirstDayOfMonth (vDate as Variant) As Variant

    If IsDate(vDate) Then
       FirstDayOfMonth = CVDate("01." & Month(vDate) & "." & Year(vDate))
    Else
       Beep
       MsgBox "Ungültiger Übergabewert vDate='" & vDate & "' !", 16, "FirstDayOfMonth"
    End If

End Function

* * * *

Ärger machen Datums-Geschichten auch im Zusammenhang mit SQL, siehe dazu auch die Funktionen Date_US (), Date_US_SQL () und Date_exact_US_SQL ().


Gleichheit   Quelle: dmt   Datum: 03.2004   nach oben

Sind 2 Datumsangaben GLEICH ?

Eine weitere Häme dieser Dreckssoftware besteht darin, bei einem Vergleich zwischen

einem Datum  '17.01.1997 20:00:00'
und          '17.01.1997 20:00:00'

, die in einer Tabelle gespeicherte Datumswerte sind, manchmal doch glatt zu behaupten, daß diese beiden Angaben verschieden wären. Ein Darstellen der Werte per CDbl ergab einen Unterschied von 2 Hundert-Millionstel in dezimaler Schreibweise, was einer Zeitdifferenz von 2 Tausendstel Sekunden entspricht.

Soll ich jetzt lachen oder was ?

Um festzustellen, ob zwei Datumsangaben gleich sind, könnten nacheinander die Datums-Teileigenschaften Day, Month, Year, Hour, Minute und Second per AND miteinander ver-glichen werden, oder aber die wunderschöne Anweisung

If DateDiff("s", von, bis) = 0

bemüht werden, die uns verrät, daß die Anzahl der verstrichenen Sekunden zwischen dem Datum 'von' und 'bis' 0 Sekunden beträgt, was mir als Menschen sagt, daß diese beiden Angaben gleich sind. Ich bin tief beeindruckt.


Jahr 2000   Quelle: dmt   Datum: 03.2004   nach oben

Das Jahr-2000-Problem / Y2k:

Laut Microsoft hat MS-Access nur ein y2k-Problem:

Die automatische Ergänzung eingegebener Datumswerte mit 2-stelliger Jahreszahl-Angabe, abhängig von gewähltem Format sowie Eingabeformat, führt unglücklicherweise zur festverdrahteten Ergänzung mit '19', anstatt die ersten beiden Ziffern eines 4-stelligen System-oder Windows-Datums oder was auch immer auszulesen und für die Ergänzung zu verwenden.

Ein durchaus motivierter Versuch der Entwicklung einer eigenen Routine wurde beigelegt, da abhängig vom gewählten Format sowie Eingabeformat Access dem Programmierer bereits vor Eintreten des Ereignisses VorAktualisierung ins Handwerk pfuscht und entsprechende Ergänzungen vornimmt. Es müßten eine Reihe von Contra-Programmierungen realisiert werden, die alle Eventualitäten evtl. eingestellter Formate und Eingabeformate abfangen, um feststellen zu können, ob eine 4-stellige Jahreszahl vom Anwender eingegeben wurde, oder von Access falsch ergänzt wurde.

Ich habe mich ungewöhnlicherweise entschieden, einer Microsoft-Empfehlung zu folgen, und das entsprechende Workaround zu verwenden:

Eingabeformat '00.00.0000;0;_'

Der Anwender muß alles eingeben und es gibt keine Probleme !!!


Zeit   Quelle: dmt   Datum: 03.2004   nach oben

ZEIT:

Dummerweise kann MS-Access zwischen dem Standardwert 00:00 und einer beabsichtigten Zeitangabe für Mitternacht ( 24:00 wird leider nicht akzeptiert ) unterscheiden. Geht ein Termin bis Mitternacht = 00:00, so wird dies z.B. von einer Routine, die die Zeitdauer berechnen soll, richtig erkannt. Bei Prüfungen, die z.B. innerhalb von Überschneidungs-Checkroutinen vorgenommen werden, wird 00:00 immer als kleinst möglicher Zeitwert angenommen. Einziger Ausweg aus dem Dilemma ist ein explizites Abfangen in den Check-Routinen.

Aber das ist ja leider nichts Neues.

Wenn Datum oder Zeit angezeigt und aktualisiert werden sollen, so kann das z.B. wie folgt geschehen:

FName = Syscmd(syscmd_setstatus,Format$(Time,>>HH:MM) & >> >> Format$(Date,>>DD.MM.YYYY"))

Über den Zeitgeber eines Formulars kann die Anzeige aktualisiert werden.

nach oben
zur Startseite dieses Webangebotes zur infobase-Hauptseite   xhtml1.0