Kolom kategori dan kunci penggabungan kini seragam, tetapi catatan pemeriksaan awal kita masih menyimpan satu baris yang tertunda: kolom tanggal. Sistem kasir lama Toko Kopi Kita mengekspor 15/01/2026, sistem baru menulis 2026-01-16, dan keduanya tersimpan sebagai teks yang tidak bisa diurutkan secara kronologis. Lebih berbahaya lagi, teks tanggal sering kali bisa diurai dengan cara yang keliru tanpa satu pun pesan galat. Pada chapter ini kita mem-parse tanggal dengan pd.to_datetime memakai format eksplisit, mengurai komponen tahun, bulan, dan hari lewat aksesor .dt, lalu menangani kolom yang memuat dua format sekaligus.

Tanggal Teks dan Jebakan Ambiguitas #

Tanggal tertulis 05/01/2026 bermakna 5 Januari bagi kasir di Indonesia, tetapi 1 Mei bagi pembaca format Amerika. Ketika kita menyerahkan teks ambigu seperti itu pada pd.to_datetime tanpa petunjuk apa pun, pandas harus menebak — dan tebakan bawaannya memprioritaskan bulan di depan. Mari kita lihat betapa senyapnya salah tebak itu.

python
import pandas as pd

tanggal_teks = pd.Series(["05/01/2026", "06/01/2026", "07/01/2026"])
print(tanggal_teks.tolist())

tanggal = pd.to_datetime(tanggal_teks)
print(tanggal.tolist())

Output yang Dihasilkan:

text
['05/01/2026', '06/01/2026', '07/01/2026']
[Timestamp('2026-05-01 00:00:00'), Timestamp('2026-06-01 00:00:00'), Timestamp('2026-07-01 00:00:00')]

Mari kita telaah kejadian di atas:

  1. Baris 3 (pd.Series([...])): tiga tanggal yang dimaksudkan kasir adalah 5, 6, dan 7 Januari 2026. Ketiganya ambigu karena angka pertamanya bisa dibaca sebagai bulan maupun hari.
  2. Baris 6 (pd.to_datetime(tanggal_teks)): tanpa parameter format, pandas memakai gaya bulan-dulu. Teks 05/01/2026 diurai sebagai 5 Mei, 06/01/2026 sebagai 5 Juni, 07/01/2026 sebagai 5 Juli.
  3. Tidak ada galat yang muncul: hasil penguraian keliru, tetapi proses berjalan lancar dan kolom bertipe datetime. Kesalahan semacam ini baru ketahuan ketika laporan menampilkan penjualan bulan Mei pada bulan Januari.

Parsing dengan Format Eksplisit #

Solusi baku untuk penguraian tanggal adalah menyebut formatnya lewat parameter format, ditulis dengan kode persen yang membaca potongan teks. Daftar berikut merangkum kode yang paling sering kita perlukan.

KodeArtiContoh pada 15 Januari 2026
%dhari dalam bulan, dua digit15
%mbulan sebagai angka, dua digit01
%Ytahun empat digit2026
%ytahun dua digit26
%H dan %Mjam dan menit, dua digit08 dan 30
%bnama bulan singkat bahasa InggrisJan

Kolom kasir kita ditulis hari-dulu-bulan-tahun, sehingga formatnya %d/%m/%Y. Format eksplisit ini juga bekerja untuk tanggal yang tadinya ambigu.

python
import pandas as pd

tanggal_teks = pd.Series(["05/01/2026", "06/01/2026",
                          "07/01/2026", "15/01/2026"])

tanggal = pd.to_datetime(tanggal_teks, format="%d/%m/%Y")
print(tanggal.tolist())
print("Tipe kolom:", tanggal.dtype)

Output yang Dihasilkan:

text
[Timestamp('2026-01-05 00:00:00'), Timestamp('2026-01-06 00:00:00'), Timestamp('2026-01-07 00:00:00'), Timestamp('2026-01-15 00:00:00')]
Tipe kolom: datetime64[ns]

Mari kita telaah hasilnya:

  1. Baris 6 (pd.to_datetime(..., format="%d/%m/%Y")): setiap teks diurai persis mengikuti pola hari-bulan-tahun. 05/01/2026 kini benar terbaca sebagai 5 Januari — ambigu tidak ada lagi karena pola kita yang menentukan.
  2. dtype hasil: datetime64[ns] — tipe khusus pandas untuk penanda waktu. Sejak kolom bertipe ini, urutan kronologis, selisih hari, dan penguraian komponen menjadi operasi yang sah.

Sebaliknya, format yang keliru memberi galat yang jelas — dan itu justru baik. Galat eksplisit menghentikan kita sebelum data masuk lebih jauh ke dalam analisis.

python
import pandas as pd

