Java Foundations/Mini-challenge: In lịch tháng hiện tại
7/35
Bài 7 / 35~18 phútNhập môn & Tư duy lập trìnhMiễ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:

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.

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

Đặt 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