Arbeit mit Datumszeitwerten

Der mssql-python-Treiber verarbeitet die Datums- und Uhrzeiten von SQL Server und ordnet sie den Modulobjekten von Python datetime zu. SQL Server bietet mehrere Datums- und Uhrzeittypen mit unterschiedlicher Präzision und Fähigkeit:

SQL-Typ Python-Typ Bereich Präzision
date datetime.date 0001-01-01 an 9999-12-31 Ein Tag
Zeit datetime.time 00:00:00.0000000 bis 23:59:59.99999999 100 Nanosekunden
datetime datetime.datetime 1753-01-01 bis 9999-12-31 3,33 Millisekunden
datetime2 datetime.datetime 0001-01-01 an 9999-12-31 100 Nanosekunden
smalldatetime datetime.datetime 01.01.1900 bis 06.06.2079 1 Minute
datetimeoffset datetime.datetime (mit tzinfo) 0001-01-01 an 9999-12-31 100 Nanosekunden + Zeitzone

Datumszeitwerte einfügen

Verwende Python Datetime-Objekte

Das folgende Beispiel fügt verschiedene Datums- und Uhrzeittypen in den SQL Server mit dem datetime Python-Modul ein:

from datetime import datetime, date, time
import mssql_python

conn = mssql_python.connect(connection_string)
cursor = conn.cursor()

# Create a temp table with date, time, and datetime columns
cursor.execute("""
    CREATE TABLE #Events (
        EventDate DATE,
        StartTime TIME,
        LogTime DATETIME2
    )
""")

# Insert date
cursor.execute(
    "INSERT INTO #Events (EventDate) VALUES (%(date)s)",
    {"date": date(2024, 12, 25)}
)

# Insert time
cursor.execute(
    "INSERT INTO #Events (StartTime) VALUES (%(time)s)",
    {"time": time(14, 30, 0)}
)

# Insert datetime
cursor.execute(
    "INSERT INTO #Events (LogTime) VALUES (%(ts)s)",
    {"ts": datetime(2024, 3, 15, 14, 30, 45)}
)

conn.commit()

DB-API Zeitstempel-Konstruktor

Verwenden Sie den DB-API Timestamp() Konstruktor als Alternative zu datetime.datetime. Das Argument microsecond ist optional und steht standardmäßig auf null:

from mssql_python import Timestamp

timestamp = Timestamp(2026, 11, 2, 14, 30, 45)
precise_timestamp = Timestamp(2026, 11, 2, 14, 30, 45, 123456)

print(timestamp)          # 2026-11-02 14:30:45
print(precise_timestamp)  # 2026-11-02 14:30:45.123456

Der Konstruktor gibt ein Python-Objekt datetime.datetime zurück, das du als Abfrageparameter weitergeben kannst. Weitere Informationen finden Sie unter PEP 249.

Aktuellen Zeitstempel einfügen

Sie können das aktuelle Datum und die Uhrzeit mit datetime.now() der GETDATE() Funktion von SQL Server einfügen:

from datetime import datetime

# Create temp table for the demo
cursor.execute("CREATE TABLE #Logs (Message NVARCHAR(200), CreatedAt DATETIME2)")

# Python current time
now = datetime.now()
cursor.execute("INSERT INTO #Logs (Message, CreatedAt) VALUES (%(msg)s, %(ts)s)",
               {"msg": "Event occurred", "ts": now})

# Or use SQL Server's GETDATE()
cursor.execute("INSERT INTO #Logs (Message, CreatedAt) VALUES (%(msg)s, GETDATE())",
               {"msg": "Event occurred"})
conn.commit()

Hochpräzise Datumszeit2

Für Zeitstempel mit höherer Genauigkeit verwenden Sie den datetime2 Typ, der eine 100-Nanosekunden-Präzision unterstützt:

from datetime import datetime

# Create temp table with a datetime2(7) column
cursor.execute("CREATE TABLE #PreciseLogs (EventTime DATETIME2(7))")

# Microsecond precision (Python supports up to microseconds)
precise_time = datetime(2024, 3, 15, 14, 30, 45, 123456)

cursor.execute(
    "INSERT INTO #PreciseLogs (EventTime) VALUES (%(ts)s)",  # datetime2(7) column
    {"ts": precise_time}
)
conn.commit()

