Datum in String finden

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Benutzeravatar
Silvernerd1
User
Beiträge: 4
Registriert: Dienstag 14. Mai 2024, 08:07

Hallo Python-Experten,

da das mein 1. Posting hier ist, sage ich erst einmal "Hallo und guten Tag und sorry für dumme Newbie-Fragen".

Ich werte mit Python eine Text-Datei aus und möchte herausbekommen, ob irgendwo in dem aktuellen Datensatz ein Datum im Format "tt.mm.jjjj" steht. "Irgendwas vorher Di, 14.05.2024 irgendetwas nachher ..." sollte also gefunden werden.

Da ich mit Python noch ziemlich am Anfang bin, würde ich eine sperrige Konstruktion wie

Code: Alles auswählen

if (myRecord.find(".01.") or myRecord.find(".02.") ....) :
hingekommen. Ich bin mir allerdings sicher, dass man das wesentlich eleganter hinbekommt.

Hätte jemand von Euch einen Tipp für mich?

Vielen Dank im Voraus
Michael
Benutzeravatar
noisefloor
User
Beiträge: 3939
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

... und willkommen im Forum :-)

Wenn man nach etwas komplexeren Zeichenketten sucht bieten sich immer reguläre Ausdrücke an. Kann Python natürlich auch, dafür gibt es das re Modul. Und in der Python-Doku gibt es auch ein HowTo dazu.

Gruß, noisefloor
Sirius3
User
Beiträge: 18051
Registriert: Sonntag 21. Oktober 2012, 17:20

Ob ein Teilstring innerhalb eines Strings vorkommt prüft man mit `in`. Variablennamen schreibt man in Python komplett klein. Präfixe wie my tragen nicht zum Verständnis bei und können weg.
Also:

Code: Alles auswählen

if ".01." in record or ".02." in record:
Wenn man das aber für alle Datumsangaben machen möchte, wird die Liste ziemlich lang. Besser ist es, nach dem Muster zu suchen. Dafür sind reguläre Ausdrücke da:

Code: Alles auswählen

match = re.search(r"\d{2}\.\d{2}\.\d{4}", record)
if match: ...
Benutzeravatar
DeaD_EyE
User
Beiträge: 1121
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

ohne regex:

Code: Alles auswählen

record = ""
patterns = (".01.", ".02.")

if any(pattern in record for pattern in patterns):
    print("Muster gefunden")
any() ist wie or. Beim ersten True wird der Rest nicht mehr ausgeführt und ein True zurückgegeben. D.h. wenn er nichts findet, dauert es am längsten.
all() ist wie and. Alle Bedingungen müssen True sein. Beim ersten False wird ein False zurückgegeben.

Die Regex-Variante sollte das Problem besser lösen. Vor allem dann, wenn z.B. Muster von .01. bis .99. entsprechen soll. So muss der gesamte Text nur einmal verarbeitet werden. Aber vorsichtig, Regex ist nicht die Lösung für alle Probleme.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Sirius3
User
Beiträge: 18051
Registriert: Sonntag 21. Oktober 2012, 17:20

Ja, doppelseitiges Klebeband ist die Lösung für alle Probleme.
Benutzeravatar
Silvernerd1
User
Beiträge: 4
Registriert: Dienstag 14. Mai 2024, 08:07

Vielen Dank für Euren großartigen Support. Dank Eurer Hilfe habe ich es hinbekommen.

Bfn
Michael
Benutzeravatar
noisefloor
User
Beiträge: 3939
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Das ist doch gut.

Um zukünftige Hilfesuchende zu unterstützen, die vielleicht das gleiche oder ein ähnliches Problem haben wie du, wäre es sehr schön, wenn du deine für dich funktionierende Lösung hier noch posten würdest. Zumindest den relevanten Teil.

Gruß, noisefloor
Benutzeravatar
__blackjack__
User
Beiträge: 13533
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Sirius3: McGyver sagt einseitiges Klebeband reicht — daraus bastelt man sich dann Doppelseitiges. 😉

Code: Alles auswählen

- (void)countSheep {
    unsigned int sheep = 0;
    while ( ! [self isAsleep]) { ++sheep; }
}
Benutzeravatar
Silvernerd1
User
Beiträge: 4
Registriert: Dienstag 14. Mai 2024, 08:07

noisefloor hat geschrieben: Mittwoch 15. Mai 2024, 10:50 ... wäre es sehr schön, wenn du deine für dich funktionierende Lösung hier noch posten würdest. Zumindest den relevanten Teil.
Sehr gerne.

Zunächst hatte ich den Vorschlag mit der Monatsliste aufgenommen

Code: Alles auswählen

    monate = (".01.", ".02.",".03.",".04.",".05.",".06.",".07.",".08.",".09.",".10.",".11.",".12.")
    if any(monat in curLine for monat in monate):
        print("Muster gefunden")
Damit hatte ich zwar den Record identifiziert, in dem ein Monat vorkam, musste aber immer noch herausbekommen, wo in dem Record der Text mit dem Datum stand. Zwei Fliegen mit einer Klappe schlägt der Einsatz von 'fnmatch'. Damit kann man einen String mit Wildcards durchsuchen und kann anschließend direkt auf die Fundstelle zugreifen.

