关于C#和js的一些时间格式问题(摘记)
浏览器默认格式字符串
根据Spec的定义,每个ECMAScript的脚本引擎(在这里就是浏览器)都需要给定以下四个方法来给出表示当前Date对象时间信息的字符串:
- Date.prototype.toString():返回表示本地日期和时间的字符串。
- Date.prototype.toDateString():返回表示本地日期的字符串。
- Date.prototype.toTimeString():返回表示本地时间的字符串。
- Date.prototype.toUTCString():返回表示UTC日期和时间的字符串。
在Spec中,只要求了这些方法返回一个“人类可读”的字符串,用于表示Date对象所保存的时间信息,但是没有规定其格式。事实上,就拿IE和FireFox来说,这些方法的调用结果的确并不相同。首先是IE浏览器中的执行结果:
var d = new Date(0); alert(d.toString()); // Thu Jan 1 08:00:00 UTC+0800 1970 alert(d.toDateString()); // Thu Jan 1 1970 alert(d.toTimeString()); // 08:00:00 UTC+0800 alert(d.toUTCString()); // Thu, 1 Jan 1970 00:00:00 UTC
在FireFox中:
var d = new Date(0); alert(d.toString()); // Thu Jan 01 1970 08:00:00 GMT+0800 alert(d.toDateString()); // Thu Jan 01 1970 alert(d.toTimeString()); // 08:00:00 GMT+0800 alert(d.toUTCString()); // Thu, 01 Jan 1970 00:00:00 GMT
由于Spec没有规定这些方法返回字符串的格式,因此这两种结果都是符合标准的。不过请注意,toString、toDateString和toTimeString三者的结果有着确定的关系,如下:
d.toString() == d.toDateString() + " " + d.toTimeString()
解析时间日期字符串
既然有了得到字符串的方法,自然也少不了将字符串解析为一个Date类型对象的方法,这个方法的定义如下:
- Date.parse(str):解析str字符串,返回表示时间的time value。
在上一节中介绍的那些方法是用于返回浏览器用于表示日期和时间的默认格式字符串,而根据Spec中对于parse方法的定义,该方法能够解析的字符串格式即为浏览器决定的“默认格式”。因此,对于任意合法的time value的值t,下面三个表达式的值都是相等的:
- t
- Date.parse(new Date(t).toString());
- Date.parse(new Date(t).toUTCString());
由于不同的浏览器对于默认日期时间格式的设定并不统一,所以Date.parse方法能够识别的字符串在不同浏览器环境下也很可能各不相同。在加上默认表示的日期时间的字符串对于用户来说并不友好,因此在实际开发过程中,使用Date.parse方法的情形并不多见。但是在扩展时依旧必须保持这个方法功能的不变,在ASP.NET AJAX Beta 1中,正是因为覆盖了Date.parse方法,导致了在页面中所有的Google AdSense运行出错。在ASP.NET AJAX正式版中,Date.parse方法就被改为Date.parseLocale和Date.parseInvariant两个方法。后话不提。
系统设定时间日期字符串
之前介绍的方法都是用于得到浏览器默认格式的日期时间字符串,在实际开发角度来说用处不大。如果能够使用客户端设定相关的格式来显示日期和时间就好多了,JavaScript中自然想到了这一点,因此在Spec中定义了以下几个方法:
- Date.prototype.toLocaleString():根据系统设定,返回表示本地日期和时间的字符串
- Date.prototype.toLocaleDateString():根据系统设定,返回表示本地日期的字符串。
- Date.prototype.toLocaleTimeString():根据系统设定,返回表示本地时间的字符串。
请注意,这里并没有返回UTC时间的说法。“UTC时间”本身就是一个“表现”上的概念,而在“系统设定”中,已经包括了“系统时区”信息,因此结果字符串表示的一定是“本地时间”。
那么,上面三个方法返回的结果是怎么样的呢?因为结果字符串的格式只和客户端操作系统设定有关,因此无论是什么浏览器,它们返回的结果是一样的。在我的机器上,请注意是“在我的机器上”,三个方法分别返回如下的结果:
var d = new Date(0); alert(d.toLocaleString()); // 1970年1月1日 8:00:00 alert(d.toLocaleDateString()); // 1970年1月1日 alert(d.toLocaleTimeString()); // 8:00:00
这个结果是由我的系统设定决定的。在我的Vista操作系统中可以使用如下的方式来查看和设定(Windows操作系统其实大都差不多):开始——控制面板——区域和语言选项:
在上面的对话框中,“长日期”中定义的格式即为toLocaleDateString方法返回结果所用的格式,而“时间”中定义的格式即为toLocaleTimeString方法返回结果所用的格式。“数字”、“货币”和“短日期”定义的格式都会在.NET Framework中使用到,不过这已经超出了这片文章讨论的范围。如果需要修改日期时间的格式,只需要在上面对话框中的下拉框中选择不同的格式即可。
不过请注意,这里只是设置了一些“格式”,如果要设定时区信息,则需要选择上面对话框中的“位置”标签,在那里您可以选择系统所使用的时区信息。系统的“格式”和“时区”信息相互独立,也就是说,我们完全可以在使用中国时区的情况下,使用美国的格式来显示信息。
不过无论格式如何,toLocaleString、toLocaleDateString和toLocaleTimeString三个方法结果之间的关系是不会改变的。
如何在实际开发中使用客户端系统的格式
大约8个月前,我正在参与一个项目的中国本地化(Localization,指开发一些市场特有的功能)以及全球化(Globalization,指开发一些让当前应用支持多市场运作的工作,例如编写市场切换逻辑,将写在页面中的文字迁移至资源文件中等等)工作。那时,对于如何在页面上显示日期和时间有过一个讨论,讨论集中在两种选择上:
- 使用当前用户即将正在访问的市场来决定日期时间的显示格式。这样做可能得到的结果就是:一个系统设定格式为“中国”的用户在访问“美国”市场时,看到的时间和日期格式都是“美国”格式,而且用户看到的时间和系统时区设置无关。
- 使用客户端系统设定的格式来显示日期和时间。这样作可能得到的结果就是:一个系统设定格式为“中国”的用户访问“美国”市场时,看到的时间和日期格式为“中国”格式,而用户看到的时间与系统时区设置有关。
后一种做法有优点也有缺点。优点在于在客户端看到的时间能够根据用户操作系统设定(这往往反映了用户的喜好)的格式和时区来显示时间日期。缺点在于可能用户在浏览一个美国市场(全是英文)的情况下,日期时间显示为中文,造成了“不和谐”。不过鉴于后面一种情况非常少见,因此项目最终选择了后一种做法。
不过,在服务器端是无法得到操作系统的时区和格式设置的。曾经有人认为,操作系统设置的格式可以从Request.UserLanguages数组中获得的Culture信息来得到。其实这个观点是错误的,Request.UserLanguages得到的Culture列表其实是在如下的对话框中设置,如下(打开IE浏览器——工具——Internet选项——语言):
理论上这个设置可以独立于系统的格式和时区设定,我们完全能够使用北京的时区、美国的格式,同时服务器端得到阿拉伯文的信息。另外,在服务器端也是无法直接得到操作系统的时区信息的(事实上,我们都可以使用一个小技巧来获得客户端的格式和时区设定,不过这将会在下一篇文章中讲述)。
如果要将时间根据客户端的设置来显示,则需要将时间的“值”输出至客户端,并且在客户端显示出来。因此,我们可以使用以下的方法。这个方法的精髓在于使用客户端的document.write方法将信息写在页面上。首先,我们定义一个方法用户生成这段脚本:
protected string GetClientDisplayDate(DateTime dt) { DateTime utc = dt.ToUniversalTime(); return String.Format( "<script language='javascript' type='text/javascript'>\n" + " document.write(new Date({0}, {1}, {2}).toLocaleDateString());\n" + "</script>", dt.Year, dt.Month - 1, dt.Day); }
上面的代码有三个地方需要注意:
- 传入的DateTime对象需要调用其ToUniversalTime方法来得到它相应的UTC时间,这点在今后的文章中会谈到。
- 我们必须使用枚举年、月、日等信息的方法来构造Date类型对象。有人认为可以使用DateTime的Ticks属性来直接使用time value在客户端构造对象,其实这是不对的。.NET Framework中DateTime类型的设计和ECMAScript中Date类型设计不同,它的Ticks属性并不等同于time value。
- 在传入月份的时候,需要减去1,因为.NET Framework中DateTime类型的Month属性使用1到12表示月份,而ECAMScript中Date类型使用0到11来表示。
于是我们可以使用上面的方法在页面合适的地方显示时间,例如:
<%= GetClientDisplayDate(DateTime.Now) %>
这样,在客户端就会出现如下的脚本代码,这段代码将会使用客户端的系统设置格式来显示日期:
<script language='javascript' type='text/javascript'> document.write(new Date(2007, 5, 6).toLocaleDateString()); </script>
C#的时间格式转化
为了达到不同的显示效果有时,我们需要对时间进行转化,默认格式为:2007-01-03 14:33:34 ,要转化为其他格式,要用到DateTime.ToString的方法(String, IFormatProvider),如下所示:
using System;
using System.Globalization;
String format="D";
DateTime date=DataTime,Now;
Response.Write(date.ToString(format, DateTimeFormatInfo.InvariantInfo));
结果输出
Thursday, June 16, 2005
参数format格式详细用法:
格式字符 关联属性/说明
d ShortDatePattern
D LongDatePattern
f 完整日期和时间(长日期和短时间)
F FullDateTimePattern(长日期和长时间)
g 常规(短日期和短时间)
G 常规(短日期和长时间)
m、M MonthDayPattern
r、R RFC1123Pattern
s 使用当地时间的 SortableDateTimePattern(基于 ISO 8601)
t ShortTimePattern
T LongTimePattern
u UniversalSortableDateTimePattern 用于显示通用时间的格式
U 使用通用时间的完整日期和时间(长日期和长时间)
y、Y YearMonthPattern
下表列出了可被合并以构造自定义模式的模式。这些模式是区分大小写的;例如,识别“MM”,但不识别“mm”。如果自定义模式包含空白字符或用单引号括起来的字符,则输出字符串页也将包含这些字符。未定义为格式模式的一部分或未定义为格式字符的字符按其原义复制。
格式模式 说明
d 月中的某一天。一位数的日期没有前导零。
dd 月中的某一天。一位数的日期有一个前导零。
ddd 周中某天的缩写名称,在 AbbreviatedDayNames 中定义。
dddd 周中某天的完整名称,在 DayNames 中定义。
M 月份数字。一位数的月份没有前导零。
MM 月份数字。一位数的月份有一个前导零。
MMM 月份的缩写名称,在 AbbreviatedMonthNames 中定义。
MMMM 月份的完整名称,在 MonthNames 中定义。
y 不包含纪元的年份。如果不包含纪元的年份小于 10,则显示不具有前导零的年份。
yy 不包含纪元的年份。如果不包含纪元的年份小于 10,则显示具有前导零的年份。
yyyy 包括纪元的四位数的年份。
gg 时期或纪元。如果要设置格式的日期不具有关联的时期或纪元字符串,则忽略该模式。
h 12 小时制的小时。一位数的小时数没有前导零。
hh 12 小时制的小时。一位数的小时数有前导零。
H 24 小时制的小时。一位数的小时数没有前导零。
HH 24 小时制的小时。一位数的小时数有前导零。
m 分钟。一位数的分钟数没有前导零。
mm 分钟。一位数的分钟数有一个前导零。
s 秒。一位数的秒数没有前导零。
ss 秒。一位数的秒数有一个前导零。
f 秒的小数精度为一位。其余数字被截断。
ff 秒的小数精度为两位。其余数字被截断。
fff 秒的小数精度为三位。其余数字被截断。
ffff 秒的小数精度为四位。其余数字被截断。
fffff 秒的小数精度为五位。其余数字被截断。
ffffff 秒的小数精度为六位。其余数字被截断。
fffffff 秒的小数精度为七位。其余数字被截断。
t 在 AMDesignator 或 PMDesignator 中定义的 AM/PM 指示项的第一个字符(如果存在)。
tt 在 AMDesignator 或 PMDesignator 中定义的 AM/PM 指示项(如果存在)。
z 时区偏移量(“+”或“-”后面仅跟小时)。一位数的小时数没有前导零。例如,太平洋标准时间是“-8”。
zz 时区偏移量(“+”或“-”后面仅跟小时)。一位数的小时数有前导零。例如,太平洋标准时间是“-08”。
zzz 完整时区偏移量(“+”或“-”后面跟有小时和分钟)。一位数的小时数和分钟数有前导零。例如,太平洋标准时间是“-08:00”。
: 在 TimeSeparator 中定义的默认时间分隔符。
/ 在 DateSeparator 中定义的默认日期分隔符。
% c 其中 c 是格式模式(如果单独使用)。如果格式模式与原义字符或其他格式模式合并,则可以省略“%”字符。
\ c 其中 c 是任意字符。照原义显示字符。若要显示反斜杠字符,请使用“\\”。
只有上面第二个表中列出的格式模式才能用于创建自定义模式;在第一个表中列出的标准格式字符不能用于创建自定义模式。自定义模式的长度至少为两个字符;例如,
DateTime.ToString( "d") 返回 DateTime 值;“d”是标准短日期模式。
DateTime.ToString( "%d") 返回月中的某天;“%d”是自定义模式。
DateTime.ToString( "d ") 返回后面跟有一个空白字符的月中的某天;“d”是自定义模式。
比较方便的是,上面的参数可以随意组合,并且不会出错,多试试,肯定会找到你要的时间格式
如要得到2005年06月 这样格式的时间
可以这样写:
date.ToString("yyyy年MM月", DateTimeFormatInfo.InvariantInfo)
日期转化二
DateTime dt = DateTime.Now;
Label1.Text = dt.ToString();//2005-11-5 13:21:25
Label2.Text = dt.ToFileTime().ToString();//127756416859912816
Label3.Text = dt.ToFileTimeUtc().ToString();//127756704859912816
Label4.Text = dt.ToLocalTime().ToString();//2005-11-5 21:21:25
Label5.Text = dt.ToLongDateString().ToString();//2005年11月5日
Label6.Text = dt.ToLongTimeString().ToString();//13:21:25
Label7.Text = dt.ToOADate().ToString();//38661.5565508218
Label8.Text = dt.ToShortDateString().ToString();//2005-11-5
Label9.Text = dt.ToShortTimeString().ToString();//13:21
Label10.Text = dt.ToUniversalTime().ToString();//2005-11-5 5:21:25
Label1.Text = dt.Year.ToString();//2005
Label2.Text = dt.Date.ToString();//2005-11-5 0:00:00
Label3.Text = dt.DayOfWeek.ToString();//Saturday
Label4.Text = dt.DayOfYear.ToString();//309
Label5.Text = dt.Hour.ToString();//13
Label6.Text = dt.Millisecond.ToString();//441
Label7.Text = dt.Minute.ToString();//30
Label8.Text = dt.Month.ToString();//11
Label9.Text = dt.Second.ToString();//28
Label10.Text = dt.Ticks.ToString();//632667942284412864
Label11.Text = dt.TimeOfDay.ToString();//13:30:28.4412864
Label1.Text = dt.ToString();//2005-11-5 13:47:04
Label2.Text = dt.AddYears(1).ToString();//2006-11-5 13:47:04
Label3.Text = dt.AddDays(1.1).ToString();//2005-11-6 16:11:04
Label4.Text = dt.AddHours(1.1).ToString();//2005-11-5 14:53:04
Label5.Text = dt.AddMilliseconds(1.1).ToString();//2005-11-5 13:47:04
Label6.Text = dt.AddMonths(1).ToString();//2005-12-5 13:47:04
Label7.Text = dt.AddSeconds(1.1).ToString();//2005-11-5 13:47:05
Label8.Text = dt.AddMinutes(1.1).ToString();//2005-11-5 13:48:10
Label9.Text = dt.AddTicks(1000).ToString();//2005-11-5 13:47:04
Label10.Text = dt.CompareTo(dt).ToString();//0
Label11.Text = dt.Add(?).ToString();//问号为一个时间段Label1.Text = dt.Equals("2005-11-6 16:11:04").ToString();//False
Label2.Text = dt.Equals(dt).ToString();//True
Label3.Text = dt.GetHashCode().ToString();//1474088234
Label4.Text = dt.GetType().ToString();//System.DateTime
Label5.Text = dt.GetTypeCode().ToString();//DateTime
Label1.Text = dt.GetDateTimeFormats("s")[0].ToString();//2005-11-05T14:06:25
Label2.Text = dt.GetDateTimeFormats("t")[0].ToString();//14:06
Label3.Text = dt.GetDateTimeFormats("y")[0].ToString();//2005年11月
Label4.Text = dt.GetDateTimeFormats("D")[0].ToString();//2005年11月5日
Label5.Text = dt.GetDateTimeFormats("D")[1].ToString();//2005 11 05
Label6.Text = dt.GetDateTimeFormats("D")[2].ToString();//星期六 2005 11 05
Label7.Text = dt.GetDateTimeFormats("D")[3].ToString();//星期六 2005年11月5日
Label8.Text = dt.GetDateTimeFormats("M")[0].ToString();//11月5日
Label9.Text = dt.GetDateTimeFormats("f")[0].ToString();//2005年11月5日 14:06
Label10.Text = dt.GetDateTimeFormats("g")[0].ToString();//2005-11-5 14:06
Label11.Text = dt.GetDateTimeFormats("r")[0].ToString();//Sat, 05 Nov 2005 14:06:25 GMT
Label1.Text = string.Format("{0:d}",dt);//2005-11-5
Label2.Text = string.Format("{0:D}",dt);//2005年11月5日
Label3.Text = string.Format("{0:f}",dt);//2005年11月5日 14:23
Label4.Text = string.Format("{0:F}",dt);//2005年11月5日 14:23:23
Label5.Text = string.Format("{0:g}",dt);//2005-11-5 14:23
Label6.Text = string.Format("{0:G}",dt);//2005-11-5 14:23:23
Label7.Text = string.Format("{0:M}",dt);//11月5日
Label8.Text = string.Format("{0:R}",dt);//Sat, 05 Nov 2005 14:23:23 GMT
Label9.Text = string.Format("{0:s}",dt);//2005-11-05T14:23:23
Label10.Text string.Format("{0:t}",dt);//14:23
Label11.Text = string.Format("{0:T}",dt);//14:23:23
Label12.Text = string.Format("{0:u}",dt);//2005-11-05 14:23:23Z
Label13.Text = string.Format("{0:U}",dt);//2005年11月5日 6:23:23
Label14.Text = string.Format("{0:Y}",dt);//2005年11月
Label15.Text = string.Format("{0}",dt);//2005-11-5 14:23:23
Label16.Text = string.Format("{0:yyyyMMddHHmmssffff}",dt);