Práce s hodnotami data a času

Ovladač mssql-python zpracovává datumové a časové typy SQL Server a mapuje je na objekty datetime modulů Python. SQL Server poskytuje více typů dat a času s různou přesností a schopnostmi:

Typ SQL Typ Pythonu Range Přesnost
date datetime.date 0001-01-01 do 9999-12-31 Jeden den
time datetime.time 00:00:00.0000000 až 23:59:59.9999999 100 nanosekund
datetime datetime.datetime 1753-01-01 až 9999-12-31 3,33 milisekund
datetime2 datetime.datetime 0001-01-01 do 9999-12-31 100 nanosekund
smalldatetime datetime.datetime 1900-01-01 až 2079-06-06 1 minuta
datetimeoffset datetime.datetime (s tzinfo) 0001-01-01 do 9999-12-31 100 nanosekund + časové pásmo

Vložte hodnoty datového času

Používejte Python datetime objekty

Následující příklad vkládá různé typy dat a času do SQL Server pomocí modulu datetime Python:

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()

Vložte aktuální časové razítko

Aktuální datum a čas můžete vložit pomocí datetime.now() nebo funkce GETDATE() serveru SQL Server:

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()

datetime2 s vysokou přesností

Pro časová razítka s vyšší přesností použijte typ, datetime2 který podporuje přesnost 100 nanosekund:

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()

Získat hodnoty datového času

Načíst datum a čas

Při načítání hodnot data a času ze serveru SQL Server jsou tyto hodnoty automaticky převáděny na objekty datetime v Pythonu:

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

Přístup k datetime komponentám

Python datetime objekty poskytují atributy pro přístup k jednotlivým datovým a časovým komponentám:

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)

Časová pásma

Datumy a časy s posunem a bez posunu

Python datetime objekty jsou buď offsetově citlivé (mají tzinfo), nebo offset-naivní (bez informací o časovém pásmu). SQL Server má oba typy sloupců:

Typ SQL Serveru Očekává Ukládá časové pásmo?
Datetime, datetime2, smalldatetime Offset-naivní Ne
datetimeoffset Se zohledněním posunu Yes

Odeslání datového času podle časového pásma do sloupce datetime2 funguje, ale informace o časovém pásmu jsou tiše vyhozeny. Odeslání naivního datového času do sloupce datetimeoffset přiřadí UTC (+00:00) jako posun. Buďte jasní ohledně toho, jaký typ posíláte:

Následující příklad ukazuje, jak vytvářet a používat časové časy naivní a offsetově citlivé na posuny:

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()

Pro převod mezi oběma:

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)

Typ datetimeoffset

SQL Server ukládá datetimeoffset informace o časových pásmech. Následující příklad ukazuje vkládání a získávání datových časů podle časových pásem:

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

Převod mezi časovými pásmy

Použijte metody časových pásem v Python pro převod hodnot datetimeoffset mezi různými reprezentacemi časových pásem:

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 v SQL

Klauzule AT TIME ZONE SQL Server převádí hodnoty datetimeoffset mezi časovými pásmy v rámci dotazu:

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}")

Datová aritmetika

Vypočítejte rozdíly v datech

Počet dní mezi dvěma daty vypočítejte odečtením objektů datového času:

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")

Přidávat intervaly

Použijte timedelta k přičtení nebo odečtení dnů, měsíců nebo jiných intervalů od data času:

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 v SQL

Funkce DATEADD SQL Server provádí datovou aritmetiku přímo v dotazech:

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

Formátování hodnot

Formát zobrazení

Formátujte hodnoty datového času pro zobrazení pomocí strftime() specifikátorů formátu:

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

Parsování ze řetězců

Převádějte řetězcové reprezentace na objekty datetime v Pythonu pomocí 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()

Formát ISO 8601

Formát ISO 8601 je užitečný pro API a serializaci JSON. Použití isoformat() a fromisoformat() pro konverzi:

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")

Obvyklé scénáře

Dotaz podle datového rozpětí

Dotazujte záznamy v rámci konkrétního datového rozmezí parametrizací začátku a konce:

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})

Vyhledat dnešní záznamy

Načtěte záznamy vytvořené dnes tak, že sloupec typu datetime převedete na datum a porovnáte ho s dnešním datem:

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)
""")

Řešení NULL dat

Před zpracováním sloupců datetime zkontrolujte hodnotyNone, protože NULL data se objevují jako None v Python:

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")

Uložit časová razítka auditu

Vytvořte auditní sloupce pro sledování, kdy jsou záznamy vytvářeny a upravovány:

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
    })

Specifické scénáře

Datování před rokem 1753

Použití datetime2 pro historická data ( datetime typ podporuje pouze data od roku 1753). Následující příklad vkládá historické datum:

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()

Pouze časové hodnoty

Ukládejte hodnoty času dne pomocí objektu time Python:

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()

Výpočet pracovních dnů

Pomocná funkce může počítat intervaly dat a přeskočit víkendy:

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)