一个代表年月的类YearMonth
测试(VS自带框架):
using System; using Microsoft.VisualStudio.TestTools.UnitTesting; /// <summary> ///This is a test class for YearMonthTest and is intended ///to contain all YearMonthTest Unit Tests ///</summary> [TestClass()] public class YearMonthTest { /// <summary> ///A test for YearMonth Constructor ///</summary> [TestMethod()] public void YearMonthConstructorTest() { var date = new DateTime(2000, 1, 1); var target = new YearMonth(date); Assert.AreEqual(2000, target.Year); Assert.AreEqual(1, target.Month); } /// <summary> ///A test for YearMonth Constructor ///</summary> [TestMethod()] public void YearMonthConstructorTest1() { YearMonth target = new YearMonth(2010, 4); Assert.AreEqual(2010, target.Year); Assert.AreEqual(4, target.Month); } /// <summary> ///A test for GetDate ///</summary> [TestMethod()] public void GetDateTest() { var target = new YearMonth(2010, 5); var expected = new DateTime(2010, 5, 3); var actual = target.GetDate(3); Assert.AreEqual(expected, actual); } [TestMethod] public void GetDateThrows() { Assert.IsTrue(Throws(typeof(ArgumentOutOfRangeException), () => new YearMonth(2012, 12).GetDate(32))); } /// <summary> ///A test for ToString ///</summary> [TestMethod()] public void ToStringTest() { var target = new YearMonth(2008, 3); Assert.AreEqual("200803", target.ToString()); } /// <summary> ///A test for IsValid ///</summary> [TestMethod()] public void IsValidTest() { Assert.IsTrue(new YearMonth(2008, 12).IsValid); Assert.IsFalse(new YearMonth(2010, 13).IsValid); Assert.IsFalse(new YearMonth(2010, 0).IsValid); Assert.IsFalse(new YearMonth(2010, -3).IsValid); Assert.IsFalse(new YearMonth(0, 3).IsValid); Assert.IsFalse(new YearMonth(-2, 3).IsValid); Assert.IsTrue(new YearMonth(9999, 12).IsValid); Assert.IsFalse(new YearMonth(10000, 1).IsValid); Assert.IsFalse(new YearMonth(-2, -3).IsValid); } /// <summary> ///A test for Equals ///</summary> [TestMethod()] public void EqualsTest() { var target = new YearMonth(2010, 4); Assert.IsTrue(target.Equals(new YearMonth(2010, 4))); Assert.IsFalse(target.Equals(new YearMonth(2010, 3))); Assert.IsFalse(target.Equals(string.Empty)); } /// <summary> ///A test for TryParse ///</summary> [TestMethod()] public void TryParseTest() { YearMonth result; var success = YearMonth.TryParse("201012", out result); Assert.IsTrue(success); Assert.AreEqual(new YearMonth(2010, 12), result); success = YearMonth.TryParse("", out result); Assert.IsFalse(success); Assert.AreEqual(result, null); } /// <summary> ///A test for Parse ///</summary> [TestMethod()] public void ParseTest() { Assert.AreEqual(new YearMonth(2008, 12), YearMonth.Parse("200812")); } [TestMethod] public void ParseThrows() { Assert.IsTrue(Throws(typeof(ArgumentNullException), () => YearMonth.Parse(null))); Assert.IsTrue(Throws(typeof(ArgumentException), () => YearMonth.Parse(""))); Assert.IsTrue(Throws(typeof(ArgumentException), () => YearMonth.Parse("aaaaaa"))); Assert.IsTrue(Throws(typeof(ArgumentException), () => YearMonth.Parse("20121221"))); } /// <summary> ///A test for AddYears ///</summary> [TestMethod()] public void AddYearsTest() { Assert.AreEqual(new YearMonth(2011, 11), new YearMonth(2008, 11).AddYears(3)); Assert.AreEqual(new YearMonth(2011, 11), new YearMonth(2011, 11).AddYears(0)); Assert.AreEqual(new YearMonth(2011, 11), new YearMonth(2012, 11).AddYears(-1)); } /// <summary> ///A test for AddMonths ///</summary> [TestMethod()] public void AddMonthsTest() { Assert.AreEqual(new YearMonth(2010, 4), new YearMonth(2010, 3).AddMonths(1)); Assert.AreEqual(new YearMonth(2010, 4), new YearMonth(2010, 4).AddMonths(0)); Assert.AreEqual(new YearMonth(2010, 4), new YearMonth(2010, 8).AddMonths(-4)); Assert.AreEqual(new YearMonth(2010, 1), new YearMonth(2009, 11).AddMonths(2)); Assert.AreEqual(new YearMonth(2009, 11), new YearMonth(2010, 1).AddMonths(-2)); Assert.AreEqual(new YearMonth(2009, 12), new YearMonth(2010, 1).AddMonths(-1)); Assert.AreEqual(new YearMonth(2010, 1), new YearMonth(2009, 12).AddMonths(1)); } static bool Throws(Type exceptionType, Action action) { try { action(); } catch (Exception ex) { if (ex.GetType() == exceptionType || ex.GetType().IsSubclassOf(exceptionType)) return true; throw; } return false; } }
代码:
using System; using System.Linq; /// <summary> /// 代表一个年份中的某个指定月份. 如:2008年10月. /// </summary> public sealed class YearMonth { private readonly int _year; private readonly int _month; /// <summary> /// 用指定的年份和月份构造一个 YearMonth 对象. /// </summary> /// <param name="year">年号,如2012.</param> /// <param name="month">月份,1代表1月,12代表12月.</param> public YearMonth(int year, int month) { _year = year; _month = month; } /// <summary> /// 根据指定日期所在的月份构造一个 YearMonth 对象. /// </summary> /// <param name="date">指定的日期.它的年份和月份部分会作为构造的YearMonth的值使用.</param> public YearMonth(DateTime date) { _year = date.Year; _month = date.Month; } public int Year { get { return _year; } } public int Month { get { return _month; } } /// <summary> /// 是否是有效的值.无效的值可能是年份小于1, 或年份大于9999, 或月份小于1, 或月份大于12. /// </summary> public bool IsValid { get { return 0 < Year && Year < 10000 && 0 < Month && Month < 13; } } /// <summary> /// 获取代表该月指定日期的 DateTime 对象 /// </summary> /// <param name="day">日期,范围1到31.</param> /// <exception cref="ArgumentOutOfRangeException">day大于本月的天数.</exception> public DateTime GetDate(int day) { return new DateTime(Year, Month, day); } /// <summary> /// 返回一个新YearMonth对象,其值为当前对象的值加上指定个年份 /// </summary> /// <param name="value">要在当前年月上增加多少年</param> public YearMonth AddYears(int value) { return new YearMonth(Year + value, Month); } /// <summary> /// 返回一个新YearMonth对象,其值为当前对象的值加上指定个月份 /// </summary> /// <param name="value">要在当前年月上增加多少个月</param> public YearMonth AddMonths(int value) { var totalMonths = Year * 12 + Month + value; var year = totalMonths / 12; var month = totalMonths % 12; if (month == 0) { month = 12; year--; } return new YearMonth(year, month); } /// <summary> /// 返回数字代表的年月份值,共6位数字,前4位代表年份,后两位代表月份,如:200805 /// </summary> public override string ToString() { return "{0:D4}{1:D2}".FormatWith(Year, Month); } /// <summary> /// 判断一个是否与当前对象代表相同的值的YearMonth对象. /// </summary> public override bool Equals(object obj) { return Equals(obj as YearMonth); } /// <summary> ///判断另一个YearMonth对象是否与当前对象代表相同的值. /// </summary> public bool Equals(YearMonth rhs) { if (ReferenceEquals(null, rhs)) return false; if (ReferenceEquals(this, rhs)) return true; return _year == rhs._year && _month == rhs._month; } public override int GetHashCode() { unchecked { return (_year * 397) ^ _month; } } public static bool operator ==(YearMonth lhs, YearMonth rhs) { return Object.Equals(lhs, rhs); } public static bool operator !=(YearMonth lhs, YearMonth rhs) { return !(lhs == rhs); } public static bool TryParse(string s, out YearMonth result) { try { result = Parse(s); return true; } catch (ArgumentException) { result = null; return false; } } /// <summary> /// 通过解析字符串构造一个YearMonth对象. /// </summary> /// <param name="s">要解析的字符串.</param> /// <exception cref="ArgumentNullException">s为null.</exception> /// <exception cref="ArgumentException">s包含非数字字符,或s的长度不为6.</exception> public static YearMonth Parse(string s) { if (s == null) throw new ArgumentNullException("s"); if (s.Length != 6 || s.Any(x => x < '0' || x > '9')) throw new ArgumentException("s应该是6位数字."); var year = int.Parse(s.Substring(0, 4)); var month = int.Parse(s.Substring(4)); return new YearMonth(year, month); } }
辅助方法:
public static class StringExtensions { public static string FormatWith(this string format, object arg0) { return string.Format(format, arg0); } public static string FormatWith(this string format, object arg0, object arg1) { return string.Format(format, arg0, arg1); } public static string FormatWith(this string format, object arg0, object arg1, object arg2) { return string.Format(format, arg0, arg1, arg2); } }