第9课:日期和时间
日期和时间是较复杂的复合型数据,在不同的环境中会有不同的处理方案。在.NET Framework平台中也定义了很多资源,接下来,我们从基本的DateTime和TimeSpan结构开始讨论。
DateTime和TimeSpan结构
在.NET Framework类库中,常用的日期和时间处理资源包括DateTime结构、TimeSpan结构等类型;其中,DateTime结构处理具体的日期和时间数据,而TimeSpan结构处理日期和时间的跨度。
使用DateTime构建日期和时间数据时,可以使用多个版本的构造函数,常用的包括:
- DateTime(),获取的时间点为公元1年1月1日0时。
- DateTime(int,int,int),使用年、月、日数据,时间为0点。
- DateTime(int,int,int,int,int,int),使用年、月、日、时、分、秒数据。
- DateTime(int,int,int,int,int,int,int),使用年、月、日、时、分、秒和毫秒数据。
- DateTime(int,int,int,Calendar),使用年、月、日,并指定日历类型。
下面的代码,我们使用中国的农历日期构建DateTime数据,执行代码会显示2021年中秋节的阳历日期。
C# |
using System;
using System.Globalization;
public partial class Test : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
DateTime dt = new DateTime(2021, 8, 15,
new ChineseLunisolarCalendar());
tWeb.WriteLine(dt.ToLongDateString());
}
}
|
代码中指定的2021年8月15日,但指定的日历为中国的农历,即八月十五中秋节,本例执行会显示“2021年9月21日”。
获取系统当前时间时可以使用DateTime.Now属性;只需要日期数据时可以使用DateTime.Toay属性,其中的时间为0点。
对日期和时间进行推算时,可以使用DateTime中的一系列AddXXX()方法,如下面的代码,就是获取指定日期前10天和后10天的日期。
C# |
using System;
public partial class Test : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
DateTime dt1 = new DateTime(2021, 8, 15);
DateTime dt2 = dt1.AddDays(-10);
DateTime dt3 = dt1.AddDays(10);
tWeb.WriteLine(dt2.ToString());
tWeb.WriteLine(dt3.ToString());
}
}
|
代码执行结果如图1。
图1
常用的日期和时间推算方法包括:
- AddYears()方法,按年计算,参数为int类型。
- AddMonths()方法,按月计算,参数为int类型。
- AddDays()方法,按天计算,参数为double类型。
- AddHours()方法,按小时计算,参数为double类型。
- AddMinutes()方法,按分钟计算,参数为double类型。
- AddSeconds()方法,按秒计算,参数为double类型。
- AddMilliseconds()方法,按毫秒计算,参数为double类型。
- AddTicks()方法,按Tick值计算,参数为long类型。这里,Tick是DateTime处理时间的最小单位,1Tick等于100纳秒,万分之一毫秒。
计算两个DateTime值的间隔时可以直接使用减法运算,运算结果为TimeSpan类型,如下面的代码。
C# |
using System;
public partial class Test : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
DateTime dt1 = new DateTime(2021, 8, 15);
DateTime dt2 = new DateTime(2021, 8, 5, 12, 0, 0);
TimeSpan ts = dt1 - dt2;
tWeb.WriteLine(ts.Days);
tWeb.WriteLine(ts.TotalDays);
}
}
|
代码执行结果如图2。
图2
可以看到,使用TimeSpan的Days属性时得到是整天数,而TotalDays属性获取的数据则是浮点数,包含了一天中的时间部分。
从TimeSpan结构中读取日期和时间数据的属性包括:
- Days属性,获取整天数据,返回类型为int。
- TotalDays属性,获取天数,返回类型为double。
- Hours属性,获取整小时数,返回类型为int。
- TotalHours属性,获取小时数,返回类型为double。
- Minutes属性,获取整分钟数,返回类型为int。
- TotalMinutes属性,获取分钟数,返回类型为double。
- Seconds属性,获取整秒数,返回类型为int。
- TotalSeconds属性,获取秒数,返回类型为double。
- Milliseconds属性,获取整毫秒数,返回类型为int。
- TotalMilliseconds属性,获取毫秒数,返回类型为double。
- Ticks属性,获取Tick值,返回类型为long。
在DateTime数据中进行推算时也可以使用TimeSpan数据,此时,可以先使用TimeSpan结构的构造函数创建数据,如:
- TimeSpan(long ticks),使用Tick值构建TimeSpan数据。
- TimeSpan(int hours,int minutes,int seconds),使用小时、分种、秒构建TimeSpan数据。
- TimeSpan(int days,int hours,int minutes,int seconds),使用天数、小时、分钟和秒数构建TimeSpan数据。
- TimeSpan(int days,int hours,int minutes,int seconds,int milliseconds),使用天数、小时、分钟、秒和毫秒构建TimeSpan数据。
创建TimeSpan数据后,可以使用DateTime结构中的Add()方法进行日期和时间的推算,如下面的代码。
C# |
using System;
public partial class Test : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
DateTime dt1 = new DateTime(2021, 8, 15);
TimeSpan ts = new TimeSpan(10, 12, 15, 30);
DateTime dt2 = dt1.Add(ts);
tWeb.WriteLine(dt2.ToString());
}
}
|
代码中,dt2会在dt1的数据上添加10天12小时15分30秒,执行结果如图3。
图3
将DateTime数据转换为字符串形式时,可以使用如下方法:
- ToLongDateString()方法,返回长格式日期字符串。
- ToShortDateString()方法,返回短格式日期字符串。
- ToLongTimeString()方法,返回长格式时间字符串。
- ToShortTimeString()方法,返回短格式时间字符串。
图4中显示了中文环境下的显示格式。
图4
无参数的ToString()方法可以返回日期和时间的标准文本形式,也可以通过设置格式化参数显示指定格式的文本形式。
此外,对于日期和时间的文本格式,也可以根据需要进行自定义,在tDate类(/common/base/tDate.cs)中定义了一些方法来完成日期和时间格式化字符串的生成,如下面的代码。
C# |
using System;
public static class tDate
{
// 其它代码…
// 日期和时间转文本格式
public static string DateTimeStr(this DateTime dt)
{
return string.Format("{0:d4}-{1:d2}-{2:d2} {3:d2}:{4:d2}:{5:d2}",
dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second);
}
//
public static string DateStr(this DateTime dt)
{
return string.Format("{0:d4}-{1:d2}-{2:d2}",
dt.Year, dt.Month, dt.Day);
}
//
public static string TimeStr(this DateTime dt)
{
return string.Format("{0:d2}:{1:d2}:{2:d2}",
dt.Hour, dt.Minute, dt.Second);
}
//
public static string HourMinuteStr(this DateTime dt)
{
return string.Format("{0:d2}:{1:d2}", dt.Hour, dt.Minute);
}
//
}
|
代码中,tDate类定义了几个扩展方法,转换的日期和时间文本形式中不包含连接符号,其中,年份使用4位字符,月份、天、时、分、秒使用2位字符等,实际应用中可以根据需要定义相应的格式。
农历
在讨论DateTime构造函数时已经了解了如何通过农历日期构建DateTime结构数据,我们再回顾一下,如下面的代码显示了2022年春节的阳历日期。
C# |
using System;
using System.Globalization;
public partial class Test : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
DateTime dt = new DateTime(2022, 1, 1,
new ChineseLunisolarCalendar());
tWeb.WriteLine(dt.ToLongDateString());
}
}
|
代码会显示“2022年2月1日”。
如果已有阳历日期,如何获取农历信息时,本节将通过封装tLunar类进行相关操作。下面的代码(/app_code/common/tool/tLunar.cs)就是tLunar类的基本定义。
C# |
using System;
using System.Globalization;
public class tLunar
{
// 构造函数
public tLunar(DateTime dt)
{
Init(dt);
}
//
public tLunar()
{
Init(DateTime.Now);
}
//
public tLunar(int year, int month, int day)
{
Init(new DateTime(year, month, day));
}
// 工厂方法
public static tLunar Get(DateTime dt)
{
return new tLunar(dt);
}
//
public static tLunar Get()
{
return new tLunar(DateTime.Now);
}
//
public static tLunar Get(int year, int month, int day)
{
return new tLunar(new DateTime(year, month, day));
}
//
public int Year { get; private set; } // 甲子,1-60
public int TianGan { get; private set; } // 天干,1-10
public int DiZhi { get; private set; } // 地支,1-12
public int Month { get; private set; } // 农历月份
public int Day { get; private set; } // 农历日
public int LeapMonth { get; private set; } // 闰月
//
public DateTime Date { get; private set; } // 阳历日期
// 初始化农历信息
protected void Init(DateTime dt)
{
ChineseLunisolarCalendar cale =
new ChineseLunisolarCalendar();
Year = cale.GetSexagenaryYear(dt);
TianGan = cale.GetCelestialStem(Year);
DiZhi = cale.GetTerrestrialBranch(Year);
Month = cale.GetMonth(dt);
Day = cale.GetDayOfMonth(dt);
// 判断闰月时注意农历冬月和腊月在阳历第二年的情况
if (dt.Month < 3 && Month > 10)
LeapMonth = cale.GetLeapMonth(dt.Year - 1);
else
LeapMonth = cale.GetLeapMonth(dt.Year);
//
Date = dt;
}
// 其它代码…
}
|
tLunar类中,首先定义了几个构造函数和工厂方法,用于创建tLunar对象,版本包括:
- tLunar(),使用系统当前日期进行初始化。
- tLunar(DateTime dt),使用指定的日期进行初始化。
- tLunar(int year, int month, int day),使用指定的阳历年、月、日进行初始化。
此外,GetByLunar()静态方法中可以返回由农历年、月、日指定的tLunar对象,如下面的代码。
C# |
public static tLunar GetByLunar(int year, int month, int day)
{
return new tLunar(new DateTime(year, month, day,
new ChineseLunisolarCalendar()));
}
|
在tLunar类中,还定义了一系列的只读属性,分别是:
- Year,甲子年份,范围在1到60之间。
- TianGan属性,天干,范围在1到10之间。
- DiZhi属性,地支,范围在1到12之间。十二地支还分别对应了一天中的十二个时辰,以及十二生肖。
- Month属性,农历月份。由于农历会出现闰月,所月份的范围在1到13之间。
- Day属性,农历日期,范围在1到30之间。
- LeapMonth属性,表示第几个月为闰月,如LeapMonth为8表示第8个月为闰月,即闰七月。
- Date属性,表示阳历日期数据,类型为DateTime。
对tLunar对象进行初始化时使用了Init()方法,其中主要使用的资源是ChineseLunisolarCalendar对象中的一些方法,包括:
- GetSexagenaryYear()方法,通DateTime数据返回甲子数据,返回值在1到60之间,表示年份是一个甲子中的第几年。
- GetCelestialStem()方法,根据甲子年份返回天干数据,返回值在1到10之间。
- GetTerrestrialBranch()方法,根据甲子年份返回地支数据,返回值在1到12之间。
- GetMonth()方法,根据DateTime数据返回农历月份,返回值在1到13之间。
- GetDayOfMonth()方法,根据DateTime数据返回农历日期,返回值在1到30之间。
- GetLeapMonth()方法,根据阳历年份值返回闰月值。这里有两点需要注意,一是参数使用的是阳历年份。二是当农历是冬月或腊月时,可能是阳历的第二年的一月或二月,在这种情况下,需要使用的参数就是阳历的前一年。
农历相关的数据会在对象初始化时进行计算,接下来可以使用一系列的属性获取农历信息的文本描述,首先看年份名称的获取,如下面的代码。
C# |
// 年份名称
private static string[] yearNames = {"",
"甲子","乙丑","丙寅","丁卯","戊辰","己巳","庚午","辛未","壬申","癸酉",
"甲戌","乙亥","丙子","丁丑","戊寅","己卯","庚辰","辛巳","壬午","癸未",
"甲申","乙酉","丙戌","丁亥","戊子","己丑","庚寅","辛卯","壬辰","癸巳",
"甲午","乙未","丙申","丁酉","戊戌","己亥","庚子","辛丑","壬寅","癸卯",
"甲辰","乙巳","丙午","丁未","戊申","己酉","庚戌","辛亥","壬子","癸丑",
"甲寅","乙卯","丙辰","丁巳","戊午","己未","庚申","辛酉","壬戌","癸亥"};
//
public string YearName
{
get { return yearNames[Year]; }
}
|
代码中,首先使用yearNames数组定义了甲子年份的名称,可以看到,年份名称是由十天二和十二地支循环组合而成。由于甲子年份的数据(Year属性)是1到60,所以,在yearNames数组的第一个成员使用了空字符串作为点位元素,这样,在YearName属性中就可以将Year属性作为索引值,直接从yearNams数组中返回甲子年份名称。
接下来是月份名称的获取,如下面的代码。
C# |
// 月份名称
private static string[] monthNames =
{"", "正月", "二月", "三月", "四月", "五月", "六月",
"七月", "八月", "九月", "十月", "冬月", "腊月"};
//
public string MonthName
{
get
{
if (LeapMonth <= 0 || Month < LeapMonth)
return monthNames[Month];
else if (Month == LeapMonth)
return "闰" + monthNames[Month - 1];
else
return monthNames[Month - 1];
}
}
|
MonthName属性中,当不存在闰月或当前月小于闰月时,直接返回农历月份名称。当前农历月份是LeapMonth属性值时,返回值会在前一月份名称前添加“润”字。当存在闰月并且月份大于LeapMonth值时,返回的月份名称应为Month属性值的前一个月。
下面的代码用于返回农历日期。
C# |
// 日子名称
private static string[] dayNames =
{"", "初一", "初二", "初三", "初四", "初五",
"初六", "初七", "初八", "初九", "初十",
"十一", "十二", "十三", "十四", "十五",
"十六", "十七", "十八", "十九", "二十",
"廿一", "廿二", "廿三", "廿四", "廿五",
"廿六", "廿七", "廿八", "廿九", "三十"};
// 当前日期名称
public string DayName
{
get { return dayNames[Day]; }
}
|
农历月份中,小月有29天,大月有30天,可以根据Day属性值直接从dayNames数组中返回日期名称。
下面的代码可以返回天干名称、地支名称,以及地支相关的生肖名称和时辰名称。
C# |
// 天干名称
private static string[] tianganNames = {"",
"甲","乙","丙","丁","戊","己","庚","辛","壬","癸" };
// 当前天干名称
public string TianGanName
{
get { return tianganNames[TianGan]; }
}
// 地支名称
private static string[] dizhiNames = {"",
"子","丑","寅","卯","辰","巳","午","未","申","酉","戌","亥" };
// 当前地支名称
public string DiZhiName
{
get { return dizhiNames[DiZhi]; }
}
// 生肖对应地支
private static string[] shengxiaoNames = {"",
"鼠","牛","虎","兔","龙","蛇","马","羊","猴","鸡","狗","猪" };
//
public string ShengXiaoName
{
get { return shengxiaoNames[DiZhi]; }
}
// 0至23点给出时辰
private static string[] shiChenNames = {
"子时","丑时","丑时","寅时","寅时","卯时","卯时",
"辰时","辰时","巳时","巳时","午时","午时","未时","未时",
"申时","申时","酉时","酉时","戌时","戌时","亥时","亥时","子时" };
//
public static string ShiChen(int hour)
{
return shiChenNames[hour];
}
//
public static string ShiChen(DateTime dt)
{
return ShiChen(dt.Hour);
}
|
tLunar类的最后定义了ToStr()方法,其功能是返回日期的完整农历信息。
C# |
public string ToStr()
{
return string.Format("{0}({1})年 {2}{3}",
YearName, ShengXiaoName, MonthName, DayName);
}
|
下面的代码演示了tLunar类的基本应用。
C# |
using System;
public partial class Test : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
tLunar lunar = tLunar.Get(2021, 9, 21);
tWeb.WriteLine("年份:" + lunar.YearName);
tWeb.WriteLine("天干:" + lunar.TianGanName);
tWeb.WriteLine("地支:" + lunar.DiZhiName);
tWeb.WriteLine("月份:" + lunar.MonthName);
tWeb.WriteLine("日期:" + lunar.DayName);
tWeb.WriteLine("生肖:" + lunar.ShengXiaoName);
tWeb.WriteLine(lunar.ToStr());
}
}
|
页面显示结果如图5。
图5
最后来看除夕的查找方法。因为腊月也会有29天或30天的情况,所以,除夕可能是腊月廿十九或腊月三十;此时,可以按春节的前一天进行推算,如下面的代码,在tLunar类中使用GetChuXi()方法。
C# |
public static tLunar GetChuXi(int year)
{
DateTime dt = new DateTime(year, 1, 1,
new ChineseLunisolarCalendar());
return new tLunar(dt.AddDays(-1));
}
|
下面的代码演示了GetChuXi()方法的应用。
C# |
using System;
public partial class Test : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
tLunar lunar = tLunar.GetChuXi(2022);
tWeb.WriteLine(lunar.ToStr());
tWeb.WriteLine(lunar.Date.ToLongDateString());
//
tLunar chunjie = tLunar.GetByLunar(2022, 1, 1);
tWeb.WriteLine(chunjie.ToStr());
tWeb.WriteLine(chunjie.Date.ToLongDateString());
}
}
|
代码执行结果如图6。
图6
本例,首先显示了2022年中除夕的日期,它对应的农历年份实际为2021年(辛丑年);然后,输出了是2022年(壬寅年)的春节,即正月初一的日期信息。获取其它的农历节日时,可以直接使用GetByLunar()方法创建tLunar对象,如下面的代码显示了2022年中的春节、端午节和中秋节的阳历日期。
C# |
using System;
public partial class Test : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
tWeb.WriteLine(
tLunar.GetByLunar(2022, 1, 1).Date.ToLongDateString());
tWeb.WriteLine(
tLunar.GetByLunar(2022, 5, 5).Date.ToLongDateString());
tWeb.WriteLine(
tLunar.GetByLunar(2022, 8, 15).Date.ToLongDateString());
}
}
|
页面显示结果如图7。
图7
更多日期和时间处理方案
C#应用开发中,日期和时间的处理是非常方便的,但需要注意应用开发中与非.NET Framework平台的数据交换问题。在不同的环境中,有一些常用的日期和时间处理方案,如:
- 时间戳。使用整数保存日期和时间数据,表示距离基准时间的秒数,比如,以1970年1月1日0时为基准时间。
- OLE自动化标准时间。以浮点数处理日期和时间,整数部分作为日期数据,小数部分作为时间数据,如Excel中使用的日期和时间处理方法。
SQLite数据库并没有内置日期和时间处理类型,在处理日期和时间数据时就需要采用适合的解决方案,后续课程会详细讨论。