《C++程序设计原理与实践》第九章习题12要改写Date类,类实现不是用年月日的方法而是用距1970年1月1日的天数来实现。当时感到有点棘手,就搜索了网上别人的实现方法,我看人实现还是保留了年月日,我觉得这违背了作者的本意。所以还是硬着头皮自己写。其实也不难,只是有一个小技巧,就是在如何计算闰年数的方法上。因为现在类里保存的是当前日期距离1970年1月1日的天数,所以不能直接用作者计算闰年的函数来实现。因为用1970年作为基准年来实现计算闰年的次数不好实现,所以我想了一个通过曲线手段来实现的方法,具体做法是仍旧从1601年开始算闰年,只是用1601年到当前年的闰年数减去1601年到1970年的闰年数来得到当前年到1970年的闰年数。
为了方便,我把输入测试日期的格式改为:y m d,而不是正规的(y,m,d)格式。
上面我说的“当前日期”、“当前年”的说法不够准确,准确的说法是Date类对象所表述的日期和年。
昨天对程序作了一点改进,定义了一个全局变量nlb来表达从1601年到1970年经历的闰年数。这样做简洁了代码,并且不用每次构造Date对象还有调用month()和day()的时候做重复计算。
const int nlb = nleaps(base_year); //计算从1601年到1970年经历的闰年数
// exercise9.12.cpp: 定义控制台应用程序的入口点。
//
// 2018/9/21 因为用1970年作为基准年来实现计算闰年的次数不好实现,所以通过曲线过程实现,具体做法是还是从1601年开始算闰年,用1601年到当前年的闰年数减去1601年到1970年的闰年数就得到当前年到1970年的闰年数。
#include "stdafx.h"
#include "..\..\std_lib_facilities.h"
#include <iostream>
using namespace std;
// file Chrono.h
namespace Chrono {
//------------------------------------------------------------------------------
class Date {
public:
enum Month {
jan = 1, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec
};
class Invalid { }; // to throw as exception
Date(int y, Month m, int d); // check for valid date and initialize
Date(); // default constructor
// the default copy operations are fine
// non-modifying operations:
int day() const;
Month month() const;
int year() const;
long get_linearday() const{ return ld; }
// modifying operations:
void add_day(int n);
void add_month(int n);
void add_year(int n);
private:
long ld; //1970.1.1到当前日的天数
};
bool is_date(int y, Date::Month m, int d); // true for valid date
bool leapyear(int y); // true if y is a leap year
bool operator==(const Date& a, const Date& b);
bool operator!=(const Date& a, const Date& b);
ostream& operator<<(ostream& os, const Date& d);
istream& operator>>(istream& is, Date& dd);
const Date& default_date();
} // Chrono
//------------------------------------------------------------------------------
//The definitions go into Chrono.cpp:
// Chrono.cpp
namespace Chrono {
// member function definitions:
enum Day { // sunday==0
sunday, monday, tuesday, wednesday, thursday, friday, saturday
};
const int first_year = 1601;// don't try dates anywhere near this far back in time
// the calendar calculation code is brittle the first date
// must be the first day of a year divisible by 400
//Januaray 1, 1600 would have been a Monday (had they had the Georgian calendar)
const int base_year = 1970;
const Date::Month base_month = Date::jan;
const int base_day = 1;
const int base_day_week = thursday;
int nleaps(int);
int nmonth(Date::Month);
int day_in_year(Date);
Date::Date(int yy, Month mm, int dd)
{
int y = yy - base_year;
if (y < 0) error("Date constructor: can't handle days before year 1970");
int m = mm - base_month;
int d = dd - base_day;
/*
If it wasn't for leap years and different lengths of month the answer would be
365*y+31*m+d
However, ther real world (the real physical world + conventions) is not that simple.
*/
if (y == 0 && m == 0) ld = d; // same month
else {
long days_in_years = 365 * y + nleaps(yy) - nleaps(base_year);//计算当前年与1970年之间的天数,因为函数nleaps是从1601起算闰年,所以要减去1601-1970之间的闰年数。
//实现成员函数month()和day()也一样考虑
int days_in_months = nmonth(mm);
if (feb < mm && leapyear(yy)) ++days_in_months;
ld = days_in_years + days_in_months + dd - 1;
}
if (!is_date(yy, mm, dd)) throw Invalid();
}
const Date& default_date();
Date::Date()
{
ld = default_date().get_linearday();
}
int days_in_month(int y, Date::Month m);
int days_in_year(int y);
int Date::year()const
{
int y = base_year;
long ild = ld;
int diy = days_in_year(y);
while (ild + 1 > diy) {
y++;
ild -= diy;
diy = days_in_year(y);
}
return y;
}
Date::Month Date::month()const {
int y = year() - base_year;
long days_in_years = 365 * y + nleaps(year()) - nleaps(base_year);
long diy = ld - days_in_years;
Month m = jan;
int dim = days_in_month(year(), m);
while (diy + 1 > dim) {
m = Month(m + 1);
diy -= dim;
dim = days_in_month(year(), m);
}
return m;
}
int Date::day()const {
int y = year() - base_year;
long days_in_years = 365 * y + nleaps(year()) - nleaps(base_year);
int days_in_months = nmonth(month());
if (Date::feb < month() && leapyear(year())) ++days_in_months; // adjust if we passed a dat added for a leapyear
return int(ld - days_in_years - days_in_months + 1);
}
void Date::add_day(int n)
{
if (n < 0) error("add_day(): can't handle negative n"); // not yet
ld += n;
}
void Date::add_month(int n)
{
if (n < 0) error("add_month(): cnot implemented"); // not yet
// ...
}
void Date::add_year(int n)
{
int y = year();
Month m = month();
int d = day();
if (m == feb && d == 29 && !leapyear(y + n)) { // beware of leap years!
// makes sense for both positive and negative n (n==0 should be impossible here)
m = mar; // use March 1 instead of February 29
d = 1;
}
y += n;
}
//------------------------------------------------------------------------------
// helper functions, etc.:
const Date& default_date()
{
static const Date dd(2001, Date::jan, 1); // start of 21st century
return dd;
}
int days_in_month(int y, Date::Month m)
{
switch (m) {
case Date::feb: // the length of February varies
return (leapyear(y)) ? 29 : 28;
case Date::apr: case Date::jun: case Date::sep: case Date::nov:
return 30;
default:
return 31;
}
}
bool is_date(int y, Date::Month m, int d)
{
// assume that y is valid
if (m< Date::jan || m>Date::dec) return false;
if (d <= 0) return false; // d must be positive
if (days_in_month(y, m)<d) return false;
return true;
}
bool leapyear(int y)
{
// See exercise 9-10
// any year divisible by 4 except centenary years not divisible by 400
if (y % 4) return false;
if (y % 100 == 0 && y % 400) return false;
return true;
}
bool operator==(const Date& a, const Date& b)
{
return a.year() == b.year()
&& a.month() == b.month()
&& a.day() == b.day();
}
bool operator!=(const Date& a, const Date& b)
{
return !(a == b);
}
ostream& operator<<(ostream& os, const Date& d)
{
return os << '(' << d.year()
<< ',' << d.month()
<< ',' << d.day()
<< ')';
}
istream& operator>>(istream& is, Date& dd)
{
int y, m, d;
char ch1, ch2, ch3, ch4;
is >> y >> m >> d ;
if (!is) {
cout << "read fail 1\n";
return is;
}
/*
if (ch1 != '(' || ch2 != ',' || ch3 != ',' || ch4 != ')') { // oops: format error
is.clear(ios_base::failbit); // set the fail bit
// cout << "read fail 2\n";
return is;
}
*/
dd = Date(y, Date::Month(m), d); // update dd
return is;
}
ostream& operator<<(ostream& os, Day d)
// sloppy: I should have used a table
{
switch (d) {
case sunday:
os << "Sunday";
break;
case monday:
os << "Monday";
break;
case tuesday:
os << "Tuesday";
break;
case wednesday:
os << "Wednesday";
break;
case thursday:
os << "Thursday";
break;
case friday:
os << "Friday";
break;
case saturday:
os << "Saturday";
break;
}
return os;
}
int nmonth(Date::Month m)
// number of days before the first day of month #m (january is #1) ignoring leap days
{
switch (m)
{
case Date::jan: return 0;
case Date::feb: return 31;
case Date::mar: return 31 + 28;
case Date::apr: return 31 + 28 + 31;
case Date::may: return 31 + 28 + 31 + 30;
case Date::jun: return 31 + 28 + 31 + 30 + 31;
case Date::jul: return 31 + 28 + 31 + 30 + 31 + 30;
case Date::aug: return 31 + 28 + 31 + 30 + 31 + 30 + 31;
case Date::sep: return 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31;
case Date::oct: return 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30;
case Date::nov: return 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31;
case Date::dec: return 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30;
}
}
int day_in_year(Date a)
// e.g. Jan 1 is day #1, Jan 2 is day #1 and feb 3 is day #34
{
// int m = nmonth(a.month() - 1);
int m = nmonth(a.month());
if (Date::feb<a.month() && leapyear(a.year())) ++m; // adjust if we passed a dat added for a leapyear
return m + a.day();
}
int days_in_year(int y)
{
int diy = 365;
if (leapyear(y)) diy++; // adjust if we passed a dat added for a leapyear
return diy;
}
int nleaps(int y)
// number of leapyears between Jan 1, y and first_year
// first_year must be divisible by 400
{
const int yy = y - first_year;
// const int yy = y - (base_year-1); //base_year-1:based non leap year;
return yy / 4 - yy / 100 + yy / 400; // number of leapyears
}
int linear_day(Date a)
// days since default date
{
int y = a.year() - base_year;
if (y<0) error("linear_day: can't handle days before (1601,jan,1)");
int m = a.month() - base_month;
int d = a.day() - base_day;
/*
If it wasn't for leap years and different lengths of month the answer would be
365*y+31*m+d
However, ther real world (the real physical world + conventions) is not that simple.
*/
if (y == 0 && m == 0) return d; // same month
int days_in_years = 365 * y + nleaps(a.year());
return days_in_years + day_in_year(a) - 1;
}
/*
Date date_from_linear(int n)
// compose the Date (2001,jan,1)+n
{
return Date(first_date.year(), first_date.month(), first_date.day() + n); // rather limited implementatiuon :-)
}
*/
int operator-(Date a, Date b)
// how many days are there between a and b?
// if b is earlier than a the answer will be negative
{
int aval = linear_day(a);
int bval = linear_day(b);
return aval - bval;
}
Date operator+(const Date& d, int dd)
// dd days beyond d
{
Date x = d;
x.add_day(dd);
return x;
}
/*
Note the difference between + and - for Date.
It makes sense to subtract two dates, but not to add two dates
*/
Day day_of_week(const Date& d)
// ``just count the days since the start of (our) dates''
{
// int x = base_day_week + linear_day(d);
int x = base_day_week + d.get_linearday();
return Day(x % 7); // every week is 7 days
}
Date next_Sunday(const Date& d)
{
Day dd = day_of_week(d);
Date ns = d;
ns.add_day(7 - dd);
return ns;
}
Date next_weekday(const Date& d)
// assume that Saturday and Sunday are not weekdays
{
Day dd = day_of_week(d);
int n = 1;
switch (dd) {
case friday: // skip Saturday and Sunday
n = 3;
break;
case saturday: // Skip Sunday
n = 2;
break;
}
return d + n;
}
int week_in_year(const Date& d)
// the number of a week in a year is defined by ISO 8601:
// week #1 is the week with the year's first Thursday in it
// Monday is the first day of the week
// 0 means that the date is in the last week ofthe previous year
{
int diy = day_in_year(d); // jan 1 is day #1
Day jan1 = day_of_week(Date(d.year(), Date::jan, 1));
int week1 = 0; // Jan 1 is in the last week of the previous year
int delta;
switch (jan1) {
// Jan 1 is in the first week of the year
case monday:
delta = 0;
break;
case tuesday:
delta = 1;
break;
case wednesday:
delta = 2;
break;
case thursday:
delta = 3;
break;
// Jan 1 is in the last week of the previous year
case friday:
delta = -3;
break;
case saturday:
delta = -2;
break;
case sunday:
delta = -1;
break;
}
return (diy + delta + 6) / 7;
}
//------------------------------------------------------------------------------
} // Chrono
//------------------------------------------------------------------------------
int zxc;
void write(const Chrono::Date& d) // debug output function
{
cout << d << ": " << day_of_week(d) << "; linear: " << d.get_linearday() << "; of week #" << week_in_year(d) << '\n';
}
int main()
try
{
/*
Chrono::Date xxx(1971, Chrono::Date::jan, 1);
cout << xxx.get_linearday() << endl;
write(xxx);
Chrono::Date xxx2(1972, Chrono::Date::jan, 1);
cout << xxx2.get_linearday() << endl;
write(xxx2);
Chrono::Date xxx3(1973, Chrono::Date::jan, 1);
cout << xxx3.get_linearday() << endl;
write(xxx3);
*/
Chrono::Date xxx(2101, Chrono::Date::jan, 1);
// cout << xxx.get_linearday() << endl;
write(xxx);
Chrono::Date xxxw = next_weekday(xxx);
write(xxxw);
Chrono::Date xx(2001, Chrono::Date::jan, 8);
write(xx);
Chrono::Date xxw = next_weekday(xx);
write(xxw);
Chrono::Date today(2010, Chrono::Date::jan, 16);
write(today);
Chrono::Date todayw = next_weekday(today);
write(todayw);
Chrono::Date vote(2010, Chrono::Date::mar, 13);
write(vote);
Chrono::Date votew = next_weekday(vote);
write(votew);
Chrono::Date myday(2018, Chrono::Date::Month(8), 24);
// cout << day_in_year(myday) << endl;
write(myday);
using namespace Chrono;
cout << "enter some dates: ";
Date d;
while (cin >> d) {
write(d);
}
// keep_window_open("~"); // For some Windows(tm) setups
}
catch (Chrono::Date::Invalid&) {
cerr << "error: Invalid date\n";
keep_window_open("~"); // For some Windows(tm) setup
return 1;
}
catch (runtime_error& e) { // this code is to produce error messages
cout << e.what() << '\n';
keep_window_open("~"); // For some Windows(tm) setups
}
catch (...) { // this code is to produce error messages
cout << "Unknown exception\n";
keep_window_open("~"); // For some Windows(tm) setups
}