Python-Objekte datetime unterstützen sechs Nachkommastellen, daher können sie nicht die siebte Ziffer von datetime2(7) darstellen. Für das Massenladen von Daten, die bereits eine 100-Nanosekunden-Genauigkeit aufweisen, behalten Sie das Arrow-Format bei und verwenden Sie bulkcopy_arrow() in Verbindung mit einer kompatiblen Spalte für Nanosekunden-Zeitstempel. Weitere Informationen finden Sie unter temporale Präzision von Arrow.

Date-Time-Werte abrufen

Abholdaten und -zeiten

Beim Abrufen von Datetime-Werten aus SQL Server werden diese automatisch in Python-Objekte datetime umgewandelt:

from datetime import date, time, datetime

# Create and populate a temp table with date, time, and datetime columns
cursor.execute("""
    CREATE TABLE #Events (
        ID INT,
        EventDate DATE,
        StartTime TIME,
        CreatedAt DATETIME2
    )
""")
cursor.execute(
    "INSERT INTO #Events (ID, EventDate, StartTime, CreatedAt) VALUES (1, %(d)s, %(t)s, %(dt)s)",
    {"d": date(2024, 12, 25), "t": time(14, 30, 0), "dt": datetime(2024, 3, 15, 14, 30, 45)}
)
conn.commit()

cursor.execute("SELECT EventDate, StartTime, CreatedAt FROM #Events WHERE ID = 1")
row = cursor.fetchone()

print(type(row.EventDate))   # <class 'datetime.date'>
print(type(row.StartTime))   # <class 'datetime.time'>
print(type(row.CreatedAt))   # <class 'datetime.datetime'>

print(row.EventDate)    # 2024-12-25
print(row.StartTime)    # 14:30:00
print(row.CreatedAt)    # 2024-03-15 14:30:45

Zugriff auf Datetime-Komponenten

Python-Datetime-Objekte bieten Attribute, um auf einzelne Datums- und Zeitkomponenten zuzugreifen:

cursor.execute("SELECT OrderDate FROM Sales.SalesOrderHeader WHERE SalesOrderID = 43659")
row = cursor.fetchone()
dt = row.OrderDate

# Date components
print(dt.year)
print(dt.month)
print(dt.day)

# Time components
print(dt.hour)
print(dt.minute)
print(dt.second)
print(dt.microsecond)

Zeitzonen

Offset-bewusste versus offset-naive DateTimes

Python-Datetime-Objekte sind entweder offset-aware (have tzinfo) oder offset-naïve (keine Zeitzoneninformation). SQL Server hat beide Arten von Spalten:

SQL Server-Typ Erwartet Zeitzone des Ladens?
Datetime, Datetime2, SmallDatetime Offset-naiv No
datetimeoffset Offset-bewusst Ja

Das Senden einer zeitzonenbewussten DateTime-Angabe an eine datetime2-Spalte funktioniert, die Zeitzoneninformationen werden jedoch stillschweigend verworfen. Das Senden einer naiven DateTime-Angabe an eine datetimeoffset-Spalte weist UTC (+00:00) als Offset zu. Geben Sie ausdrücklich an, welche Art Sie senden:

Das folgende Beispiel zeigt, wie man Offset-naive und offset-aware Datetimes erstellt und verwendet:

from datetime import datetime, timezone, timedelta

# Create temp tables: one datetime2 column and one datetimeoffset column
cursor.execute("CREATE TABLE #Logs (LogTime DATETIME2)")
cursor.execute("CREATE TABLE #GlobalEvents (EventTime DATETIMEOFFSET)")

# Offset-naive - use for datetime2 columns
naive_dt = datetime(2024, 3, 15, 14, 30, 45)
cursor.execute(
    "INSERT INTO #Logs (LogTime) VALUES (%(log_time)s)",  # datetime2 column
    {"log_time": naive_dt}
)

# Offset-aware - use for datetimeoffset columns
eastern = timezone(timedelta(hours=-5))
aware_dt = datetime(2024, 3, 15, 14, 30, 45, tzinfo=eastern)
cursor.execute(
    "INSERT INTO #GlobalEvents (EventTime) VALUES (%(event_time)s)",  # datetimeoffset column
    {"event_time": aware_dt}
)
conn.commit()

Um zwischen den beiden zu konvertieren:

from datetime import datetime, timezone

