Menu

Ngày tháng trong R: as.Date, format() và tính toán ngày

Lớp Date của R là một số đếm ngày khoác bộ áo ngày tháng. Cách đọc chuỗi bằng as.Date, định dạng ngày khi in, trừ hai ngày, tạo dãy ngày và khi nào cần POSIXct.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

Ngày trong R là một số đếm ngày khoác bộ áo ngày tháng

Để biến một chuỗi thành ngày thật, dùng as.Date(); để lấy ngày hôm nay, dùng Sys.Date(). Đằng sau cả hai là một mẹo đơn giản: lớp Date của R chỉ là một con số, tức số ngày kể từ ngày 1 tháng 1 năm 1970, khoác một bộ áo khiến nó được in ra như ngày trên lịch. Bạn có thể cởi bộ áo đó ra bằng unclass():

Con số 20672 đó là số ngày thô, và nó là thứ khiến mọi điều khác trên trang này hoạt động: so sánh, phép trừ và tạo dãy thực chất đều là phép toán số nguyên bên dưới. Bộ áo là thứ làm nó dùng được: cùng một con số được in ra như ngày, định dạng như ngày và vẽ trên trục ngày.

as.Date() đọc ngay định dạng ISO "YYYY-MM-DD" mà không cần trợ giúp, đây là một trong nhiều lý do chính đáng để lưu ngày theo cách đó trong tệp của bạn. Một thói quen nên tập sớm: đọc thất bại sẽ lặng lẽ trả về NA chứ không báo lỗi, nên luôn liếc qua kết quả sau khi đọc một thứ mới.

Chuỗi chỉ trông giống ngày thì vẫn chỉ là chuỗi: "2026-08-07" < "2026-11-01" tình cờ so sánh đúng vì thứ tự ISO trùng với thứ tự bảng chữ cái, nhưng "07/08/2026" thì sắp xếp vô nghĩa. Hãy chuyển đổi trước, rồi mới so sánh.

Đọc các định dạng khác với format =

Thế giới không viết ngày theo ISO. Với mọi trường hợp khác, as.Date() nhận đối số format =: một công thức mô tả chuỗi đầu vào, ghép từ các mã %.

Cả ba đều cho cùng một ngày. Công thức phải khớp chính xác với chuỗi, kể cả ký tự phân cách: "%d/%m/%Y" sẽ không đọc được "07-08-2026". Các mã bạn sẽ thực sự dùng:

MãÝ nghĩaVí dụ
%YNăm 4 chữ số2026
%yNăm 2 chữ số26
%mSố tháng08
%dNgày trong tháng07
%bTên tháng viết tắtAug
%BTên tháng đầy đủAugust
%aTên thứ viết tắtFri
%ATên thứ đầy đủFriday
%HGiờ (00-23)14
%MPhút30
%SGiây05

Cái bẫy kinh điển chính là "07/08/2026": ngày/tháng/năm ở hầu hết thế giới, tháng/ngày/năm ở Mỹ, và R không thể đoán tệp của bạn dùng kiểu nào. Tệ hơn, đoán sai thường vẫn "chạy được" vì cả hai cách đọc đều là ngày hợp lệ, cho đến khi gặp ngày 13 của một tháng nào đó và bị đọc thành NA (không có tháng 13). Hãy biết quy ước của dữ liệu trước khi viết công thức.

Định dạng ngày khi xuất ra

format() là hình ảnh phản chiếu của as.Date(): cùng các mã %, nhưng chạy theo chiều ngược lại, nhận Date và trả ra chuỗi.

Dùng cách này cho tiêu đề báo cáo, nhãn và tên tệp: format(Sys.Date(), "%Y-%m-%d") tạo ra những cái tên sắp xếp được theo thời gian. Hai chi tiết nên biết: %d thêm số 0 phía trước ("August 07"), và %B/%A cho ra tên theo cài đặt ngôn ngữ của hệ thống: một phiên được cấu hình tiếng Đức sẽ in "Freitag" thay vì "Friday". Hãy để việc định dạng ở rìa script, dành cho người đọc; bên trong, cứ để ngày là Date.

Tính toán ngày và tạo dãy ngày

Vì Date là số đếm ngày, phép tính hoạt động đúng như bạn mong đợi. Trừ hai ngày cho ra một đối tượng difftime:

Có ba thói quen rút ra từ khối code đó. Thứ nhất, bọc difftime trong as.numeric() trước khi tính toán, vì difftime mang theo thuộc tính đơn vị có thể gây bất ngờ cho các phép tính sau. Thứ hai, khi đơn vị quan trọng, hãy ghi rõ bằng difftime(units = ) thay vì chấp nhận đơn vị R tự chọn. Thứ ba, cộng một số thông thường nghĩa là cộng ngày: base R không có + 1 month, vì "một tháng" không phải số ngày cố định.

Để bước theo lịch, dùng seq(), hàm hiểu được "day", "week", "month", "quarter" và "year":

Dãy theo tháng là công cụ chuẩn cho kỳ báo cáo và các mốc trên trục. Một điểm lạ: nếu bắt đầu dãy tháng từ ngày 31, R vẫn đếm ngày thật qua những tháng không có ngày 31, nên mục "tháng Hai" rơi vào đầu tháng Ba. Hãy neo dãy tháng vào ngày 1 rồi suy ra mọi thứ khác từ đó.

