File: System\ServiceModel\Syndication\DateTimeHelper.cs
Web Access
Project: src\src\libraries\System.ServiceModel.Syndication\src\System.ServiceModel.Syndication.csproj (System.ServiceModel.Syndication)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Globalization;
using System.Text;
 
namespace System.ServiceModel.Syndication
{
    internal static class DateTimeHelper
    {
        private const string Rfc3339DateTimeFormat = "yyyy-MM-ddTHH:mm:ssK";
 
        public static bool DefaultRss20DateTimeParser(XmlDateTimeData XmlDateTimeData, out DateTimeOffset dateTimeOffset)
        {
            string dateTimeString = XmlDateTimeData.DateTimeString;
 
            // First check if DateTimeOffset default parsing can parse the date
            if (DateTimeOffset.TryParse(dateTimeString, out dateTimeOffset))
            {
                return true;
            }
 
            // RSS specifies RFC822
            if (Rfc822DateTimeParser(dateTimeString, out dateTimeOffset))
            {
                return true;
            }
 
            // Event though RCS3339 is for Atom, someone might be using this for RSS
            if (Rfc3339DateTimeParser(dateTimeString, out dateTimeOffset))
            {
                return true;
            }
 
            return false;
        }
 
        public static bool DefaultAtom10DateTimeParser(XmlDateTimeData XmlDateTimeData, out DateTimeOffset dateTimeOffset)
        {
            return Rfc3339DateTimeParser(XmlDateTimeData.DateTimeString, out dateTimeOffset);
        }
 
        private static bool Rfc3339DateTimeParser(string dateTimeString, out DateTimeOffset dto)
        {
            dto = default(DateTimeOffset);
            dateTimeString = dateTimeString.Trim();
            if (dateTimeString.Length < 20)
            {
                return false;
            }
 
            if (dateTimeString[19] == '.')
            {
                // remove any fractional seconds, we choose to ignore them
                int i = 20;
                while (dateTimeString.Length > i && char.IsDigit(dateTimeString[i]))
                {
                    ++i;
                }
 
#if NET
                dateTimeString = string.Concat(dateTimeString.AsSpan(0, 19), dateTimeString.AsSpan(i));
#else
                dateTimeString = dateTimeString.Substring(0, 19) + dateTimeString.Substring(i);
#endif
            }
 
            return DateTimeOffset.TryParseExact(dateTimeString, Rfc3339DateTimeFormat, CultureInfo.InvariantCulture.DateTimeFormat, DateTimeStyles.None, out dto);
        }
 
        private static bool Rfc822DateTimeParser(string dateTimeString, out DateTimeOffset dto)
        {
            dto = default(DateTimeOffset);
            StringBuilder dateTimeStringBuilder = new StringBuilder(dateTimeString.Trim());
            if (dateTimeStringBuilder.Length < 18)
            {
                return false;
            }
 
            int timeZoneStartIndex;
            for (timeZoneStartIndex = dateTimeStringBuilder.Length - 1; dateTimeStringBuilder[timeZoneStartIndex] != ' '; timeZoneStartIndex--)
                ;
            timeZoneStartIndex++;
 
            int timeZoneLength = dateTimeStringBuilder.Length - timeZoneStartIndex;
            string timeZoneSuffix = dateTimeStringBuilder.ToString(timeZoneStartIndex, timeZoneLength);
            dateTimeStringBuilder.Remove(timeZoneStartIndex, timeZoneLength);
            bool isUtc;
            dateTimeStringBuilder.Append(NormalizeTimeZone(timeZoneSuffix, out isUtc));
            string wellFormattedString = dateTimeStringBuilder.ToString();
 
            DateTimeOffset theTime;
            string[] parseFormat =
            {
                "ddd, dd MMMM yyyy HH:mm:ss zzz",
                "dd MMMM yyyy HH:mm:ss zzz",
                "ddd, dd MMM yyyy HH:mm:ss zzz",
                "dd MMM yyyy HH:mm:ss zzz",
 
                "ddd, dd MMMM yyyy HH:mm zzz",
                "dd MMMM yyyy HH:mm zzz",
                "ddd, dd MMM yyyy HH:mm zzz",
                "dd MMM yyyy HH:mm zzz",
 
                // The original RFC822 spec listed 2 digit years. RFC1123 updated the format to include 4 digit years and states that you should use 4 digits.
                // Technically RSS2.0 specifies RFC822 but it's presumed that RFC1123 will be used as we're now past Y2K and everyone knows better. The 4 digit
                // formats are listed first for performance reasons as it's presumed they will be more likely to match first.
                "ddd, dd MMMM yy HH:mm:ss zzz",
                "dd MMMM yyyy HH:mm:ss zzz",
                "ddd, dd MMM yy HH:mm:ss zzz",
                "dd MMM yyyy HH:mm:ss zzz",
 
                "ddd, dd MMMM yy HH:mm zzz",
                "dd MMMM yyyy HH:mm zzz",
                "ddd, dd MMM yy HH:mm zzz",
                "dd MMM yyyy HH:mm zzz"
            };
 
            if (DateTimeOffset.TryParseExact(wellFormattedString, parseFormat,
                CultureInfo.InvariantCulture.DateTimeFormat,
                (isUtc ? DateTimeStyles.AdjustToUniversal : DateTimeStyles.None), out theTime))
            {
                dto = theTime;
                return true;
            }
 
            return false;
        }
 
        private static string NormalizeTimeZone(string rfc822TimeZone, out bool isUtc)
        {
            isUtc = false;
            // return a string in "-08:00" format
            if (rfc822TimeZone[0] == '+' || rfc822TimeZone[0] == '-')
            {
                // the time zone is supposed to be 4 digits but some feeds omit the initial 0
                StringBuilder result = new StringBuilder(rfc822TimeZone);
                if (result.Length == 4)
                {
                    // the timezone is +/-HMM. Convert to +/-HHMM
                    result.Insert(1, '0');
                }
                result.Insert(3, ':');
                return result.ToString();
            }
            switch (rfc822TimeZone)
            {
                case "UT":
                case "Z":
                    isUtc = true;
                    return "-00:00";
                case "GMT":
                    return "-00:00";
                case "A":
                    return "-01:00";
                case "B":
                    return "-02:00";
                case "C":
                    return "-03:00";
                case "D":
                case "EDT":
                    return "-04:00";
                case "E":
                case "EST":
                case "CDT":
                    return "-05:00";
                case "F":
                case "CST":
                case "MDT":
                    return "-06:00";
                case "G":
                case "MST":
                case "PDT":
                    return "-07:00";
                case "H":
                case "PST":
                    return "-08:00";
                case "I":
                    return "-09:00";
                case "K":
                    return "-10:00";
                case "L":
                    return "-11:00";
                case "M":
                    return "-12:00";
                case "N":
                    return "+01:00";
                case "O":
                    return "+02:00";
                case "P":
                    return "+03:00";
                case "Q":
                    return "+04:00";
                case "R":
                    return "+05:00";
                case "S":
                    return "+06:00";
                case "T":
                    return "+07:00";
                case "U":
                    return "+08:00";
                case "V":
                    return "+09:00";
                case "W":
                    return "+10:00";
                case "X":
                    return "+11:00";
                case "Y":
                    return "+12:00";
                default:
                    return "";
            }
        }
 
    }
}