# Make naive datetime timezone-aware
naive = datetime(2024, 3, 15, 14, 30)
aware = naive.replace(tzinfo=timezone.utc)

# Strip timezone from aware datetime
aware = datetime.now(timezone.utc)
naive = aware.replace(tzinfo=None)

Datetimeoffset-Typ

SQL Server speichert Zeitzoneninformationendatetimeoffset. Das folgende Beispiel zeigt, wie man zeitzonenbewusste Datumszeiten einfügt und abruft:

from datetime import datetime, timezone, timedelta

# Create temp table with a datetimeoffset column
cursor.execute("CREATE TABLE #GlobalEvents (ID INT, EventTime DATETIMEOFFSET)")

# Create timezone-aware datetime
eastern = timezone(timedelta(hours=-5))
dt_eastern = datetime(2024, 3, 15, 14, 30, tzinfo=eastern)

cursor.execute(
    "INSERT INTO #GlobalEvents (ID, EventTime) VALUES (1, %(ts)s)",  # datetimeoffset column
    {"ts": dt_eastern}
)
conn.commit()

# Retrieve timezone-aware datetime
cursor.execute("SELECT EventTime FROM #GlobalEvents WHERE ID = 1")
row = cursor.fetchone()
print(row.EventTime)          # 2024-03-15 14:30:00-05:00
print(row.EventTime.tzinfo)   # UTC-05:00

Wechsel zwischen Zeitzonen

Verwenden Sie die Zeitzonenmethoden von Python, um datetimeoffset-Werte zwischen verschiedenen Zeitzonenrepräsentationen umzuwandeln:

from datetime import datetime, timezone, timedelta

# Create and populate a temp table with a datetimeoffset column
cursor.execute("CREATE TABLE #GlobalEvents (EventTime DATETIMEOFFSET)")
eastern = timezone(timedelta(hours=-5))
cursor.execute(
    "INSERT INTO #GlobalEvents (EventTime) VALUES (%(ts)s)",
    {"ts": datetime(2024, 3, 15, 14, 30, tzinfo=eastern)}
)
conn.commit()

# Retrieve as-stored
cursor.execute("SELECT EventTime FROM #GlobalEvents")
row = cursor.fetchone()
stored_time = row.EventTime  # Has timezone info

# Convert to UTC
utc_time = stored_time.astimezone(timezone.utc)
print(utc_time)

# Convert to local timezone
local_time = stored_time.astimezone()  # System timezone
print(local_time)

AT TIME ZONE in SQL

Die Klausel von AT TIME ZONE SQL Server wandelt datetimeoffset-Werte zwischen Zeitzonen innerhalb einer Abfrage um:

from datetime import datetime, timezone, timedelta

# Create and populate a temp table with a datetimeoffset column
cursor.execute("CREATE TABLE #GlobalEvents (EventTime DATETIMEOFFSET)")
eastern = timezone(timedelta(hours=-5))
cursor.execute(
    "INSERT INTO #GlobalEvents (EventTime) VALUES (%(ts)s)",
    {"ts": datetime(2024, 3, 15, 14, 30, tzinfo=eastern)}
)
conn.commit()

# Convert timezone in SQL Server (2016+)
cursor.execute("""
    SELECT 
        EventTime,
        EventTime AT TIME ZONE 'Pacific Standard Time' AS PacificTime,
        EventTime AT TIME ZONE 'UTC' AS UTCTime
    FROM #GlobalEvents
""")

for row in cursor:
    print(f"Original: {row.EventTime}")
    print(f"Pacific: {row.PacificTime}")
    print(f"UTC: {row.UTCTime}")

Datumsarithmetik

Datumsunterschiede berechnen

Berechnen Sie die Anzahl der Tage zwischen zwei Daten, indem Sie Date-Time-Objekte subtrahieren:

from datetime import timedelta

cursor.execute("SELECT OrderDate, ShipDate FROM Sales.SalesOrderHeader WHERE SalesOrderID = 43659")
row = cursor.fetchone()

# Days between dates
if row.ShipDate and row.OrderDate:
    difference = row.ShipDate - row.OrderDate
    print(f"Shipped in {difference.days} days")

Intervalle hinzufügen

Verwenden Sie timedelta , um Tage, Monate oder andere Intervalle von einem Datum zu addieren oder abzuziehen:

from datetime import datetime, timedelta

