Python CLI Tool: File Timestamp Modifier

Mit diesem Python-Skript kannst du das Änderungsdatum und die Uhrzeit eines Ordners sowie aller darin enthaltenen Dateien und Unterordner rekursiv anpassen. Unter Windows lässt sich optional auch die Erstellungszeit umschreiben.





#!/usr/bin/env python3
# *****************************************************************************
# Grafische Oberfläche (PyQt6) zum Ändern von Datum und Uhrzeit
# eines Ordners und aller darin enthaltenen Dateien und Unterordner.
# Installation der Abhängigkeit: pip install PyQt6
# Start: python datum_aendern_gui.py
# (c) Hans Busche Datum: 03.07.2026
# *****************************************************************************
import os
import sys
import time
import platform
from datetime import datetime

from PyQt6.QtCore import Qt, QThread, pyqtSignal, QDateTime
from PyQt6.QtWidgets import (
    QApplication,
    QMainWindow,
    QWidget,
    QVBoxLayout,
    QHBoxLayout,
    QLabel,
    QLineEdit,
    QPushButton,
    QFileDialog,
    QDateTimeEdit,
    QCheckBox,
    QTextEdit,
    QProgressBar,
    QMessageBox,
)

# *****************************************************************************
# Logik zum Setzen der Zeitstempel.
# Setzt unter Windows zusätzlich die Erstellungszeit (benötigt pywin32). 
# *****************************************************************************
def set_creation_time_windows(pfad: str, ziel_zeit: datetime):
    if platform.system() != "Windows":
        return
    try:
        import pywintypes
        import win32file
        import win32con
        wintime = pywintypes.Time(ziel_zeit)
        flags = win32con.FILE_FLAG_BACKUP_SEMANTICS if os.path.isdir(pfad) else 0
        handle = win32file.CreateFile(
            pfad,
            win32con.GENERIC_WRITE,
            0,
            None,
            win32con.OPEN_EXISTING,
            flags,
            None,
        )
        win32file.SetFileTime(handle, wintime, None, None)
        handle.close()
    except ImportError:
        pass
    except Exception:
        pass

# *****************************************************************************
# Hilfsfunktion für "set_creation_time_windows"
# *****************************************************************************
def setze_zeit(pfad: str, timestamp: float, ziel_zeit: datetime, mit_erstellungszeit: bool):
    os.utime(pfad, (timestamp, timestamp))
    if mit_erstellungszeit:
        set_creation_time_windows(pfad, ziel_zeit)

# *****************************************************************************
# Thread! Damit die GUI während der Verarbeitung nicht einfriert.
# Wenn ein Ordner sehr viele Dateien enthält, würde das Ändern aller Zeiten ein 
# paar Sekunden dauern
# *****************************************************************************
class Worker(QThread):
    fortschritt = pyqtSignal(int, int)   # aktuell, gesamt
    log = pyqtSignal(str)
    fertig = pyqtSignal(int, int, list)  # anzahl_dateien, anzahl_ordner, fehler

    def __init__(self, ordner: str, ziel_zeit: datetime, mit_erstellungszeit: bool):
        super().__init__()
        self.ordner = ordner
        self.ziel_zeit = ziel_zeit
        self.mit_erstellungszeit = mit_erstellungszeit

    def run(self):
        timestamp = time.mktime(self.ziel_zeit.timetuple())
        fehler = []
        # Erst alle Pfade sammeln, damit die Fortschrittsanzeige stimmt
        alle_pfade = []
        for wurzel, unterordner, dateien in os.walk(self.ordner, topdown=False):
            for datei in dateien:
                alle_pfade.append(os.path.join(wurzel, datei))
            for sub in unterordner:
                alle_pfade.append(os.path.join(wurzel, sub))
        alle_pfade.append(self.ordner)
        gesamt = len(alle_pfade)
        anzahl_dateien = 0
        anzahl_ordner = 0
        for i, pfad in enumerate(alle_pfade, start=1):
            try:
                setze_zeit(pfad, timestamp, self.ziel_zeit, self.mit_erstellungszeit)
                if os.path.isdir(pfad):
                    anzahl_ordner += 1
                    self.log.emit(f"Ordner aktualisiert: {pfad}")
                else:
                    anzahl_dateien += 1
                    self.log.emit(f"Datei aktualisiert:  {pfad}")
            except Exception as e:
                fehler.append(f"{pfad}: {e}")
                self.log.emit(f"FEHLER bei '{pfad}': {e}")
            self.fortschritt.emit(i, gesamt)
        self.fertig.emit(anzahl_dateien, anzahl_ordner, fehler)

# *****************************************************************************
# Fenster! PyQt6-spezifisch.
# *****************************************************************************
class HauptFenster(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Ordner, Datei(en), Unterordner: Datum & Uhrzeit ändern")
        self.setMinimumWidth(600)
        self.worker = None
        zentral = QWidget()
        self.setCentralWidget(zentral)
        layout = QVBoxLayout(zentral)
        # --- Ordnerauswahl ---
        ordner_layout = QHBoxLayout()
        ordner_layout.addWidget(QLabel("Ordner:"))
        self.ordner_feld = QLineEdit()
        self.ordner_feld.setPlaceholderText("Ordner auswählen …")
        ordner_layout.addWidget(self.ordner_feld)
        durchsuchen_btn = QPushButton("Durchsuchen …")
        durchsuchen_btn.clicked.connect(self.ordner_waehlen)
        durchsuchen_btn.setStyleSheet(
            """
            QPushButton {
                background-color: #b8b55c;
                color: white;
                font-size: 15px;
                font-weight: bold;
                border-radius: 6px;
                padding: 8px 16px;
            }
            QPushButton:hover {
                background-color: #4f4d0b;
            }
            QPushButton:pressed {
                background-color: #1b5e20;
            }
            QPushButton:disabled {
                background-color: #a5d6a7;
                color: #eeeeee;
            }
            """
        )
        ordner_layout.addWidget(durchsuchen_btn)
        layout.addLayout(ordner_layout)
        # --- Datum/Uhrzeit-Auswahl ---
        datum_layout = QHBoxLayout()
        datum_layout.addWidget(QLabel("Ziel-Datum/Uhrzeit:"))
        self.datum_feld = QDateTimeEdit()
        self.datum_feld.setCalendarPopup(True)
        self.datum_feld.setDisplayFormat("dd.MM.yyyy HH:mm:ss")
        self.datum_feld.setDateTime(QDateTime.currentDateTime())
        datum_layout.addWidget(self.datum_feld)
        layout.addLayout(datum_layout)
        # --- Optionen ---
        self.erstellungszeit_checkbox = QCheckBox(
            "Erstellungszeit ebenfalls setzen (nur Windows, benötigt pywin32)"
        )
        if platform.system() != "Windows":
            self.erstellungszeit_checkbox.setEnabled(False)
            self.erstellungszeit_checkbox.setToolTip(
                "Nur unter Windows verfügbar."
            )
        layout.addWidget(self.erstellungszeit_checkbox)
        # --- Start-Button ---
        self.start_btn = QPushButton("Datum/Uhrzeit setzen")
        self.start_btn.clicked.connect(self.starten)
        self.start_btn.setCursor(Qt.CursorShape.PointingHandCursor)
        self.start_btn.setMinimumHeight(45)
        self.start_btn.setStyleSheet(
            """
            QPushButton {
                background-color: #2e7d32;
                color: white;
                font-size: 15px;
                font-weight: bold;
                border-radius: 6px;
                padding: 8px 16px;
            }
            QPushButton:hover {
                background-color: #388e3c;
            }
            QPushButton:pressed {
                background-color: #1b5e20;
            }
            QPushButton:disabled {
                background-color: #a5d6a7;
                color: #eeeeee;
            }
            """
        )
        layout.addWidget(self.start_btn)
        # --- Fortschrittsbalken ---
        self.fortschritt_balken = QProgressBar()
        layout.addWidget(self.fortschritt_balken)
        # --- Log-Ausgabe ---
        layout.addWidget(QLabel("Protokoll:"))
        self.log_ausgabe = QTextEdit()
        self.log_ausgabe.setReadOnly(True)
        layout.addWidget(self.log_ausgabe)

    def ordner_waehlen(self):
        ordner = QFileDialog.getExistingDirectory(self, "Ordner auswählen")
        if ordner:
            self.ordner_feld.setText(ordner)

    def starten(self):
        ordner = self.ordner_feld.text().strip()
        if not ordner or not os.path.isdir(ordner):
            QMessageBox.warning(self, "Fehler", "Bitte einen gültigen Ordner auswählen.")
            return
        qdatetime = self.datum_feld.dateTime()
        ziel_zeit = datetime(
            qdatetime.date().year(),
            qdatetime.date().month(),
            qdatetime.date().day(),
            qdatetime.time().hour(),
            qdatetime.time().minute(),
            qdatetime.time().second(),
        )
        antwort = QMessageBox.question(
            self,
            "Bestätigung",
            f"Datum/Uhrzeit von allen Dateien und Unterordnern in\n\n"
            f"'{ordner}'\n\n"
            f"wird auf {ziel_zeit.strftime('%d.%m.%Y %H:%M:%S')} gesetzt.\n\n"
            f"Fortfahren?",
            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
        )
        if antwort != QMessageBox.StandardButton.Yes:
            return
        self.log_ausgabe.clear()
        self.fortschritt_balken.setValue(0)
        self.start_btn.setEnabled(False)
        mit_erstellungszeit = self.erstellungszeit_checkbox.isChecked()
        self.worker = Worker(ordner, ziel_zeit, mit_erstellungszeit)
        self.worker.fortschritt.connect(self.fortschritt_aktualisieren)
        self.worker.log.connect(self.log_ausgabe.append)
        self.worker.fertig.connect(self.verarbeitung_fertig)
        self.worker.start()

    def fortschritt_aktualisieren(self, aktuell: int, gesamt: int):
        if gesamt > 0:
            self.fortschritt_balken.setMaximum(gesamt)
            self.fortschritt_balken.setValue(aktuell)

    def verarbeitung_fertig(self, anzahl_dateien: int, anzahl_ordner: int, fehler: list):
        self.start_btn.setEnabled(True)
        meldung = f"Fertig!\n{anzahl_dateien} Datei(en) und {anzahl_ordner} Ordner aktualisiert."
        if fehler:
            meldung += f"\n\n{len(fehler)} Fehler aufgetreten (siehe Protokoll)."
        self.log_ausgabe.append("\n" + meldung)
        QMessageBox.information(self, "Abgeschlossen", meldung)

# *****************************************************************************
# Main
# *****************************************************************************
def main():
    app = QApplication(sys.argv)
    fenster = HauptFenster()
    fenster.show()
    sys.exit(app.exec())

if __name__ == "__main__":
    main()


        
🐍 Skript herunterladen (.py)