This post provides a class that converts an RFC 822 formatted text representation of a date time to a DateTime object in C#. Really Simple Syndication (RSS) documents, according to the RSS specification, require that all date-times in RSS conform to the Date and Time Specification as outlined in RFC 822. Therefore, when working with RSS documents in AspNetCore and C#, it is necessary to convert the date-time in the RSS Document to a datetime object in C#. With understanding that the DateTime.Parse and DateTime.ParseExact methods of the C# datetime object have been tested and do not handle the parsing of all forms of an RFC 822 compliant date, it was deemed necessary to develop a more robust solution.
Using The Class
PFSRfc822DateTimeConverter rfc822Conv = new PFSRfc822DateTimeConverter(); if (rfc822Conv.TryParse("Mon, 11 Mar 2019 01:57:00 EST" out DateTime rslt) { console.WriteLine(rslt.ToString("yyyy-MM-dd HH:mm:ss zzz"); } try { DateTime rt = rfc822Conv.Parse("Mon, 11 Mar 2019 01:57 A"); Console.WriteLine(rslt.ToString("yyyy-MM-dd HH:mm:ss zzz"); } catch(Exception) {}
The class was developed in order to ease the parsing of RFC 822 compliant date times which are part of RSS documents. It seemed that every feed formatted their publication dates in a different manner; all within the spec mind you. As stated above, the DateTime.Parse method always threw an exception and the DateTime.ParseExact method required analyzing every combination possible...which resulted in hundreds of template strings.
To see the issue up close, one should realize that RFC 822 compliant dates can come in any number of formats. For example:
- Mon, 11 Mar 2019 01:45:26 EST
- Mon, 11 Mar 2019 01:45:26 EDT
- Mon, 11 Mar 2019 01:45:25 -0500
- Mon, 11 Mar 2019 01:45 0500
- Mon, 11 Mar 2019 01:45:00 Z
- Mon, 11 Mar 2019 01:45:00 GMT
- Mon, 11 Mar 2019 01:45 A
- Mon, 11 Mar 2019 01:45:26 M
As one can see, it is pretty random. Looking more closely, it can be seen that the primary differences occur in the timezone specification. There is EST (Eastern Standard Time), EDT (Eastern Daylight Savings Time), Z (Zulu or Greenwich Mean Time), GMT (Greenwich Mean Time), A (Military -1 hours), M (Military -12 hours), etc.
Also, look at the timezone offset with the hours and minutes. THERE IS NO COLON separating the hours and minutes. This differs from ordinary timezone offset specifications. In addition, the plus, minus sign is optional with the omission of the sign defaulting to plus.
RFC 822
The part of the RFC 822 that deals with the date time is included below:
5. DATE AND TIME SPECIFICATION 5.1. SYNTAX date-time = [ day "," ] date time ; dd mm yy ; hh:mm:ss zzz day = "Mon" / "Tue" / "Wed" / "Thu" / "Fri" / "Sat" / "Sun" date = 1*2DIGIT month 2DIGIT ; day month year ; e.g. 20 Jun 82 month = "Jan" / "Feb" / "Mar" / "Apr" / "May" / "Jun" / "Jul" / "Aug" / "Sep" / "Oct" / "Nov" / "Dec" time = hour zone ; ANSI and Military hour = 2DIGIT ":" 2DIGIT [":" 2DIGIT] ; 00:00:00 - 23:59:59 zone = "UT" / "GMT" ; Universal Time ; North American : UT / "EST" / "EDT" ; Eastern: - 5/ - 4 / "CST" / "CDT" ; Central: - 6/ - 5 / "MST" / "MDT" ; Mountain: - 7/ - 6 / "PST" / "PDT" ; Pacific: - 8/ - 7 / 1ALPHA ; Military: Z = UT; ; A:-1; (J not used) ; M:-12; N:+1; Y:+12 / ( ("+" / "-") 4DIGIT ) ; Local differential ; hours+min. (HHMM) 5.2. SEMANTICS If included, day-of-week must be the day implied by the date specification. Time zone may be indicated in several ways. "UT" is Univer- sal Time (formerly called "Greenwich Mean Time"); "GMT" is per- mitted as a reference to Universal Time. The military standard uses a single character for each zone. "Z" is Universal Time. "A" indicates one hour earlier, and "M" indicates 12 hours ear- lier; "N" is one hour later, and "Y" is 12 hours later. The letter "J" is not used. The other remaining two forms are taken from ANSI standard X3.51-1975. One allows explicit indication of the amount of offset from UT; the other uses common 3-character strings for indicating time zones in North America.
Certainly, this is all a bit cryptic at best.
The Class Code
The source code for the class is provided below. Note, the source code is set up for Debugging, thus you see a myriad of Debug statements. Certainly the class can be optimized but the more important point is to show the logic flow clearly; without a lot of syntactical sugar.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Xml; using System.Diagnostics; using System.IO; using System.Net; namespace Sirpenski.AspNetCore.Utilities { public class PFSRfc822DateTimeConverter { // these constants indicate how the timezone was set. const int TIMEZONE_NOT_SET = 0; const int TIMEZONE_SET_BY_TIMEZONE_INFO = 1; const int TIMEZONE_SET_BY_MILITARY_MINUS_OFFSET_TO_UTC = 2; const int TIMEZONE_SET_BY_MILITARY_PLUS_OFFSET_TO_UTC = 3; const int TIMEZONE_SET_BY_4_CHARACTER_TIMEZONE_OFFSET = 4; const int TIMEZONE_SET_BY_5_CHARACTER_TIMEZONE_OFFSET = 5; // DatePart keys public const string DATEPART_DAY_OF_WEEK = "day_of_week"; public const string DATEPART_DAY = "day"; public const string DATEPART_MONTH = "month"; public const string DATEPART_YEAR = "year"; public const string DATEPART_HOUR = "hour"; public const string DATEPART_MINUTE = "minute"; public const string DATEPART_SECOND = "second"; public const string DATEPART_TIMEZONE = "timezone"; public const string DATEPART_TIMEZONE_OFFSET = "timezone_offset"; public const string DATEPART_TIMEZONE_INFO_NAME = "timezone_info_name"; // public list of keys to allow iteration of the dateparts public ListDATEPART_KEYS = new List () { DATEPART_DAY_OF_WEEK, DATEPART_DAY, DATEPART_MONTH, DATEPART_YEAR, DATEPART_HOUR, DATEPART_MINUTE, DATEPART_SECOND, DATEPART_TIMEZONE, DATEPART_TIMEZONE_OFFSET, DATEPART_TIMEZONE_INFO_NAME}; // working array to quickly validate day of week parameter private List DAYS_OF_WEEK = new List () { "MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN" }; // private variables private string strInput = ""; private DateTime rt = DateTime.MinValue; private DateTime rtUtc = DateTime.MinValue.ToUniversalTime(); private TimeSpan tsUtcOffset = TimeSpan.Zero; private bool boolTimeZoneUsesDaylightSavingsTime = false; private bool InvalidTokenFormatError = false; private HashSet > arrDateParts = new HashSet >(); /// /// Input Value. Set by parameter to parse /// public string Input { get { return strInput; } } ////// Result In Local Time> /// public DateTime Result { get { return rt; } } ////// Result In Universal Time /// public DateTime ResultUtc { get { return rtUtc; } } ////// TImezone Offset of Local Time /// public TimeSpan UtcOffset { get { return tsUtcOffset; } } ////// Flag indicating whether Input Timezone uses daylight savings time /// public bool TimeZoneUsesDaylightSavingsTime { get { return boolTimeZoneUsesDaylightSavingsTime; } } ////// Gets the local time result in an RFC 822 compliant string /// public string ResultRfc822 { get { return rt.ToString("ddd, dd MMM yyyy HH:mm:ss ") + UtcOffset.Hours.ToString("00") + UtcOffset.Minutes.ToString("00"); } } ////// Error Flag /// public bool Error { get { return InvalidTokenFormatError; } } ////// DateParts, ie the tokens /// public HashSet> DateParts { get { return arrDateParts; } } /// /// Gets the date part /// /// ///public string GetDatePartValue(string key) { string rt = ""; KeyValuePair kvp = arrDateParts.FirstOrDefault(x => x.Key == key); if (!kvp.Equals(default(KeyValuePair ))) { rt = kvp.Value; } return rt; } /// /// Parse parses the input but throws an exception if the input is not /// in RFC 822 compliant format. It behaves like the DateTime.Parse method /// /// RFC 822 Date Compliant String ///DateTime public DateTime Parse(string rfcDateTimeInput) { DateTime rt = ParseRfc822(rfcDateTimeInput); if (Error) { throw new Exception("DateTime String Input Is Not RFC 822 Compliant"); } return rt; } ////// TryParse tries to do a parse and if successful, sets the reference. It behaves like microsoft /// TryParse DateTime method /// /// RFC 822 Date Compliant String /// Input Parameter ///boolean public bool TryParse(string rfcDateTimeInput, out DateTime dt) { bool rt = false; // set the date time to the minimum value dt = DateTime.MinValue; // parse the results DateTime rslt = ParseRfc822(rfcDateTimeInput); if (!Error) { dt = rslt; rt = true; } return rt; } ////// Converts a datetime string in RFC 822 format to a datetime value /// /// string ///public DateTime ParseRfc822(string txt) { // these are indexes that will be used to regenerate the // datetime after parsing const int DAY_INDEX = 0; const int MONTH_INDEX = 1; const int YEAR_INDEX = 2; const int HOUR_INDEX = 3; const int MINUTE_INDEX = 4; const int SECOND_INDEX = 5; const int DATEPART_DAY_OF_WEEK_INDEX = 0; const int DATEPART_DAY_INDEX = 1; const int DATEPART_MONTH_INDEX = 2; const int DATEPART_YEAR_INDEX = 3; const int DATEPART_TIME_INDEX = 4; const int DATEPART_TIMEZONE_INDEX = 5; // Debug.WriteLine("RFC822 BEGIN CONVERT " + txt); // set the input string strInput = txt; // default to min date value rt = DateTime.MinValue; // clear the universal time; rtUtc = rt.ToUniversalTime(); // set the timezone info to null TimeZoneInfo tzInfo = null; // set the uses daylightsavings time to false boolTimeZoneUsesDaylightSavingsTime = false; // error handler flag InvalidTokenFormatError = false; // a working array to hold the date parts. default to min date time // equivalent to 1/1/0001 00:00:00 Universal time. Note, array index 0 corresponds to the // day in words token, we will ignore that seel later int[] wrkDt = new int[6] { 1, 1, 1, 0, 0, 0 }; // default datetime offset tsUtcOffset = TimeSpan.Zero; // this holds the count of time tokens. It will be used // to validate the format int TimeTokenComponentCount = 0; // set the code of the part that actually sets the timezone to nothing. int TimeZoneSetByCode = TIMEZONE_NOT_SET; // define a list of days, we will use this as a look up tabl List MONTHS = new List () { "", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" }; // split the string into parts by splitting on a space. note that we trim the input text string first. string[] tokens = txt.Trim().Split(' '); // now, we are going to loop through the tokens. Because the date component might have more // than one space between the date components, we have to iterate skipping the tokens that are // zero length // first, int currentDatePartIndex = DATEPART_DAY_OF_WEEK_INDEX; // ITERATE THROUGH EACH TOKEN in the input string for (int tokenIndex = 0; tokenIndex < tokens.Length && !InvalidTokenFormatError; tokenIndex++) { // trim the token. clear out any spaces, etc. string tkn = tokens[tokenIndex].Trim(); // if the token has a non zero length, it is something if (tkn.Length > 0) { // switch based upon the current part of the date we are examining. switch (currentDatePartIndex) { // this is supposed to be the day of week token, it may be the // day token. we have to check it to see if it is a day of week token case DATEPART_DAY_OF_WEEK_INDEX: // set the day of week to the token string DayOfWeek = tkn; // lets get the first 3 letters of the token if (tkn.Length >= 3) { // upper case the token DayOfWeek = tkn.Substring(0, 3); } // now do a lookup in the DAYS_OF_WEEKS list int DayOfWeekIndexFound = DAYS_OF_WEEK.FindIndex(x => x == DayOfWeek.ToUpper()); // if the token was not found, the token is actually the day. Thus, // we need to decrement tokenIndex so the token will be processed again. // however, we need to increment the current datepart index so this process // gets skipped next iteration if (DayOfWeekIndexFound == -1) { // one more check, we have to validate that this is a number if (int.TryParse(tkn, out int rslt2)) { // reset tokenIndex -= 1; DayOfWeek = ""; } // otherwise, the first token isnt a day or a number so it is an // invalid format else { InvalidTokenFormatError = true; } } // set the datepart array AddToDatePartList(DATEPART_DAY_OF_WEEK, DayOfWeek); // increment the next part of the date currentDatePartIndex += 1; break; // try and parse the token into a day integer value. if successful, we // will store it in the working array which will be used to rebuild a // valid date time. case DATEPART_DAY_INDEX: if (int.TryParse(tkn, out wrkDt[DAY_INDEX])) { // add to the public datepart list AddToDatePartList(DATEPART_DAY, wrkDt[DAY_INDEX].ToString()); // we have a good day token, proceed to month token currentDatePartIndex += 1; } // otherwise, we have an invalid day token, bail out of loop else { InvalidTokenFormatError = true; } break; // token two is the month in 3 letters. we do a lookup in the MONTHS array for the // index. The index is the numeric representation of the month. if we find it, we // wil insert it into the working date time array which will be used to rebuild the // date time. case DATEPART_MONTH_INDEX: // Debug.WriteLine("RFC822 PROCESSING MONTH TOKEN: " + tkn); // Debug.WriteLine(""); wrkDt[MONTH_INDEX] = MONTHS.FindIndex(x => x == tkn.ToUpper()); // if the month index was found, it will be greater than zero if (wrkDt[MONTH_INDEX] >= 0) { // add to the public datepart list AddToDatePartList(DATEPART_MONTH, wrkDt[MONTH_INDEX].ToString()); // proceed to next token currentDatePartIndex += 1; } // otherwise, we have an invalid month token, bail out of loop else { InvalidTokenFormatError = true; } break; // process the year token. If valid, set it in the datetime working array // which we will use to reconstruct a valid datetime case DATEPART_YEAR_INDEX: // Debug.WriteLine("RFC822 PROCESSING YEAR TOKEN: " + tkn); // Debug.WriteLine(""); if (int.TryParse(tkn, out wrkDt[YEAR_INDEX])) { // add to the public datepart list AddToDatePartList(DATEPART_YEAR, wrkDt[YEAR_INDEX].ToString()); currentDatePartIndex += 1; } // otherwise, bail else { InvalidTokenFormatError = true; } break; // the time part is of the form hh:mm:ss case DATEPART_TIME_INDEX: // Debug.WriteLine("RFC822 PROCESSING TIME TOKEN: " + tkn); // Debug.WriteLine(""); // we need to split the time compent up by the colons string[] tm = tkn.Split(':'); // here we are going to do a trick. We are going to set the // invalid token format to true. If we get at least two time tokens, // we will reset it to false. InvalidTokenFormatError = true; if (tm.Length > 0) { // the first token is the hour if (int.TryParse(tm[0], out wrkDt[HOUR_INDEX])) { // reset to false, we have at least one time tokens (hr) InvalidTokenFormatError = false; // add to date part list AddToDatePartList(DATEPART_HOUR, wrkDt[HOUR_INDEX].ToString()); // we are also going to increment the current date part index currentDatePartIndex += 1; // keep track of time token count. TimeTokenComponentCount += 1; if (tm.Length > 1) { // now do the second token if (int.TryParse(tm[1], out wrkDt[MINUTE_INDEX])) { // add to the public datepart list AddToDatePartList(DATEPART_MINUTE, wrkDt[MINUTE_INDEX].ToString()); // increment and keep track of time token component count TimeTokenComponentCount += 1; if (tm.Length > 2) { if (int.TryParse(tm[2], out wrkDt[SECOND_INDEX])) { // add to the public datepart list AddToDatePartList(DATEPART_SECOND, wrkDt[SECOND_INDEX].ToString()); TimeTokenComponentCount += 1; } } // end if time tokens length is greater than 2 } // end if try parsing minute index } // end if time tokens length greater than 1 } // end try parse hour } // end if tm tokens length is greaterthan zero break; // now do the timezone. case DATEPART_TIMEZONE_INDEX: currentDatePartIndex += 1; // upper case timezone token. string tzToken = tkn.ToUpper(); // switch on the tzToken looking for the timezone info that corresponds to the 3 letter // timezone specified. try { switch (tzToken) { case "EST": tzInfo = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time"); break; case "EDT": tzInfo = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time"); boolTimeZoneUsesDaylightSavingsTime = true; break; case "CST": tzInfo = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time"); break; case "CDT": tzInfo = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time"); boolTimeZoneUsesDaylightSavingsTime = true; break; case "MST": tzInfo = TimeZoneInfo.FindSystemTimeZoneById("Mountain Standard Time"); break; case "MDT": tzInfo = TimeZoneInfo.FindSystemTimeZoneById("Mountain Standard Time"); boolTimeZoneUsesDaylightSavingsTime = true; break; case "PST": tzInfo = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time"); break; case "PDT": tzInfo = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time"); boolTimeZoneUsesDaylightSavingsTime = true; break; case "Z": tzInfo = TimeZoneInfo.FindSystemTimeZoneById("GMT Standard Time"); break; case "UT": tzInfo = TimeZoneInfo.FindSystemTimeZoneById("GMT Standard Time"); break; case "GMT": tzInfo = TimeZoneInfo.FindSystemTimeZoneById("GMT Standard Time"); break; default: break; } } catch (Exception) { } // if we found it for the timezone id, if (tzInfo != null) { // calculate the offset from Universal time. tsUtcOffset = tzInfo.BaseUtcOffset; // add to the public datepart list AddToDatePartList(DATEPART_TIMEZONE, tzToken); AddToDatePartList(DATEPART_TIMEZONE_INFO_NAME, tzInfo.StandardName); // set the control flag TimeZoneSetByCode = TIMEZONE_SET_BY_TIMEZONE_INFO; } // if timespan not set, then let's check the 1 character military time for negative // offsets COdes A-M (excludes J) if (TimeZoneSetByCode == TIMEZONE_NOT_SET) { // if the timezone token length is 1, then proceed if (tzToken.Length == 1) { string MIL1 = " ABCDEFGHIKLM"; int ndx = MIL1.IndexOf(tzToken); if (ndx != -1) { TimeSpan tstmp = new TimeSpan(ndx, 0, 0); long tsTicks = tstmp.Ticks * -1; tsUtcOffset = new TimeSpan(tsTicks); TimeZoneSetByCode = TIMEZONE_SET_BY_MILITARY_MINUS_OFFSET_TO_UTC; boolTimeZoneUsesDaylightSavingsTime = false; AddToDatePartList(DATEPART_TIMEZONE, tzToken); } } } // If timespan still not set, check the positive Military 1 character codes N-Y if (TimeZoneSetByCode == TIMEZONE_NOT_SET) { // if timezone token length is 1, proceed if (tzToken.Length == 1) { string MIL2 = " NOPQRSTUVWXY"; int ndx = MIL2.IndexOf(tzToken); if (ndx != -1) { tsUtcOffset = new TimeSpan(ndx, 0, 0); TimeZoneSetByCode = TIMEZONE_SET_BY_MILITARY_PLUS_OFFSET_TO_UTC; boolTimeZoneUsesDaylightSavingsTime = false; AddToDatePartList(DATEPART_TIMEZONE, tzToken); } } } // if the timezone info was null, then we have to do more work, // ie parse the info and calculate the hours and minutes offset. if (TimeZoneSetByCode == TIMEZONE_NOT_SET) { // Debug.WriteLine("RFC822 TESTNG FOR HOUR-MINUTE OFFSET TIMEZONE SPECIFICATION"); // define a ticks multiplier, long tsTickMultiplier = 1; int tsHr = 0; int tsMin = 0; // set the invalid format flag, we will unset if we get // a good timezone offset conversion InvalidTokenFormatError = true; // parse timezone offset checking for a plus, minus at the beginning if (tkn.Length == 5) { // check first character to see if plus or minus. If minus, // set a negative multiplier if (string.Compare(tkn.Substring(0, 1), "-") == 0) { tsTickMultiplier = -1; } // parse the timezone offset, hours in positions 1,2 if (int.TryParse(tkn.Substring(1, 2), out tsHr)) { // parse minutes offset, minutes is in positions 3,4 if (int.TryParse(tkn.Substring(3, 2), out tsMin)) { InvalidTokenFormatError = false; TimeZoneSetByCode = TIMEZONE_SET_BY_5_CHARACTER_TIMEZONE_OFFSET; } } } // if the timezone info contains only 4 digits, assume a plus else if (tkn.Length == 4) { // parse the timezone offset. Hour is in positions 0,1 if (int.TryParse(tkn.Substring(0, 2), out tsHr)) { // parse the minutes. Minutes is in position 2,3 if (int.TryParse(tkn.Substring(2, 2), out tsMin)) { InvalidTokenFormatError = false; TimeZoneSetByCode = TIMEZONE_SET_BY_4_CHARACTER_TIMEZONE_OFFSET; } } } // otherwise, it is an invalid timezone format else { InvalidTokenFormatError = true; } // if the timezone is valid, let's compute the timestamp offset if (!InvalidTokenFormatError) { // compute a new timespan TimeSpan tsWrk = new TimeSpan(tsHr, tsMin, 0); // convert to ticks and apply the multiplier so we either get // a positive or negative offset long tsTicks = tsWrk.Ticks * tsTickMultiplier; // now finally create the timspan offset tsUtcOffset = new TimeSpan(tsTicks); // add to public datepart list AddToDatePartList(DATEPART_TIMEZONE, tzToken); } // end if not invalid token } // end if not TimeSpanOffsetSet by 3 char code or 1 char militarycode break; } // end switch } // end token length > 0 } // end foreach datepart token // final format checks. First, if the date format is not military, then // ensure that there are at least two time tokens (hour and minute) if (TimeZoneSetByCode != TIMEZONE_SET_BY_MILITARY_MINUS_OFFSET_TO_UTC && TimeZoneSetByCode != TIMEZONE_SET_BY_MILITARY_PLUS_OFFSET_TO_UTC) { if (TimeTokenComponentCount < 2) { InvalidTokenFormatError = true; } } // if we did not have an invalid format error if (!InvalidTokenFormatError) { // now we begin to build a base universal time DateTime dtUniversalTime = new DateTime(wrkDt[YEAR_INDEX], wrkDt[MONTH_INDEX], wrkDt[DAY_INDEX], wrkDt[HOUR_INDEX], wrkDt[MINUTE_INDEX], wrkDt[SECOND_INDEX], DateTimeKind.Utc); // now, if we got the timezone offset using a 3 character timezone specification, like EDT, PDT, MDT, CDT then // we need to check if daylight savings time is in effect if (tzInfo != null) { // verify timezone set by the timezone info structure if (TimeZoneSetByCode == TIMEZONE_SET_BY_TIMEZONE_INFO) { // if the timezone uses daylight savings time if (TimeZoneUsesDaylightSavingsTime) { // recalculate offset to universal time tsUtcOffset = tzInfo.GetUtcOffset(dtUniversalTime); } } } // figure out plus, minus sign string plusMinusSign = "-"; if (tsUtcOffset.Ticks > 0) { plusMinusSign = "+"; } else if (tsUtcOffset.Ticks == 0) { plusMinusSign = ""; } // AddToDatePart List AddToDatePartList(DATEPART_TIMEZONE_OFFSET, plusMinusSign + tsUtcOffset.ToString("hh") + ":" + tsUtcOffset.ToString("mm")); // now, negate the time span in order to calculate the universal time TimeSpan tsAdd = new TimeSpan(tsUtcOffset.Ticks * -1); // now we add the timespan to get to the true universal time dtUniversalTime = dtUniversalTime.Add(tsAdd); // set the utc result rtUtc = dtUniversalTime; // convert to local time and set the return rt = dtUniversalTime.ToLocalTime(); } // end if not invalid token // return the local time return rt; } /// ************************************************************************* /// ************************************************************************* /// /// This formats any DateTime to an RFC822 compliant string /// /// DateTime ///string /// ************************************************************************* /// ************************************************************************* public string FormatDateTime(DateTime dt) { return dt.ToString("ddd, dd MMM yyyy HH:mm:ss") + " " + dt.ToString("zzz").Replace(":",""); } ////// This is a helper routine to add items to the datepart array /// /// Key /// Value private void AddToDatePartList(string key, string v) { KeyValuePairpart = new KeyValuePair (key, v); KeyValuePair kvp = arrDateParts.FirstOrDefault(x => x.Key == part.Key); if (kvp.Equals(default(KeyValuePair ))) { arrDateParts.Add(part); } else { kvp = part; } } } }
Conclusion
A project demonstrating the class use can be found on my GitHub.