Lấy từng phần của một ngày

Base R có hai hàm trích xuất tiện lợi và một cách làm chung. weekdays() và months() trả về tên; để lấy số, đi vòng qua format():

Cách dùng format() rồi as.integer() trông có vẻ vòng vèo, nhưng đó là cách làm chuẩn trong base R, và nó được vector hóa: đưa vào cả một cột ngày, bạn nhận lại cả một cột năm. Đó chính xác là điều bạn cần sau khi nhập một bộ dữ liệu có ngày tháng từ tệp CSV: đọc cột ngày một lần bằng as.Date(), rồi tạo ra các cột năm và tháng để nhóm dữ liệu.

Khi ngày là chưa đủ: POSIXct và lubridate

Date không có khái niệm giờ. Ngay khi dữ liệu có dấu thời gian (dòng log, số đo cảm biến, "đơn hàng đặt lúc 14:32"), bạn cần POSIXct, lớp ngày giờ của R: số giây kể từ 1970 thay vì số ngày, và có xét đến múi giờ.

Mọi thứ đều chuyển sang được: cùng các mã % (giờ có thêm %H, %M, %S) dùng cho việc đọc và cho format(), phép trừ cho ra difftime, Sys.time() là "bây giờ". Gánh nặng mới là múi giờ: hãy truyền tz = một cách tường minh khi đọc, vì nếu không, hai phiên ở hai múi giờ khác nhau sẽ đọc cùng một chuỗi thành hai thời điểm khác nhau. Và đừng lưu dữ liệu lịch dưới dạng POSIXct "cho chắc": một ngày sinh lưu dưới dạng nửa đêm ở múi giờ nào đó có thể bị in thành ngày hôm trước ở múi giờ khác, một bug thực sự khổ sở để truy tìm. Ngày nguyên: Date. Thời điểm: POSIXct.

Cuối cùng, package lubridate là gương mặt thân thiện hơn mà phần lớn code tidyverse khoác lên tất cả những thứ trên, đáng dùng khi bạn đã hiểu cơ chế của base R:

library(lubridate)

ymd("2026-08-07")            # order-based parsing: no format string
mdy("08/07/2026")            # ...you just say which order the parts come in
dmy("7 August 2026")
floor_date(Sys.Date(), "month")   # snap to the start of the period

ymd(), mdy() và dmy() thay công thức định dạng bằng một khai báo đơn giản về thứ tự các phần, còn floor_date() giải bài toán "nhóm các dấu thời gian này theo tháng" chỉ trong một lần gọi. lubridate vẫn chạy trên cùng các lớp Date/POSIXct bên dưới: một vô lăng tốt hơn, không phải một động cơ khác.

Những điều cần nhớ

  • Date là số ngày kể từ 1970-01-01, được trang điểm để in ra như ngày; unclass() cho thấy con số đó.
  • as.Date("2026-08-07") đọc ISO trực tiếp; mọi định dạng khác cần công thức format = gồm các mã %, và đọc thất bại sẽ lặng lẽ trả về NA.
  • format(date, "...") là chiều ngược lại: cùng các mã, tạo ra chuỗi để hiển thị.
  • Phép trừ cho ra difftime: đặt units = tường minh, dùng as.numeric() trước khi tính toán. + n cộng thêm ngày; với tháng, dùng seq(by = "month").
  • weekdays(), months() và cách làm as.integer(format(...)) lấy ra từng phần, có vector hóa.
  • Date cho ngày nguyên, POSIXct (với tz tường minh) cho dấu thời gian; lubridate làm cả hai thân thiện hơn.

Tiếp theo: làm gì khi R báo lỗi, cách đọc thông báo lỗi và gỡ lỗi một cách có phương pháp.

Câu hỏi thường gặp

Làm sao để chuyển chuỗi thành ngày trong R?

Dùng as.Date(). Nếu chuỗi theo định dạng ISO thì chạy ngay: as.Date("2026-08-07"). Mọi cách viết khác đều cần một công thức format = ghép từ các mã %: as.Date("07/08/2026", format = "%d/%m/%Y"). Khi đọc thất bại, kết quả là NA chứ không phải lỗi, nên hãy kiểm tra kết quả.

Làm sao để lấy ngày hôm nay trong R?

Sys.Date() trả về hôm nay dưới dạng đối tượng Date; Sys.time() trả về ngày giờ hiện tại dưới dạng POSIXct. Cả hai đều lấy từ đồng hồ và cài đặt múi giờ trên máy của bạn.

Làm sao để tính khoảng cách giữa hai ngày trong R?

Trừ chúng cho nhau: as.Date("2026-08-07") - as.Date("2026-01-01") cho một difftime 218 ngày. Bọc nó trong as.numeric() để dùng con số đó trong phép tính, hoặc gọi difftime(d1, d2, units = "weeks") để tự chọn đơn vị thay vì để R chọn.

Date và POSIXct trong R khác nhau thế nào?

Date lưu số ngày nguyên (bên trong là số ngày kể từ 1970-01-01) và không biết gì về giờ hay múi giờ. POSIXct lưu một thời điểm (số giây kể từ 1970) và có xét đến múi giờ. Dùng Date cho dữ liệu lịch như ngày sinh và hạn chót; chỉ dùng POSIXct khi giờ trong ngày quan trọng, như dấu thời gian và log.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu