一个简单的日历系统(C++)
一、整体架构
本次实验采用多文件结构进行开发,建立了Date类(Date.cpp,Date.h)和Time类(Time.cpp,Time.h),实现了Date类中对日期的加减运算(运算符重载)和Time类中的前置与后置自增自减运算。在main.cpp中实现了相关基础菜单界面,并在display.cpp中实现了月历和年历的打印。
整体架构思路如下图。
二、功能实现
(一)主菜单界面
根据想要实现的功能依次设计并实现了如下的菜单命令,为了用户友好在选项后给出提示样例。
每次用户选择相应选项后调用system("cls");命令清屏,显示下一级菜单,并在不退出程序的情况下循环打印主菜单。
此外,对于用户的错误和不合法输入给出了相应的提示。
另外,在实际开发时为了避免对象经过操作后数据变得不合法为每个类设置了Examine函数,在开发测试时调用查看操作后数据是否正常。后将其作为输入数据实例化对象后,对象数据是否合法的判断依据。
(二)日历查询
此处受到Linux操作系统下的cal命令启发,实现了类似的月历和年历的打印。
首先进行相应数据的准备工作,定义并实现nyear和nmonth函数,实现闰年判断及相应年月对应天数的获取。设立1990年1月1日为基准点(星期一),实现GetDays函数获得操作对象距离1990年1月1日的天数。
月历打印最重要的是确立第一天的起始点(对应星期),因此实现GetWeekdays获取对应星期数。在开发过程中,开始是通过之前设立的1990年1月1日进行模拟来获得对应星期数,后查询资料了解到蔡勒公式,改用蔡勒公式获得对应星期数,大大降低了程序复杂度并提升了可输入数据的范围。
蔡勒(Zeller)公式,是一个计算星期的公式,随便给一个日期,就能用这个公式推算出是星期几。
实现效果图如下。
实现月历打印后,类似的进行年历的打印,难点在于界面的整洁如何实现(间距的调整)。
此处采用三层循环的方式来进行间距的控制。每层循环通过控制月份里第一行的空格数、最后一行要的空格数、与下一个月相隔的距离等数据来实现界面的整洁。
此处的循环非常多,在代码实现时要注意给每层循环编号做好注释,以便在debug能迅速找到想改变的地方。
实现效果图如下。
此外,还实现了Counterdays函数来计算目标日期距离今日的天数。调用ctime库来获取当前系统时间,利用之前设置基准点得到当前日期和目标日期距基准点天数,然后相减得到时间差。此处考虑到目标天数在当前系统日期之前、恰在当前系统日期、在当前系统日期之后三种情况,设置三种不同的输出。
Case 1:
Case 2:
Case 3:
(三)日期时间加减
定义并实现Date类中的运算符重载完成相关日期的操作。Date类体内给出对日期的初始化构造函数,默认时间为公元1990年1月1日。
首先实现了对于日期的前置自增和自减运算,此时从内到外考虑天数自增自减带来变化(天数超过当前月份最大天数值、天数为0及其引发的月份的改变,还有可能引发相应年份的改变)。
测试样例如下。
然后实现日期的加减运算,此处考虑输入数较大的情况,从外到内考虑(从内到外复杂度过高,此处受蔡勒公式计算的启发)。
对于加运算,首先考虑年,当天数大于等于365时,三月及以上考虑的是第二年的闰年情况,先++;二月及以下考虑当年闰年影响,后++ 。然后考虑月,先考虑i>=28使得在后面的日子的处理过程中最多只变换一次月份。最后考虑日子和最后要加的天数超过所在月的处理情况以及在之前处理使得day为负数的情况。
测试样例如下。
类似的,对于减运算,首先考虑年,当天数大于等于365时,二月及以下考虑的是前一年的闰年情况,先--;三月及以上考虑当年闰年影响,后--。然后考虑月,先考虑i>=28使得在后面的日子的处理过程中最多只变换一次月份。最后考虑日子减去天数小于0的处理情况。
测试样例如下。
然后以Date类为基类派生出Time类。在Time类中实现了构造函数和前置与后置自增自减运算的运算符重载。
和Date类相同,对于前置自增运算从内到外考虑(秒->分->时->日->月->年)。
前置自增自减的测试样例如下。
对于后置自增自减运算,首先创建一个old对象存储原对象数据,然后对操作对象(*this)进行前置自增自减运算,最后返回old对象,从而达到后置的效果。
后置自增自减测试样例如下。
(三)程序评价
I. 优点
- 界面较为友好,提供了提示样例以及对各种错误输入进行了考虑和处理
- 在年历打印中使用三层循环不断调试,使得年历显示十分整齐
- 在开发过程中使用bool型的Examine函数时刻测试结果的合法性
- 使用蔡勒公式获取星期数,降低了复杂度并提高了数据支持性
- 日期加减中对大数处理时从外到内考虑,大大降低了程序的复杂度
II. 缺点
- 尽管对不合法数据进行了一定的限制,若输入数字为单字符型,仍可能强转为整型使得数据错误
- 界面整体还能继续优化(某些菜单的间距调整)
- 未考虑儒略历的影响(减少的十天)
- 计算相距日期用的仍是基准点法,数据支持范围受限制
III. 扩展
- 可以使用QT进行相应的图形界面开发
- 可以引入农历系统,从而实现节假日标红的功能
- 可以引入文件操作,实现日程安排的功能(web以及多客户端实现日程数据共享)
- 时间类也可以实现相应的加减操作,并且可以设置闹钟功能和日程功能结合
(四)开发过程的反思总结
本次开发中,我明白了提前设置数据合法检测的重要性,Examine函数的设置帮助我避免了很多麻烦。其次,通过年历打印,我发现自己在界面间距把控上的能力还有些许欠缺(调试了好久才完成整洁的界面),对于C++中的间距控制函数也不是非常熟练,以后要多加练习。最后,通过本次实验,积累了很多优化和调试的经验。在年历打印上,初次采用的是基准点的方式实现,后来查阅资料得知蔡勒公式后改为蔡勒公式获取星期数,降低了复杂度。在日期加减的大数操作中,初次和自增自减运算一样,从内到外考虑一层层消去,后来受蔡勒公式启发改为从外到内考虑,降低了复杂度(增大了思维量,需要不断调试改进程序)。
程序代码
Date.h
#pragma once
class Date
{
protected:
int year, month, day;
public:
//默认时间为公元1990年1月1日
Date() { year = 1990, month = 1, day = 1; };
//构造函数赋值
Date(int NewY, int NewM, int NewD)
{
year = NewY; month = NewM; day = NewD;
}
Date& operator +(int days);
Date& operator -(int days);
Date& operator ++();
Date& operator --();
bool Examine();
void Print();
};
Date.cpp
#include "Date.h"
#include <iostream>
#include "display.h"
using namespace std;
Date& Date::operator +(int days)
{
while (days >= 365)
{
if (month >= 3)//三月及以上考虑的是第二年的闰年情况,先++
{
year++;
days -= nyear(year);
}
if (month <= 2)//二月及以下考虑当年闰年影响,后++
{
days -= nyear(year);
year++;
}
}
while (days >= 28)//考虑月份,这里考虑i>=28是希望在后面的日子的处理过程中最多只变换一次月份
{
days -= nmonth(year, month);
month++;
if (month > 12)
{
month = 1;
year++;
}
}
if ((day + days) > nmonth(year, month))//日子和最后要加的天数超过所在月的处理情况
{
days -= nmonth(year, month);
if (month == 12)
{
month = 1;
year++;
}
else month++;
}
day = day + days;
if (day <= 0)//这里考虑在之前处理中i为负数且day很小的情况下导致day为负数的情况
{
if (month == 1)
{
year--;
month = 12;
}
else month--;
day += nmonth(year, month);
}
return *this;
}
Date& Date::operator -(int days)
{
while (days >= 365)//考虑数字很大时的处理情况
{
if (month <= 2)//二月及以下考虑的是前一年的闰年情况,先--
{
year--;
days -= nyear(year);
}
if (month >= 3)//三月及以上考虑当年闰年影响,后--
{
days -= nyear(year);
year--;
}
}
while (days >= 28)//考虑月份,这里考虑i>=28是希望在后面的日子的处理过程中最多只变换一次月份
{
if (month == 1)
{
month = 12;
year--;
days -= 31;
}
else
{
days -= nmonth(year, month - 1);
month--;
}
}
if ((day - days) <= 0)//日子减去天数小于0的处理情况
{
if (month == 1)
{
month = 12;
year--;
}
else month--;
days -= nmonth(year, month);
}
day = day - days;
/*
day=26,i=27,day-i=-1;
month=4,i=i-31=-4;
month=3
day=26-(-4)=30;--> 3 30
在i<28,day-i<0的情况下,
day=day-i+month<month
进入上面判断if((day-i)<=0)
不会再进入下面 if(day>=months[month])
*/
if (day > nmonth(year, month))
{
day -= nmonth(year, month);
if (month == 12)
{
year++;
month = 1;
}
else month++;
}
return *this;
}
Date& Date::operator ++()
{
int monthnow;
monthnow = nmonth(year, month);
if (day == monthnow)
{
day = 1;
month += 1;
if (month > 12)
{
month = 1;
year += 1;
}
}
else
day += 1;
return *this;
}
Date& Date::operator --()
{
if (day == 1)
{
day = nmonth(year, month - 1);
month -= 1;
if (month < 1)
{
month = 12;
year -= 1;
}
}
else
day -= 1;
return *this;
}
bool Date::Examine()
{
int monthmax;
monthmax = nmonth(year, month);
if (year <= 0 || month <= 0 || day <= 0 || month > 12 || day > monthmax) return false;
else return true;
}
void Date::Print()
{
cout << endl << "当前日期date0为" << year << "年" << month << "月" << day << "日" << endl;
}
Time.h
#pragma once
#include "Date.h"
class Time:protected Date
{
private:
int hour, minute, second;
public:
//构造函数赋值
Time(int y, int m, int d, int NewH, int NewM, int NewS) :Date(y, m, d)
{
hour = NewH; minute = NewM; second = NewS;
}
Time operator ++();
Time operator ++(int);
Time operator --();
Time operator --(int);
bool Examine();
void Print();
};
Time.cpp
#include "Time.h"
#include "display.h"
#include <iostream>
using namespace std;
Time Time::operator++()
{
second++;
if (second >= 60)
{
second -= 60;
minute++;
if (minute >= 60)
{
minute -= 60;
hour++;
if (hour >= 24)
{
hour -= 24;
day++;
int monthnow;
monthnow = nmonth(year, month);
if (day > monthnow)
{
day = 1;
month += 1;
if (month > 12)
{
month = 1;
year += 1;
}
}
}
}
}
return *this;
}
Time Time::operator++(int)
{
Time old = *this;
++(*this);
return old;
}
Time Time::operator--()
{
second--;
if (second < 0)
{
second += 60;
minute--;
if (minute < 0)
{
minute += 60;
hour--;
if (hour < 0)
{
hour += 24;
day--;
if (day == 0)
{
day = nmonth(year, month - 1);
month -= 1;
if (month < 1)
{
month = 12;
year -= 1;
}
}
}
}
}
return *this;
}
Time Time::operator--(int)
{
Time old = *this;
--(*this);
return old;
}
bool Time::Examine()
{
int monthmax;
monthmax = nmonth(year, month);
if (year <= 0 || month <= 0 || day <= 0 || month > 12 || day > monthmax || hour < 0 || hour >= 24 || minute < 0 || minute >= 60 || second < 0 || second >= 60) return false;
else return true;
}
void Time::Print()
{
cout << endl << "当前时间time0为" << year << "年" << month << "月" << day << "日" << hour << "时" << minute << "分" << second << "秒" << endl;
}
display.h
#pragma once
void display();
int nyear(int year);
int nmonth(int y, int m);
int GetDays(int year, int month);
void Displaymonth();
void Displayyear();
void Counterdays(int year, int month, int day);
void display();
display.cpp
#define _CRT_SECURE_NO_WARNINGS
#include "display.h"
#include <iostream>
#include <ctime>
#include<iomanip>
#include <Windows.h>
using namespace std;
//1990年1月1号是星期一
int nyear(int year) //闰年判断
{
if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)
return 366;
else
return 365;
}
int nmonth(int y, int m) //根据年份和月份判断是哪一个月的天数
{
if (m == 0 || m == 1 || m == 3 || m == 5 || m == 7 || m == 8 || m == 10 || m == 12)
return 31;
else if (nyear(y) == 366 && m == 2)
return 29;
else if (nyear(y) == 365 && m == 2)
return 28;
else
return 30;
}
int GetDays(int year, int month) //输入年份和月份确定到1990.1.1总共多少天
{
int i = 0;
int sum = 0;
if (year > 1990)
for (i = 1990; i < year; i++)
sum += nyear(i);
if (month > 1)
for (i = 1; i < month; i++)
{
sum += nmonth(year, i);
}
return sum;
}
//蔡勒公式
int GetWeekdays(int year, int month, int day)
{
if (month < 3)
{
year -= 1;
month += 12;
}
int c = int(year / 100),y = year - 100 * c;
int w = int(c / 4) - 2 * c + y + int(y / 4) + (26 * (month + 1) / 10) + day - 1;
w = (w % 7 + 7) % 7;
return w;
}
void Displaymonth()
{
int i, j, year, month, day, sum, daycount, cases = 1;
cout << "请输入您要查找的年份和月份(XXXX XX):";
while (cin >> year >> month)
{
//sum = GetDays(year, month);
//day = (sum + 1) % 7; //得出一个月第一行前要空几格
day = GetWeekdays(year, month, 1);
daycount = nmonth(year, month); //算出这一个月的天数
cout << endl << " " << year << "年 " << month << "月" << endl;
cout << " 日 一 二 三 四 五 六 " << endl;
for (i = 0; i < day; i++)
cout << setw(4) << " ";
for (i = 1, j = day + i; i <= daycount; i++, j++)
{
cout << setw(4) << i;
if (j % 7 == 0)
cout << endl;
}
cout << endl << endl << "输入0返回主菜单,输入其他继续查询:";
int num;
cin >> num;
if (num == 0)
break;
else
cout << "请输入您要查找的年份和月份(XXXX XX):";
}
}
void Displayyear()
{
int i = 0, year;
int sum1, sum2, sum3;
int day1, day2, day3;
int daycount1, daycount2, daycount3;
cout << "请输入您要查找的年份(XXXX):";
while (cin >> year)
{
for (i = 1; i < 12; i += 3)
{
int j = 0, m = 1; //控制打印日期的第一层
int n = 0, q = 1; //控制打印日期的第二层
int k = 0, d = 1; //控制打印日期的第三层
//sum1 = GetDays(year, i); //到1990.1.1的总天数
//sum2 = GetDays(year, i + 1);
//sum3 = GetDays(year, i + 2);
//day1 = (sum1 + 1) % 7; //得出一个月第一行前要空几格
//day2 = (sum2 + 1) % 7;
//day3 = (sum3 + 1) % 7;
day1 = GetWeekdays(year, i, 1);
day2 = GetWeekdays(year, i + 1, 1);
day3 = GetWeekdays(year, i + 2, 1);
daycount1 = nmonth(year, i); //一个月的总天数
daycount2 = nmonth(year, i + 1);
daycount3 = nmonth(year, i + 2);
cout << setw(10) << " " << year << "年" << setw(2) << i << "月" << setw(21) << " " << " " << year << "年" << setw(2) << i + 1 << "月" << setw(21) << " " << " " << year << "年" << setw(2) << i + 2 << "月" << endl;
cout << " 日 一 二 三 四 五 六 " << setw(4) << " " << " 日 一 二 三 四 五 六 " << setw(4) << " " << " 日 一 二 三 四 五 六 " << endl;
for (j = 0; j < day1; j++) //1.第一层循环,月份里第一行的空格数
cout << setw(4) << " ";
for (j = day1 + m; m <= daycount1 + 1; m++, j++) //1.三层循环第一层
{//1
if (m > daycount1) //多打印一行空格
{
for (int a = 0; a < 7; a++)
cout << setw(4) << " ";
j = 7;
}
else
cout << setw(4) << m;
if (m == daycount1) //1.最后一行要的空格数
{
for (int a = 0; a < ((7 - (m + day1) % 7) % 7); a++)
cout << setw(4) << " ";
j = 7;
}
if (j % 7 == 0)
{
cout << setw(5) << " "; //1.与下一个月相隔的距离
//#//////////////////////////////////////////--2
if (n == 0) //2.当为月的第一行时
{
for (n = 0; n < day2; n++) //2.第二层循环,月份里第一行的空格数
cout << setw(4) << " ";
}
for (n = day2 + q; q <= daycount2 + 2; q++, n++) //2.第二层循环
{//2
if (q > daycount2) //多打印一行空格
{
for (int a = 0; a < 7; a++)
cout << setw(4) << " ";
n = 7;
}
else
cout << setw(4) << q;
if (q == daycount2) //2.最后一行要的空格数
{
for (int a = 0; a < ((7 - (q + day2) % 7) % 7); a++)
cout << setw(4) << " ";
n = 7; //2.使其可以进入下一个循环
}
if (n % 7 == 0)
{
q++;
n++;
cout << setw(5) << " "; //2.与下一个月相隔的距离
//#//////////////////////////////////////--3
if (k == 0) //3.月份的第一行
{
for (k = 0; k < day3; k++) //3.月份的第一行要打印的空格数
cout << setw(4) << " ";
}
for (k = day3 + d; d <= daycount3 + 1; d++, k++) //3.打印月份的日期
{//3
if (d > daycount3)
{
for (int a = 0; a < 7; a++)
cout << setw(4) << " "; //多打印一行空格
k = 7;
m++;
j++;
}
else
cout << setw(4) << d;
if (d == daycount3)
{
k++;
d++;
cout << endl;
break;
}
if (k % 7 == 0)
{
k++;
d++;
cout << endl;
break;
}
}//3
break;
}
}//2
}
}//1
/////////////////////
}
cout << endl << endl << "输入0返回主菜单,输入其他继续查询:";
int num;
cin >> num;
if (num == 0)
break;
else
cout << "请输入您要查找的年份(XXXX):";
}
}
void Counterdays(int year, int month, int day)
{
int year1, month1, day1, days1, year2, month2, day2, days2, days;
time_t timep;
struct tm* p;
time(&timep);
//获取当前系统日期
p = gmtime(&timep);
year1 = (1900 + p->tm_year);
month1 = (1 + p->tm_mon);
day1 = (p->tm_mday);
year2 = year;
month2 = month;
day2 = day;
//当前时间距离基准值过了多少天
days1 = GetDays(year1, month1) + day1 - 1;
//目标时间距离基准值过了多少天
days2 = GetDays(year2, month2) + day2 - 1;
//时间差
days = days2 - days1;
if (days > 0)
cout << endl << "今天距" << year << "年" << month << "月" << day << "日还有" << days << "天" << endl;
else if (days == 0)
cout << endl << "今天就是" << year << "年" << month << "月" << day << "日" << endl;
else
cout << endl << year << "年" << month << "月" << day << "日已过去" << -days << "天" << endl;
}
void display()
{
int nSelection = -1;
cout << "********************************" << endl;
cout << "* 1.月历打印 * 2.年历打印 *" << endl;
cout << "********************************" << endl;
cout << "* 3.日期计算 * 4.退出程序 *" << endl;
cout << "********************************" << endl << endl;
cout << "请输入功能项序号(1~4)";
cin >> nSelection;
switch (nSelection)
{
case 1:
{
system("cls");
Displaymonth();
break;
}
case 2:
{
system("cls");
Displayyear();
break;
}
case 3:
{
system("cls");
cout << endl << "请输入目标日期(XXXX XX XX):";
int year, month, day;
cin >> year >> month >> day;
Counterdays(year, month, day);
break;
}
case 4:
{
system("cls");
cout << endl << "*********************************" << endl;
cout << "* *";
cout << endl << "* 感谢您使用本系统 *" << endl;
cout << "* *" << endl;
cout << "*********************************" << endl;
exit(0);
break;
}
default:
{
system("cls");
cout << endl << " 输入选项有误!" << endl;
break;
}
}
}
main.cpp
#include <iostream>
#include <Windows.h>
#include "Date.h"
#include "display.h"
#include "Time.h"
using namespace std;
void outputMenu();
int main()
{
//输入的菜单编号
int nSelection = -1;
cout << endl << " 欢迎进入日历查询系统" << endl;
do
{
//输出系统菜单
outputMenu();
//输入菜单编号
cin >> nSelection;
//输出选择的子菜单
switch (nSelection)
{
case 1:
{
system("cls");
cout << endl << " ******************"<<endl;
cout << " * *" << endl;
cout << " * 日期加减 *" << endl;
cout << " * *" << endl;
cout << " ******************" << endl << endl;
cout << "请输入要操作的日期(如2020 5 20):";
int year0, month0, day0;
cin >> year0 >> month0 >> day0;
Date date0(year0, month0, day0);
if (date0.Examine() == true)
date0.Print();
else
{
cout << endl << "输入日期不合法!请重新操作!" << endl;
break;
}
cout << endl << "------------操作项目------------" << endl;
cout << "* 1.自增运算 * 2.自减运算 *" << endl;
cout << "--------------------------------" << endl;
cout << "* 3.加法运算 * 4.减法运算 *" << endl;
cout << "--------------------------------" << endl;
cout << endl << "请选择操作项目(1~4):";
int num;
cin >> num;
switch (num)
{
case 1:
++date0;
date0.Print();
break;
case 2:
--date0;
date0.Print();
break;
case 3:
cout << "请输入增加天数:";
int add;
cin >> add;
date0 = date0 + add;
date0.Print();
break;
case 4:
cout << "请输入减少天数:";
int sub;
cin >> sub;
date0 = date0 - sub;
date0.Print();
break;
default:
system("cls");
cout << endl << "*********************************" << endl;
cout << " 输入菜单编号有误!" << endl;
cout << " (请输入1~4)" << endl;
cout << "*********************************" << endl << endl;
break;
}
break;
}
case 2:
{
system("cls");
cout << endl << " ******************" << endl;
cout << " * *" << endl;
cout << " * 日历查询 *" << endl;
cout << " * *" << endl;
cout << " ******************" << endl << endl;
display();
break;
}
case 3:
{
system("cls");
cout << endl << " ******************" << endl;
cout << " * *" << endl;
cout << " * 时间加减 *" << endl;
cout << " * *" << endl;
cout << " ******************" << endl << endl;
cout << "请输入要操作的时间(如2020 5 20 20 13 14):";
int year0, month0, day0, hour0, minute0, second0;
cin >> year0 >> month0 >> day0 >> hour0 >> minute0 >> second0;
Time time0(year0, month0, day0, hour0, minute0, second0);
if (time0.Examine() == true)
time0.Print();
else
{
cout << endl << "输入时间不合法!请重新操作!" << endl;
break;
}
cout << endl << "------------操作项目------------" << endl;
cout << "* 1.前置自增 * 2.后置自增 *" << endl;
cout << "--------------------------------" << endl;
cout << "* 3.前置自减 * 4.后置自减 *" << endl;
cout << "--------------------------------" << endl;
cout << endl << "请选择操作项目(1~4):";
int num;
cin >> num;
switch (num)
{
case 1:
(++time0).Print();
break;
case 2:
(time0++).Print();
break;
case 3:
(--time0).Print();
break;
case 4:
(time0--).Print();
break;
default:
system("cls");
cout << endl << "*********************************" << endl;
cout << " 输入菜单编号有误!" << endl;
cout << " (请输入1~4)" << endl;
cout << "*********************************" << endl << endl;
break;
}
break;
}
case 4:
{
system("cls");
cout << endl << "*********************************" << endl;
cout << "* *";
cout << endl << "* 感谢您使用本系统 *" << endl;
cout << "* *" << endl;
cout << "*********************************" << endl;
exit(0);
break;
}
default:
{
system("cls");
cout << endl << "*********************************" << endl;
cout << " 输入菜单编号有误!" << endl;
cout << " (请输入1~4)" << endl;
cout << "*********************************" << endl << endl;
break;
}
}
} while (nSelection != 0);
return 0;
}
void outputMenu()
{
//输出菜单
cout << endl << "--------------菜单--------------" << endl;
cout << "* 1.日期加减 * 2.日历查询 *" << endl;
cout << "--------------------------------" << endl;
cout << "* 3.时间加减 * 4.退出程序 *" << endl;
cout << "--------------------------------" << endl;
cout << endl << "请选择菜单项编号(1~4):";
}