Code: Alles auswählen

import fnmatch
...
def DatumErmitteln(curLine) :
    lineList=curLine.split()
    filtered = fnmatch.filter(lineList, '??.??.????')
    if (len(filtered) > 0) :
        deDate = filtered[0]    # hier steht z.B. "15.05.2024"
    else:
        deDate = ""
    return deDate
...
    with open(fileinName, "r", encoding="UTF-8") as filein:
        while line := filein.readline():
            tmpDate = DatumErmitteln(line)
            if (tmpDate != '') :
                curDate = tmpDate
            fileout.writelines(curDate + " " +line)
Bfn
Michael
Sirius3
User
Beiträge: 18051
Registriert: Sonntag 21. Oktober 2012, 17:20

Variablennamen schreibt man komplett klein, Funktionsnamen ebenso.
Die Klammern um die if-Bedingungen sind überflüssig und können weg.
Benutze keine kryptischen Abkürzungen. Was ist eine "Datei im Namen"?
Wenn man soetwas wie "nicht gefunden" zurückgeben will, benutzt man None und keinen leeren String.
Strings stückelt man nicht mit + zusammen, sondern benutzt f-Strings.
Die Mischung von Deutsch und Englisch macht das Lesen insgesamt schwieriger.
Datei-Objekte sind Iteratoren. Eine for-Schleife wäre also viel Lesbarer als Deine while-Schleife.
curDate ist im zweifel nicht definiert. `writelines` dazu zu benutzen, um eine Zeile Zeichenweise zu schreiben, ist quatsch.

Das ganze sähe dann so aus:

Code: Alles auswählen

def extract_date(line):
    filtered_parts = fnmatch.filter(line.split(), '??.??.????')
    if filtered_parts:
        # take the first one
        return filtered_parts[0]
    # date not found
    return None

...

    current_date = "???"
    with open(input_filename, "r", encoding="UTF-8") as lines:
        for line in lines:
            new_date = extract_date(line)
            if new_date:
                current_date = new_date
            output_file.write(f"{current_date} {line}")
Das prüft aber nicht, ob die Fragezeichen auch wirklich ein Datum bilden.

Das ganze mit regulärem Ausdruck:

Code: Alles auswählen

def extract_date(line):
    match = re.search(r"[0123]\d\.[01]\d\.\d{4}", line)
    return match.group(0) if match else None
Prüft zwar auch nicht auf ein korrektes Datum, zumindest aber, dass es sich um Ziffern handelt.
Benutzeravatar
__blackjack__
User
Beiträge: 13533
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Bei der Lösung mit dem regulären Ausdruck verliert man so wie er im Moment ist aber das davor und danach entweder Whitespace oder Anfang oder Ende der Zeichenkette sein muss. Das bekommt man bei der anderen Lösung durch das `split()`.

Edit:

Code: Alles auswählen

import re
from datetime import datetime as DateTime

from more_itertools import first, map_except


def extract_date(line):
    return first(
        map_except(
            lambda match: DateTime.strptime(match.group(0), "%d.%m.%Y"),
            re.finditer(r"\b[0123]\d\.[01]\d\.\d{4}\b", line),
            ValueError,
        ),
        None,
    )

Code: Alles auswählen

- (void)countSheep {
    unsigned int sheep = 0;
    while ( ! [self isAsleep]) { ++sheep; }
}
Benutzeravatar
DeaD_EyE
User
Beiträge: 1121
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Code: Alles auswählen

import datetime
import re


def parse_dates(text):
    date_format=r"(?P<day>\d{2})\.(?P<month>\d{2})\.(?P<year>\d{4})"
    # über Treffer iterieren
    
    for match in re.finditer(date_format, text):
        # str in int umwandeln
        dt = {k: int(v) for k, v in match.groupdict().items()}
        try:
            # date objekt mit dem dict erstellen
            yield datetime.date(**dt)
        except ValueError:
            print("Datum ist ungültig:", dt)
            continue


text = " sölfhasödfh 15.05.2024            sdfasfd  \n\rasdfkjhadlfg 29.02.2023"
for dt in parse_dates(text):
    print(dt)
Der erste Teil sucht nur Treffer mit xx.xx.xxxx wobei x eine Zahl von 0 bis 9 sein kann.

Der zweite Teil wandelt der Werte von str nach int um und übergibt es datetime.date mittels kwargs.
Da beim Regex die Namen angegeben sind, kann man groupdict() verwenden. Ich habe sie entsprechend den Argumenten von datetime.date vergeben.

Achtung: Wenn die Namen im Regex fehlen, wird ein leeres dict ausgegeben. Das wäre dann so, als würde man datetime.date() ohne Argumente aufrufen => TypeError.

Bei einem illegalen Datum wird ein ValueError ausgelöst. Diesen kann man abfangen und überspringen oder ggf. eine Meldung ausgeben.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Benutzeravatar
Silvernerd1
User
Beiträge: 4
Registriert: Dienstag 14. Mai 2024, 08:07

Vielen Dank für Eure Tipps. Ein Python-Newbie hat viel gelernt.

Bfn
Michael
Antworten