如何在 .Net 中正式正确解析 ISO8601 日期时间?
How to do formally correct parsing of ISO8601 date times in .Net?
有很多关于在 .NET 和 C# 中解析 ISO8601 date/times 的 SO 问题和答案。但是,似乎在任何地方都没有 'definitive' 答案,即提供正式正确的 ISO8601 解析器的答案,该解析器将正确解析 ISO8601 中所有可能的格式变体,并且不允许非 ISO8601 变体。
这个 SO 答案是迄今为止最接近的匹配...
How to create a .NET DateTime from ISO 8601 format
好的。让我们从您自己对 ISO 8601 施加的限制开始:
- 您需要 DateTime,因此不需要所有仅产生时间、持续时间、年份或月份和年份的格式。
- 您需要 DateTime,因此时区信息将变为 "Unspecified"、"UTC" 或 "Local",但无法往返返回同一时区。
- 您需要一个 DateTime,因此失去超过 100ns 的精度。
这让我们只支持不到一半的 ISO 8601 格式,并解决了模棱两可的情况,因为它在仅日期和仅时间含义之间存在歧义。
让我们从我们可以处理的那些开始 DateTime.ParseExact
:
DateTime.ParseExact(dateString, new string[]
{
"yyyy-MM-ddK", "yyyyMMddK", "yy-MM-ddK", "yyMMddK",
"yyyy-MM-ddTHH:mm:ss.fffffffK", "yyyyMMddTHH:mm:ss.fffffffK", "yy-MM-ddTHH:mm:ss.fffffffK", "yyMMddTHH:mm:ss.fffffffK",
"yyyy-MM-ddTHH:mm:ss,fffffffK", "yyyyMMddTHH:mm:ss,fffffffK", "yy-MM-ddTHH:mm:ss,fffffffK", "yyMMddTHH:mm:ss,fffffffK",
"yyyy-MM-ddTHH:mm:ss.ffffffK", "yyyyMMddTHH:mm:ss.ffffffK", "yy-MM-ddTHH:mm:ss.ffffffK", "yyMMddTHH:mm:ss.ffffffK",
"yyyy-MM-ddTHH:mm:ss,ffffffK", "yyyyMMddTHH:mm:ss,ffffffK", "yy-MM-ddTHH:mm:ss,ffffffK", "yyMMddTHH:mm:ss,ffffffK",
"yyyy-MM-ddTHH:mm:ss.fffffK", "yyyyMMddTHH:mm:ss.fffffK", "yy-MM-ddTHH:mm:ss.fffffK", "yyMMddTHH:mm:ss.fffffK",
"yyyy-MM-ddTHH:mm:ss,fffffK", "yyyyMMddTHH:mm:ss,fffffK", "yy-MM-ddTHH:mm:ss,fffffK", "yyMMddTHH:mm:ss,fffffK",
"yyyy-MM-ddTHH:mm:ss.ffffK", "yyyyMMddTHH:mm:ss.ffffK", "yy-MM-ddTHH:mm:ss.ffffK", "yyMMddTHH:mm:ss.ffffK",
"yyyy-MM-ddTHH:mm:ss,ffffK", "yyyyMMddTHH:mm:ss,ffffK", "yy-MM-ddTHH:mm:ss,ffffK", "yyMMddTHH:mm:ss,ffffK",
"yyyy-MM-ddTHH:mm:ss.ffK", "yyyyMMddTHH:mm:ss.ffK", "yy-MM-ddTHH:mm:ss.ffK", "yyMMddTHH:mm:ss.ffK",
"yyyy-MM-ddTHH:mm:ss,ffK", "yyyyMMddTHH:mm:ss,ffK", "yy-MM-ddTHH:mm:ss,ffK", "yyMMddTHH:mm:ss,ffK",
"yyyy-MM-ddTHH:mm:ss.fK", "yyyyMMddTHH:mm:ss.fK", "yy-MM-ddTHH:mm:ss.fK", "yyMMddTHH:mm:ss.fK",
"yyyy-MM-ddTHH:mm:ss,fK", "yyyyMMddTHH:mm:ss,fK", "yy-MM-ddTHH:mm:ss,fK", "yyMMddTHH:mm:ss,fK",
"yyyy-MM-ddTHH:mm:ssK", "yyyyMMddTHH:mm:ssK", "yy-MM-ddTHH:mm:ssK", "yyMMddTHH:mm:ssK",
"yyyy-MM-ddTHHmmss.fffffffK", "yyyyMMddTHHmmss.fffffffK", "yy-MM-ddTHHmmss.fffffffK", "yyMMddTHHmmss.fffffffK",
"yyyy-MM-ddTHHmmss,fffffffK", "yyyyMMddTHHmmss,fffffffK", "yy-MM-ddTHHmmss,fffffffK", "yyMMddTHHmmss,fffffffK",
"yyyy-MM-ddTHHmmss.ffffffK", "yyyyMMddTHHmmss.ffffffK", "yy-MM-ddTHHmmss.ffffffK", "yyMMddTHHmmss.ffffffK",
"yyyy-MM-ddTHHmmss,ffffffK", "yyyyMMddTHHmmss,ffffffK", "yy-MM-ddTHHmmss,ffffffK", "yyMMddTHHmmss,ffffffK",
"yyyy-MM-ddTHHmmss.fffffK", "yyyyMMddTHHmmss.fffffK", "yy-MM-ddTHHmmss.fffffK", "yyMMddTHHmmss.fffffK",
"yyyy-MM-ddTHHmmss,fffffK", "yyyyMMddTHHmmss,fffffK", "yy-MM-ddTHHmmss,fffffK", "yyMMddTHHmmss,fffffK",
"yyyy-MM-ddTHHmmss.ffffK", "yyyyMMddTHHmmss.ffffK", "yy-MM-ddTHHmmss.ffffK", "yyMMddTHHmmss.ffffK",
"yyyy-MM-ddTHHmmss,ffffK", "yyyyMMddTHHmmss,ffffK", "yy-MM-ddTHHmmss,ffffK", "yyMMddTHHmmss,ffffK",
"yyyy-MM-ddTHHmmss.ffK", "yyyyMMddTHHmmss.ffK", "yy-MM-ddTHHmmss.ffK", "yyMMddTHHmmss.ffK",
"yyyy-MM-ddTHHmmss,ffK", "yyyyMMddTHHmmss,ffK", "yy-MM-ddTHHmmss,ffK", "yyMMddTHHmmss,ffK",
"yyyy-MM-ddTHHmmss.fK", "yyyyMMddTHHmmss.fK", "yy-MM-ddTHHmmss.fK", "yyMMddTHHmmss.fK",
"yyyy-MM-ddTHHmmss,fK", "yyyyMMddTHHmmss,fK", "yy-MM-ddTHHmmss,fK", "yyMMddTHHmmss,fK",
"yyyy-MM-ddTHHmmssK", "yyyyMMddTHHmmssK", "yy-MM-ddTHHmmssK", "yyMMddTHHmmssK",
"yyyy-MM-ddTHH:mmK", "yyyyMMddTHH:mmK", "yy-MM-ddTHH:mmK", "yyMMddTHH:mmK",
"yyyy-MM-ddTHHK", "yyyyMMddTHHK", "yy-MM-ddTHHK", "yyMMddTHHK"
},
CultureInfo.CurrentCulture, DateTimeStyles.AllowLeadingWhite | DateTimeStyles.AllowTrailingWhite | DateTimeStyles.AdjustToUniversal)
)
识别那些时区与当地时间匹配的日期会很好,但它变得非常快。
如果您不想支持 ISO 8601:2000 和更早版本允许但在 ISO 8601:2004 中禁止的两位数年份,则删除所有带有 "yy" 的字符串比 "yyyy" 以上:
DateTime.ParseExact(dateString, new string[]
{
"yyyy-MM-ddK", "yyyyMMddK",
"yyyy-MM-ddTHH:mm:ss.fffffffK", "yyyyMMddTHH:mm:ss.fffffffK",
"yyyy-MM-ddTHH:mm:ss,fffffffK", "yyyyMMddTHH:mm:ss,fffffffK",
"yyyy-MM-ddTHH:mm:ss.ffffffK", "yyyyMMddTHH:mm:ss.ffffffK",
"yyyy-MM-ddTHH:mm:ss,ffffffK", "yyyyMMddTHH:mm:ss,ffffffK",
"yyyy-MM-ddTHH:mm:ss.fffffK", "yyyyMMddTHH:mm:ss.fffffK",
"yyyy-MM-ddTHH:mm:ss,fffffK", "yyyyMMddTHH:mm:ss,fffffK",
"yyyy-MM-ddTHH:mm:ss.ffffK", "yyyyMMddTHH:mm:ss.ffffK",
"yyyy-MM-ddTHH:mm:ss,ffffK", "yyyyMMddTHH:mm:ss,ffffK",
"yyyy-MM-ddTHH:mm:ss.ffK", "yyyyMMddTHH:mm:ss.ffK",
"yyyy-MM-ddTHH:mm:ss,ffK", "yyyyMMddTHH:mm:ss,ffK",
"yyyy-MM-ddTHH:mm:ss.fK", "yyyyMMddTHH:mm:ss.fK",
"yyyy-MM-ddTHH:mm:ss,fK", "yyyyMMddTHH:mm:ss,fK",
"yyyy-MM-ddTHH:mm:ssK", "yyyyMMddTHH:mm:ssK",
"yyyy-MM-ddTHHmmss.fffffffK", "yyyyMMddTHHmmss.fffffffK",
"yyyy-MM-ddTHHmmss,fffffffK", "yyyyMMddTHHmmss,fffffffK",
"yyyy-MM-ddTHHmmss.ffffffK", "yyyyMMddTHHmmss.ffffffK",
"yyyy-MM-ddTHHmmss,ffffffK", "yyyyMMddTHHmmss,ffffffK",
"yyyy-MM-ddTHHmmss.fffffK", "yyyyMMddTHHmmss.fffffK",
"yyyy-MM-ddTHHmmss,fffffK", "yyyyMMddTHHmmss,fffffK",
"yyyy-MM-ddTHHmmss.ffffK", "yyyyMMddTHHmmss.ffffK",
"yyyy-MM-ddTHHmmss,ffffK", "yyyyMMddTHHmmss,ffffK",
"yyyy-MM-ddTHHmmss.ffK", "yyyyMMddTHHmmss.ffK",
"yyyy-MM-ddTHHmmss,ffK", "yyyyMMddTHHmmss,ffK",
"yyyy-MM-ddTHHmmss.fK", "yyyyMMddTHHmmss.fK",
"yyyy-MM-ddTHHmmss,fK", "yyyyMMddTHHmmss,fK",
"yyyy-MM-ddTHHmmssK", "yyyyMMddTHHmmssK",
"yyyy-MM-ddTHH:mmK", "yyyyMMddTHH:mmK",
"yyyy-MM-ddTHHK", "yyyyMMddTHHK"
},
CultureInfo.CurrentCulture, DateTimeStyles.AllowLeadingWhite | DateTimeStyles.AllowTrailingWhite)
)
这仍然给我们留下了 2009-W53-7
形式的日期问题,表示 2010 年 1 月 3 日:
DateTime ParseISO8601(string dateString, bool allowTwoYear = false)
{
var match = new Regex(@"\b(\d{4})(-W(\d{2})-|W(\d{2}))(\d)(T\S+)?\b").Match(dateString);
if(match.Success)
{
int year = int.Parse(match.Groups[1].Value);
int week = int.Parse(match.Groups[3].Value + match.Groups[4].Value);
int day = int.Parse(match.Groups[5].Value);
if(year < 1 || year > 9999 || week < 1 || week > 53 || day < 1 || day > 7)
throw new FormatException();
var firstJan = new DateTime(year, 1, 1);
var firstWeek = firstJan.DayOfWeek >= DayOfWeek.Friday
? firstJan.AddDays(firstJan.DayOfWeek - DayOfWeek.Monday - 1)
: firstJan.AddDays(DayOfWeek.Monday - firstJan.DayOfWeek);
DateTime fromWeekAndDay = firstWeek.AddDays((week - 1) * 7 + day - 1);
if(week > 51 && fromWeekAndDay > ParseISO8601(fromWeekAndDay.Year + "-W01-1"))
throw new FormatException();
if(match.Groups[6].Success)
{
// We're just going to let the handling for the other formats deal with any time portion:
dateString = fromWeekAndDay.ToString("yyyy-MM-dd") + match.Groups[6].Value;
}
else
return fromWeekAndDay;
}
var formats = allowTwoYear
? new []
{
"yyyy-MM-ddK", "yyyyMMddK", "yy-MM-ddK", "yyMMddK",
"yyyy-MM-ddTHH:mm:ss.fffffffK", "yyyyMMddTHH:mm:ss.fffffffK", "yy-MM-ddTHH:mm:ss.fffffffK", "yyMMddTHH:mm:ss.fffffffK",
"yyyy-MM-ddTHH:mm:ss,fffffffK", "yyyyMMddTHH:mm:ss,fffffffK", "yy-MM-ddTHH:mm:ss,fffffffK", "yyMMddTHH:mm:ss,fffffffK",
"yyyy-MM-ddTHH:mm:ss.ffffffK", "yyyyMMddTHH:mm:ss.ffffffK", "yy-MM-ddTHH:mm:ss.ffffffK", "yyMMddTHH:mm:ss.ffffffK",
"yyyy-MM-ddTHH:mm:ss,ffffffK", "yyyyMMddTHH:mm:ss,ffffffK", "yy-MM-ddTHH:mm:ss,ffffffK", "yyMMddTHH:mm:ss,ffffffK",
"yyyy-MM-ddTHH:mm:ss.fffffK", "yyyyMMddTHH:mm:ss.fffffK", "yy-MM-ddTHH:mm:ss.fffffK", "yyMMddTHH:mm:ss.fffffK",
"yyyy-MM-ddTHH:mm:ss,fffffK", "yyyyMMddTHH:mm:ss,fffffK", "yy-MM-ddTHH:mm:ss,fffffK", "yyMMddTHH:mm:ss,fffffK",
"yyyy-MM-ddTHH:mm:ss.ffffK", "yyyyMMddTHH:mm:ss.ffffK", "yy-MM-ddTHH:mm:ss.ffffK", "yyMMddTHH:mm:ss.ffffK",
"yyyy-MM-ddTHH:mm:ss,ffffK", "yyyyMMddTHH:mm:ss,ffffK", "yy-MM-ddTHH:mm:ss,ffffK", "yyMMddTHH:mm:ss,ffffK",
"yyyy-MM-ddTHH:mm:ss.ffK", "yyyyMMddTHH:mm:ss.ffK", "yy-MM-ddTHH:mm:ss.ffK", "yyMMddTHH:mm:ss.ffK",
"yyyy-MM-ddTHH:mm:ss,ffK", "yyyyMMddTHH:mm:ss,ffK", "yy-MM-ddTHH:mm:ss,ffK", "yyMMddTHH:mm:ss,ffK",
"yyyy-MM-ddTHH:mm:ss.fK", "yyyyMMddTHH:mm:ss.fK", "yy-MM-ddTHH:mm:ss.fK", "yyMMddTHH:mm:ss.fK",
"yyyy-MM-ddTHH:mm:ss,fK", "yyyyMMddTHH:mm:ss,fK", "yy-MM-ddTHH:mm:ss,fK", "yyMMddTHH:mm:ss,fK",
"yyyy-MM-ddTHH:mm:ssK", "yyyyMMddTHH:mm:ssK", "yy-MM-ddTHH:mm:ssK", "yyMMddTHH:mm:ssK",
"yyyy-MM-ddTHHmmss.fffffffK", "yyyyMMddTHHmmss.fffffffK", "yy-MM-ddTHHmmss.fffffffK", "yyMMddTHHmmss.fffffffK",
"yyyy-MM-ddTHHmmss,fffffffK", "yyyyMMddTHHmmss,fffffffK", "yy-MM-ddTHHmmss,fffffffK", "yyMMddTHHmmss,fffffffK",
"yyyy-MM-ddTHHmmss.ffffffK", "yyyyMMddTHHmmss.ffffffK", "yy-MM-ddTHHmmss.ffffffK", "yyMMddTHHmmss.ffffffK",
"yyyy-MM-ddTHHmmss,ffffffK", "yyyyMMddTHHmmss,ffffffK", "yy-MM-ddTHHmmss,ffffffK", "yyMMddTHHmmss,ffffffK",
"yyyy-MM-ddTHHmmss.fffffK", "yyyyMMddTHHmmss.fffffK", "yy-MM-ddTHHmmss.fffffK", "yyMMddTHHmmss.fffffK",
"yyyy-MM-ddTHHmmss,fffffK", "yyyyMMddTHHmmss,fffffK", "yy-MM-ddTHHmmss,fffffK", "yyMMddTHHmmss,fffffK",
"yyyy-MM-ddTHHmmss.ffffK", "yyyyMMddTHHmmss.ffffK", "yy-MM-ddTHHmmss.ffffK", "yyMMddTHHmmss.ffffK",
"yyyy-MM-ddTHHmmss,ffffK", "yyyyMMddTHHmmss,ffffK", "yy-MM-ddTHHmmss,ffffK", "yyMMddTHHmmss,ffffK",
"yyyy-MM-ddTHHmmss.ffK", "yyyyMMddTHHmmss.ffK", "yy-MM-ddTHHmmss.ffK", "yyMMddTHHmmss.ffK",
"yyyy-MM-ddTHHmmss,ffK", "yyyyMMddTHHmmss,ffK", "yy-MM-ddTHHmmss,ffK", "yyMMddTHHmmss,ffK",
"yyyy-MM-ddTHHmmss.fK", "yyyyMMddTHHmmss.fK", "yy-MM-ddTHHmmss.fK", "yyMMddTHHmmss.fK",
"yyyy-MM-ddTHHmmss,fK", "yyyyMMddTHHmmss,fK", "yy-MM-ddTHHmmss,fK", "yyMMddTHHmmss,fK",
"yyyy-MM-ddTHHmmssK", "yyyyMMddTHHmmssK", "yy-MM-ddTHHmmssK", "yyMMddTHHmmssK",
"yyyy-MM-ddTHH:mmK", "yyyyMMddTHH:mmK", "yy-MM-ddTHH:mmK", "yyMMddTHH:mmK",
"yyyy-MM-ddTHHK", "yyyyMMddTHHK", "yy-MM-ddTHHK", "yyMMddTHHK"
}
: new []
{
"yyyy-MM-ddK", "yyyyMMddK",
"yyyy-MM-ddTHH:mm:ss.fffffffK", "yyyyMMddTHH:mm:ss.fffffffK",
"yyyy-MM-ddTHH:mm:ss,fffffffK", "yyyyMMddTHH:mm:ss,fffffffK",
"yyyy-MM-ddTHH:mm:ss.ffffffK", "yyyyMMddTHH:mm:ss.ffffffK",
"yyyy-MM-ddTHH:mm:ss,ffffffK", "yyyyMMddTHH:mm:ss,ffffffK",
"yyyy-MM-ddTHH:mm:ss.fffffK", "yyyyMMddTHH:mm:ss.fffffK",
"yyyy-MM-ddTHH:mm:ss,fffffK", "yyyyMMddTHH:mm:ss,fffffK",
"yyyy-MM-ddTHH:mm:ss.ffffK", "yyyyMMddTHH:mm:ss.ffffK",
"yyyy-MM-ddTHH:mm:ss,ffffK", "yyyyMMddTHH:mm:ss,ffffK",
"yyyy-MM-ddTHH:mm:ss.ffK", "yyyyMMddTHH:mm:ss.ffK",
"yyyy-MM-ddTHH:mm:ss,ffK", "yyyyMMddTHH:mm:ss,ffK",
"yyyy-MM-ddTHH:mm:ss.fK", "yyyyMMddTHH:mm:ss.fK",
"yyyy-MM-ddTHH:mm:ss,fK", "yyyyMMddTHH:mm:ss,fK",
"yyyy-MM-ddTHH:mm:ssK", "yyyyMMddTHH:mm:ssK",
"yyyy-MM-ddTHHmmss.fffffffK", "yyyyMMddTHHmmss.fffffffK",
"yyyy-MM-ddTHHmmss,fffffffK", "yyyyMMddTHHmmss,fffffffK",
"yyyy-MM-ddTHHmmss.ffffffK", "yyyyMMddTHHmmss.ffffffK",
"yyyy-MM-ddTHHmmss,ffffffK", "yyyyMMddTHHmmss,ffffffK",
"yyyy-MM-ddTHHmmss.fffffK", "yyyyMMddTHHmmss.fffffK",
"yyyy-MM-ddTHHmmss,fffffK", "yyyyMMddTHHmmss,fffffK",
"yyyy-MM-ddTHHmmss.ffffK", "yyyyMMddTHHmmss.ffffK",
"yyyy-MM-ddTHHmmss,ffffK", "yyyyMMddTHHmmss,ffffK",
"yyyy-MM-ddTHHmmss.ffK", "yyyyMMddTHHmmss.ffK",
"yyyy-MM-ddTHHmmss,ffK", "yyyyMMddTHHmmss,ffK",
"yyyy-MM-ddTHHmmss.fK", "yyyyMMddTHHmmss.fK",
"yyyy-MM-ddTHHmmss,fK", "yyyyMMddTHHmmss,fK",
"yyyy-MM-ddTHHmmssK", "yyyyMMddTHHmmssK",
"yyyy-MM-ddTHH:mmK", "yyyyMMddTHH:mmK",
"yyyy-MM-ddTHHK", "yyyyMMddTHHK"
};
return DateTime.ParseExact(dateString, formats, CultureInfo.InvariantCulture, DateTimeStyles.AllowLeadingWhite | DateTimeStyles.AllowTrailingWhite);
}
最后,如果您收到的日期和时间精度超过 100ns,您必须决定要做什么:
public static DateTime ParseISO8601(string dateString, MidpointRounding rounding = MidpointRounding.ToEven, bool allowTwoYear = false)
{
var match = new Regex(@"\b(\d{4})(-W(\d{2})-|W(\d{2}))(\d)(T\S+)?\b").Match(dateString);
if(match.Success)
{
int year = int.Parse(match.Groups[1].Value);
int week = int.Parse(match.Groups[3].Value + match.Groups[4].Value);
int day = int.Parse(match.Groups[5].Value);
if(year < 1 || year > 9999 || week < 1 || week > 53 || day < 1 || day > 7)
throw new FormatException();
var firstJan = new DateTime(year, 1, 1);
var firstWeek = firstJan.DayOfWeek >= DayOfWeek.Friday
? firstJan.AddDays(firstJan.DayOfWeek - DayOfWeek.Monday - 1)
: firstJan.AddDays(DayOfWeek.Monday - firstJan.DayOfWeek);
DateTime fromWeekAndDay = firstWeek.AddDays((week - 1) * 7 + day - 1);
if(week > 51 && fromWeekAndDay > ParseISO8601(fromWeekAndDay.Year + "-W01-1"))
throw new FormatException();
if(match.Groups[6].Success)
{
// We're just going to let the handling for the other formats deal with any time portion:
dateString = fromWeekAndDay.ToString("yyyy-MM-dd") + match.Groups[6].Value;
}
else
return fromWeekAndDay;
}
var excessiveFractions = new Regex(@"(\d(\.|,)\d{8,})");
if(excessiveFractions.IsMatch(dateString))
dateString = excessiveFractions.Replace(
dateString,
m => decimal.Round(decimal.Parse(m.Value.Substring(0, Math.Max(m.Value.Length, 10)).Replace(',', '.')), 7, rounding).ToString()
);
var formats = allowTwoYear
? new []
{
"yyyy-MM-ddK", "yyyyMMddK", "yy-MM-ddK", "yyMMddK",
"yyyy-MM-ddTHH:mm:ss.fffffffK", "yyyyMMddTHH:mm:ss.fffffffK", "yy-MM-ddTHH:mm:ss.fffffffK", "yyMMddTHH:mm:ss.fffffffK",
"yyyy-MM-ddTHH:mm:ss,fffffffK", "yyyyMMddTHH:mm:ss,fffffffK", "yy-MM-ddTHH:mm:ss,fffffffK", "yyMMddTHH:mm:ss,fffffffK",
"yyyy-MM-ddTHH:mm:ss.ffffffK", "yyyyMMddTHH:mm:ss.ffffffK", "yy-MM-ddTHH:mm:ss.ffffffK", "yyMMddTHH:mm:ss.ffffffK",
"yyyy-MM-ddTHH:mm:ss,ffffffK", "yyyyMMddTHH:mm:ss,ffffffK", "yy-MM-ddTHH:mm:ss,ffffffK", "yyMMddTHH:mm:ss,ffffffK",
"yyyy-MM-ddTHH:mm:ss.fffffK", "yyyyMMddTHH:mm:ss.fffffK", "yy-MM-ddTHH:mm:ss.fffffK", "yyMMddTHH:mm:ss.fffffK",
"yyyy-MM-ddTHH:mm:ss,fffffK", "yyyyMMddTHH:mm:ss,fffffK", "yy-MM-ddTHH:mm:ss,fffffK", "yyMMddTHH:mm:ss,fffffK",
"yyyy-MM-ddTHH:mm:ss.ffffK", "yyyyMMddTHH:mm:ss.ffffK", "yy-MM-ddTHH:mm:ss.ffffK", "yyMMddTHH:mm:ss.ffffK",
"yyyy-MM-ddTHH:mm:ss,ffffK", "yyyyMMddTHH:mm:ss,ffffK", "yy-MM-ddTHH:mm:ss,ffffK", "yyMMddTHH:mm:ss,ffffK",
"yyyy-MM-ddTHH:mm:ss.ffK", "yyyyMMddTHH:mm:ss.ffK", "yy-MM-ddTHH:mm:ss.ffK", "yyMMddTHH:mm:ss.ffK",
"yyyy-MM-ddTHH:mm:ss,ffK", "yyyyMMddTHH:mm:ss,ffK", "yy-MM-ddTHH:mm:ss,ffK", "yyMMddTHH:mm:ss,ffK",
"yyyy-MM-ddTHH:mm:ss.fK", "yyyyMMddTHH:mm:ss.fK", "yy-MM-ddTHH:mm:ss.fK", "yyMMddTHH:mm:ss.fK",
"yyyy-MM-ddTHH:mm:ss,fK", "yyyyMMddTHH:mm:ss,fK", "yy-MM-ddTHH:mm:ss,fK", "yyMMddTHH:mm:ss,fK",
"yyyy-MM-ddTHH:mm:ssK", "yyyyMMddTHH:mm:ssK", "yy-MM-ddTHH:mm:ssK", "yyMMddTHH:mm:ssK",
"yyyy-MM-ddTHHmmss.fffffffK", "yyyyMMddTHHmmss.fffffffK", "yy-MM-ddTHHmmss.fffffffK", "yyMMddTHHmmss.fffffffK",
"yyyy-MM-ddTHHmmss,fffffffK", "yyyyMMddTHHmmss,fffffffK", "yy-MM-ddTHHmmss,fffffffK", "yyMMddTHHmmss,fffffffK",
"yyyy-MM-ddTHHmmss.ffffffK", "yyyyMMddTHHmmss.ffffffK", "yy-MM-ddTHHmmss.ffffffK", "yyMMddTHHmmss.ffffffK",
"yyyy-MM-ddTHHmmss,ffffffK", "yyyyMMddTHHmmss,ffffffK", "yy-MM-ddTHHmmss,ffffffK", "yyMMddTHHmmss,ffffffK",
"yyyy-MM-ddTHHmmss.fffffK", "yyyyMMddTHHmmss.fffffK", "yy-MM-ddTHHmmss.fffffK", "yyMMddTHHmmss.fffffK",
"yyyy-MM-ddTHHmmss,fffffK", "yyyyMMddTHHmmss,fffffK", "yy-MM-ddTHHmmss,fffffK", "yyMMddTHHmmss,fffffK",
"yyyy-MM-ddTHHmmss.ffffK", "yyyyMMddTHHmmss.ffffK", "yy-MM-ddTHHmmss.ffffK", "yyMMddTHHmmss.ffffK",
"yyyy-MM-ddTHHmmss,ffffK", "yyyyMMddTHHmmss,ffffK", "yy-MM-ddTHHmmss,ffffK", "yyMMddTHHmmss,ffffK",
"yyyy-MM-ddTHHmmss.ffK", "yyyyMMddTHHmmss.ffK", "yy-MM-ddTHHmmss.ffK", "yyMMddTHHmmss.ffK",
"yyyy-MM-ddTHHmmss,ffK", "yyyyMMddTHHmmss,ffK", "yy-MM-ddTHHmmss,ffK", "yyMMddTHHmmss,ffK",
"yyyy-MM-ddTHHmmss.fK", "yyyyMMddTHHmmss.fK", "yy-MM-ddTHHmmss.fK", "yyMMddTHHmmss.fK",
"yyyy-MM-ddTHHmmss,fK", "yyyyMMddTHHmmss,fK", "yy-MM-ddTHHmmss,fK", "yyMMddTHHmmss,fK",
"yyyy-MM-ddTHHmmssK", "yyyyMMddTHHmmssK", "yy-MM-ddTHHmmssK", "yyMMddTHHmmssK",
"yyyy-MM-ddTHH:mmK", "yyyyMMddTHH:mmK", "yy-MM-ddTHH:mmK", "yyMMddTHH:mmK",
"yyyy-MM-ddTHHK", "yyyyMMddTHHK", "yy-MM-ddTHHK", "yyMMddTHHK"
}
: new []
{
"yyyy-MM-ddK", "yyyyMMddK",
"yyyy-MM-ddTHH:mm:ss.fffffffK", "yyyyMMddTHH:mm:ss.fffffffK",
"yyyy-MM-ddTHH:mm:ss,fffffffK", "yyyyMMddTHH:mm:ss,fffffffK",
"yyyy-MM-ddTHH:mm:ss.ffffffK", "yyyyMMddTHH:mm:ss.ffffffK",
"yyyy-MM-ddTHH:mm:ss,ffffffK", "yyyyMMddTHH:mm:ss,ffffffK",
"yyyy-MM-ddTHH:mm:ss.fffffK", "yyyyMMddTHH:mm:ss.fffffK",
"yyyy-MM-ddTHH:mm:ss,fffffK", "yyyyMMddTHH:mm:ss,fffffK",
"yyyy-MM-ddTHH:mm:ss.ffffK", "yyyyMMddTHH:mm:ss.ffffK",
"yyyy-MM-ddTHH:mm:ss,ffffK", "yyyyMMddTHH:mm:ss,ffffK",
"yyyy-MM-ddTHH:mm:ss.ffK", "yyyyMMddTHH:mm:ss.ffK",
"yyyy-MM-ddTHH:mm:ss,ffK", "yyyyMMddTHH:mm:ss,ffK",
"yyyy-MM-ddTHH:mm:ss.fK", "yyyyMMddTHH:mm:ss.fK",
"yyyy-MM-ddTHH:mm:ss,fK", "yyyyMMddTHH:mm:ss,fK",
"yyyy-MM-ddTHH:mm:ssK", "yyyyMMddTHH:mm:ssK",
"yyyy-MM-ddTHHmmss.fffffffK", "yyyyMMddTHHmmss.fffffffK",
"yyyy-MM-ddTHHmmss,fffffffK", "yyyyMMddTHHmmss,fffffffK",
"yyyy-MM-ddTHHmmss.ffffffK", "yyyyMMddTHHmmss.ffffffK",
"yyyy-MM-ddTHHmmss,ffffffK", "yyyyMMddTHHmmss,ffffffK",
"yyyy-MM-ddTHHmmss.fffffK", "yyyyMMddTHHmmss.fffffK",
"yyyy-MM-ddTHHmmss,fffffK", "yyyyMMddTHHmmss,fffffK",
"yyyy-MM-ddTHHmmss.ffffK", "yyyyMMddTHHmmss.ffffK",
"yyyy-MM-ddTHHmmss,ffffK", "yyyyMMddTHHmmss,ffffK",
"yyyy-MM-ddTHHmmss.ffK", "yyyyMMddTHHmmss.ffK",
"yyyy-MM-ddTHHmmss,ffK", "yyyyMMddTHHmmss,ffK",
"yyyy-MM-ddTHHmmss.fK", "yyyyMMddTHHmmss.fK",
"yyyy-MM-ddTHHmmss,fK", "yyyyMMddTHHmmss,fK",
"yyyy-MM-ddTHHmmssK", "yyyyMMddTHHmmssK",
"yyyy-MM-ddTHH:mmK", "yyyyMMddTHH:mmK",
"yyyy-MM-ddTHHK", "yyyyMMddTHHK"
};
return DateTime.ParseExact(dateString, formats, CultureInfo.InvariantCulture, DateTimeStyles.AllowLeadingWhite | DateTimeStyles.AllowTrailingWhite);
}
现在,虽然我们使用像 2015-03-29T12:53:20.238748294819293021383+01:00
这样的字符串失去了精度,但我们仍然可以尽可能地解析它。
现在我们需要捕获 24:00:00
作为有效时间,然后捕获像 2015-06-30T23:59:60
这样的有效时间(尽管我没有检查从未发生过的 2014-06-30T23:59:60
,因为需要一个不断更新的闰秒数据库):
public static DateTime ParseISO8601(string dateString, MidpointRounding rounding = MidpointRounding.ToEven, bool allowTwoYear = false, bool leapSecondMeansNextDay = false)
{
var match = new Regex(@"\b(\d{4})(-W(\d{2})-|W(\d{2}))(\d)(T\S+)?\b").Match(dateString);
if(match.Success)
{
int year = int.Parse(match.Groups[1].Value);
int week = int.Parse(match.Groups[3].Value + match.Groups[4].Value);
int day = int.Parse(match.Groups[5].Value);
if(year < 1 || year > 9999 || week < 1 || week > 53 || day < 1 || day > 7)
throw new FormatException();
var firstJan = new DateTime(year, 1, 1);
var firstWeek = firstJan.DayOfWeek >= DayOfWeek.Friday
? firstJan.AddDays(firstJan.DayOfWeek - DayOfWeek.Monday - 1)
: firstJan.AddDays(DayOfWeek.Monday - firstJan.DayOfWeek);
DateTime fromWeekAndDay = firstWeek.AddDays((week - 1) * 7 + day - 1);
if(week > 51 && fromWeekAndDay > ParseISO8601(fromWeekAndDay.Year + "-W01-1"))
throw new FormatException();
if(match.Groups[6].Success)
{
// We're just going to let the handling for the other formats deal with any time fraction:
dateString = fromWeekAndDay.ToString("yyyy-MM-dd") + match.Groups[6].Value;
}
return fromWeekAndDay;
}
var excessiveFractions = new Regex(@"(\d(\.|,)\d{8,})");
if(excessiveFractions.IsMatch(dateString))
dateString = excessiveFractions.Replace(
dateString,
m => decimal.Round(decimal.Parse(m.Value.Substring(0, Math.Max(m.Value.Length, 10))), 7, rounding).ToString()
);
if(dateString.Contains("T24"))
{
var yesterday = ParseISO8601(dateString.Replace("T24", "T00"), rounding, allowTwoYear);
if(yesterday.TimeOfDay != TimeSpan.Zero)
throw new FormatException();
return yesterday.AddDays(1);
}
var leapSecond = new Regex("T23:?59:?60");
if(leapSecond.IsMatch(dateString))
{
var secondBefore = ParseISO8601(leapSecond.Replace(dateString, "T23:59:59"));
if(secondBefore.TimeOfDay != new TimeSpan(23, 59, 59)) // can't have fractions past second 60
throw new FormatException();
// Can only be on --12-31 or --06-30
if((secondBefore.Month == 12 && secondBefore.Day == 31) || (secondBefore.Month == 6 && secondBefore.Day == 30))
// since DateTime can't handle leap seconds, we need a policy as to which side of it to be on.
return leapSecondMeansNextDay ? secondBefore.AddSeconds(1) : secondBefore;
throw new FormatException();
}
var formats = allowTwoYear
? new []
{
"yyyy-MM-ddK", "yyyyMMddK", "yy-MM-ddK", "yyMMddK",
"yyyy-MM-ddTHH:mm:ss.fffffffK", "yyyyMMddTHH:mm:ss.fffffffK", "yy-MM-ddTHH:mm:ss.fffffffK", "yyMMddTHH:mm:ss.fffffffK",
"yyyy-MM-ddTHH:mm:ss,fffffffK", "yyyyMMddTHH:mm:ss,fffffffK", "yy-MM-ddTHH:mm:ss,fffffffK", "yyMMddTHH:mm:ss,fffffffK",
"yyyy-MM-ddTHH:mm:ss.ffffffK", "yyyyMMddTHH:mm:ss.ffffffK", "yy-MM-ddTHH:mm:ss.ffffffK", "yyMMddTHH:mm:ss.ffffffK",
"yyyy-MM-ddTHH:mm:ss,ffffffK", "yyyyMMddTHH:mm:ss,ffffffK", "yy-MM-ddTHH:mm:ss,ffffffK", "yyMMddTHH:mm:ss,ffffffK",
"yyyy-MM-ddTHH:mm:ss.fffffK", "yyyyMMddTHH:mm:ss.fffffK", "yy-MM-ddTHH:mm:ss.fffffK", "yyMMddTHH:mm:ss.fffffK",
"yyyy-MM-ddTHH:mm:ss,fffffK", "yyyyMMddTHH:mm:ss,fffffK", "yy-MM-ddTHH:mm:ss,fffffK", "yyMMddTHH:mm:ss,fffffK",
"yyyy-MM-ddTHH:mm:ss.ffffK", "yyyyMMddTHH:mm:ss.ffffK", "yy-MM-ddTHH:mm:ss.ffffK", "yyMMddTHH:mm:ss.ffffK",
"yyyy-MM-ddTHH:mm:ss,ffffK", "yyyyMMddTHH:mm:ss,ffffK", "yy-MM-ddTHH:mm:ss,ffffK", "yyMMddTHH:mm:ss,ffffK",
"yyyy-MM-ddTHH:mm:ss.ffK", "yyyyMMddTHH:mm:ss.ffK", "yy-MM-ddTHH:mm:ss.ffK", "yyMMddTHH:mm:ss.ffK",
"yyyy-MM-ddTHH:mm:ss,ffK", "yyyyMMddTHH:mm:ss,ffK", "yy-MM-ddTHH:mm:ss,ffK", "yyMMddTHH:mm:ss,ffK",
"yyyy-MM-ddTHH:mm:ss.fK", "yyyyMMddTHH:mm:ss.fK", "yy-MM-ddTHH:mm:ss.fK", "yyMMddTHH:mm:ss.fK",
"yyyy-MM-ddTHH:mm:ss,fK", "yyyyMMddTHH:mm:ss,fK", "yy-MM-ddTHH:mm:ss,fK", "yyMMddTHH:mm:ss,fK",
"yyyy-MM-ddTHH:mm:ssK", "yyyyMMddTHH:mm:ssK", "yy-MM-ddTHH:mm:ssK", "yyMMddTHH:mm:ssK",
"yyyy-MM-ddTHHmmss.fffffffK", "yyyyMMddTHHmmss.fffffffK", "yy-MM-ddTHHmmss.fffffffK", "yyMMddTHHmmss.fffffffK",
"yyyy-MM-ddTHHmmss,fffffffK", "yyyyMMddTHHmmss,fffffffK", "yy-MM-ddTHHmmss,fffffffK", "yyMMddTHHmmss,fffffffK",
"yyyy-MM-ddTHHmmss.ffffffK", "yyyyMMddTHHmmss.ffffffK", "yy-MM-ddTHHmmss.ffffffK", "yyMMddTHHmmss.ffffffK",
"yyyy-MM-ddTHHmmss,ffffffK", "yyyyMMddTHHmmss,ffffffK", "yy-MM-ddTHHmmss,ffffffK", "yyMMddTHHmmss,ffffffK",
"yyyy-MM-ddTHHmmss.fffffK", "yyyyMMddTHHmmss.fffffK", "yy-MM-ddTHHmmss.fffffK", "yyMMddTHHmmss.fffffK",
"yyyy-MM-ddTHHmmss,fffffK", "yyyyMMddTHHmmss,fffffK", "yy-MM-ddTHHmmss,fffffK", "yyMMddTHHmmss,fffffK",
"yyyy-MM-ddTHHmmss.ffffK", "yyyyMMddTHHmmss.ffffK", "yy-MM-ddTHHmmss.ffffK", "yyMMddTHHmmss.ffffK",
"yyyy-MM-ddTHHmmss,ffffK", "yyyyMMddTHHmmss,ffffK", "yy-MM-ddTHHmmss,ffffK", "yyMMddTHHmmss,ffffK",
"yyyy-MM-ddTHHmmss.ffK", "yyyyMMddTHHmmss.ffK", "yy-MM-ddTHHmmss.ffK", "yyMMddTHHmmss.ffK",
"yyyy-MM-ddTHHmmss,ffK", "yyyyMMddTHHmmss,ffK", "yy-MM-ddTHHmmss,ffK", "yyMMddTHHmmss,ffK",
"yyyy-MM-ddTHHmmss.fK", "yyyyMMddTHHmmss.fK", "yy-MM-ddTHHmmss.fK", "yyMMddTHHmmss.fK",
"yyyy-MM-ddTHHmmss,fK", "yyyyMMddTHHmmss,fK", "yy-MM-ddTHHmmss,fK", "yyMMddTHHmmss,fK",
"yyyy-MM-ddTHHmmssK", "yyyyMMddTHHmmssK", "yy-MM-ddTHHmmssK", "yyMMddTHHmmssK",
"yyyy-MM-ddTHH:mmK", "yyyyMMddTHH:mmK", "yy-MM-ddTHH:mmK", "yyMMddTHH:mmK",
"yyyy-MM-ddTHHK", "yyyyMMddTHHK", "yy-MM-ddTHHK", "yyMMddTHHK"
}
: new []
{
"yyyy-MM-ddK", "yyyyMMddK",
"yyyy-MM-ddTHH:mm:ss.fffffffK", "yyyyMMddTHH:mm:ss.fffffffK",
"yyyy-MM-ddTHH:mm:ss,fffffffK", "yyyyMMddTHH:mm:ss,fffffffK",
"yyyy-MM-ddTHH:mm:ss.ffffffK", "yyyyMMddTHH:mm:ss.ffffffK",
"yyyy-MM-ddTHH:mm:ss,ffffffK", "yyyyMMddTHH:mm:ss,ffffffK",
"yyyy-MM-ddTHH:mm:ss.fffffK", "yyyyMMddTHH:mm:ss.fffffK",
"yyyy-MM-ddTHH:mm:ss,fffffK", "yyyyMMddTHH:mm:ss,fffffK",
"yyyy-MM-ddTHH:mm:ss.ffffK", "yyyyMMddTHH:mm:ss.ffffK",
"yyyy-MM-ddTHH:mm:ss,ffffK", "yyyyMMddTHH:mm:ss,ffffK",
"yyyy-MM-ddTHH:mm:ss.ffK", "yyyyMMddTHH:mm:ss.ffK",
"yyyy-MM-ddTHH:mm:ss,ffK", "yyyyMMddTHH:mm:ss,ffK",
"yyyy-MM-ddTHH:mm:ss.fK", "yyyyMMddTHH:mm:ss.fK",
"yyyy-MM-ddTHH:mm:ss,fK", "yyyyMMddTHH:mm:ss,fK",
"yyyy-MM-ddTHH:mm:ssK", "yyyyMMddTHH:mm:ssK",
"yyyy-MM-ddTHHmmss.fffffffK", "yyyyMMddTHHmmss.fffffffK",
"yyyy-MM-ddTHHmmss,fffffffK", "yyyyMMddTHHmmss,fffffffK",
"yyyy-MM-ddTHHmmss.ffffffK", "yyyyMMddTHHmmss.ffffffK",
"yyyy-MM-ddTHHmmss,ffffffK", "yyyyMMddTHHmmss,ffffffK",
"yyyy-MM-ddTHHmmss.fffffK", "yyyyMMddTHHmmss.fffffK",
"yyyy-MM-ddTHHmmss,fffffK", "yyyyMMddTHHmmss,fffffK",
"yyyy-MM-ddTHHmmss.ffffK", "yyyyMMddTHHmmss.ffffK",
"yyyy-MM-ddTHHmmss,ffffK", "yyyyMMddTHHmmss,ffffK",
"yyyy-MM-ddTHHmmss.ffK", "yyyyMMddTHHmmss.ffK",
"yyyy-MM-ddTHHmmss,ffK", "yyyyMMddTHHmmss,ffK",
"yyyy-MM-ddTHHmmss.fK", "yyyyMMddTHHmmss.fK",
"yyyy-MM-ddTHHmmss,fK", "yyyyMMddTHHmmss,fK",
"yyyy-MM-ddTHHmmssK", "yyyyMMddTHHmmssK",
"yyyy-MM-ddTHH:mmK", "yyyyMMddTHH:mmK",
"yyyy-MM-ddTHHK", "yyyyMMddTHHK"
};
return DateTime.ParseExact(dateString, formats, CultureInfo.InvariantCulture, DateTimeStyles.AllowLeadingWhite | DateTimeStyles.AllowTrailingWhite);
}
所有这些工作量很大,但也很有趣。而且我们仍然没有涉及维护时区(对上面的 DateTimeOffset
来说不是一个棘手的变化)仅时间字符串(应该 return 和 TimeSpan
),持续时间(也应该 return一个TimeSpan
), 周期或重复周期。
同时,ISO 8601 旨在用于定义一个或多个 配置文件 ,这些配置文件又定义了一个或多个允许格式的子集,可能还有其他规则。一般来说,我们希望对这些配置文件之一进行编程,而不是通常对 ISO 8601 进行编程。例如,上面的代码对于解析 web 日期时间是无用的,因为它接受 2010 年 1 月 3 日的 2009-W53-7
,这是正确的 ISO 8601 处理,但 ISO 8601 的 web-datetime 配置文件不允许。
有很多关于在 .NET 和 C# 中解析 ISO8601 date/times 的 SO 问题和答案。但是,似乎在任何地方都没有 'definitive' 答案,即提供正式正确的 ISO8601 解析器的答案,该解析器将正确解析 ISO8601 中所有可能的格式变体,并且不允许非 ISO8601 变体。
这个 SO 答案是迄今为止最接近的匹配...
How to create a .NET DateTime from ISO 8601 format
好的。让我们从您自己对 ISO 8601 施加的限制开始:
- 您需要 DateTime,因此不需要所有仅产生时间、持续时间、年份或月份和年份的格式。
- 您需要 DateTime,因此时区信息将变为 "Unspecified"、"UTC" 或 "Local",但无法往返返回同一时区。
- 您需要一个 DateTime,因此失去超过 100ns 的精度。
这让我们只支持不到一半的 ISO 8601 格式,并解决了模棱两可的情况,因为它在仅日期和仅时间含义之间存在歧义。
让我们从我们可以处理的那些开始 DateTime.ParseExact
:
DateTime.ParseExact(dateString, new string[]
{
"yyyy-MM-ddK", "yyyyMMddK", "yy-MM-ddK", "yyMMddK",
"yyyy-MM-ddTHH:mm:ss.fffffffK", "yyyyMMddTHH:mm:ss.fffffffK", "yy-MM-ddTHH:mm:ss.fffffffK", "yyMMddTHH:mm:ss.fffffffK",
"yyyy-MM-ddTHH:mm:ss,fffffffK", "yyyyMMddTHH:mm:ss,fffffffK", "yy-MM-ddTHH:mm:ss,fffffffK", "yyMMddTHH:mm:ss,fffffffK",
"yyyy-MM-ddTHH:mm:ss.ffffffK", "yyyyMMddTHH:mm:ss.ffffffK", "yy-MM-ddTHH:mm:ss.ffffffK", "yyMMddTHH:mm:ss.ffffffK",
"yyyy-MM-ddTHH:mm:ss,ffffffK", "yyyyMMddTHH:mm:ss,ffffffK", "yy-MM-ddTHH:mm:ss,ffffffK", "yyMMddTHH:mm:ss,ffffffK",
"yyyy-MM-ddTHH:mm:ss.fffffK", "yyyyMMddTHH:mm:ss.fffffK", "yy-MM-ddTHH:mm:ss.fffffK", "yyMMddTHH:mm:ss.fffffK",
"yyyy-MM-ddTHH:mm:ss,fffffK", "yyyyMMddTHH:mm:ss,fffffK", "yy-MM-ddTHH:mm:ss,fffffK", "yyMMddTHH:mm:ss,fffffK",
"yyyy-MM-ddTHH:mm:ss.ffffK", "yyyyMMddTHH:mm:ss.ffffK", "yy-MM-ddTHH:mm:ss.ffffK", "yyMMddTHH:mm:ss.ffffK",
"yyyy-MM-ddTHH:mm:ss,ffffK", "yyyyMMddTHH:mm:ss,ffffK", "yy-MM-ddTHH:mm:ss,ffffK", "yyMMddTHH:mm:ss,ffffK",
"yyyy-MM-ddTHH:mm:ss.ffK", "yyyyMMddTHH:mm:ss.ffK", "yy-MM-ddTHH:mm:ss.ffK", "yyMMddTHH:mm:ss.ffK",
"yyyy-MM-ddTHH:mm:ss,ffK", "yyyyMMddTHH:mm:ss,ffK", "yy-MM-ddTHH:mm:ss,ffK", "yyMMddTHH:mm:ss,ffK",
"yyyy-MM-ddTHH:mm:ss.fK", "yyyyMMddTHH:mm:ss.fK", "yy-MM-ddTHH:mm:ss.fK", "yyMMddTHH:mm:ss.fK",
"yyyy-MM-ddTHH:mm:ss,fK", "yyyyMMddTHH:mm:ss,fK", "yy-MM-ddTHH:mm:ss,fK", "yyMMddTHH:mm:ss,fK",
"yyyy-MM-ddTHH:mm:ssK", "yyyyMMddTHH:mm:ssK", "yy-MM-ddTHH:mm:ssK", "yyMMddTHH:mm:ssK",
"yyyy-MM-ddTHHmmss.fffffffK", "yyyyMMddTHHmmss.fffffffK", "yy-MM-ddTHHmmss.fffffffK", "yyMMddTHHmmss.fffffffK",
"yyyy-MM-ddTHHmmss,fffffffK", "yyyyMMddTHHmmss,fffffffK", "yy-MM-ddTHHmmss,fffffffK", "yyMMddTHHmmss,fffffffK",
"yyyy-MM-ddTHHmmss.ffffffK", "yyyyMMddTHHmmss.ffffffK", "yy-MM-ddTHHmmss.ffffffK", "yyMMddTHHmmss.ffffffK",
"yyyy-MM-ddTHHmmss,ffffffK", "yyyyMMddTHHmmss,ffffffK", "yy-MM-ddTHHmmss,ffffffK", "yyMMddTHHmmss,ffffffK",
"yyyy-MM-ddTHHmmss.fffffK", "yyyyMMddTHHmmss.fffffK", "yy-MM-ddTHHmmss.fffffK", "yyMMddTHHmmss.fffffK",
"yyyy-MM-ddTHHmmss,fffffK", "yyyyMMddTHHmmss,fffffK", "yy-MM-ddTHHmmss,fffffK", "yyMMddTHHmmss,fffffK",
"yyyy-MM-ddTHHmmss.ffffK", "yyyyMMddTHHmmss.ffffK", "yy-MM-ddTHHmmss.ffffK", "yyMMddTHHmmss.ffffK",
"yyyy-MM-ddTHHmmss,ffffK", "yyyyMMddTHHmmss,ffffK", "yy-MM-ddTHHmmss,ffffK", "yyMMddTHHmmss,ffffK",
"yyyy-MM-ddTHHmmss.ffK", "yyyyMMddTHHmmss.ffK", "yy-MM-ddTHHmmss.ffK", "yyMMddTHHmmss.ffK",
"yyyy-MM-ddTHHmmss,ffK", "yyyyMMddTHHmmss,ffK", "yy-MM-ddTHHmmss,ffK", "yyMMddTHHmmss,ffK",
"yyyy-MM-ddTHHmmss.fK", "yyyyMMddTHHmmss.fK", "yy-MM-ddTHHmmss.fK", "yyMMddTHHmmss.fK",
"yyyy-MM-ddTHHmmss,fK", "yyyyMMddTHHmmss,fK", "yy-MM-ddTHHmmss,fK", "yyMMddTHHmmss,fK",
"yyyy-MM-ddTHHmmssK", "yyyyMMddTHHmmssK", "yy-MM-ddTHHmmssK", "yyMMddTHHmmssK",
"yyyy-MM-ddTHH:mmK", "yyyyMMddTHH:mmK", "yy-MM-ddTHH:mmK", "yyMMddTHH:mmK",
"yyyy-MM-ddTHHK", "yyyyMMddTHHK", "yy-MM-ddTHHK", "yyMMddTHHK"
},
CultureInfo.CurrentCulture, DateTimeStyles.AllowLeadingWhite | DateTimeStyles.AllowTrailingWhite | DateTimeStyles.AdjustToUniversal)
)
识别那些时区与当地时间匹配的日期会很好,但它变得非常快。
如果您不想支持 ISO 8601:2000 和更早版本允许但在 ISO 8601:2004 中禁止的两位数年份,则删除所有带有 "yy" 的字符串比 "yyyy" 以上:
DateTime.ParseExact(dateString, new string[]
{
"yyyy-MM-ddK", "yyyyMMddK",
"yyyy-MM-ddTHH:mm:ss.fffffffK", "yyyyMMddTHH:mm:ss.fffffffK",
"yyyy-MM-ddTHH:mm:ss,fffffffK", "yyyyMMddTHH:mm:ss,fffffffK",
"yyyy-MM-ddTHH:mm:ss.ffffffK", "yyyyMMddTHH:mm:ss.ffffffK",
"yyyy-MM-ddTHH:mm:ss,ffffffK", "yyyyMMddTHH:mm:ss,ffffffK",
"yyyy-MM-ddTHH:mm:ss.fffffK", "yyyyMMddTHH:mm:ss.fffffK",
"yyyy-MM-ddTHH:mm:ss,fffffK", "yyyyMMddTHH:mm:ss,fffffK",
"yyyy-MM-ddTHH:mm:ss.ffffK", "yyyyMMddTHH:mm:ss.ffffK",
"yyyy-MM-ddTHH:mm:ss,ffffK", "yyyyMMddTHH:mm:ss,ffffK",
"yyyy-MM-ddTHH:mm:ss.ffK", "yyyyMMddTHH:mm:ss.ffK",
"yyyy-MM-ddTHH:mm:ss,ffK", "yyyyMMddTHH:mm:ss,ffK",
"yyyy-MM-ddTHH:mm:ss.fK", "yyyyMMddTHH:mm:ss.fK",
"yyyy-MM-ddTHH:mm:ss,fK", "yyyyMMddTHH:mm:ss,fK",
"yyyy-MM-ddTHH:mm:ssK", "yyyyMMddTHH:mm:ssK",
"yyyy-MM-ddTHHmmss.fffffffK", "yyyyMMddTHHmmss.fffffffK",
"yyyy-MM-ddTHHmmss,fffffffK", "yyyyMMddTHHmmss,fffffffK",
"yyyy-MM-ddTHHmmss.ffffffK", "yyyyMMddTHHmmss.ffffffK",
"yyyy-MM-ddTHHmmss,ffffffK", "yyyyMMddTHHmmss,ffffffK",
"yyyy-MM-ddTHHmmss.fffffK", "yyyyMMddTHHmmss.fffffK",
"yyyy-MM-ddTHHmmss,fffffK", "yyyyMMddTHHmmss,fffffK",
"yyyy-MM-ddTHHmmss.ffffK", "yyyyMMddTHHmmss.ffffK",
"yyyy-MM-ddTHHmmss,ffffK", "yyyyMMddTHHmmss,ffffK",
"yyyy-MM-ddTHHmmss.ffK", "yyyyMMddTHHmmss.ffK",
"yyyy-MM-ddTHHmmss,ffK", "yyyyMMddTHHmmss,ffK",
"yyyy-MM-ddTHHmmss.fK", "yyyyMMddTHHmmss.fK",
"yyyy-MM-ddTHHmmss,fK", "yyyyMMddTHHmmss,fK",
"yyyy-MM-ddTHHmmssK", "yyyyMMddTHHmmssK",
"yyyy-MM-ddTHH:mmK", "yyyyMMddTHH:mmK",
"yyyy-MM-ddTHHK", "yyyyMMddTHHK"
},
CultureInfo.CurrentCulture, DateTimeStyles.AllowLeadingWhite | DateTimeStyles.AllowTrailingWhite)
)
这仍然给我们留下了 2009-W53-7
形式的日期问题,表示 2010 年 1 月 3 日:
DateTime ParseISO8601(string dateString, bool allowTwoYear = false)
{
var match = new Regex(@"\b(\d{4})(-W(\d{2})-|W(\d{2}))(\d)(T\S+)?\b").Match(dateString);
if(match.Success)
{
int year = int.Parse(match.Groups[1].Value);
int week = int.Parse(match.Groups[3].Value + match.Groups[4].Value);
int day = int.Parse(match.Groups[5].Value);
if(year < 1 || year > 9999 || week < 1 || week > 53 || day < 1 || day > 7)
throw new FormatException();
var firstJan = new DateTime(year, 1, 1);
var firstWeek = firstJan.DayOfWeek >= DayOfWeek.Friday
? firstJan.AddDays(firstJan.DayOfWeek - DayOfWeek.Monday - 1)
: firstJan.AddDays(DayOfWeek.Monday - firstJan.DayOfWeek);
DateTime fromWeekAndDay = firstWeek.AddDays((week - 1) * 7 + day - 1);
if(week > 51 && fromWeekAndDay > ParseISO8601(fromWeekAndDay.Year + "-W01-1"))
throw new FormatException();
if(match.Groups[6].Success)
{
// We're just going to let the handling for the other formats deal with any time portion:
dateString = fromWeekAndDay.ToString("yyyy-MM-dd") + match.Groups[6].Value;
}
else
return fromWeekAndDay;
}
var formats = allowTwoYear
? new []
{
"yyyy-MM-ddK", "yyyyMMddK", "yy-MM-ddK", "yyMMddK",
"yyyy-MM-ddTHH:mm:ss.fffffffK", "yyyyMMddTHH:mm:ss.fffffffK", "yy-MM-ddTHH:mm:ss.fffffffK", "yyMMddTHH:mm:ss.fffffffK",
"yyyy-MM-ddTHH:mm:ss,fffffffK", "yyyyMMddTHH:mm:ss,fffffffK", "yy-MM-ddTHH:mm:ss,fffffffK", "yyMMddTHH:mm:ss,fffffffK",
"yyyy-MM-ddTHH:mm:ss.ffffffK", "yyyyMMddTHH:mm:ss.ffffffK", "yy-MM-ddTHH:mm:ss.ffffffK", "yyMMddTHH:mm:ss.ffffffK",
"yyyy-MM-ddTHH:mm:ss,ffffffK", "yyyyMMddTHH:mm:ss,ffffffK", "yy-MM-ddTHH:mm:ss,ffffffK", "yyMMddTHH:mm:ss,ffffffK",
"yyyy-MM-ddTHH:mm:ss.fffffK", "yyyyMMddTHH:mm:ss.fffffK", "yy-MM-ddTHH:mm:ss.fffffK", "yyMMddTHH:mm:ss.fffffK",
"yyyy-MM-ddTHH:mm:ss,fffffK", "yyyyMMddTHH:mm:ss,fffffK", "yy-MM-ddTHH:mm:ss,fffffK", "yyMMddTHH:mm:ss,fffffK",
"yyyy-MM-ddTHH:mm:ss.ffffK", "yyyyMMddTHH:mm:ss.ffffK", "yy-MM-ddTHH:mm:ss.ffffK", "yyMMddTHH:mm:ss.ffffK",
"yyyy-MM-ddTHH:mm:ss,ffffK", "yyyyMMddTHH:mm:ss,ffffK", "yy-MM-ddTHH:mm:ss,ffffK", "yyMMddTHH:mm:ss,ffffK",
"yyyy-MM-ddTHH:mm:ss.ffK", "yyyyMMddTHH:mm:ss.ffK", "yy-MM-ddTHH:mm:ss.ffK", "yyMMddTHH:mm:ss.ffK",
"yyyy-MM-ddTHH:mm:ss,ffK", "yyyyMMddTHH:mm:ss,ffK", "yy-MM-ddTHH:mm:ss,ffK", "yyMMddTHH:mm:ss,ffK",
"yyyy-MM-ddTHH:mm:ss.fK", "yyyyMMddTHH:mm:ss.fK", "yy-MM-ddTHH:mm:ss.fK", "yyMMddTHH:mm:ss.fK",
"yyyy-MM-ddTHH:mm:ss,fK", "yyyyMMddTHH:mm:ss,fK", "yy-MM-ddTHH:mm:ss,fK", "yyMMddTHH:mm:ss,fK",
"yyyy-MM-ddTHH:mm:ssK", "yyyyMMddTHH:mm:ssK", "yy-MM-ddTHH:mm:ssK", "yyMMddTHH:mm:ssK",
"yyyy-MM-ddTHHmmss.fffffffK", "yyyyMMddTHHmmss.fffffffK", "yy-MM-ddTHHmmss.fffffffK", "yyMMddTHHmmss.fffffffK",
"yyyy-MM-ddTHHmmss,fffffffK", "yyyyMMddTHHmmss,fffffffK", "yy-MM-ddTHHmmss,fffffffK", "yyMMddTHHmmss,fffffffK",
"yyyy-MM-ddTHHmmss.ffffffK", "yyyyMMddTHHmmss.ffffffK", "yy-MM-ddTHHmmss.ffffffK", "yyMMddTHHmmss.ffffffK",
"yyyy-MM-ddTHHmmss,ffffffK", "yyyyMMddTHHmmss,ffffffK", "yy-MM-ddTHHmmss,ffffffK", "yyMMddTHHmmss,ffffffK",
"yyyy-MM-ddTHHmmss.fffffK", "yyyyMMddTHHmmss.fffffK", "yy-MM-ddTHHmmss.fffffK", "yyMMddTHHmmss.fffffK",
"yyyy-MM-ddTHHmmss,fffffK", "yyyyMMddTHHmmss,fffffK", "yy-MM-ddTHHmmss,fffffK", "yyMMddTHHmmss,fffffK",
"yyyy-MM-ddTHHmmss.ffffK", "yyyyMMddTHHmmss.ffffK", "yy-MM-ddTHHmmss.ffffK", "yyMMddTHHmmss.ffffK",
"yyyy-MM-ddTHHmmss,ffffK", "yyyyMMddTHHmmss,ffffK", "yy-MM-ddTHHmmss,ffffK", "yyMMddTHHmmss,ffffK",
"yyyy-MM-ddTHHmmss.ffK", "yyyyMMddTHHmmss.ffK", "yy-MM-ddTHHmmss.ffK", "yyMMddTHHmmss.ffK",
"yyyy-MM-ddTHHmmss,ffK", "yyyyMMddTHHmmss,ffK", "yy-MM-ddTHHmmss,ffK", "yyMMddTHHmmss,ffK",
"yyyy-MM-ddTHHmmss.fK", "yyyyMMddTHHmmss.fK", "yy-MM-ddTHHmmss.fK", "yyMMddTHHmmss.fK",
"yyyy-MM-ddTHHmmss,fK", "yyyyMMddTHHmmss,fK", "yy-MM-ddTHHmmss,fK", "yyMMddTHHmmss,fK",
"yyyy-MM-ddTHHmmssK", "yyyyMMddTHHmmssK", "yy-MM-ddTHHmmssK", "yyMMddTHHmmssK",
"yyyy-MM-ddTHH:mmK", "yyyyMMddTHH:mmK", "yy-MM-ddTHH:mmK", "yyMMddTHH:mmK",
"yyyy-MM-ddTHHK", "yyyyMMddTHHK", "yy-MM-ddTHHK", "yyMMddTHHK"
}
: new []
{
"yyyy-MM-ddK", "yyyyMMddK",
"yyyy-MM-ddTHH:mm:ss.fffffffK", "yyyyMMddTHH:mm:ss.fffffffK",
"yyyy-MM-ddTHH:mm:ss,fffffffK", "yyyyMMddTHH:mm:ss,fffffffK",
"yyyy-MM-ddTHH:mm:ss.ffffffK", "yyyyMMddTHH:mm:ss.ffffffK",
"yyyy-MM-ddTHH:mm:ss,ffffffK", "yyyyMMddTHH:mm:ss,ffffffK",
"yyyy-MM-ddTHH:mm:ss.fffffK", "yyyyMMddTHH:mm:ss.fffffK",
"yyyy-MM-ddTHH:mm:ss,fffffK", "yyyyMMddTHH:mm:ss,fffffK",
"yyyy-MM-ddTHH:mm:ss.ffffK", "yyyyMMddTHH:mm:ss.ffffK",
"yyyy-MM-ddTHH:mm:ss,ffffK", "yyyyMMddTHH:mm:ss,ffffK",
"yyyy-MM-ddTHH:mm:ss.ffK", "yyyyMMddTHH:mm:ss.ffK",
"yyyy-MM-ddTHH:mm:ss,ffK", "yyyyMMddTHH:mm:ss,ffK",
"yyyy-MM-ddTHH:mm:ss.fK", "yyyyMMddTHH:mm:ss.fK",
"yyyy-MM-ddTHH:mm:ss,fK", "yyyyMMddTHH:mm:ss,fK",
"yyyy-MM-ddTHH:mm:ssK", "yyyyMMddTHH:mm:ssK",
"yyyy-MM-ddTHHmmss.fffffffK", "yyyyMMddTHHmmss.fffffffK",
"yyyy-MM-ddTHHmmss,fffffffK", "yyyyMMddTHHmmss,fffffffK",
"yyyy-MM-ddTHHmmss.ffffffK", "yyyyMMddTHHmmss.ffffffK",
"yyyy-MM-ddTHHmmss,ffffffK", "yyyyMMddTHHmmss,ffffffK",
"yyyy-MM-ddTHHmmss.fffffK", "yyyyMMddTHHmmss.fffffK",
"yyyy-MM-ddTHHmmss,fffffK", "yyyyMMddTHHmmss,fffffK",
"yyyy-MM-ddTHHmmss.ffffK", "yyyyMMddTHHmmss.ffffK",
"yyyy-MM-ddTHHmmss,ffffK", "yyyyMMddTHHmmss,ffffK",
"yyyy-MM-ddTHHmmss.ffK", "yyyyMMddTHHmmss.ffK",
"yyyy-MM-ddTHHmmss,ffK", "yyyyMMddTHHmmss,ffK",
"yyyy-MM-ddTHHmmss.fK", "yyyyMMddTHHmmss.fK",
"yyyy-MM-ddTHHmmss,fK", "yyyyMMddTHHmmss,fK",
"yyyy-MM-ddTHHmmssK", "yyyyMMddTHHmmssK",
"yyyy-MM-ddTHH:mmK", "yyyyMMddTHH:mmK",
"yyyy-MM-ddTHHK", "yyyyMMddTHHK"
};
return DateTime.ParseExact(dateString, formats, CultureInfo.InvariantCulture, DateTimeStyles.AllowLeadingWhite | DateTimeStyles.AllowTrailingWhite);
}
最后,如果您收到的日期和时间精度超过 100ns,您必须决定要做什么:
public static DateTime ParseISO8601(string dateString, MidpointRounding rounding = MidpointRounding.ToEven, bool allowTwoYear = false)
{
var match = new Regex(@"\b(\d{4})(-W(\d{2})-|W(\d{2}))(\d)(T\S+)?\b").Match(dateString);
if(match.Success)
{
int year = int.Parse(match.Groups[1].Value);
int week = int.Parse(match.Groups[3].Value + match.Groups[4].Value);
int day = int.Parse(match.Groups[5].Value);
if(year < 1 || year > 9999 || week < 1 || week > 53 || day < 1 || day > 7)
throw new FormatException();
var firstJan = new DateTime(year, 1, 1);
var firstWeek = firstJan.DayOfWeek >= DayOfWeek.Friday
? firstJan.AddDays(firstJan.DayOfWeek - DayOfWeek.Monday - 1)
: firstJan.AddDays(DayOfWeek.Monday - firstJan.DayOfWeek);
DateTime fromWeekAndDay = firstWeek.AddDays((week - 1) * 7 + day - 1);
if(week > 51 && fromWeekAndDay > ParseISO8601(fromWeekAndDay.Year + "-W01-1"))
throw new FormatException();
if(match.Groups[6].Success)
{
// We're just going to let the handling for the other formats deal with any time portion:
dateString = fromWeekAndDay.ToString("yyyy-MM-dd") + match.Groups[6].Value;
}
else
return fromWeekAndDay;
}
var excessiveFractions = new Regex(@"(\d(\.|,)\d{8,})");
if(excessiveFractions.IsMatch(dateString))
dateString = excessiveFractions.Replace(
dateString,
m => decimal.Round(decimal.Parse(m.Value.Substring(0, Math.Max(m.Value.Length, 10)).Replace(',', '.')), 7, rounding).ToString()
);
var formats = allowTwoYear
? new []
{
"yyyy-MM-ddK", "yyyyMMddK", "yy-MM-ddK", "yyMMddK",
"yyyy-MM-ddTHH:mm:ss.fffffffK", "yyyyMMddTHH:mm:ss.fffffffK", "yy-MM-ddTHH:mm:ss.fffffffK", "yyMMddTHH:mm:ss.fffffffK",
"yyyy-MM-ddTHH:mm:ss,fffffffK", "yyyyMMddTHH:mm:ss,fffffffK", "yy-MM-ddTHH:mm:ss,fffffffK", "yyMMddTHH:mm:ss,fffffffK",
"yyyy-MM-ddTHH:mm:ss.ffffffK", "yyyyMMddTHH:mm:ss.ffffffK", "yy-MM-ddTHH:mm:ss.ffffffK", "yyMMddTHH:mm:ss.ffffffK",
"yyyy-MM-ddTHH:mm:ss,ffffffK", "yyyyMMddTHH:mm:ss,ffffffK", "yy-MM-ddTHH:mm:ss,ffffffK", "yyMMddTHH:mm:ss,ffffffK",
"yyyy-MM-ddTHH:mm:ss.fffffK", "yyyyMMddTHH:mm:ss.fffffK", "yy-MM-ddTHH:mm:ss.fffffK", "yyMMddTHH:mm:ss.fffffK",
"yyyy-MM-ddTHH:mm:ss,fffffK", "yyyyMMddTHH:mm:ss,fffffK", "yy-MM-ddTHH:mm:ss,fffffK", "yyMMddTHH:mm:ss,fffffK",
"yyyy-MM-ddTHH:mm:ss.ffffK", "yyyyMMddTHH:mm:ss.ffffK", "yy-MM-ddTHH:mm:ss.ffffK", "yyMMddTHH:mm:ss.ffffK",
"yyyy-MM-ddTHH:mm:ss,ffffK", "yyyyMMddTHH:mm:ss,ffffK", "yy-MM-ddTHH:mm:ss,ffffK", "yyMMddTHH:mm:ss,ffffK",
"yyyy-MM-ddTHH:mm:ss.ffK", "yyyyMMddTHH:mm:ss.ffK", "yy-MM-ddTHH:mm:ss.ffK", "yyMMddTHH:mm:ss.ffK",
"yyyy-MM-ddTHH:mm:ss,ffK", "yyyyMMddTHH:mm:ss,ffK", "yy-MM-ddTHH:mm:ss,ffK", "yyMMddTHH:mm:ss,ffK",
"yyyy-MM-ddTHH:mm:ss.fK", "yyyyMMddTHH:mm:ss.fK", "yy-MM-ddTHH:mm:ss.fK", "yyMMddTHH:mm:ss.fK",
"yyyy-MM-ddTHH:mm:ss,fK", "yyyyMMddTHH:mm:ss,fK", "yy-MM-ddTHH:mm:ss,fK", "yyMMddTHH:mm:ss,fK",
"yyyy-MM-ddTHH:mm:ssK", "yyyyMMddTHH:mm:ssK", "yy-MM-ddTHH:mm:ssK", "yyMMddTHH:mm:ssK",
"yyyy-MM-ddTHHmmss.fffffffK", "yyyyMMddTHHmmss.fffffffK", "yy-MM-ddTHHmmss.fffffffK", "yyMMddTHHmmss.fffffffK",
"yyyy-MM-ddTHHmmss,fffffffK", "yyyyMMddTHHmmss,fffffffK", "yy-MM-ddTHHmmss,fffffffK", "yyMMddTHHmmss,fffffffK",
"yyyy-MM-ddTHHmmss.ffffffK", "yyyyMMddTHHmmss.ffffffK", "yy-MM-ddTHHmmss.ffffffK", "yyMMddTHHmmss.ffffffK",
"yyyy-MM-ddTHHmmss,ffffffK", "yyyyMMddTHHmmss,ffffffK", "yy-MM-ddTHHmmss,ffffffK", "yyMMddTHHmmss,ffffffK",
"yyyy-MM-ddTHHmmss.fffffK", "yyyyMMddTHHmmss.fffffK", "yy-MM-ddTHHmmss.fffffK", "yyMMddTHHmmss.fffffK",
"yyyy-MM-ddTHHmmss,fffffK", "yyyyMMddTHHmmss,fffffK", "yy-MM-ddTHHmmss,fffffK", "yyMMddTHHmmss,fffffK",
"yyyy-MM-ddTHHmmss.ffffK", "yyyyMMddTHHmmss.ffffK", "yy-MM-ddTHHmmss.ffffK", "yyMMddTHHmmss.ffffK",
"yyyy-MM-ddTHHmmss,ffffK", "yyyyMMddTHHmmss,ffffK", "yy-MM-ddTHHmmss,ffffK", "yyMMddTHHmmss,ffffK",
"yyyy-MM-ddTHHmmss.ffK", "yyyyMMddTHHmmss.ffK", "yy-MM-ddTHHmmss.ffK", "yyMMddTHHmmss.ffK",
"yyyy-MM-ddTHHmmss,ffK", "yyyyMMddTHHmmss,ffK", "yy-MM-ddTHHmmss,ffK", "yyMMddTHHmmss,ffK",
"yyyy-MM-ddTHHmmss.fK", "yyyyMMddTHHmmss.fK", "yy-MM-ddTHHmmss.fK", "yyMMddTHHmmss.fK",
"yyyy-MM-ddTHHmmss,fK", "yyyyMMddTHHmmss,fK", "yy-MM-ddTHHmmss,fK", "yyMMddTHHmmss,fK",
"yyyy-MM-ddTHHmmssK", "yyyyMMddTHHmmssK", "yy-MM-ddTHHmmssK", "yyMMddTHHmmssK",
"yyyy-MM-ddTHH:mmK", "yyyyMMddTHH:mmK", "yy-MM-ddTHH:mmK", "yyMMddTHH:mmK",
"yyyy-MM-ddTHHK", "yyyyMMddTHHK", "yy-MM-ddTHHK", "yyMMddTHHK"
}
: new []
{
"yyyy-MM-ddK", "yyyyMMddK",
"yyyy-MM-ddTHH:mm:ss.fffffffK", "yyyyMMddTHH:mm:ss.fffffffK",
"yyyy-MM-ddTHH:mm:ss,fffffffK", "yyyyMMddTHH:mm:ss,fffffffK",
"yyyy-MM-ddTHH:mm:ss.ffffffK", "yyyyMMddTHH:mm:ss.ffffffK",
"yyyy-MM-ddTHH:mm:ss,ffffffK", "yyyyMMddTHH:mm:ss,ffffffK",
"yyyy-MM-ddTHH:mm:ss.fffffK", "yyyyMMddTHH:mm:ss.fffffK",
"yyyy-MM-ddTHH:mm:ss,fffffK", "yyyyMMddTHH:mm:ss,fffffK",
"yyyy-MM-ddTHH:mm:ss.ffffK", "yyyyMMddTHH:mm:ss.ffffK",
"yyyy-MM-ddTHH:mm:ss,ffffK", "yyyyMMddTHH:mm:ss,ffffK",
"yyyy-MM-ddTHH:mm:ss.ffK", "yyyyMMddTHH:mm:ss.ffK",
"yyyy-MM-ddTHH:mm:ss,ffK", "yyyyMMddTHH:mm:ss,ffK",
"yyyy-MM-ddTHH:mm:ss.fK", "yyyyMMddTHH:mm:ss.fK",
"yyyy-MM-ddTHH:mm:ss,fK", "yyyyMMddTHH:mm:ss,fK",
"yyyy-MM-ddTHH:mm:ssK", "yyyyMMddTHH:mm:ssK",
"yyyy-MM-ddTHHmmss.fffffffK", "yyyyMMddTHHmmss.fffffffK",
"yyyy-MM-ddTHHmmss,fffffffK", "yyyyMMddTHHmmss,fffffffK",
"yyyy-MM-ddTHHmmss.ffffffK", "yyyyMMddTHHmmss.ffffffK",
"yyyy-MM-ddTHHmmss,ffffffK", "yyyyMMddTHHmmss,ffffffK",
"yyyy-MM-ddTHHmmss.fffffK", "yyyyMMddTHHmmss.fffffK",
"yyyy-MM-ddTHHmmss,fffffK", "yyyyMMddTHHmmss,fffffK",
"yyyy-MM-ddTHHmmss.ffffK", "yyyyMMddTHHmmss.ffffK",
"yyyy-MM-ddTHHmmss,ffffK", "yyyyMMddTHHmmss,ffffK",
"yyyy-MM-ddTHHmmss.ffK", "yyyyMMddTHHmmss.ffK",
"yyyy-MM-ddTHHmmss,ffK", "yyyyMMddTHHmmss,ffK",
"yyyy-MM-ddTHHmmss.fK", "yyyyMMddTHHmmss.fK",
"yyyy-MM-ddTHHmmss,fK", "yyyyMMddTHHmmss,fK",
"yyyy-MM-ddTHHmmssK", "yyyyMMddTHHmmssK",
"yyyy-MM-ddTHH:mmK", "yyyyMMddTHH:mmK",
"yyyy-MM-ddTHHK", "yyyyMMddTHHK"
};
return DateTime.ParseExact(dateString, formats, CultureInfo.InvariantCulture, DateTimeStyles.AllowLeadingWhite | DateTimeStyles.AllowTrailingWhite);
}
现在,虽然我们使用像 2015-03-29T12:53:20.238748294819293021383+01:00
这样的字符串失去了精度,但我们仍然可以尽可能地解析它。
现在我们需要捕获 24:00:00
作为有效时间,然后捕获像 2015-06-30T23:59:60
这样的有效时间(尽管我没有检查从未发生过的 2014-06-30T23:59:60
,因为需要一个不断更新的闰秒数据库):
public static DateTime ParseISO8601(string dateString, MidpointRounding rounding = MidpointRounding.ToEven, bool allowTwoYear = false, bool leapSecondMeansNextDay = false)
{
var match = new Regex(@"\b(\d{4})(-W(\d{2})-|W(\d{2}))(\d)(T\S+)?\b").Match(dateString);
if(match.Success)
{
int year = int.Parse(match.Groups[1].Value);
int week = int.Parse(match.Groups[3].Value + match.Groups[4].Value);
int day = int.Parse(match.Groups[5].Value);
if(year < 1 || year > 9999 || week < 1 || week > 53 || day < 1 || day > 7)
throw new FormatException();
var firstJan = new DateTime(year, 1, 1);
var firstWeek = firstJan.DayOfWeek >= DayOfWeek.Friday
? firstJan.AddDays(firstJan.DayOfWeek - DayOfWeek.Monday - 1)
: firstJan.AddDays(DayOfWeek.Monday - firstJan.DayOfWeek);
DateTime fromWeekAndDay = firstWeek.AddDays((week - 1) * 7 + day - 1);
if(week > 51 && fromWeekAndDay > ParseISO8601(fromWeekAndDay.Year + "-W01-1"))
throw new FormatException();
if(match.Groups[6].Success)
{
// We're just going to let the handling for the other formats deal with any time fraction:
dateString = fromWeekAndDay.ToString("yyyy-MM-dd") + match.Groups[6].Value;
}
return fromWeekAndDay;
}
var excessiveFractions = new Regex(@"(\d(\.|,)\d{8,})");
if(excessiveFractions.IsMatch(dateString))
dateString = excessiveFractions.Replace(
dateString,
m => decimal.Round(decimal.Parse(m.Value.Substring(0, Math.Max(m.Value.Length, 10))), 7, rounding).ToString()
);
if(dateString.Contains("T24"))
{
var yesterday = ParseISO8601(dateString.Replace("T24", "T00"), rounding, allowTwoYear);
if(yesterday.TimeOfDay != TimeSpan.Zero)
throw new FormatException();
return yesterday.AddDays(1);
}
var leapSecond = new Regex("T23:?59:?60");
if(leapSecond.IsMatch(dateString))
{
var secondBefore = ParseISO8601(leapSecond.Replace(dateString, "T23:59:59"));
if(secondBefore.TimeOfDay != new TimeSpan(23, 59, 59)) // can't have fractions past second 60
throw new FormatException();
// Can only be on --12-31 or --06-30
if((secondBefore.Month == 12 && secondBefore.Day == 31) || (secondBefore.Month == 6 && secondBefore.Day == 30))
// since DateTime can't handle leap seconds, we need a policy as to which side of it to be on.
return leapSecondMeansNextDay ? secondBefore.AddSeconds(1) : secondBefore;
throw new FormatException();
}
var formats = allowTwoYear
? new []
{
"yyyy-MM-ddK", "yyyyMMddK", "yy-MM-ddK", "yyMMddK",
"yyyy-MM-ddTHH:mm:ss.fffffffK", "yyyyMMddTHH:mm:ss.fffffffK", "yy-MM-ddTHH:mm:ss.fffffffK", "yyMMddTHH:mm:ss.fffffffK",
"yyyy-MM-ddTHH:mm:ss,fffffffK", "yyyyMMddTHH:mm:ss,fffffffK", "yy-MM-ddTHH:mm:ss,fffffffK", "yyMMddTHH:mm:ss,fffffffK",
"yyyy-MM-ddTHH:mm:ss.ffffffK", "yyyyMMddTHH:mm:ss.ffffffK", "yy-MM-ddTHH:mm:ss.ffffffK", "yyMMddTHH:mm:ss.ffffffK",
"yyyy-MM-ddTHH:mm:ss,ffffffK", "yyyyMMddTHH:mm:ss,ffffffK", "yy-MM-ddTHH:mm:ss,ffffffK", "yyMMddTHH:mm:ss,ffffffK",
"yyyy-MM-ddTHH:mm:ss.fffffK", "yyyyMMddTHH:mm:ss.fffffK", "yy-MM-ddTHH:mm:ss.fffffK", "yyMMddTHH:mm:ss.fffffK",
"yyyy-MM-ddTHH:mm:ss,fffffK", "yyyyMMddTHH:mm:ss,fffffK", "yy-MM-ddTHH:mm:ss,fffffK", "yyMMddTHH:mm:ss,fffffK",
"yyyy-MM-ddTHH:mm:ss.ffffK", "yyyyMMddTHH:mm:ss.ffffK", "yy-MM-ddTHH:mm:ss.ffffK", "yyMMddTHH:mm:ss.ffffK",
"yyyy-MM-ddTHH:mm:ss,ffffK", "yyyyMMddTHH:mm:ss,ffffK", "yy-MM-ddTHH:mm:ss,ffffK", "yyMMddTHH:mm:ss,ffffK",
"yyyy-MM-ddTHH:mm:ss.ffK", "yyyyMMddTHH:mm:ss.ffK", "yy-MM-ddTHH:mm:ss.ffK", "yyMMddTHH:mm:ss.ffK",
"yyyy-MM-ddTHH:mm:ss,ffK", "yyyyMMddTHH:mm:ss,ffK", "yy-MM-ddTHH:mm:ss,ffK", "yyMMddTHH:mm:ss,ffK",
"yyyy-MM-ddTHH:mm:ss.fK", "yyyyMMddTHH:mm:ss.fK", "yy-MM-ddTHH:mm:ss.fK", "yyMMddTHH:mm:ss.fK",
"yyyy-MM-ddTHH:mm:ss,fK", "yyyyMMddTHH:mm:ss,fK", "yy-MM-ddTHH:mm:ss,fK", "yyMMddTHH:mm:ss,fK",
"yyyy-MM-ddTHH:mm:ssK", "yyyyMMddTHH:mm:ssK", "yy-MM-ddTHH:mm:ssK", "yyMMddTHH:mm:ssK",
"yyyy-MM-ddTHHmmss.fffffffK", "yyyyMMddTHHmmss.fffffffK", "yy-MM-ddTHHmmss.fffffffK", "yyMMddTHHmmss.fffffffK",
"yyyy-MM-ddTHHmmss,fffffffK", "yyyyMMddTHHmmss,fffffffK", "yy-MM-ddTHHmmss,fffffffK", "yyMMddTHHmmss,fffffffK",
"yyyy-MM-ddTHHmmss.ffffffK", "yyyyMMddTHHmmss.ffffffK", "yy-MM-ddTHHmmss.ffffffK", "yyMMddTHHmmss.ffffffK",
"yyyy-MM-ddTHHmmss,ffffffK", "yyyyMMddTHHmmss,ffffffK", "yy-MM-ddTHHmmss,ffffffK", "yyMMddTHHmmss,ffffffK",
"yyyy-MM-ddTHHmmss.fffffK", "yyyyMMddTHHmmss.fffffK", "yy-MM-ddTHHmmss.fffffK", "yyMMddTHHmmss.fffffK",
"yyyy-MM-ddTHHmmss,fffffK", "yyyyMMddTHHmmss,fffffK", "yy-MM-ddTHHmmss,fffffK", "yyMMddTHHmmss,fffffK",
"yyyy-MM-ddTHHmmss.ffffK", "yyyyMMddTHHmmss.ffffK", "yy-MM-ddTHHmmss.ffffK", "yyMMddTHHmmss.ffffK",
"yyyy-MM-ddTHHmmss,ffffK", "yyyyMMddTHHmmss,ffffK", "yy-MM-ddTHHmmss,ffffK", "yyMMddTHHmmss,ffffK",
"yyyy-MM-ddTHHmmss.ffK", "yyyyMMddTHHmmss.ffK", "yy-MM-ddTHHmmss.ffK", "yyMMddTHHmmss.ffK",
"yyyy-MM-ddTHHmmss,ffK", "yyyyMMddTHHmmss,ffK", "yy-MM-ddTHHmmss,ffK", "yyMMddTHHmmss,ffK",
"yyyy-MM-ddTHHmmss.fK", "yyyyMMddTHHmmss.fK", "yy-MM-ddTHHmmss.fK", "yyMMddTHHmmss.fK",
"yyyy-MM-ddTHHmmss,fK", "yyyyMMddTHHmmss,fK", "yy-MM-ddTHHmmss,fK", "yyMMddTHHmmss,fK",
"yyyy-MM-ddTHHmmssK", "yyyyMMddTHHmmssK", "yy-MM-ddTHHmmssK", "yyMMddTHHmmssK",
"yyyy-MM-ddTHH:mmK", "yyyyMMddTHH:mmK", "yy-MM-ddTHH:mmK", "yyMMddTHH:mmK",
"yyyy-MM-ddTHHK", "yyyyMMddTHHK", "yy-MM-ddTHHK", "yyMMddTHHK"
}
: new []
{
"yyyy-MM-ddK", "yyyyMMddK",
"yyyy-MM-ddTHH:mm:ss.fffffffK", "yyyyMMddTHH:mm:ss.fffffffK",
"yyyy-MM-ddTHH:mm:ss,fffffffK", "yyyyMMddTHH:mm:ss,fffffffK",
"yyyy-MM-ddTHH:mm:ss.ffffffK", "yyyyMMddTHH:mm:ss.ffffffK",
"yyyy-MM-ddTHH:mm:ss,ffffffK", "yyyyMMddTHH:mm:ss,ffffffK",
"yyyy-MM-ddTHH:mm:ss.fffffK", "yyyyMMddTHH:mm:ss.fffffK",
"yyyy-MM-ddTHH:mm:ss,fffffK", "yyyyMMddTHH:mm:ss,fffffK",
"yyyy-MM-ddTHH:mm:ss.ffffK", "yyyyMMddTHH:mm:ss.ffffK",
"yyyy-MM-ddTHH:mm:ss,ffffK", "yyyyMMddTHH:mm:ss,ffffK",
"yyyy-MM-ddTHH:mm:ss.ffK", "yyyyMMddTHH:mm:ss.ffK",
"yyyy-MM-ddTHH:mm:ss,ffK", "yyyyMMddTHH:mm:ss,ffK",
"yyyy-MM-ddTHH:mm:ss.fK", "yyyyMMddTHH:mm:ss.fK",
"yyyy-MM-ddTHH:mm:ss,fK", "yyyyMMddTHH:mm:ss,fK",
"yyyy-MM-ddTHH:mm:ssK", "yyyyMMddTHH:mm:ssK",
"yyyy-MM-ddTHHmmss.fffffffK", "yyyyMMddTHHmmss.fffffffK",
"yyyy-MM-ddTHHmmss,fffffffK", "yyyyMMddTHHmmss,fffffffK",
"yyyy-MM-ddTHHmmss.ffffffK", "yyyyMMddTHHmmss.ffffffK",
"yyyy-MM-ddTHHmmss,ffffffK", "yyyyMMddTHHmmss,ffffffK",
"yyyy-MM-ddTHHmmss.fffffK", "yyyyMMddTHHmmss.fffffK",
"yyyy-MM-ddTHHmmss,fffffK", "yyyyMMddTHHmmss,fffffK",
"yyyy-MM-ddTHHmmss.ffffK", "yyyyMMddTHHmmss.ffffK",
"yyyy-MM-ddTHHmmss,ffffK", "yyyyMMddTHHmmss,ffffK",
"yyyy-MM-ddTHHmmss.ffK", "yyyyMMddTHHmmss.ffK",
"yyyy-MM-ddTHHmmss,ffK", "yyyyMMddTHHmmss,ffK",
"yyyy-MM-ddTHHmmss.fK", "yyyyMMddTHHmmss.fK",
"yyyy-MM-ddTHHmmss,fK", "yyyyMMddTHHmmss,fK",
"yyyy-MM-ddTHHmmssK", "yyyyMMddTHHmmssK",
"yyyy-MM-ddTHH:mmK", "yyyyMMddTHH:mmK",
"yyyy-MM-ddTHHK", "yyyyMMddTHHK"
};
return DateTime.ParseExact(dateString, formats, CultureInfo.InvariantCulture, DateTimeStyles.AllowLeadingWhite | DateTimeStyles.AllowTrailingWhite);
}
所有这些工作量很大,但也很有趣。而且我们仍然没有涉及维护时区(对上面的 DateTimeOffset
来说不是一个棘手的变化)仅时间字符串(应该 return 和 TimeSpan
),持续时间(也应该 return一个TimeSpan
), 周期或重复周期。
同时,ISO 8601 旨在用于定义一个或多个 配置文件 ,这些配置文件又定义了一个或多个允许格式的子集,可能还有其他规则。一般来说,我们希望对这些配置文件之一进行编程,而不是通常对 ISO 8601 进行编程。例如,上面的代码对于解析 web 日期时间是无用的,因为它接受 2010 年 1 月 3 日的 2009-W53-7
,这是正确的 ISO 8601 处理,但 ISO 8601 的 web-datetime 配置文件不允许。