tanggal_teks = pd.Series(["05/01/2026", "15/01/2026"])

try:
    salah = pd.to_datetime(tanggal_teks, format="%m/%d/%Y")
    print(salah.tolist())
except ValueError as galat:
    print("Galat:", galat)

Output yang Dihasilkan:

text
Galat: time data "15/01/2026" doesn't match format "%m/%d/%Y", at position 1. You might want to try:
    - passing `format` if your strings have a consistent format;
    - passing `format='ISO8601'` if your strings are all ISO8601 but not necessarily in exactly the same format;
    - passing `format='mixed'`, and the format will be inferred for each element individually. You might want to use `dayfirst` alongside this.

Mari kita baca galatnya:

  1. Baris 6 (format="%m/%d/%Y"): pola bulan-dulu-hari diterapkan pada kolom hari-dulu-bulan. Teks pertama 05/01/2026 lolos karena polosnya cocok (bulan 05, hari 01), tetapi teks kedua 15/01/2026 menabrak: tidak ada bulan ke-15.
  2. Pesan ValueError: pandas menyebut posisi baris yang gagal dan menyodorkan tiga jalan keluar, termasuk format='mixed' untuk kolom yang formatnya berubah-ubah — kasus yang kita tangani pada bagian akhir chapter ini.
  3. try/except: pola yang sudah menjadi kebiasaan kita sejak chapter Memperbaiki Tipe Data — tangkap galat, cetak pesannya, lalu putuskan perbaikannya dengan sadar.

Mengurai Komponen Tanggal dengan Aksesor .dt #

Setelah kolom bertipe datetime64, aksesor .dt membuka seluruh komponen tanggal: .dt.year, .dt.month, .dt.day, sampai nama hari. Komponen-komponen ini adalah bahan baku analisis musiman — penjualan per bulan, ramai hari apa, pada jam berapa.

python
import pandas as pd

penjualan = pd.DataFrame({
    "tanggal": ["15/01/2026", "16/01/2026", "17/01/2026", "18/01/2026"],
    "terjual": [3, 2, 2, 4],
})
penjualan["tanggal"] = pd.to_datetime(penjualan["tanggal"], format="%d/%m/%Y")

penjualan["tahun"] = penjualan["tanggal"].dt.year
penjualan["bulan"] = penjualan["tanggal"].dt.month
penjualan["hari"] = penjualan["tanggal"].dt.day
penjualan["nama_hari"] = penjualan["tanggal"].dt.day_name()

print(penjualan)
print()
print(penjualan["tanggal"].dt.month_name().tolist())

Output yang Dihasilkan:

text
     tanggal  terjual  tahun  bulan  hari nama_hari
0 2026-01-15        3   2026      1    15  Thursday
1 2026-01-16        2   2026      1    16    Friday
2 2026-01-17        2   2026      1    17  Saturday
3 2026-01-18        4   2026      1    18    Sunday

['January', 'January', 'January', 'January']

Mari kita telaah komponen-komponennya:

  1. Baris 7 (pd.to_datetime(..., format=...)): kolom teks berubah menjadi datetime64 lebih dulu — aksesor .dt hanya bekerja pada kolom bertipe ini; pada kolom teks ia menolak bekerja.
  2. Baris 9-12 (.dt.year, .dt.month, .dt.day, .dt.day_name()): setiap pemanggilan menghasilkan kolom baru berisi komponen tanggal. day_name() dan month_name() mengembalikan nama bahasa Inggris — January, Thursday — sesuai konvensi pustaka standar.
  3. Nama berbahasa Inggris: bila laporan membutuhkan Kamis alih-alih Thursday, kita terjemahkan dengan kamus pengganti persis seperti kamus cash ke tunai pada chapter Menangani Data Tidak Konsisten — pola keputusan yang sama, objek yang berbeda.

Menyeragamkan Bentuk Keluaran dengan strftime #

Arah kebalikan dari parsing adalah pemformatan: mengubah tanggal menjadi teks dengan strftime, misalnya saat tampilan laporan ditentukan pihak lain. Manfaat sekundernya lebih penting: format %Y-%m-%d (baku ISO 8601) mengurutkan benar bahkan sebagai teks — itulah mengapa format ini menjadi pilihan penyimpanan.

python
import pandas as pd

tanggal_teks = pd.Series(["15/01/2026", "02/02/2026", "20/01/2026"])
print("Urutan sebagai teks  :", sorted(tanggal_teks.tolist()))

tanggal = pd.to_datetime(tanggal_teks, format="%d/%m/%Y")
urut = tanggal.sort_values()
print("Urutan sebagai tanggal:", urut.dt.strftime("%d/%m/%Y").tolist())
print("Bentuk baku ISO       :", tanggal.dt.strftime("%Y-%m-%d").tolist())

Output yang Dihasilkan:

