前言
编写程序的关键不在于代码量的多少,而在于是否融入了独特的思考与创新。缺乏这些,技术的成长之路将会停滞不前。有时,我们会遇到一些被戏称为“屎山代码”的实例,这些代码中充斥着层层嵌套的if语句,甚至在if语句内部还嵌套着循环,导致代码几乎无法阅读,更谈不上任何创新或优化。
如何编写一个日历思路
关于如何编写一个日历的问题,我经过长时间的思考后,今天决定给出一个解答方案。在构建日历的过程中,一个核心问题是确定任意一天是星期几。幸运的是,前人已经为我们提供了有效的解决方案——蔡勒公式。这一公式专门用于计算给定日期的星期数,网络上有着丰富的相关资料和详细的推导过程,因此在这里我不再重复介绍。
int dayOftheWeekThisYear(int year)
{
int i = ((5 * (year - 1) / 4 - (year - 1) / 100 + (year - 1) / 400) % 7) + 1;
return i;
//蔡勒公式,此处是计算这一年的第一天是星期几
}
若已知某一年一月一日是星期几,我们就可以推算出该年中任何月份一号所对应的星期数。虽然最直观的方法是逐日累加来计算,但这显然效率不高。更高效的做法是计算两个日期之间的天数差,随后将这个差值加上起始日期的星期数,再对7取余,即可迅速得出目标月份一号是星期几。值得庆幸的是,对于日期差的计算,Easyx官网已有解决方案。
// 计算日期差
// 编译环境:VS2017,C++ 语言
//
// 计算从 0001-1-1 起的天数
int countdays(int y, int m, int d)
{
if (m < 3) y--, m += 12;
return 365 * y + (y >> 2) - y / 100 + y / 400 + (153 * m - 457) / 5 + d - 306;
}
int main()
{
// 输入目标日期
int year, month, day;
scanf_s("%d-%d-%d", &year, &month, &day);
// 输出当前日期与 1949-10-1 相差的天数
printf("%d\n", countdays(year, month, day) - countdays(1949, 10, 1));
return 0;
}
这个代码涉及到的公式推导,官方上有详细的过程,如果感兴趣可以去了解一下。
根据以上的思路,我们就可计算出来某月一号是星期几了。知道该月份一号是星期几之后,我们就可以依此顺着往下排列日期,将这个月的日历编写出来。但是如何计算某年某月有多少天,这时候我们就举起拳头,开始数一月大二月小三月大......
对于某年某月有多少天这个问题,解决方法很多种,有人用15行代码可以搞定,也有人可以用一行代码搞定,但是无论如何,它的原理都是一样的。我前面文章也专门出过一期关于某年某月有多少天?
使用一行代码解决
m<1||m>12 ? 0 : m^2&&m^4&&m^6&&m^9&&m^11 ? 31 : m^2 ? 28+!(y%400||y%4&&!y%100) : 30
有了以上的理论基础,使用Easyx只是将它表现出来就行。这里我们直接获取本地时间,绘制出这个月的日历。
源码
///////////////////////////////////////////////////
// 程序名称:Easyx日历
// 编译环境:Mictosoft Visual Studio 2022, EasyX_20200315(beta)
// 作 者:luoyh <2864292458@qq.com>
// 公 众 号:C语言研究
// 最后修改:2024-10-10
//
SYSTEMTIME t; //定义变量保存当前时间
void Drawtime(); // 实时更新时间
void DrawWeek(); // 绘制星期
void DrawDays(); // 绘制日期
int dayOftheWeekThisYear(int year);
int dayOftheWeekThisYearQueryMonth(int year, int month);
int countdays(int y, int m, int d); // 计算从 0001-1-1 起的天数
int monthdays(int y, int m); // 计算某月的天数
int main()
{
initgraph(530, 420);
setbkcolor(RGB(220, 216, 207));
cleardevice();
BeginBatchDraw();
while (true)
{
Drawtime();
DrawWeek();
DrawDays();
FlushBatchDraw();
}
EndBatchDraw();
_getch();
return 0;
}
void Drawtime() // 实时更新时间
{
setfillcolor(RGB(72, 255, 205));
solidrectangle(10, 10, 515, 407);
setfillcolor(RGB(70, 141, 255));
solidrectangle(10, 10, 515, 32);
setbkmode(TRANSPARENT);
settextcolor(RGB(1, 0, 241));
settextstyle(20, 0, L"黑体");
RECT r = { 10, 10, 515, 32 };
SYSTEMTIME t; //定义变量保存当前时间
wchar_t s2[126];
GetLocalTime(&t);
swprintf_s(s2, 126, L"%d年%d月%d日 %d:%02d:%02d", t.wYear, t.wMonth, t.wDay, t.wHour, t.wMinute, t.wSecond);
drawtext(s2, &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
}
void DrawWeek() // 绘制星期
{
settextstyle(15, 0, L"宋体");
settextcolor(BLACK);
TCHAR str[25];
setlinecolor(WHITE);
setfillcolor(YELLOW);
for (int i = 0; i < 7; i++)
{
str[0] = L"星"[0];
str[1] = L"期"[0];
str[2] = L"日一二三四五六"[i];
str[3] = L""[0];
fillrectangle(10 + 72 * i, 32, 82 + 72 * i, 56);
RECT r = { 10 + 72 * i,32,82 + 72 * i,56 };
drawtext(str, &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
}
}
void DrawDays() // 绘制日期
{
GetLocalTime(&t);
int weeks = dayOftheWeekThisYearQueryMonth(t.wYear, t.wMonth); // 获取该月1号是星期几
int WEEKSJ = weeks;
int days = monthdays(t.wYear, t.wMonth); // 获取该月天数
TCHAR str_day[25];
settextcolor(RGB(0, 21, 218));
// 设置输出效果为抗锯齿 (VC6 / VC2008 / VC2010 / VC2012)
LOGFONT f;
gettextstyle(&f); // 获取当前字体设置
f.lfHeight = 40; // 设置字体高度为 40
wcscpy_s(f.lfFaceName, _T("微软雅黑")); // 设置字体为“黑体”(高版本 VC 推荐使用 _tcscpy_s 函数)
f.lfQuality = ANTIALIASED_QUALITY; // 设置输出效果为抗锯齿
f.lfWeight = 1000;
settextstyle(&f); // 设置字体样式
int GD = 0;
int KD = 0;
for (int i = 0; i < days; i++)
{
_stprintf_s(str_day, _T("%d"), i + 1);
if ((WEEKSJ + i) % 7 == 0)
{
GD++;
KD = 0;
weeks = 0;
settextcolor(RED);
}
if ((WEEKSJ + i) % 7 == 6)
{
settextcolor(RED);
}
RECT r = { 10 + 72 * (KD + weeks),60 + 60 * GD,82 + 72 * (KD + weeks),110 + 60 * GD };
drawtext(str_day, &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
if ((i + 1) == t.wDay) // 如果是今天的日期
{
setfillcolor(BLUE);
fillrectangle(10 + 72 * (KD + weeks), 60 + 60 * GD, 82 + 72 * (KD + weeks), 110 + 60 * GD);
settextcolor(WHITE);
drawtext(str_day, &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
}
settextcolor(RGB(0, 21, 218));
KD++;
}
}
int dayOftheWeekThisYear(int year)
{
int i = ((5 * (year - 1) / 4 - (year - 1) / 100 + (year - 1) / 400) % 7) + 1;
return i;
//蔡勒公式,此处是计算这一年的第一天是星期几
}
//查询某年某月的第一天星期几,需要调用上一个函数实现
int dayOftheWeekThisYearQueryMonth(int year, int month)
{
int totalDays = countdays(year, month, 1) - countdays(year, 1, 1);
totalDays = totalDays + dayOftheWeekThisYear(year);
return (totalDays % 7);
//把这个天数和加上之前计算的查询年的第一天的星期几在进行计算,即可求出查询月的第一天是星期几
}
int countdays(int y, int m, int d)
{
if (m < 3) y--, m += 12;
return 365 * y + (y >> 2) - y / 100 + y / 400 + (153 * m - 457) / 5 + d - 306;
}
int monthdays(int y, int m)
{
if (m == 2)
return ((y % 4 == 0 && y % 100 != 0) || y % 400 == 0) ? 29 : 28;
else
return 31 - (m - 3) % 5 % 2;
}
改进和优化
该程序在现有基础上仍有两大优化方向。首先,为了使其成为一个功能完备的日历应用,我们需要补充农历数据的算法。这包括但不限于农历月份、天数、节气等复杂计算,从而确保用户能够查询到准确的农历信息。
其次,为了提升用户体验,我们需要增强程序的交互性。具体而言,使用户能够通过鼠标轻松调节年份和月份,实时查看并切换日历页面。这样的设计不仅提高了程序的易用性,还使得查看不同日期的日历信息变得更加直观和便捷。