Mini-challenge: In lịch tháng hiện tại
Viết chương trình Java in lịch tháng giống lệnh cal trên Unix — luyện tư duy I-P-O, java.time API, printf formatting, và xử lý edge case.
Đây là bài thực hành khép lại Module 1. Không có lý thuyết mới — chỉ có một bài toán thực tế để bạn áp dụng những gì đã học: tư duy I-P-O, cấu trúc class Java, console output, và lần đầu làm quen với java.time API.
🎯 Đề bài
Viết chương trình Java in lịch tháng hiện tại ra console, giống lệnh cal trên Unix.
Output mẫu (với tháng 4/2026):
April 2026
Mon Tue Wed Thu Fri Sat Sun
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
Yêu cầu:
- Tự động lấy tháng/năm hiện tại (không hardcode)
- Header tuần bắt đầu từ Monday (chuẩn ISO)
- Căn cột đúng — các ngày thẳng hàng theo thứ
- Cuối tháng xuống dòng đúng chỗ
🔍 Phân tích I-P-O
Trước khi viết code, hãy suy nghĩ theo ba bước:
Input: Không có input từ người dùng — chương trình tự lấy thời gian hệ thống.
Processing:
- Xác định tháng và năm hiện tại
- Lấy tên tháng (January, February...)
- Xác định ngày 1 của tháng rơi vào thứ mấy (để căn lề đầu hàng)
- Đếm số ngày trong tháng
- In từng ngày, xuống dòng sau mỗi Chủ nhật
Output: Lịch tháng dạng text, căn cột, ra console.
📦 Công cụ Java cần dùng
Bạn chưa học java.time chi tiết — Module sau sẽ dạy kỹ. Ở đây chỉ cần biết đủ để dùng:
| API | Dùng để làm gì |
|---|---|
YearMonth.now() | Lấy tháng/năm hiện tại từ đồng hồ hệ thống |
yearMonth.getYear() | Lấy năm (ví dụ: 2026) |
yearMonth.getMonth() | Lấy enum Month (JANUARY, FEBRUARY...) |
yearMonth.lengthOfMonth() | Số ngày trong tháng (28, 29, 30, hoặc 31) |
yearMonth.atDay(1).getDayOfWeek().getValue() | Thứ của ngày 1: 1=Monday, 7=Sunday (ISO-8601) |
month.getDisplayName(TextStyle.FULL, Locale.ENGLISH) | Tên tháng tiếng Anh: "April", "May"... |
System.out.printf("%3d ", n) | In số có padding: 1, 10, 30 |
▶️ Starter code
Copy file này, điền vào các TODO:
import java.time.YearMonth;
import java.time.format.TextStyle;
import java.util.Locale;
public class LichThang {
public static void main(String[] args) {
YearMonth thangHienTai = YearMonth.now();
// TODO: lay ten thang (tieng Anh) va nam
// TODO: in tieu de " April 2026"
// TODO: in header "Mon Tue Wed Thu Fri Sat Sun"
// TODO: tinh ngay 1 roi vao thu may (1=Mon..7=Sun)
// TODO: in khoang trang dau hang cho cac o trong truoc ngay 1
// TODO: lap qua tung ngay, in so, xuong dong sau Chu nhat
}
}
Chạy thử:
javac LichThang.java
java LichThang
Dành 15-20 phút thử tự làm trước khi xem gợi ý.
💡 Gợi ý
Lấy tên tháng:
String tenThang = thangHienTai.getMonth()
.getDisplayName(TextStyle.FULL, Locale.ENGLISH);
Ngày 1 rơi vào thứ mấy:
int ngayDauTuan = thangHienTai.atDay(1).getDayOfWeek().getValue();
// 1 = Monday, 2 = Tuesday, ..., 7 = Sunday
In số có padding 3 ký tự:
System.out.printf("%3d ", ngay);
// " 1 ", " 9 ", "10 ", "30 "
In ô trống đầu hàng — nếu tháng bắt đầu thứ Tư (ngayDauTuan == 3), cần in 2 ô trống (cho Mon và Tue):
for (int i = 1; i < ngayDauTuan; i++) {
System.out.print(" "); // 4 ky tu, khop voi "%3d "
}
Xuống dòng mỗi Chủ nhật — dùng biến cot từ 1 đến 7, reset sau khi in cột 7:
if (cot == 7) {
System.out.println();
cot = 1;
} else {
cot++;
}
✅ Lời giải
import java.time.YearMonth;
import java.time.format.TextStyle;
import java.util.Locale;
public class LichThang {
public static void main(String[] args) {
YearMonth thangHienTai = YearMonth.now();
String tenThang = thangHienTai.getMonth()
.getDisplayName(TextStyle.FULL, Locale.ENGLISH);
int nam = thangHienTai.getYear();
// In tieu de va header
System.out.printf(" %s %d%n", tenThang, nam);
System.out.println("Mon Tue Wed Thu Fri Sat Sun");
// Ngay 1 roi vao thu may? (1=Mon..7=Sun theo ISO-8601)
int ngayDauTuan = thangHienTai.atDay(1).getDayOfWeek().getValue();
int soNgay = thangHienTai.lengthOfMonth();
// In o trong dau hang: neu ngay 1 la Thu Tu (3), in 2 o trong
for (int i = 1; i < ngayDauTuan; i++) {
System.out.print(" ");
}
// In tung ngay, xuong dong sau Chu Nhat
int cot = ngayDauTuan;
for (int ngay = 1; ngay <= soNgay; ngay++) {
System.out.printf("%3d ", ngay);
if (cot == 7) {
System.out.println();
cot = 1;
} else {
cot++;
}
}
// Xuong dong o cuoi neu hang cuoi chua day
if (cot != 1) System.out.println();
}
}
Giải thích từng phần:
YearMonth.now()— lấy tháng hiện tại từ đồng hồ hệ thống; không hardcode tháng/năm nên code luôn đúng.printf(" %s %d%n", ...)— in tiêu đề với 6 khoảng trắng đầu để căn giữa (cho tuần 7 cột × 4 ký tự = 28 ký tự).ngayDauTuan— biết ngày 1 rơi vào thứ mấy để tính số ô trống đầu hàng đầu tiên.for (i=1; i < ngayDauTuan; i++)— inngayDauTuan - 1ô trống. Nếu tháng bắt đầu Monday (1): 0 ô trống. Nếu bắt đầu Sunday (7): 6 ô trống.cot— đếm cột từ 1 đến 7. Khicot == 7(vừa in Chủ nhật), xuống dòng và reset về 1.if (cot != 1) println()— nếu hàng cuối chưa đủ 7 ngày (không kết thúc đúng Chủ nhật), vẫn xuống dòng cuối cùng.
🎓 Mở rộng (nếu bạn muốn đi xa hơn)
Đã hoàn thành bài cơ bản? Thử tiếp:
Mức 1 — Nhận tháng/năm từ command-line args:
// java LichThang 8 2025 -> in lich thang 8/2025
if (args.length == 2) {
int thang = Integer.parseInt(args[0]);
int nam = Integer.parseInt(args[1]);
thangHienTai = YearMonth.of(nam, thang);
}
Mức 2 — Highlight ngày hôm nay bằng dấu []:
import java.time.LocalDate;
LocalDate homNay = LocalDate.now();
// Neu ngay == homNay.getDayOfMonth() va thang khop
// -> in "[%2d]" thay vi "%3d "
Mức 3 — Hỗ trợ tiếng Việt:
Locale localeViet = Locale.forLanguageTag("vi");
String tenThang = thangHienTai.getMonth()
.getDisplayName(TextStyle.FULL, localeViet);
// -> "tháng 4", "tháng 5"...
Mức 4 — Tuần bắt đầu từ Chủ nhật (kiểu US):
Điều chỉnh ngayDauTuan: Sunday (7) trở thành cột 1, Monday trở thành cột 2...
📚 Học thêm
- YearMonth API — Java 21 — đầy đủ các method:
atDay,lengthOfMonth,isLeapYear... - DayOfWeek API — Java 21 — enum thứ trong tuần,
getValue()trả 1-7 - Formatter — System.out.printf format syntax — toàn bộ format specifiers:
%d,%s,%n,%-5s...
✨ Điều bạn vừa làm được
Hoàn thành mini-challenge này, bạn đã:
- Dùng
java.time.YearMonth— modern date-time API của Java 8+, chuẩn thực tế thay thếDate/Calendarcũ - Áp dụng
System.out.printfđể format output có padding — kỹ năng cần thiết mọi lúc in bảng, report, log - Kết hợp
forloop + biến đếm cột + modulo logic — pattern phổ biến khi render grid/table - Xử lý edge case: tháng bắt đầu không phải Monday, hàng cuối không đủ 7 ngày
- Áp dụng tư duy I-P-O — phân tích bài toán trước khi viết một dòng code
Chúc mừng — bạn đã hoàn thành Module 1! Module 2 sẽ đi vào cú pháp Java: biến, kiểu dữ liệu, toán tử, và cấu trúc điều kiện.
Bài này có giúp bạn hiểu bản chất không?
Hỏi đáp về bài này
Chưa có câu hỏi
Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).
Đặt câu hỏi đầu tiên