# Create and populate a temp table
cursor.execute("""
    CREATE TABLE #Subscriptions (
        ID INT,
        CreatedAt DATETIME2,
        ExpiresAt DATETIME2
    )
""")
cursor.execute(
    "INSERT INTO #Subscriptions (ID, CreatedAt) VALUES (1, %(created)s)",
    {"created": datetime(2024, 1, 1, 12, 0, 0)}
)
conn.commit()

cursor.execute("SELECT CreatedAt FROM #Subscriptions WHERE ID = 1")
row = cursor.fetchone()

# Calculate expiration (30 days from creation)
expiration = row.CreatedAt + timedelta(days=30)
print(f"Expires: {expiration}")

# Update with calculated date
cursor.execute(
    "UPDATE #Subscriptions SET ExpiresAt = %(exp)s WHERE ID = %(id)s",
    {"exp": expiration, "id": 1}
)
conn.commit()

DATEADD in SQL

Die DATEADD Funktion von SQL Server führt die Datumsarithmetik direkt in Abfragen durch:

# Use SQL Server date functions
cursor.execute("""
    SELECT 
        OrderDate,
        DATEADD(day, 30, OrderDate) AS DueDate,
        DATEADD(month, 1, OrderDate) AS NextMonth
    FROM Sales.SalesOrderHeader
""")

Formatieren von Werten

Format für die Darstellung

Formatiere Datums- und Uhrzeitwerte zur Anzeige mit strftime() und Formatspezifizierern:

from datetime import datetime

cursor.execute("SELECT TOP(10) OrderDate FROM Sales.SalesOrderHeader")

for row in cursor:
    # strftime formatting
    print(row.OrderDate.strftime("%Y-%m-%d"))           # 2024-03-15
    print(row.OrderDate.strftime("%B %d, %Y"))          # March 15, 2024
    print(row.OrderDate.strftime("%m/%d/%Y %I:%M %p"))  # 03/15/2024 02:30 PM

Aus Zeichenfolgen parsen

Konvertiere String-Darstellungen in Python-Datetime-Objekte mit strptime():

from datetime import datetime

# Create temp table for the demo
cursor.execute("CREATE TABLE #Events (EventDate DATE)")

# Parse incoming date strings
date_string = "2024-03-15"
parsed_date = datetime.strptime(date_string, "%Y-%m-%d").date()

cursor.execute(
    "INSERT INTO #Events (EventDate) VALUES (%(date)s)",
    {"date": parsed_date}
)
conn.commit()

ISO 8601-Format

Das ISO-8601-Format ist nützlich für APIs und JSON-Serialisierung. Verwenden Sie isoformat() und fromisoformat() für die Konvertierung:

from datetime import datetime

# Create and populate a temp table
cursor.execute("CREATE TABLE #Logs (ID INT, CreatedAt DATETIME2)")
cursor.execute(
    "INSERT INTO #Logs (ID, CreatedAt) VALUES (1, %(ts)s)",
    {"ts": datetime(2024, 3, 15, 14, 30, 45)}
)
conn.commit()

cursor.execute("SELECT CreatedAt FROM #Logs WHERE ID = 1")
row = cursor.fetchone()

# ISO format for APIs/JSON
iso_string = row.CreatedAt.isoformat()
print(iso_string)  # 2024-03-15T14:30:45

# Parse ISO format
dt = datetime.fromisoformat("2024-03-15T14:30:45")

Allgemeine Muster

Abfrage nach Datumsbereich

Abfragen Sie Datensätze innerhalb eines bestimmten Datumsbereichs, indem Sie die Start- und Enddaten parametrisieren:

from datetime import date, datetime

# Query by date range
start_date = date(2024, 1, 1)
end_date = date(2024, 12, 31)

cursor.execute("""
    SELECT SalesOrderID, OrderDate, TotalDue
    FROM Sales.SalesOrderHeader
    WHERE OrderDate >= %(start)s AND OrderDate < %(end)s
""", {"start": start_date, "end": end_date})

Heutige Datensätze abfragen

Datensätze abfragen, die heute erstellt wurden, indem die Datetime-Spalte in ein Datum umgewandelt und mit dem heutigen Datum verglichen wird:

from datetime import date

# Create and populate a temp table with a row logged today
cursor.execute("CREATE TABLE #Logs (LogTime DATETIME2)")
cursor.execute("INSERT INTO #Logs (LogTime) VALUES (GETDATE())")
conn.commit()

cursor.execute("""
    SELECT * FROM #Logs 
    WHERE CAST(LogTime AS DATE) = %(today)s
""", {"today": date.today()})

# Or using SQL Server function
cursor.execute("""
    SELECT * FROM #Logs 
    WHERE CAST(LogTime AS DATE) = CAST(GETDATE() AS DATE)
""")

Behandle NULL-Daten

Überprüfen Sie Werte None vor der Verarbeitung der Datum-Zeit-Spalten, da NULL-Daten wie None in Python erscheinen:

cursor.execute("SELECT TOP 5 FirstName, ModifiedDate FROM Person.Person")

for row in cursor:
    if row.ModifiedDate is None:
        print(f"{row.FirstName}: Date not on file")
    else:
        age = (date.today() - row.ModifiedDate.date()).days // 365
        print(f"{row.FirstName}: modified {age} years ago")

Zeitstempel für Store-Audits

Erstellen Sie Audit-Spalten, um zu verfolgen, wann Datensätze erstellt und geändert werden:

from datetime import datetime

# Create temp table for audit timestamp demo
cursor.execute("""
    CREATE TABLE #AuditOrders (
        ID INT IDENTITY(1,1) PRIMARY KEY,
        CustomerID INT,
        Total DECIMAL(10,2),
        CreatedAt DATETIME2,
        ModifiedAt DATETIME2
    )
""")

def create_order(cursor, customer_id: int, total: float) -> int:
    now = datetime.now()
    cursor.execute("""
        INSERT INTO #AuditOrders (CustomerID, Total, CreatedAt, ModifiedAt)
        VALUES (%(cust)s, %(total)s, %(created)s, %(modified)s);
        SELECT SCOPE_IDENTITY();
    """, {
        "cust": customer_id,
        "total": total,
        "created": now,
        "modified": now
    })
    return cursor.fetchval()

def update_order(cursor, order_id: int, total: float):
    cursor.execute("""
        UPDATE #AuditOrders
        SET Total = %(total)s, ModifiedAt = %(modified)s
        WHERE ID = %(id)s
    """, {
        "total": total,
        "modified": datetime.now(),
        "id": order_id
    })

Spezifische Szenarien

Daten vor 1753

Verwendung datetime2 für historische Daten (der datetime Typ unterstützt nur Daten ab 1753). Das folgende Beispiel fügt ein historisches Datum hinzu:

from datetime import datetime

# Create temp table with a datetime2 column
cursor.execute("CREATE TABLE #HistoricalEvents (EventDate DATETIME2, Description NVARCHAR(200))")

# Historical date (works with datetime2, fails with datetime)
historical = datetime(1500, 1, 1)

cursor.execute(
    "INSERT INTO #HistoricalEvents (EventDate, Description) VALUES (%(date)s, %(desc)s)",
    {"date": historical, "desc": "Archived historical record"}
)
conn.commit()

Reine Zeitwerte

Tageszeitwerte mit dem Objekt von Python time speichern:

from datetime import time

# Create temp table with time columns
cursor.execute("""
    CREATE TABLE #BusinessHours (
        DayOfWeek NVARCHAR(10),
        OpenTime TIME,
        CloseTime TIME
    )
""")

# Store time of day without date
opening_time = time(9, 0, 0)   # 9:00 AM
closing_time = time(17, 30, 0) # 5:30 PM

cursor.execute("""
    INSERT INTO #BusinessHours (DayOfWeek, OpenTime, CloseTime)
    VALUES (%(day)s, %(open)s, %(close)s)
""", {"day": "Monday", "open": opening_time, "close": closing_time})
conn.commit()

Berechnen von Arbeitstagen

Eine Helferfunktion kann Datumsintervalle berechnen und dabei Wochenenden überspringen:

from datetime import date, timedelta

def add_business_days(start: date, days: int) -> date:
    """Add business days (skip weekends)."""
    current = start
    remaining = days
    while remaining > 0:
        current += timedelta(days=1)
        if current.weekday() < 5:  # Monday = 0, Friday = 4
            remaining -= 1
    return current

order_date = date(2024, 3, 15)  # Friday
due_date = add_business_days(order_date, 5)  # Skip weekend
print(f"Due date: {due_date}")  # 2024-03-22 (next Friday)