Bekerja dengan nilai tanggal dan waktu

Driver mssql-python menangani jenis tanggal dan waktu SQL Server dan memetakannya ke objek modul Pythondatetime. SQL Server menyediakan beberapa jenis tanggal dan waktu dengan presisi dan kemampuan yang bervariasi:

Jenis SQL Jenis Python Jangkauan Presisi
tanggal datetime.date 0001-01-01 hingga 9999-12-31 Satu hari
time datetime.time 00:00:00.0000000 hingga 23:59:59.99999999 100 nanodetik
datetime datetime.datetime 1753-01-01 hingga 9999-12-31 3,33 milidetik
datetime2 datetime.datetime 0001-01-01 hingga 9999-12-31 100 nanodetik
smalldatetime datetime.datetime 1900-01-01 hingga 2079-06-06 1 menit
datetimeoffset datetime.datetime (dengan tzinfo) 0001-01-01 hingga 9999-12-31 100 nanodetik + zona waktu

Menyisipkan nilai tanggalwaktu

Menggunakan objek datetime Python

Contoh berikut menyisipkan jenis tanggal dan waktu yang berbeda ke dalam SQL Server menggunakan modul Pythondatetime:

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

Sisipkan stempel waktu saat ini

Anda dapat menyisipkan tanggal dan waktu saat ini menggunakan datetime.now() atau fungsi SQL ServerGETDATE():

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

Tanggalwaktu presisi tinggi2

Untuk stempel waktu presisi yang lebih tinggi, gunakan jenis yang datetime2 mendukung presisi 100 nanodetik:

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

Mengambil nilai tanggal dan waktu

Ambil tanggal dan waktu

Saat mengambil nilai tanggalwaktu dari SQL Server, nilai tersebut secara otomatis dikonversi ke objek Pythondatetime:

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

Mengakses komponen tanggal dan waktu

Objek datetime Python menyediakan atribut untuk mengakses komponen tanggal dan waktu individual:

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)

Zona waktu

Tanggal-waktu dengan offset dibanding tanggal-waktu tanpa offset

Objek datetime Python bersifat offset-aware (memiliki tzinfo) atau offset-naif (tidak memiliki informasi zona waktu). SQL Server memiliki kedua jenis kolom:

Jenis SQL Server Mengharapkan Zona waktu toko?
datetime, datetime2, smalldatetime Tanpa offset Tidak.
datetimeoffset Memperhitungkan offset Yes

Mengirim datetime yang menyertakan informasi zona waktu ke kolom datetime2 berfungsi, tetapi informasi zona waktunya dihapus secara diam-diam. Mengirim datetime naif ke kolom datetimeoffset akan menetapkan offset ke UTC (+00:00). Bersikaplah eksplisit tentang jenis yang Anda kirim:

Contoh berikut menunjukkan cara membuat dan menggunakan datetime yang offset-naive dan offset-aware:

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

Untuk mengonversi antara keduanya:

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)

Tipe datetimeoffset

SQL Server datetimeoffset menyimpan informasi zona waktu. Contoh berikut menunjukkan cara menyisipkan dan mengambil nilai datetime yang menyertakan informasi zona waktu:

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

Konversi antar zona waktu

Gunakan metode zona waktu Python untuk mengonversi nilai datetimeoffset antara representasi zona waktu yang berbeda:

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

Klausa SQL Server AT TIME ZONE mengonversi nilai datetimeoffset antara zona waktu dalam kueri:

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

Aritmatika tanggal

Menghitung perbedaan tanggal

Hitung jumlah hari antara dua tanggal dengan mengurangi objek tanggalwaktu:

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

Tambahkan interval

Gunakan timedelta untuk menambahkan atau mengurangi hari, bulan, atau interval lain dari tanggalwaktu:

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

Fungsi SQL Server DATEADD melakukan aritmatika tanggal secara langsung dalam kueri:

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

Format nilai

Format untuk tampilan

Format nilai tanggalwaktu untuk ditampilkan menggunakan strftime() dengan penentu format:

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

Mengurai dari string

Konversi representasi string ke objek datetime Python menggunakan 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()

Format ISO 8601

Format ISO 8601 berguna untuk API dan serialisasi JSON. Gunakan isoformat() dan fromisoformat() untuk konversi:

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

Pola umum

Kueri berdasarkan rentang tanggal

Kueri rekaman dalam rentang tanggal tertentu dengan mengparameterkan tanggal mulai dan berakhir:

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

Kueri rekaman hari ini

Kueri record yang dibuat hari ini dengan mengonversi kolom datetime menjadi date dan membandingkannya dengan tanggal hari ini:

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

Menangani tanggal NULL

Periksa nilai None sebelum memproses kolom datetime, karena tanggal NULL muncul sebagai None di 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")

Simpan cap waktu audit

Buat kolom audit untuk melacak kapan rekaman dibuat dan dimodifikasi:

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

Skenario khusus

Tanggal sebelum 1753

Gunakan datetime2 untuk tanggal historis (jenis ini datetime hanya mendukung tanggal dari 1753). Contoh berikut menyisipkan tanggal historis:

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

Nilai waktu saja

Simpan nilai waktu menggunakan objek Pythontime:

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

Menghitung hari kerja

Fungsi pembantu dapat menghitung interval tanggal saat melewatkan akhir pekan:

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)