Java Foundations
8/39
Bài 8 / 39~18 phútNhập môn & Tư duy lập trìnhLộ trình · chặng 02/21Miễn phí… lượt xem

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:

  1. Xác định tháng và năm hiện tại
  2. Lấy tên tháng (January, February...)
  3. Xác định ngày 1 của tháng rơi vào thứ mấy (để căn lề đầu hàng)
  4. Đếm số ngày trong tháng
  5. 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:

APIDù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:

Java
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ử:

Bash
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:

Java
String tenThang = thangHienTai.getMonth()
    .getDisplayName(TextStyle.FULL, Locale.ENGLISH);

Ngày 1 rơi vào thứ mấy:

Java
int ngayDauTuan = thangHienTai.atDay(1).getDayOfWeek().getValue();
// 1 = Monday, 2 = Tuesday, ..., 7 = Sunday

In số có padding 3 ký tự:

Java
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):

Java
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:

Java
if (cot == 7) {
    System.out.println();
    cot = 1;
} else {
    cot++;
}

✅ Lời giải

✅ Lời giải — xem sau khi đã thử
Java
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++) — in ngayDauTuan - 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. Khi cot == 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
// 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 []:

Java
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:

Java
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

✨ Đ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/Calendar cũ
  • Á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 for loop + 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.

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

Bài tiếp theo

Module 1 — Tổng kết & cheat sheet