应使用 LocalDate 替代 Calendar:LocalDate 不可变、线程安全、月份1~12直观,避免 Calendar 的可变性、时区陷阱和月份从0开始等缺陷;日历打印需结合 DayOfWeek 和 TemporalAdjusters 计算起止日,并用 DateTimeFormatter 配合 Locale 格式化输出。

用 LocalDate 替代 Calendar 是更安全的选择
Java 8 引入的 LocalDate 是不可变、线程安全、语义清晰的日历表示方式,而老式 Calendar 类存在可变状态、时区陷阱和月份从 0 开始等反直觉设计。除非维护遗留代码,否则别碰 Calendar。
常见错误现象:calendar.set(Calendar.MONTH, 1) 实际设的是二月(因为一月是 0),调试时容易漏掉这个偏移;Calendar 实例在多线程下共享会互相覆盖字段值。
-
LocalDate的月份是 1~12,年/月/日构造直观:LocalDate.of(2024, 3, 15) - 所有操作返回新对象,不修改原值,避免副作用
- 默认使用系统默认时区的本地日期,不涉及时间或时区计算时更轻量
打印当月日历:先获取首尾日期,再对齐星期
核心不是“画表格”,而是算出当月 1 日是星期几、该月有多少天,然后补上前导空格和后续空白日。用 LocalDate + DayOfWeek 就能干净搞定。
使用场景:控制台输出类似 Unix cal 命令的文本日历,不需要 GUI 或 HTML 渲染。
立即学习“Java免费学习笔记(深入)”;
- 用
localDate.withDayOfMonth(1)获取当月第一天 - 用
firstDay.getDayOfWeek().getValue()得到星期几(周一为 1,周日为 7) - 用
localDate.lengthOfMonth()拿天数,不用手动判断闰年或大小月 - 注意:
DayOfWeek.SUNDAY.getValue()是 7,但很多日历习惯以周日为首列,需按需调整对齐逻辑
格式化输出必须用 DateTimeFormatter,别拼字符串
直接调用 localDate.toString() 只能得 2024-03-15 这种 ISO 格式,要打印“三月 15 日,星期五”这类中文格式,必须用 DateTimeFormatter,且要配对 Locale。
容易踩的坑:DateTimeFormatter.ofPattern("MMM dd, EEEE") 在英文环境输出正常,但中文环境下若没指定 Locale.CHINA,月份和星期仍是英文缩写。
- 正确写法:
DateTimeFormatter.ofPattern("MMMM dd, EEEE", Locale.CHINA) - 月份全称用
MMMM,缩写用MMM;星期同理:EEEE是全称,“星期五”,EEE是“周五” - 避免用
SimpleDateFormat—— 它不是线程安全的,且已过时
跨月显示(如带前后月部分日期)需手动计算边界
标准日历视图常显示上月最后几天 + 本月全部 + 下月头几天,凑满 6 行 × 7 列。这一步没有现成 API,得靠 LocalDate.minusDays() 和 plusDays() 推算起止点。
性能影响极小,但逻辑容易写错:比如把“从周日开始排”误解为“从本月 1 日往前推到周日”,实际应是“从本月 1 日往前推到它所属星期的周日”。
- 先求本月 1 日:
LocalDate first = date.withDayOfMonth(1) - 再求该周周日:
LocalDate start = first.with(TemporalAdjusters.previousOrSame(DayOfWeek.SUNDAY)) - 结束日是
start.plusDays(41)(6×7=42 天,索引 0~41) - 遍历时用
current.getMonth() == date.getMonth()判断是否属于当前月,决定是否高亮或加粗
真正麻烦的是对齐和换行控制——LocalDate 不管你终端宽度多少,得自己按 7 天一组插入换行符。这事说清了就结束。











