《C++程式設計原理與實踐》第九章習題12要改寫Date類,類實現不是用年月日的方法而是用距1970年1月1日的天數來實現。當時感到有點棘手,就搜尋了網上別人的實現方法,我看人實現還是保留了年月日,我覺得這違背了作者的本意。所以還是硬著頭皮自己寫。其實也不難,只是有一個小技巧,就是在如何計算閏年數的方法上。因為現在類裡儲存的是當前日期距離1970年1月1日的天數,所以不能直接用作者計算閏年的函式來實現。因為用1970年作為基準年來實現計算閏年的次數不好實現,所以我想了一個通過曲線手段來實現的方法,具體做法是仍舊從1601年開始算閏年,只是用1601年到當前年的閏年數減去1601年到1970年的閏年數來得到當前年到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
}