J
Jon Davis
I have put my users through so much crap with this bug it is an absolute
shame.
I have a product that reads/writes RSS 2.0 documents, among other things.
The RSS 2.0 spec mandates an en-US style of date formatting (RFC 822). I
have been using a variation of RFC 1123 (just change the time zone to an
offset, i.e. "-0800"). It seems to be writing okay, but it's failing to
parse.
I've tried changing the regional & language settings in my Windows XP
control panel so that I could test, but after getting it to work that way, a
guy from Pakistan (I think?) is still getting date parsing problems.
Here's what I've got. See ParseDateTime() below.
/// <summary>
/// Currently, this method simply calls DateToRFC1123String(),
/// but then changes the time zone to "+/-####", i.e. "-0700".
/// </summary>
public static string DateToRFC822String(DateTime dt, bool
fromLocalTimeZone) {
string dts = DateToRFC1123String(dt, false);
if (fromLocalTimeZone) {
string offset = MiscUtil.SysDTOffset.ToString();
offset = offset.Replace(":", "").Replace(" ", "");
if (offset.Substring(0, 1) != "-") {
if (offset.Substring(0, 1) == "+") {
offset = offset.Substring(0, 5);
} else {
offset = "+" + offset.Substring(0, 4);
}
} else {
offset = offset.Substring(0, 5);
}
dts = dts.Replace("GMT", offset);
}
return dts;
}
public static TimeSpan SysDTOffset {
get {
return System.TimeZone.CurrentTimeZone.GetUtcOffset(DateTime.Now);
}
}
/// <summary>
/// Converts a time zone (e.g. "PST") to an offset string (e.g. "-0700").
/// </summary>
/// <param name="tz">The time zone to convert.</param>
/// <returns>The offset string (e.g. "-0700").</returns>
public static string TimeZoneToOffset(string tz) {
tz = tz.ToUpper().Trim()
.Replace("PACIFIC", "PST")
.Replace("MOUNTAIN", "MST")
.Replace("CENTRAL", "CST")
.Replace("EASTERN", "EST");
for (int i=0; i<TimeZones.Length; i++) {
if (((string)((string[])TimeZones.GetValue(i)).GetValue(0)) == tz) {
return ((string)((string[])TimeZones.GetValue(i)).GetValue(1));
}
}
return
System.TimeZone.CurrentTimeZone.GetUtcOffset(DateTime.Now).ToString()
.Replace(":", "").Substring(0, 5);
}
public static string OffsetToTimeZone(string offset) {
foreach (string[] tz in TimeZones) {
if (((string)tz.GetValue(1)) == offset) {
return (string)tz.GetValue(0);
}
}
return "GMT";
}
public static string OffsetToTimeZone(string offset, bool
isDaylightSavings) {
foreach (string[] tz in TimeZones) {
if (((string)tz.GetValue(1)) == offset) {
string tzs = (string)tz.GetValue(0);
if (isDaylightSavings) {
switch (tzs) {
case "PDT":
return tzs;
case "MDT":
return tzs;
case "CDT":
return tzs;
case "EDT":
return tzs;
case "MST":
return "PDT";
case "CST":
return "MDT";
case "EST":
return "CDT";
default:
return tzs;
}
} else {
return tzs;
}
//return;
//return (string)tz.GetValue(0);
}
}
return "GMT";
}
public static string DateToRFC1123String(DateTime dt, bool
fromLocalTimeZone) {
if (fromLocalTimeZone) {
dt = dt.ToUniversalTime();
}
System.Globalization.CultureInfo ci = new
System.Globalization.CultureInfo("en-US", false);
string ret = dt.ToString(ci.DateTimeFormat.RFC1123Pattern, ci);
return ret;
}
/// <summary>
/// Parses dates with time zones in the following formats:
/// "Thu, 17 Jul 2003 12:35:18 PST",
/// "Thu, 17 Jul 2003 12:35:18 -0700".
/// Converts the time to the local time zone.
/// </summary>
/// <param name="dateTime">The date/time to parse.</param>
/// <returns>A DateTime object</returns>
public static DateTime ParseDateTime(string dateTime) {
System.Globalization.CultureInfo ci = null;
try {
ci = new System.Globalization.CultureInfo("en-US", false);
return DateTime.Parse(dateTime,
ci,
System.Globalization.DateTimeStyles.AllowWhiteSpaces);
} catch (FormatException fex0) {
try {
fex0=fex0; // ignore
return DateTime.Parse(dateTime,
System.Globalization.DateTimeFormatInfo.InvariantInfo,
System.Globalization.DateTimeStyles.AllowWhiteSpaces);
} catch (FormatException fex) {
try {
try {
string iso8601_date = dateTime;
ci = new System.Globalization.CultureInfo("en-US", false);
return DateTime.ParseExact(
iso8601_date,
ci.DateTimeFormat.SortableDateTimePattern,
ci.DateTimeFormat);
} catch {}
string loc = fex.Message.Substring(fex.Message.LastIndexOf(" "));
loc = loc.Substring(0, loc.LastIndexOf("."));
string tz = "";
if (loc.Trim() == "DateTime") {
tz = dateTime.Substring(dateTime.LastIndexOf(" ")).Trim();
dateTime = dateTime.Substring(0, dateTime.Length - tz.Length);
} else {
try {
int iLoc = int.Parse(loc);
tz = dateTime.Substring(iLoc);
tz = TimeZoneToOffset(tz);
dateTime = dateTime.Substring(0, iLoc);
} catch {
}
}
ci = new System.Globalization.CultureInfo("en-US", false);
DateTime ret = DateTime.Parse(dateTime,
ci,
System.Globalization.DateTimeStyles.AllowWhiteSpaces);
// offset for time zone
if (tz.Length > 0) {
try {
if (tz.Length == 4 && tz.Substring(0, 1) != "-") {
try {
int.Parse(tz.Substring(0, 1));
tz = "+" + tz;
} catch {
}
}
if (tz.Length == 5 && tz.Substring(0, 1) == "-" ||
tz.Length == 5 && tz.Substring(0, 1) == "+") {
try {
int h = int.Parse(tz.Substring(1, 2));
int m = int.Parse(tz.Substring(3, 2));
if (tz.Substring(0, 1) == "-") {
ret = ret.AddHours((h * -1) - SysDTOffset.Hours);
ret = ret.AddMinutes((m * -1) - SysDTOffset.Minutes);
} else {
ret = ret.AddHours(h - SysDTOffset.Hours);
ret = ret.AddMinutes(m - SysDTOffset.Minutes);
}
} catch {
}
}
} catch {}
}
return ret;
} catch {
return new DateTime(0);
}
}
}
}
/// <summary>
/// An array of time zones
/// (e.g. new string[] {"PST", "-0700", "(US) Pacific Standard"}).
/// </summary>
public static string[][] TimeZones = new string[][] {
new string[] {"ACDT", "+1030", "Australian Central Daylight"},
new string[] {"ACST", "+0930", "Australian Central Standard"},
new string[] {"ADT", "-0300", "(US) Atlantic Daylight"},
new string[] {"AEDT", "+1100", "Australian East Daylight"},
new string[] {"AEST", "+1000", "Australian East Standard"},
new string[] {"AHDT", "-0900", "AHDT"},
new string[] {"AHST", "-1000", "AHST"},
new string[] {"AST", "-0400", "(US) Atlantic Standard"},
new string[] {"AT", "-0200", "Azores"},
new string[] {"AWDT", "+0900", "Australian West Daylight"},
new string[] {"AWST", "+0800", "Australian West Standard"},
new string[] {"BAT", "+0300", "Bhagdad"},
new string[] {"BDST", "+0200", "British Double Summer"},
new string[] {"BET", "-1100", "Bering Standard"},
new string[] {"BST", "-0300", "Brazil Standard"},
new string[] {"BT", "+0300", "Baghdad"},
new string[] {"BZT2", "-0300", "Brazil Zone 2"},
new string[] {"CADT", "+1030", "Central Australian Daylight"},
new string[] {"CAST", "+0930", "Central Australian Standard"},
new string[] {"CAT", "-1000", "Central Alaska"},
new string[] {"CCT", "+0800", "China Coast"},
new string[] {"CDT", "-0500", "(US) Central Daylight"},
new string[] {"CED", "+0200", "Central European Daylight"},
new string[] {"CET", "+0100", "Central European"},
new string[] {"CST", "-0600", "(US) Central Standard"},
new string[] {"EAST", "+1000", "Eastern Australian Standard"},
new string[] {"EDT", "-0400", "(US) Eastern Daylight"},
new string[] {"EED", "+0300", "Eastern European Daylight"},
new string[] {"EET", "+0200", "Eastern Europe"},
new string[] {"EEST", "+0300", "Eastern Europe Summer"},
new string[] {"EST", "-0500", "(US) Eastern Standard"},
new string[] {"FST", "+0200", "French Summer"},
new string[] {"FWT", "+0100", "French Winter"},
new string[] {"GMT", "-0000", "Greenwich Mean"},
new string[] {"GST", "+1000", "Guam Standard"},
new string[] {"HDT", "-0900", "Hawaii Daylight"},
new string[] {"HST", "-1000", "Hawaii Standard"},
new string[] {"IDLE", "+1200", "Internation Date Line East"},
new string[] {"IDLW", "-1200", "Internation Date Line West"},
new string[] {"IST", "+0530", "Indian Standard"},
new string[] {"IT", "+0330", "Iran"},
new string[] {"JST", "+0900", "Japan Standard"},
new string[] {"JT", "+0700", "Java"},
new string[] {"MDT", "-0600", "(US) Mountain Daylight"},
new string[] {"MED", "+0200", "Middle European Daylight"},
new string[] {"MET", "+0100", "Middle European"},
new string[] {"MEST", "+0200", "Middle European Summer"},
new string[] {"MEWT", "+0100", "Middle European Winter"},
new string[] {"MST", "-0700", "(US) Mountain Standard"},
new string[] {"MT", "+0800", "Moluccas"},
new string[] {"NDT", "-0230", "Newfoundland Daylight"},
new string[] {"NFT", "-0330", "Newfoundland"},
new string[] {"NT", "-1100", "Nome"},
new string[] {"NST", "+0630", "North Sumatra"},
new string[] {"NZ", "+1100", "New Zealand "},
new string[] {"NZST", "+1200", "New Zealand Standard"},
new string[] {"NZDT", "+1300", "New Zealand Daylight"},
new string[] {"NZT", "+1200", "New Zealand"},
new string[] {"PDT", "-0700", "(US) Pacific Daylight"},
new string[] {"PST", "-0800", "(US) Pacific Standard"},
new string[] {"ROK", "+0900", "Republic of Korea"},
new string[] {"SAD", "+1000", "South Australia Daylight"},
new string[] {"SAST", "+0900", "South Australia Standard"},
new string[] {"SAT", "+0900", "South Australia Standard"},
new string[] {"SDT", "+1000", "South Australia Daylight"},
new string[] {"SST", "+0200", "Swedish Summer"},
new string[] {"SWT", "+0100", "Swedish Winter"},
new string[] {"USZ3", "+0400", "USSR Zone 3"},
new string[] {"USZ4", "+0500", "USSR Zone 4"},
new string[] {"USZ5", "+0600", "USSR Zone 5"},
new string[] {"USZ6", "+0700", "USSR Zone 6"},
new string[] {"UT", "-0000", "Universal Coordinated"},
new string[] {"UTC", "-0000", "Universal Coordinated"},
new string[] {"UZ10", "+1100", "USSR Zone 10"},
new string[] {"WAT", "-0100", "West Africa"},
new string[] {"WET", "-0000", "West European"},
new string[] {"WST", "+0800", "West Australian Standard"},
new string[] {"YDT", "-0800", "Yukon Daylight"},
new string[] {"YST", "-0900", "Yukon Standard"},
new string[] {"ZP4", "+0400", "USSR Zone 3"},
new string[] {"ZP5", "+0500", "USSR Zone 4"},
new string[] {"ZP6", "+0600", "USSR Zone 5"}
};
shame.
I have a product that reads/writes RSS 2.0 documents, among other things.
The RSS 2.0 spec mandates an en-US style of date formatting (RFC 822). I
have been using a variation of RFC 1123 (just change the time zone to an
offset, i.e. "-0800"). It seems to be writing okay, but it's failing to
parse.
I've tried changing the regional & language settings in my Windows XP
control panel so that I could test, but after getting it to work that way, a
guy from Pakistan (I think?) is still getting date parsing problems.
Here's what I've got. See ParseDateTime() below.
/// <summary>
/// Currently, this method simply calls DateToRFC1123String(),
/// but then changes the time zone to "+/-####", i.e. "-0700".
/// </summary>
public static string DateToRFC822String(DateTime dt, bool
fromLocalTimeZone) {
string dts = DateToRFC1123String(dt, false);
if (fromLocalTimeZone) {
string offset = MiscUtil.SysDTOffset.ToString();
offset = offset.Replace(":", "").Replace(" ", "");
if (offset.Substring(0, 1) != "-") {
if (offset.Substring(0, 1) == "+") {
offset = offset.Substring(0, 5);
} else {
offset = "+" + offset.Substring(0, 4);
}
} else {
offset = offset.Substring(0, 5);
}
dts = dts.Replace("GMT", offset);
}
return dts;
}
public static TimeSpan SysDTOffset {
get {
return System.TimeZone.CurrentTimeZone.GetUtcOffset(DateTime.Now);
}
}
/// <summary>
/// Converts a time zone (e.g. "PST") to an offset string (e.g. "-0700").
/// </summary>
/// <param name="tz">The time zone to convert.</param>
/// <returns>The offset string (e.g. "-0700").</returns>
public static string TimeZoneToOffset(string tz) {
tz = tz.ToUpper().Trim()
.Replace("PACIFIC", "PST")
.Replace("MOUNTAIN", "MST")
.Replace("CENTRAL", "CST")
.Replace("EASTERN", "EST");
for (int i=0; i<TimeZones.Length; i++) {
if (((string)((string[])TimeZones.GetValue(i)).GetValue(0)) == tz) {
return ((string)((string[])TimeZones.GetValue(i)).GetValue(1));
}
}
return
System.TimeZone.CurrentTimeZone.GetUtcOffset(DateTime.Now).ToString()
.Replace(":", "").Substring(0, 5);
}
public static string OffsetToTimeZone(string offset) {
foreach (string[] tz in TimeZones) {
if (((string)tz.GetValue(1)) == offset) {
return (string)tz.GetValue(0);
}
}
return "GMT";
}
public static string OffsetToTimeZone(string offset, bool
isDaylightSavings) {
foreach (string[] tz in TimeZones) {
if (((string)tz.GetValue(1)) == offset) {
string tzs = (string)tz.GetValue(0);
if (isDaylightSavings) {
switch (tzs) {
case "PDT":
return tzs;
case "MDT":
return tzs;
case "CDT":
return tzs;
case "EDT":
return tzs;
case "MST":
return "PDT";
case "CST":
return "MDT";
case "EST":
return "CDT";
default:
return tzs;
}
} else {
return tzs;
}
//return;
//return (string)tz.GetValue(0);
}
}
return "GMT";
}
public static string DateToRFC1123String(DateTime dt, bool
fromLocalTimeZone) {
if (fromLocalTimeZone) {
dt = dt.ToUniversalTime();
}
System.Globalization.CultureInfo ci = new
System.Globalization.CultureInfo("en-US", false);
string ret = dt.ToString(ci.DateTimeFormat.RFC1123Pattern, ci);
return ret;
}
/// <summary>
/// Parses dates with time zones in the following formats:
/// "Thu, 17 Jul 2003 12:35:18 PST",
/// "Thu, 17 Jul 2003 12:35:18 -0700".
/// Converts the time to the local time zone.
/// </summary>
/// <param name="dateTime">The date/time to parse.</param>
/// <returns>A DateTime object</returns>
public static DateTime ParseDateTime(string dateTime) {
System.Globalization.CultureInfo ci = null;
try {
ci = new System.Globalization.CultureInfo("en-US", false);
return DateTime.Parse(dateTime,
ci,
System.Globalization.DateTimeStyles.AllowWhiteSpaces);
} catch (FormatException fex0) {
try {
fex0=fex0; // ignore
return DateTime.Parse(dateTime,
System.Globalization.DateTimeFormatInfo.InvariantInfo,
System.Globalization.DateTimeStyles.AllowWhiteSpaces);
} catch (FormatException fex) {
try {
try {
string iso8601_date = dateTime;
ci = new System.Globalization.CultureInfo("en-US", false);
return DateTime.ParseExact(
iso8601_date,
ci.DateTimeFormat.SortableDateTimePattern,
ci.DateTimeFormat);
} catch {}
string loc = fex.Message.Substring(fex.Message.LastIndexOf(" "));
loc = loc.Substring(0, loc.LastIndexOf("."));
string tz = "";
if (loc.Trim() == "DateTime") {
tz = dateTime.Substring(dateTime.LastIndexOf(" ")).Trim();
dateTime = dateTime.Substring(0, dateTime.Length - tz.Length);
} else {
try {
int iLoc = int.Parse(loc);
tz = dateTime.Substring(iLoc);
tz = TimeZoneToOffset(tz);
dateTime = dateTime.Substring(0, iLoc);
} catch {
}
}
ci = new System.Globalization.CultureInfo("en-US", false);
DateTime ret = DateTime.Parse(dateTime,
ci,
System.Globalization.DateTimeStyles.AllowWhiteSpaces);
// offset for time zone
if (tz.Length > 0) {
try {
if (tz.Length == 4 && tz.Substring(0, 1) != "-") {
try {
int.Parse(tz.Substring(0, 1));
tz = "+" + tz;
} catch {
}
}
if (tz.Length == 5 && tz.Substring(0, 1) == "-" ||
tz.Length == 5 && tz.Substring(0, 1) == "+") {
try {
int h = int.Parse(tz.Substring(1, 2));
int m = int.Parse(tz.Substring(3, 2));
if (tz.Substring(0, 1) == "-") {
ret = ret.AddHours((h * -1) - SysDTOffset.Hours);
ret = ret.AddMinutes((m * -1) - SysDTOffset.Minutes);
} else {
ret = ret.AddHours(h - SysDTOffset.Hours);
ret = ret.AddMinutes(m - SysDTOffset.Minutes);
}
} catch {
}
}
} catch {}
}
return ret;
} catch {
return new DateTime(0);
}
}
}
}
/// <summary>
/// An array of time zones
/// (e.g. new string[] {"PST", "-0700", "(US) Pacific Standard"}).
/// </summary>
public static string[][] TimeZones = new string[][] {
new string[] {"ACDT", "+1030", "Australian Central Daylight"},
new string[] {"ACST", "+0930", "Australian Central Standard"},
new string[] {"ADT", "-0300", "(US) Atlantic Daylight"},
new string[] {"AEDT", "+1100", "Australian East Daylight"},
new string[] {"AEST", "+1000", "Australian East Standard"},
new string[] {"AHDT", "-0900", "AHDT"},
new string[] {"AHST", "-1000", "AHST"},
new string[] {"AST", "-0400", "(US) Atlantic Standard"},
new string[] {"AT", "-0200", "Azores"},
new string[] {"AWDT", "+0900", "Australian West Daylight"},
new string[] {"AWST", "+0800", "Australian West Standard"},
new string[] {"BAT", "+0300", "Bhagdad"},
new string[] {"BDST", "+0200", "British Double Summer"},
new string[] {"BET", "-1100", "Bering Standard"},
new string[] {"BST", "-0300", "Brazil Standard"},
new string[] {"BT", "+0300", "Baghdad"},
new string[] {"BZT2", "-0300", "Brazil Zone 2"},
new string[] {"CADT", "+1030", "Central Australian Daylight"},
new string[] {"CAST", "+0930", "Central Australian Standard"},
new string[] {"CAT", "-1000", "Central Alaska"},
new string[] {"CCT", "+0800", "China Coast"},
new string[] {"CDT", "-0500", "(US) Central Daylight"},
new string[] {"CED", "+0200", "Central European Daylight"},
new string[] {"CET", "+0100", "Central European"},
new string[] {"CST", "-0600", "(US) Central Standard"},
new string[] {"EAST", "+1000", "Eastern Australian Standard"},
new string[] {"EDT", "-0400", "(US) Eastern Daylight"},
new string[] {"EED", "+0300", "Eastern European Daylight"},
new string[] {"EET", "+0200", "Eastern Europe"},
new string[] {"EEST", "+0300", "Eastern Europe Summer"},
new string[] {"EST", "-0500", "(US) Eastern Standard"},
new string[] {"FST", "+0200", "French Summer"},
new string[] {"FWT", "+0100", "French Winter"},
new string[] {"GMT", "-0000", "Greenwich Mean"},
new string[] {"GST", "+1000", "Guam Standard"},
new string[] {"HDT", "-0900", "Hawaii Daylight"},
new string[] {"HST", "-1000", "Hawaii Standard"},
new string[] {"IDLE", "+1200", "Internation Date Line East"},
new string[] {"IDLW", "-1200", "Internation Date Line West"},
new string[] {"IST", "+0530", "Indian Standard"},
new string[] {"IT", "+0330", "Iran"},
new string[] {"JST", "+0900", "Japan Standard"},
new string[] {"JT", "+0700", "Java"},
new string[] {"MDT", "-0600", "(US) Mountain Daylight"},
new string[] {"MED", "+0200", "Middle European Daylight"},
new string[] {"MET", "+0100", "Middle European"},
new string[] {"MEST", "+0200", "Middle European Summer"},
new string[] {"MEWT", "+0100", "Middle European Winter"},
new string[] {"MST", "-0700", "(US) Mountain Standard"},
new string[] {"MT", "+0800", "Moluccas"},
new string[] {"NDT", "-0230", "Newfoundland Daylight"},
new string[] {"NFT", "-0330", "Newfoundland"},
new string[] {"NT", "-1100", "Nome"},
new string[] {"NST", "+0630", "North Sumatra"},
new string[] {"NZ", "+1100", "New Zealand "},
new string[] {"NZST", "+1200", "New Zealand Standard"},
new string[] {"NZDT", "+1300", "New Zealand Daylight"},
new string[] {"NZT", "+1200", "New Zealand"},
new string[] {"PDT", "-0700", "(US) Pacific Daylight"},
new string[] {"PST", "-0800", "(US) Pacific Standard"},
new string[] {"ROK", "+0900", "Republic of Korea"},
new string[] {"SAD", "+1000", "South Australia Daylight"},
new string[] {"SAST", "+0900", "South Australia Standard"},
new string[] {"SAT", "+0900", "South Australia Standard"},
new string[] {"SDT", "+1000", "South Australia Daylight"},
new string[] {"SST", "+0200", "Swedish Summer"},
new string[] {"SWT", "+0100", "Swedish Winter"},
new string[] {"USZ3", "+0400", "USSR Zone 3"},
new string[] {"USZ4", "+0500", "USSR Zone 4"},
new string[] {"USZ5", "+0600", "USSR Zone 5"},
new string[] {"USZ6", "+0700", "USSR Zone 6"},
new string[] {"UT", "-0000", "Universal Coordinated"},
new string[] {"UTC", "-0000", "Universal Coordinated"},
new string[] {"UZ10", "+1100", "USSR Zone 10"},
new string[] {"WAT", "-0100", "West Africa"},
new string[] {"WET", "-0000", "West European"},
new string[] {"WST", "+0800", "West Australian Standard"},
new string[] {"YDT", "-0800", "Yukon Daylight"},
new string[] {"YST", "-0900", "Yukon Standard"},
new string[] {"ZP4", "+0400", "USSR Zone 3"},
new string[] {"ZP5", "+0500", "USSR Zone 4"},
new string[] {"ZP6", "+0600", "USSR Zone 5"}
};