infobase: EDV - MS-Access
Datum & Zeit
allgemein
Quelle: dmt
Datum: 03.2004
- 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
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
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):
-
Die Angabe von Datumswerten muß in amerikanischer SQL-Schreibweise erfolgen.
Siehe dazu auch die Funktionen Date_US (), Date_US_SQL () und Date_exact_US_SQL ().
-
Und dann war da noch die an sich peinliche Frage, wie die Microsoft-Programmierer wohl den Begriff "between"
auffassen. Und siehe da, sie rauchten Joints, klickten und drückten viele Ok-Buttons und es ward Scheiße ...
Bei "BETWEEN #09/01/1997# AND #09/30/1997#" könnte man sich fragen, ob Datensätze vom 01.09.1997 wie auch dem 30.09.1997 in den Abfrage-Ergebnissen enthalten sind oder nicht, je nach dem, ob Between die angegebenen Parameter exklusiv oder inklusiv verarbeitet.
Die Antwortet lautet: weder noch !
Die Säcke brachten es mind. bei Access 2.0 doch glatt fertig, den ersten Parameter inklusiv und den zweiten exclusiv zu implementieren.
Sprich: #09/01/1997# als erster Parameter findet sehr wohl Datensätze vom 01.09.1997.
#09/30/1997# als zweiter Parameter schließt aber alle Datensätze vom 30.09.1997 aus !
Um diese Daten zu erhalten, muß der Datumswert des zweiten Parameters um einen Tag in die Zukunft verschoben werden.
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
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
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
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
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
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
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
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
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
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
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.