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 — đây là framework bạn đã học ở Bài 05 — Tư duy I-P-O:
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.
Learning outcomes của module này: bài mini-challenge tổng hợp hai outcome chính — O3 (Implement và chạy được chương trình Java hoàn chỉnh từ javac đến java) và O4 (Apply tư duy I-P-O để phân tích bài toán trước khi viết code). Bạn đang làm đúng quy trình: phân tích I-P-O trước, rồi mới code.
📦 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. Quá trình compile và chạy lại y hệt Bài 06 — javac và java:
| 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.print(String.format("%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.print(String.format(" %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.print(String.format("%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 để thụt đầu vài khoảng trắng cho tiêu đề (header "Mon Tue Wed Thu Fri Sat Sun" rộng 27 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...
Thử ngẫmlệnh cal thật còn in được cả năm. Với I-P-O bạn vừa phân tích, bước processing nào giữ nguyên, bước nào phải lặp thêm một tầng? Code hiện tại của bạn chịu được thay đổi đó không?
📚 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 tiếp theo: Tổng kết module
Bài này có giúp bạn hiểu bản chất không?
Bài này đáng gửi cho bạn học cùng?
Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.
Compile & Run — javac, bytecode, và vòng đời một chương trình Java
Bài tiếpModule 1 — Tổng kết & cheat sheet
Lộ trình Junior Java Backend Developer
Chặng 2/21 · Lập trình từ số 0 với Java — bài 8/32 của chặng
Thảo luận & góp ý
Hỏi đáp về bài này