Java — Từ Zero đến Senior/Nhập môn & Tư duy lập trình/Mini-challenge: In lịch tháng hiện tại
7/7
~18 phútNhập môn & Tư duy lập trình

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:

  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.

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

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:

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++) — 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 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

✨ Đ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
  • Á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.