text
Urutan sebagai teks  : ['02/02/2026', '15/01/2026', '20/01/2026']
Urutan sebagai tanggal: ['15/01/2026', '20/01/2026', '02/02/2026']
Bentuk baku ISO       : ['2026-01-15', '2026-02-02', '2026-01-20']

Mari kita telaah urutannya:

  1. Baris 4 (sorted(tanggal_teks.tolist())): sebagai teks, 02/02/2026 menempati urutan pertama karena karakter 0 lebih kecil dari 1. Urutan kronologis rusak — 2 Februari tampil sebelum 15 Januari.
  2. Baris 7-8 (sort_values() + strftime): pada kolom datetime, sort_values() mengurutkan menurut waktu yang sesungguhnya. strftime("%d/%m/%Y") kemudian menyusun teks laporan sesuai permintaan, sementara urutan kronologis tetap terjaga.
  3. Baris 9 (strftime("%Y-%m-%d")): bentuk ISO menaruh tahun di depan sehingga urutan teks sama dengan urutan waktu. Simpan tanggal dalam bentuk ini bila berkas harus dibaca sistem lain.

Dua Format dalam Satu Kolom dan Nilai NaT #

Skenario yang paling sering kita jumpai: migrasi sistem membuat satu kolom memuat dua format. Kolom gabungan di bawah memuat gaya kasir lama (15/01/2026), gaya sistem baru (2026-01-16), dan satu tanggal mustahil (29/02/2026 — 2026 bukan tahun kabisat).

python
import pandas as pd

tanggal_campur = pd.Series(["15/01/2026", "2026-01-16",
                            "17/01/2026", "29/02/2026"])

paksa = pd.to_datetime(tanggal_campur, format="%d/%m/%Y", errors="coerce")
print(paksa.tolist())
print()

def ke_tanggal(teks):
    for pola in ("%d/%m/%Y", "%Y-%m-%d"):
        try:
            return pd.to_datetime(teks, format=pola)
        except ValueError:
            continue
    return pd.NaT

hasil = tanggal_campur.apply(ke_tanggal)
print(hasil.tolist())
print("Nilai yang gagal diurai:", hasil.isna().sum())

Output yang Dihasilkan:

text
[Timestamp('2026-01-15 00:00:00'), NaT, Timestamp('2026-01-17 00:00:00'), NaT]

[Timestamp('2026-01-15 00:00:00'), Timestamp('2026-01-16 00:00:00'), Timestamp('2026-01-17 00:00:00'), NaT]
Nilai yang gagal diurai: 1

Mari kita telaah dua pendekatan itu:

  1. Baris 6 (errors="coerce"): dengan satu format saja, teks 2026-01-16 yang tidak cocok pola langsung menjadi NaT, begitu pula tanggal mustahil 29/02/2026. Pendekatan ini jujur: dua nilai hilang, selanjutnya ditangani sebagai missing values.
  2. Baris 10-16 (ke_tanggal): fungsi ini mencoba dua pola secara berurutan memakai try/except — gaya lama lebih dulu, gaya baru menyusul. Teks 2026-01-16 gagal pada pola pertama, lalu berhasil pada pola kedua; hanya tanggal mustahil yang tetap NaT.
  3. Baris 18 (apply(ke_tanggal)): fungsi diterapkan pada tiap nilai kolom. Hasilnya tiga tanggal sah terurai dan satu NaT — satu-satunya nilai yang memang tidak bisa diselamatkan format apa pun.

Rangkuman #

  • Penguraian tanpa format membuat pandas menebak; untuk teks ambigu, tebakan bawaannya memprioritaskan bulan sehingga 05/01/2026 terbaca 5 Mei.
  • pd.to_datetime dengan format="%d/%m/%Y" mengurai tanggal gaya Indonesia secara eksplisit; format keliru memunculkan ValueError yang jelas.
  • Aksesor .dt mengurai komponen — .dt.year, .dt.month, .dt.day, .dt.day_name() — untuk analisis musiman.
  • strftime menyusun teks tanggal untuk laporan; bentuk %Y-%m-%d (ISO) mengurutkan benar bahkan sebagai teks.
  • Kolom dua format diurai dengan fungsi percoba dua pola; errors="coerce" dan tanggal mustahil menghasilkan NaT yang ditangani seperti missing values.

Kolom tanggal kini bertipe datetime64 dan komponennya siap dianalisis. Catatan pemeriksaan awal kita tinggal satu baris yang belum selesai: harga 240000 untuk segelas kopi susu — nilai yang menjulang jauh di atas kawanannya. Nilai seperti itu disebut pencilan, dan menentukan ia salah ketik atau transaksi sah membutuhkan alat deteksi. Kita bawa kasus itu ke chapter berikutnya, Menangani Outlier .