# Python自动化办公:打造专业级农历Excel日历全攻略
在传统节日安排、个人日程管理或企业考勤系统中,农历日期的重要性不言而喻。本文将带您从零开始,使用Python的lunardate和openpyxl库,构建一个功能完善、样式精美的Excel农历日历生成器。不同于基础教程,我们将深入探讨日期转换原理、样式优化技巧以及实际业务场景中的应用方案。
## 1. 环境准备与工具选型
在开始编码前,我们需要搭建合适的开发环境并理解各工具库的核心价值。Python生态中有多个处理农历日期的库,如lunarcalendar、chinese-calendar等,但lunardate以其简洁API和准确算法成为我们的首选。
**核心库安装**:
```bash
pip install lunardate openpyxl pandas
```
**版本兼容性检查**:
```python
import sys
print(f"Python版本: {sys.version}")
# 输出示例:Python 3.9.7 (应确保使用3.7+版本)
```
**库功能对比**:
| 库名称 | 农历支持 | 节气计算 | 节假日识别 | Excel集成 | 维护状态 |
|------------------|----------|----------|------------|-----------|----------|
| lunardate | ✓ | ✗ | ✗ | ✗ | 活跃 |
| chinese-calendar | ✓ | ✓ | ✓ | ✗ | 活跃 |
| lunar-python | ✓ | ✓ | ✓ | ✗ | 一般 |
| ephem | ✓ | ✓ | ✗ | ✗ | 活跃 |
> 提示:虽然chinese-calendar功能更全面,但lunardate的轻量级特性更适合单纯的日期转换场景
**常见安装问题排查**:
- 若遇到C编译错误,可尝试使用预编译版本:
```bash
pip install --prefer-binary lunardate
```
- 权限问题可添加`--user`参数或使用虚拟环境
## 2. 农历日期转换核心技术
理解公历与农历的转换逻辑是构建可靠日历的基础。lunardate库采用天文算法而非简单查表法,支持1900-2100年的日期转换,准确度经实际验证。
**基础转换示例**:
```python
from lunardate import LunarDate
from datetime import datetime
# 公历转农历
solar_date = datetime(2023, 9, 10)
lunar_date = LunarDate.fromSolarDate(solar_date.year,
solar_date.month,
solar_date.day)
print(f"农历: {lunar_date.year}年{lunar_date.month}月{lunar_date.day}日")
# 农历转公历
lunar_new_year = LunarDate(2023, 1, 1) # 2023年春节
solar_new_year = lunar_new_year.toSolarDate()
print(f"公历: {solar_new_year.strftime('%Y-%m-%d')}")
```
**特殊日期处理**:
农历存在闰月情况,需要特别处理:
```python
# 判断是否为闰月
leap_month = LunarDate(2023, 4, 1).isleap
print(f"2023年4月是否为闰月: {'是' if leap_month else '否'}")
# 闰月日期转换
leap_date = LunarDate(2023, 4, 1, isleap=True)
print(leap_date.toSolarDate())
```
**性能优化技巧**:
当需要批量转换大量日期时,可预计算月份数据:
```python
def generate_month_lunar(year, month):
"""生成整月农历日期对照表"""
start_date = datetime(year, month, 1)
end_date = datetime(year, month + 1, 1) if month < 12 else datetime(year + 1, 1, 1)
delta = end_date - start_date
lunar_dates = []
for i in range(delta.days):
current_date = start_date + timedelta(days=i)
lunar_dates.append(LunarDate.fromSolarDate(
current_date.year, current_date.month, current_date.day))
return lunar_dates
```
## 3. Excel日历生成实战
openpyxl提供了完整的Excel操作接口,我们可以创建专业级的日历模板。以下实现支持农历显示、周末高亮、节假日标记等功能。
**基础日历框架**:
```python
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment
from openpyxl.utils import get_column_letter
def create_calendar_template(year):
wb = Workbook()
ws = wb.active
ws.title = f"{year}日历"
# 设置列宽
for col in range(1, 8):
ws.column_dimensions[get_column_letter(col)].width = 12
# 星期标题
weekdays = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"]
for i, day in enumerate(weekdays, 1):
cell = ws.cell(row=1, column=i, value=day)
cell.font = Font(bold=True)
cell.alignment = Alignment(horizontal='center')
return wb, ws
```
**日期填充算法**:
```python
def fill_calendar_dates(ws, year, month):
first_day = datetime(year, month, 1)
start_row = 3 # 从第3行开始填充日期
# 计算当月天数
if month == 12:
next_month = datetime(year + 1, 1, 1)
else:
next_month = datetime(year, month + 1, 1)
days_in_month = (next_month - first_day).days
# 定位起始列 (0=周一, 6=周日)
start_col = first_day.weekday()
# 填充日期
for day in range(1, days_in_month + 1):
current_date = datetime(year, month, day)
lunar_date = LunarDate.fromSolarDate(year, month, day)
# 计算单元格位置
offset = day - 1 + start_col
row = start_row + offset // 7
col = offset % 7 + 1
# 写入公历日期
cell = ws.cell(row=row, column=col, value=day)
cell.alignment = Alignment(horizontal='left', vertical='top')
# 添加农历备注
ws.cell(row=row, column=col).comment = f"农历{lunar_date.month}月{lunar_date.day}"
# 周末高亮
if col >= 6: # 周六、周日
cell.fill = PatternFill(start_color="FFDDDD", fill_type="solid")
```
**样式增强功能**:
```python
def enhance_calendar_style(ws):
# 设置全局字体
for row in ws.iter_rows():
for cell in row:
cell.font = Font(name='微软雅黑', size=10)
# 行高设置
for row in range(1, ws.max_row + 1):
ws.row_dimensions[row].height = 20
# 添加条件格式
from openpyxl.formatting.rule import FormulaRule
from openpyxl.styles import Font
red_font = Font(color="FF0000")
weekend_rule = FormulaRule(formula=['WEEKDAY(A1,2)>5'], font=red_font)
ws.conditional_formatting.add(f'A1:{get_column_letter(7)}{ws.max_row}', weekend_rule)
```
## 4. 高级功能实现
基础日历生成后,我们可以添加更多实用功能以满足不同场景需求。
**节假日自动标记**:
```python
holidays = {
(1, 1): "元旦",
(5, 1): "劳动节",
(10, 1): "国庆节",
# 添加更多公历节日...
}
lunar_holidays = {
(1, 1): "春节",
(1, 15): "元宵节",
(5, 5): "端午节",
(8, 15): "中秋节",
# 添加更多农历节日...
}
def mark_holidays(ws, year, month):
for row in ws.iter_rows(min_row=3):
for cell in row:
if cell.value and isinstance(cell.value, int):
# 检查公历节日
if (month, cell.value) in holidays:
cell.font = Font(color="FF0000", bold=True)
cell.comment.text += f"\n{holidays[(month, cell.value)]}"
# 获取农历日期
lunar_day = LunarDate.fromSolarDate(year, month, cell.value)
# 检查农历节日
if (lunar_day.month, lunar_day.day) in lunar_holidays:
cell.font = Font(color="FF0000", bold=True)
cell.comment.text += f"\n{lunar_holidays[(lunar_day.month, lunar_day.day)]}"
```
**多月份批量生成**:
```python
def generate_year_calendar(year, filename):
wb = Workbook()
wb.remove(wb.active) # 删除默认sheet
for month in range(1, 13):
ws = wb.create_sheet(title=f"{month}月")
setup_calendar_template(ws, year, month)
fill_calendar_dates(ws, year, month)
enhance_calendar_style(ws)
mark_holidays(ws, year, month)
wb.save(filename)
# 使用示例
generate_year_calendar(2023, "2023年日历.xlsx")
```
**性能优化方案**:
当处理多年份日历时,可采用以下优化策略:
1. 使用`write_only=True`模式减少内存消耗
2. 实现日期计算的缓存机制
3. 采用多进程生成不同月份
```python
from multiprocessing import Pool
def generate_month_wrapper(args):
year, month = args
wb = Workbook(write_only=True)
ws = wb.create_sheet()
# ...生成单月日历...
return ws
def parallel_generate_year(year):
with Pool() as p:
months = [(year, m) for m in range(1, 13)]
sheets = p.map(generate_month_wrapper, months)
# 合并结果
master_wb = Workbook()
for sheet in sheets:
master_wb.add_sheet(sheet)
return master_wb
```
## 5. 企业级应用扩展
将基础日历生成器扩展为满足企业需求的解决方案,需要增加以下功能:
**考勤系统集成**:
```python
def add_attendance_markers(ws, attendance_data):
"""添加考勤标记(迟到、早退、请假等)"""
for date_str, status in attendance_data.items():
date = datetime.strptime(date_str, "%Y-%m-%d")
cell = find_date_cell(ws, date.day, date.month)
if status == "late":
cell.fill = PatternFill(start_color="FFF2CC", fill_type="solid")
elif status == "leave":
cell.fill = PatternFill(start_color="E6B8AF", fill_type="solid")
# 其他状态标记...
def find_date_cell(ws, day, month):
"""根据日期查找对应单元格"""
for row in ws.iter_rows():
for cell in row:
if cell.value == day and ws.title.startswith(f"{month}"):
return cell
return None
```
**数据库集成方案**:
```python
import sqlite3
from contextlib import closing
def save_to_database(filename):
"""将日历数据保存到SQLite数据库"""
with closing(sqlite3.connect("calendar.db")) as conn:
c = conn.cursor()
# 创建表
c.execute("""CREATE TABLE IF NOT EXISTS calendar
(date TEXT PRIMARY KEY,
solar_date TEXT,
lunar_date TEXT,
holiday TEXT)""")
# 从Excel读取数据
wb = load_workbook(filename)
for ws in wb:
for row in ws.iter_rows(values_only=True):
if row and isinstance(row[0], int): # 日期行
date_str = f"{year}-{ws.title[:-1]}-{row[0]}"
lunar_date = LunarDate.fromSolarDate(year, int(ws.title[:-1]), row[0])
holiday = detect_holiday(year, int(ws.title[:-1]), row[0])
c.execute("INSERT INTO calendar VALUES (?,?,?,?)",
(date_str, date_str,
f"{lunar_date.year}-{lunar_date.month}-{lunar_date.day}",
holiday))
conn.commit()
```
**Web服务集成**:
使用Flask创建REST API供其他系统调用:
```python
from flask import Flask, jsonify, request
import tempfile
app = Flask(__name__)
@app.route('/api/generate-calendar', methods=['POST'])
def generate_calendar_api():
data = request.json
year = data.get('year')
options = data.get('options', {})
with tempfile.NamedTemporaryFile(suffix='.xlsx') as tmp:
generate_year_calendar(year, tmp.name, **options)
return send_file(tmp.name, as_attachment=True,
download_name=f"{year}日历.xlsx")
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
```
## 6. 错误处理与调试
健壮的生产环境代码需要完善的错误处理机制。
**常见异常处理**:
```python
def safe_lunar_conversion(year, month, day):
try:
lunar_date = LunarDate.fromSolarDate(year, month, day)
return lunar_date
except ValueError as e:
print(f"日期转换错误: {year}-{month}-{day} - {str(e)}")
# 返回一个默认值或进行其他处理
return LunarDate(year, 1, 1) # 返回当年春节作为默认值
except Exception as e:
print(f"未知错误: {str(e)}")
raise # 重新抛出未知异常
```
**日期验证装饰器**:
```python
from functools import wraps
def validate_date_params(func):
@wraps(func)
def wrapper(year, month=None, day=None):
if not (1900 <= year <= 2100):
raise ValueError("年份必须在1900-2100之间")
if month and not (1 <= month <= 12):
raise ValueError("月份必须在1-12之间")
if day and not (1 <= day <= 31):
raise ValueError("日期必须在1-31之间")
return func(year, month, day)
return wrapper
@validate_date_params
def get_lunar_date_details(year, month, day):
return LunarDate.fromSolarDate(year, month, day)
```
**日志记录配置**:
```python
import logging
from logging.handlers import RotatingFileHandler
def setup_logging():
logger = logging.getLogger("calendar_generator")
logger.setLevel(logging.DEBUG)
# 文件日志(最大10MB,保留3个备份)
file_handler = RotatingFileHandler(
'calendar.log', maxBytes=10*1024*1024, backupCount=3)
file_handler.setFormatter(logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
# 控制台日志
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
logger.addHandler(file_handler)
logger.addHandler(console_handler)
return logger
logger = setup_logging()
```
## 7. 部署与自动化
将日历生成器集成到日常工作流程中,实现自动化运行。
**Windows任务计划**:
创建批处理文件`generate_calendar.bat`:
```bat
@echo off
set PYTHONPATH=C:\Python39
%PYTHONPATH%\python.exe C:\path\to\calendar_generator.py --year %date:~0,4%
```
**Linux Cron作业**:
添加到crontab每月1日运行:
```bash
0 0 1 * * /usr/bin/python3 /path/to/calendar_generator.py --year $(date +%Y)
```
**邮件自动发送**:
```python
import smtplib
from email.mime.application import MIMEApplication
from email.mime.multipart import MIMEMultipart
def send_calendar_email(recipient, filename):
msg = MIMEMultipart()
msg['Subject'] = f"{datetime.now().year}年日历"
msg['From'] = 'calendar@company.com'
msg['To'] = recipient
with open(filename, 'rb') as f:
part = MIMEApplication(f.read(), Name=filename)
part['Content-Disposition'] = f'attachment; filename="{filename}"'
msg.attach(part)
with smtplib.SMTP('smtp.company.com') as s:
s.send_message(msg)
```
## 8. 替代方案与扩展思路
当项目需求超出基础日历时,可考虑以下扩展方向:
**Web日历应用**:
使用FullCalendar.js等前端库配合后端API:
```python
# Flask API端点示例
@app.route('/api/events')
def get_calendar_events():
events = []
for month in range(1, 13):
lunar_holidays = get_lunar_holidays(year, month)
events.extend([{
'title': holiday['name'],
'start': holiday['date'],
'color': '#FF0000'
} for holiday in lunar_holidays])
return jsonify(events)
```
**移动端集成**:
将日历数据导出为ICS格式:
```python
from icalendar import Calendar, Event
def create_ics_calendar(year, filename):
cal = Calendar()
cal.add('prodid', '-//Lunar Calendar//example.com//')
cal.add('version', '2.0')
for month in range(1, 13):
holidays = get_holidays(year, month)
for holiday in holidays:
event = Event()
event.add('summary', holiday['name'])
event.add('dtstart', holiday['date'])
event.add('dtend', holiday['date'] + timedelta(days=1))
cal.add_component(event)
with open(filename, 'wb') as f:
f.write(cal.to_ical())
```
**数据分析扩展**:
```python
import pandas as pd
def analyze_attendance(calendar_file, attendance_file):
# 读取日历数据
calendar_data = pd.read_excel(calendar_file, sheet_name=None)
# 读取考勤数据
attendance = pd.read_csv(attendance_file)
# 合并分析
analysis_results = []
for month, df in calendar_data.items():
month_num = int(month[:-1])
month_attendance = attendance[
(attendance['date'].dt.month == month_num)]
# 计算各类型考勤统计
stats = month_attendance['status'].value_counts().to_dict()
analysis_results.append({
'month': month_num,
**stats
})
return pd.DataFrame(analysis_results)
```
## 9. 完整实现代码示例
以下是一个整合所有功能的完整实现:
```python
"""
专业级农历Excel日历生成器
功能:
1. 支持1900-2100年公历转农历
2. 自动标记传统节日
3. 周末高亮显示
4. 支持多月份批量生成
5. 可定制样式和布局
"""
import argparse
from datetime import datetime, timedelta
from lunardate import LunarDate
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from openpyxl.utils import get_column_letter
from typing import Dict, Tuple, Optional
# 节假日定义
SOLAR_HOLIDAYS = {
(1, 1): "元旦",
(5, 1): "劳动节",
(10, 1): "国庆节"
}
LUNAR_HOLIDAYS = {
(1, 1): "春节",
(1, 15): "元宵节",
(5, 5): "端午节",
(8, 15): "中秋节"
}
class LunarCalendarGenerator:
def __init__(self, year: int):
self.year = year
self.styles = {
'header': Font(name='微软雅黑', size=12, bold=True, color='FFFFFF'),
'weekend': Font(name='微软雅黑', size=10, bold=True, color='FF0000'),
'holiday': Font(name='微软雅黑', size=10, bold=True, color='FF0000'),
'normal': Font(name='微软雅黑', size=10),
'header_fill': PatternFill(start_color='4F81BD', fill_type='solid'),
'weekend_fill': PatternFill(start_color='F2F2F2', fill_type='solid')
}
def generate_year_calendar(self, filename: str):
"""生成整年日历"""
wb = Workbook()
wb.remove(wb.active) # 删除默认sheet
for month in range(1, 13):
ws = wb.create_sheet(title=f"{month}月")
self._setup_month_template(ws)
self._fill_month_dates(ws, month)
self._apply_styles(ws)
wb.save(filename)
return filename
def _setup_month_template(self, ws):
"""设置月份模板"""
# 设置列宽
for col in range(1, 8):
ws.column_dimensions[get_column_letter(col)].width = 15
# 星期标题
weekdays = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"]
for col, day in enumerate(weekdays, 1):
cell = ws.cell(row=1, column=col, value=day)
cell.font = self.styles['header']
cell.fill = self.styles['header_fill']
cell.alignment = Alignment(horizontal='center')
def _fill_month_dates(self, ws, month: int):
"""填充月份日期"""
first_day = datetime(self.year, month, 1)
start_row = 2
# 计算当月天数
if month == 12:
next_month = datetime(self.year + 1, 1, 1)
else:
next_month = datetime(self.year, month + 1, 1)
days_in_month = (next_month - first_day).days
# 定位起始列 (0=周一, 6=周日)
start_col = first_day.weekday()
# 填充日期
for day in range(1, days_in_month + 1):
current_date = datetime(self.year, month, day)
lunar_date = LunarDate.fromSolarDate(self.year, month, day)
# 计算单元格位置
offset = day - 1 + start_col
row = start_row + offset // 7
col = offset % 7 + 1
# 写入公历日期和农历
cell = ws.cell(row=row, column=col)
cell.value = day
cell.alignment = Alignment(horizontal='left', vertical='top')
# 添加农历备注
lunar_text = f"{lunar_date.month}月{lunar_date.day}"
if (lunar_date.month, lunar_date.day) in LUNAR_HOLIDAYS:
lunar_text += f"({LUNAR_HOLIDAYS[(lunar_date.month, lunar_date.day)]})"
cell.comment = lunar_text
# 标记周末
if col >= 6:
cell.font = self.styles['weekend']
cell.fill = self.styles['weekend_fill']
# 标记节日
if (month, day) in SOLAR_HOLIDAYS:
cell.font = self.styles['holiday']
cell.comment.text += f"\n{SOLAR_HOLIDAYS[(month, day)]}"
def _apply_styles(self, ws):
"""应用样式"""
thin_border = Border(left=Side(style='thin'),
right=Side(style='thin'),
top=Side(style='thin'),
bottom=Side(style='thin'))
for row in ws.iter_rows():
for cell in row:
cell.border = thin_border
if not cell.font:
cell.font = self.styles['normal']
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='生成带农历的Excel日历')
parser.add_argument('--year', type=int, default=datetime.now().year,
help='要生成的年份(默认当前年份)')
parser.add_argument('--output', type=str, default='calendar.xlsx',
help='输出文件名')
args = parser.parse_args()
print(f"开始生成{args.year}年日历...")
generator = LunarCalendarGenerator(args.year)
output_file = generator.generate_year_calendar(args.output)
print(f"日历已生成并保存到: {output_file}")
```
## 10. 实际应用案例
**企业考勤系统集成**:
```python
def integrate_with_attendance_system(calendar_file, db_connection):
"""将日历数据导入考勤系统数据库"""
import pandas as pd
from sqlalchemy import create_engine
# 读取Excel数据
calendar_data = pd.read_excel(calendar_file, sheet_name=None)
# 创建数据库引擎
engine = create_engine(db_connection)
# 处理并导入每个月份数据
for month_name, df in calendar_data.items():
month = int(month_name[:-1])
# 提取有效日期数据
dates = []
for _, row in df.iterrows():
for col in df.columns:
if isinstance(row[col], int): # 日期单元格
date_str = f"{args.year}-{month:02d}-{row[col]:02d}"
lunar_date = LunarDate.fromSolarDate(args.year, month, row[col])
is_weekend = col >= 5 # 假设前5列是周一到周五
dates.append({
'solar_date': date_str,
'lunar_date': f"{lunar_date.year}-{lunar_date.month}-{lunar_date.day}",
'is_weekend': is_weekend,
'is_holiday': check_if_holiday(args.year, month, row[col])
})
# 转换为DataFrame并导入数据库
pd.DataFrame(dates).to_sql('calendar_dates', engine,
if_exists='append', index=False)
print("日历数据已成功导入考勤系统")
```
**学校校历生成**:
```python
def generate_school_calendar(year, terms):
"""生成包含学期安排的校历"""
wb = Workbook()
ws = wb.active
ws.title = "校历总览"
# 添加学期信息表
ws.append(["学期", "开始日期", "结束日期", "周数", "假期"])
for term in terms:
start_date = term['start']
end_date = term['end']
weeks = (end_date - start_date).days // 7
ws.append([term['name'], start_date, end_date, weeks, term['holidays']])
# 生成各月份详细日历
for month in range(1, 13):
ws = wb.create_sheet(title=f"{month}月")
fill_month_calendar(ws, year, month)
# 标记学期特殊日期
for term in terms:
if term['start'].month == month or term['end'].month == month:
mark_term_dates(ws, term)
return wb
```
**个人日程规划**:
```python
def add_personal_schedule(calendar_file, schedule_data):
"""在日历中添加个人日程"""
from openpyxl import load_workbook
wb = load_workbook(calendar_file)
for date_str, events in schedule_data.items():
date = datetime.strptime(date_str, "%Y-%m-%d")
month_sheet = f"{date.month}月"
if month_sheet in wb:
ws = wb[month_sheet]
cell = find_date_cell(ws, date.day)
if cell:
# 添加日程备注
if not cell.comment:
cell.comment = "\n".join(events)
else:
cell.comment.text += "\n" + "\n".join(events)
# 标记特殊颜色
cell.fill = PatternFill(start_color="E6EFC2", fill_type="solid")
wb.save(calendar_file)
return calendar_file
```
## 11. 性能优化与大规模处理
当需要生成多年份或大量日历时,性能成为关键考虑因素。
**内存优化技巧**:
```python
def generate_large_calendar(years, output_file):
"""高效生成多年日历"""
from openpyxl import Workbook
from openpyxl.worksheet.write_only import WriteOnlyCell
wb = Workbook(write_only=True)
for year in years:
for month in range(1, 13):
ws = wb.create_sheet(title=f"{year}-{month}")
# 添加标题行
header_row = []
for day in ["周一", "周二", "周三", "周四", "周五", "周六", "周日"]:
cell = WriteOnlyCell(ws, value=day)
cell.font = Font(bold=True)
header_row.append(cell)
ws.append(header_row)
# 填充日期
fill_month_dates_writeonly(ws, year, month)
wb.save(output_file)
```
**并行处理实现**:
```python
from concurrent.futures import ProcessPoolExecutor
def parallel_generate_calendars(years):
"""并行生成多个年份日历"""
with ProcessPoolExecutor() as executor:
futures = []
for year in years:
futures.append(executor.submit(
generate_year_calendar,
year,
f"calendar_{year}.xlsx"
))
# 等待所有任务完成
for future in futures:
try:
future.result()
except Exception as e:
print(f"生成日历出错: {str(e)}")
```
**缓存机制**:
```python
from functools import lru_cache
@lru_cache(maxsize=1024)
def get_cached_lunar_date(year, month, day):
"""带缓存的农历日期获取"""
return LunarDate.fromSolarDate(year, month, day)
def fill_dates_with_cache(ws, year, month):
"""使用缓存填充日期"""
for day in range(1, 32):
try:
lunar_date = get_cached_lunar_date(year, month, day)
# ...填充单元格...
except ValueError:
break # 无效日期
```
## 12. 用户界面与交互
为方便非技术用户使用,可添加命令行界面或简单GUI。
**增强版命令行界面**:
```python
import click
@click.command()
@click.option('--year', default=datetime.now().year,
help='要生成的年份')
@click.option('--output', default='calendar.xlsx',
help='输出文件路径')
@click.option('--template', type=click.Path(exists=True),
help='使用自定义模板文件')
@click.option('--holidays', is_flag=True,
help='包含节假日标记')
def generate_calendar(year, output, template, holidays):
"""生成带农历的Excel日历"""
click.echo(f"开始生成 {year} 年日历...")
generator = LunarCalendarGenerator(year)
if template:
generator.load_template(template)
output_file = generator.generate_year_calendar(
output,
mark_holidays=holidays
)
click.echo(f"日历已生成: {output_file}")
click.launch(output_file)
if __name__ == '__main__':
generate_calendar()
```
**简易GUI实现**:
```python
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
class CalendarApp:
def __init__(self, root):
self.root = root
self.root.title("农历日历生成器")
# 年份选择
ttk.Label(root, text="年份:").grid(row=0, column=0, padx=5, pady=5)
self.year_var = tk.IntVar(value=datetime.now().year)
ttk.Spinbox(root, from_=1900, to=2100,
textvariable=self.year_var).grid(row=0, column=1)
# 输出路径
ttk.Label(root, text="保存路径:").grid(row=1, column=0)
self.path_var = tk.StringVar()
ttk.Entry(root, textvariable=self.path_var).grid(row=1, column=1)
ttk.Button(root, text="浏览...",
command=self.select_path).grid(row=1, column=2)
# 生成按钮
ttk.Button(root, text="生成日历",
command=self.generate).grid(row=2, column=1, pady=10)
def select_path(self):
path = filedialog.asksaveasfilename(
defaultextension=".xlsx",
filetypes=[("Excel文件", "*.xlsx")])
if path:
self.path_var.set(path)
def generate(self):
try:
generator = LunarCalendarGenerator(self.year_var.get())
output = generator.generate_year_calendar(self.path_var.get())
messagebox.showinfo("成功", f"日历已生成到:\n{output}")
except Exception as e:
messagebox.showerror("错误", str(e))
if __name__ == '__main__':
root = tk.Tk()
app = CalendarApp(root)
root.mainloop()
```
## 13. 测试与质量保证
确保代码质量的关键是建立完善的测试体系。
**单元测试示例**:
```python
import unittest
from datetime import date
class TestLunarCalendar(unittest.TestCase):
def test_lunar_conversion(self):
# 测试已知的农历日期转换
test_cases = [
((2023, 1, 22), (2023, 1, 1)), # 2023年春节
((2023, 2, 5), (2023, 1, 15)), # 元宵节
((2023, 6, 22), (2023, 5, 5)), # 端午节
]
for solar, lunar in test_cases:
result = LunarDate.fromSolarDate(*solar)
self.assertEqual((result.year, result.month, result.day), lunar)
def test_calendar_generation(self):
# 测试日历生成基本功能
generator = LunarCalendarGenerator(2023)
with tempfile.NamedTemporaryFile(suffix='.xlsx') as tmp:
output = generator.generate_year_calendar(tmp.name)
self.assertTrue(os.path.exists(output))
# 验证工作表数量
wb = load_workbook(output)
self.assertEqual(len(wb.sheetnames), 12)
if __name__ == '__main__':
unittest.main()
```
**集成测试方案