Đâ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 ý
💡 💡 Gợi ý — đọc khi bị kẹt
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
ℹ️ ✅ Lời giải — xem sau khi đã thử
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.