Code:
/ 4.0 / 4.0 / untmp / DEVDIV_TFS / Dev10 / Releases / RTMRel / ndp / fx / src / Net / System / Net / Mail / SmtpDateTime.cs / 1305376 / SmtpDateTime.cs
//------------------------------------------------------------------------------ //// Copyright (c) Microsoft Corporation. All rights reserved. // //----------------------------------------------------------------------------- namespace System.Net.Mime { using System; using System.Net.Mail; using System.Globalization; using System.Collections.Generic; using System.Diagnostics; #region RFC2822 date time string format description // Format of Date Time string as described by RFC 2822 section 4.3 which obsoletes // some field formats that were allowed under RFC 822 // date-time = [ day-of-week "," ] date FWS time [CFWS] // day-of-week = ([FWS] day-name) / obs-day-of-week // day-name = "Mon" / "Tue" / "Wed" / "Thu" / "Fri" / "Sat" / "Sun" // date = day month year // year = 4*DIGIT / obs-year // month = (FWS month-name FWS) / obs-month // month-name = "Jan" / "Feb" / "Mar" / "Apr" / "May" / "Jun" / "Jul" / "Aug" / // "Sep" / "Oct" / "Nov" / "Dec" // day = ([FWS] 1*2DIGIT) / obs-day // time = time-of-day FWS zone // time-of-day = hour ":" minute [ ":" second ] // hour = 2DIGIT / obs-hour // minute = 2DIGIT / obs-minute // second = 2DIGIT / obs-second // zone = (( "+" / "-" ) 4DIGIT) / obs-zone #endregion // stores a Date and a Time Zone. These are parsed and formatted according to the // rules in RFC 2822 section 3.3. // This class is immutable internal class SmtpDateTime { #region constants // use this when a time zone is unknown or is not supplied internal const string unknownTimeZoneDefaultOffset = "-0000"; internal const string utcDefaultTimeZoneOffset = "+0000"; internal const int offsetLength = 5; // range for absolute value of minutes. it is not necessary to include a max value for hours since // the two-digit value that is parsed can't exceed the max value of hours, which is 99 internal const int maxMinuteValue = 59; // possible valid values for a date string // these do NOT include the timezone internal const string dateFormatWithDayOfWeek = "ddd, dd MMM yyyy HH:mm:ss"; internal const string dateFormatWithoutDayOfWeek = "dd MMM yyyy HH:mm:ss"; internal const string dateFormatWithDayOfWeekAndNoSeconds = "ddd, dd MMM yyyy HH:mm"; internal const string dateFormatWithoutDayOfWeekAndNoSeconds = "dd MMM yyyy HH:mm"; #endregion #region static fields // array of all possible date time values // if a string matches any one of these it will be parsed correctly internal readonly static string[] validDateTimeFormats = new string[]{ dateFormatWithDayOfWeek, dateFormatWithoutDayOfWeek, dateFormatWithDayOfWeekAndNoSeconds, dateFormatWithoutDayOfWeekAndNoSeconds }; internal readonly static char[] allowedWhiteSpaceChars = new char[] { ' ', '\t' }; internal static IDictionarytimeZoneOffsetLookup = SmtpDateTime.InitializeShortHandLookups(); // a TimeSpan must be between these two values in order for it to be within the range allowed // by RFC 2822 internal readonly static long timeSpanMaxTicks = TimeSpan.TicksPerHour * 99 + TimeSpan.TicksPerMinute * 59; // allowed max values for each digit. min value is always 0 internal readonly static int offsetMaxValue = 9959; #endregion #region static initializers internal static IDictionary InitializeShortHandLookups() { timeZoneOffsetLookup = new Dictionary (); // all well-known short hand time zone values and their semantic equivalents timeZoneOffsetLookup.Add("UT", TimeSpan.Zero); // +0000 timeZoneOffsetLookup.Add("GMT", TimeSpan.Zero); // +0000 timeZoneOffsetLookup.Add("EDT", new TimeSpan(-4, 0, 0)); // -0400 timeZoneOffsetLookup.Add("EST", new TimeSpan(-5, 0, 0)); // -0500 timeZoneOffsetLookup.Add("CDT", new TimeSpan(-5, 0, 0)); // -0500 timeZoneOffsetLookup.Add("CST", new TimeSpan(-6, 0, 0)); // -0600 timeZoneOffsetLookup.Add("MDT", new TimeSpan(-6, 0, 0)); // -0600 timeZoneOffsetLookup.Add("MST", new TimeSpan(-7, 0, 0)); // -0700 timeZoneOffsetLookup.Add("PDT", new TimeSpan(-7, 0, 0)); // -0700 timeZoneOffsetLookup.Add("PST", new TimeSpan(-8, 0, 0)); // -0800 return timeZoneOffsetLookup; } #endregion #region private fields private readonly DateTime date; private readonly TimeSpan timeZone; // true if the time zone is unspecified i.e. -0000 // the time zone will usually be specified private readonly bool unknownTimeZone = false; #endregion #region constructors internal SmtpDateTime(DateTime value) { date = value; switch (value.Kind) { case DateTimeKind.Local: // GetUtcOffset takes local time zone information into account e.g. daylight savings time TimeSpan localTimeZone = TimeZoneInfo.Local.GetUtcOffset(value); this.timeZone = ValidateAndGetSanitizedTimeSpan(localTimeZone); break; case DateTimeKind.Unspecified: this.unknownTimeZone = true; break; case DateTimeKind.Utc: this.timeZone = TimeSpan.Zero; break; } } internal SmtpDateTime(string value) { string timeZoneOffset; this.date = ParseValue(value, out timeZoneOffset); if (!TryParseTimeZoneString(timeZoneOffset, out timeZone)) { // time zone is unknown this.unknownTimeZone = true; } } #endregion #region internal properties internal DateTime Date { get { if (this.unknownTimeZone) { return DateTime.SpecifyKind(this.date, DateTimeKind.Unspecified); } else { // DateTimeOffset will convert the value of this.date to the time as // specified in this.timeZone DateTimeOffset offset = new DateTimeOffset(this.date, this.timeZone); return offset.LocalDateTime; } } } #if DEBUG // this method is only called by test code internal string TimeZone { get { if (this.unknownTimeZone) { return unknownTimeZoneDefaultOffset; } return TimeSpanToOffset(this.timeZone); } } #endif #endregion #region internals // outputs the RFC 2822 formatted date string including time zone public override string ToString() { if (unknownTimeZone) { return String.Format("{0} {1}", FormatDate(this.date), unknownTimeZoneDefaultOffset); } else { return String.Format("{0} {1}", FormatDate(this.date), TimeSpanToOffset(this.timeZone)); } } // returns true if the offset is of the form [+|-]dddd and // within the range 0000 to 9959 internal void ValidateAndGetTimeZoneOffsetValues( string offset, out bool positive, out int hours, out int minutes) { Debug.Assert(!String.IsNullOrEmpty(offset), "violation of precondition: offset must not be null or empty"); Debug.Assert(offset != unknownTimeZoneDefaultOffset, "Violation of precondition: do not pass an unknown offset"); Debug.Assert(offset.StartsWith("-") || offset.StartsWith("+"), "offset initial character was not a + or -"); if (offset.Length != offsetLength) { throw new FormatException(SR.GetString(SR.MailDateInvalidFormat)); } positive = offset.StartsWith("+"); // TryParse will parse in base 10 by default. do not allow any styles of input beyond the default // which is numeric values only if (!Int32.TryParse(offset.Substring(1, 2), NumberStyles.None, CultureInfo.InvariantCulture, out hours)) { throw new FormatException(SR.GetString(SR.MailDateInvalidFormat)); } if (!Int32.TryParse(offset.Substring(3, 2), NumberStyles.None, CultureInfo.InvariantCulture, out minutes)) { throw new FormatException(SR.GetString(SR.MailDateInvalidFormat)); } // we only explicitly validate the minutes. they must be below 59 // the hours are implicitly validated as a number formed from a string of length // 2 can only be <= 99 if (minutes > maxMinuteValue) { throw new FormatException(SR.GetString(SR.MailDateInvalidFormat)); } } // returns true if the time zone short hand is all alphabetical characters internal void ValidateTimeZoneShortHandValue(string value) { // time zones can't be empty Debug.Assert(!String.IsNullOrEmpty(value), "violation of precondition: offset must not be null or empty"); // time zones must all be alphabetical characters for (int i = 0; i < value.Length; i++) { if (!Char.IsLetter(value, i)) { throw new FormatException(SR.GetString(SR.MailHeaderFieldInvalidCharacter)); } } } // formats a date only. Does not include time zone internal string FormatDate(DateTime value) { string output = value.ToString("ddd, dd MMM yyyy H:mm:ss"); return output; } // parses the date and time zone // postconditions: // return value is valid DateTime representation of the Date portion of data // timeZone is the portion of data which should contain the time zone data // timeZone is NOT evaluated by ParseValue internal DateTime ParseValue(string data, out string timeZone) { // check that there is something to parse if (string.IsNullOrEmpty(data)) { throw new FormatException(SR.GetString(SR.MailDateInvalidFormat)); } // find the first occurrence of ':' // this tells us where the separator between hour and minute are int indexOfHourSeparator = data.IndexOf(':'); // no ':' means invalid value if (indexOfHourSeparator == -1) { throw new FormatException(SR.GetString(SR.MailHeaderFieldInvalidCharacter)); } // now we know where hours and minutes are separated. The first whitespace after // that MUST be the separator between the time portion and the timezone portion // timezone may have additional spaces, characters, or comments after it but // this is ok since we'll parse that whole section later int indexOfTimeZoneSeparator = data.IndexOfAny(allowedWhiteSpaceChars, indexOfHourSeparator); if (indexOfTimeZoneSeparator == -1) { throw new FormatException(SR.GetString(SR.MailHeaderFieldInvalidCharacter)); } // extract the time portion and remove all leading and trailing whitespace string date = data.Substring(0, indexOfTimeZoneSeparator).Trim(); // attempt to parse the DateTime component. DateTime dateValue; if (!DateTime.TryParseExact(date, validDateTimeFormats, CultureInfo.InvariantCulture, DateTimeStyles.AllowWhiteSpaces, out dateValue)) { throw new FormatException(SR.GetString(SR.MailDateInvalidFormat)); } // kind property will be Unspecified since no timezone info was in the date string Debug.Assert(dateValue.Kind == DateTimeKind.Unspecified); // extract the second half of the string. This will start with at least one whitespace character. // Trim the string to remove these characters. string timeZoneString = data.Substring(indexOfTimeZoneSeparator).Trim(); // find, if any, the first whitespace character after the timezone. // These will be CFWS and must be ignored. Remove them. int endOfTimeZoneOffset = timeZoneString.IndexOfAny(allowedWhiteSpaceChars); if (endOfTimeZoneOffset != -1) { timeZoneString = timeZoneString.Substring(0, endOfTimeZoneOffset); } if (String.IsNullOrEmpty(timeZoneString)) { throw new FormatException(SR.GetString(SR.MailDateInvalidFormat)); } timeZone = timeZoneString; return dateValue; } // if this returns true, timeZone is the correct TimeSpan representation of the input // if it returns false then the time zone is unknown and so timeZone must be ignored internal bool TryParseTimeZoneString(string timeZoneString, out TimeSpan timeZone) { // initialize default timeZone = TimeSpan.Zero; // see if the zone is the special unspecified case, a numeric offset, or a shorthand string if (timeZoneString == unknownTimeZoneDefaultOffset) { // The inputed time zone is the special value "unknown", -0000 return false; } else if ((timeZoneString[0] == '+' || timeZoneString[0] == '-')) { bool positive; int hours; int minutes; ValidateAndGetTimeZoneOffsetValues(timeZoneString, out positive, out hours, out minutes); // Apply the negative sign, if applicable, to whichever of hours or minutes is NOT 0. if (!positive) { if (hours != 0) { hours *= -1; } else if (minutes != 0) { minutes *= -1; } } timeZone = new TimeSpan((int) hours, (int) minutes, 0); return true; } else { // not an offset so ensure that it contains no invalid characters ValidateTimeZoneShortHandValue(timeZoneString); // check if the shorthand value has a semantically equivalent offset if (timeZoneOffsetLookup.ContainsKey(timeZoneString)) { timeZone = timeZoneOffsetLookup[timeZoneString]; return true; } } // default time zone is the unspecified zone: -0000 return false; } internal TimeSpan ValidateAndGetSanitizedTimeSpan(TimeSpan span) { // sanitize the time span by removing the seconds and milliseconds. Days are not handled here TimeSpan sanitizedTimeSpan = new TimeSpan(span.Days, span.Hours, span.Minutes, 0, 0); // validate range of time span if (Math.Abs(sanitizedTimeSpan.Ticks) > timeSpanMaxTicks) { throw new FormatException(SR.GetString(SR.MailDateInvalidFormat)); } return sanitizedTimeSpan; } // precondition: span must be sanitized and within a valid range internal string TimeSpanToOffset(TimeSpan span) { Debug.Assert(span.Seconds == 0, "Span had seconds value"); Debug.Assert(span.Milliseconds == 0, "Span had milliseconds value"); if (span.Ticks == 0) { return utcDefaultTimeZoneOffset; } else { string output; // get the total number of hours since TimeSpan.Hours won't go beyond 24 // ensure that it's a whole number since the fractional part represents minutes uint hours = (uint)Math.Abs(Math.Floor(span.TotalHours)); uint minutes = (uint)Math.Abs(span.Minutes); Debug.Assert((hours != 0) || (minutes !=0), "Input validation ensures hours or minutes isn't zero"); output = span.Ticks > 0 ? "+" : "-"; // hours and minutes must be two digits if (hours < 10) { output += "0"; } output += hours.ToString(); if (minutes < 10) { output += "0"; } output += minutes.ToString(); return output; } } #endregion } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007.
Link Menu
This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- RoleService.cs
- ColorMatrix.cs
- TextDecorationCollection.cs
- InstanceOwnerException.cs
- CompilationLock.cs
- SqlDataSourceParameterParser.cs
- DesignerDataRelationship.cs
- QueryResponse.cs
- Gdiplus.cs
- JsonCollectionDataContract.cs
- IdentitySection.cs
- Exception.cs
- TextFormatterContext.cs
- TdsParserHelperClasses.cs
- ProcessModuleCollection.cs
- CodeFieldReferenceExpression.cs
- BaseDataListComponentEditor.cs
- ContextMenuService.cs
- EventLogException.cs
- WindowPattern.cs
- SqlNotificationEventArgs.cs
- DescendantOverDescendantQuery.cs
- ControlPager.cs
- ConvertEvent.cs
- CustomGrammar.cs
- TiffBitmapDecoder.cs
- thaishape.cs
- InsufficientMemoryException.cs
- Container.cs
- NGCPageContentSerializerAsync.cs
- UrlAuthorizationModule.cs
- ColumnCollectionEditor.cs
- RangeBase.cs
- GAC.cs
- Cursor.cs
- ConfigurationStrings.cs
- Cursor.cs
- TreeNodeClickEventArgs.cs
- XamlReaderHelper.cs
- GenerateDerivedKeyRequest.cs
- TdsRecordBufferSetter.cs
- SchemaNames.cs
- WebPartConnectionCollection.cs
- UnmanagedMemoryStreamWrapper.cs
- Condition.cs
- InputQueueChannelAcceptor.cs
- CompiledXpathExpr.cs
- WebPartUserCapability.cs
- XmlConvert.cs
- PagedDataSource.cs
- DbConnectionStringCommon.cs
- KeyPressEvent.cs
- XmlMemberMapping.cs
- CompleteWizardStep.cs
- Utils.cs
- Single.cs
- ContextDataSourceView.cs
- Token.cs
- BitmapEditor.cs
- NativeMethodsCLR.cs
- CallSiteHelpers.cs
- RuntimeArgumentHandle.cs
- StackBuilderSink.cs
- SafeProcessHandle.cs
- ConfigurationManagerInternal.cs
- WaitHandleCannotBeOpenedException.cs
- SqlReorderer.cs
- UIElementHelper.cs
- ActiveXHost.cs
- SiteMapDesignerDataSourceView.cs
- CompressEmulationStream.cs
- SoapEnumAttribute.cs
- HtmlInputPassword.cs
- NestedContainer.cs
- BindingBase.cs
- SqlXml.cs
- ToolStripPanelCell.cs
- InlinedAggregationOperator.cs
- ManagementBaseObject.cs
- WebConfigurationFileMap.cs
- BigInt.cs
- StoreContentChangedEventArgs.cs
- WriteStateInfoBase.cs
- MbpInfo.cs
- ReturnType.cs
- ToolStripItemImageRenderEventArgs.cs
- ToolStripRendererSwitcher.cs
- BitStack.cs
- ButtonColumn.cs
- KeySpline.cs
- TextDecoration.cs
- XmlElementElement.cs
- DnsEndpointIdentity.cs
- Serializer.cs
- DetailsViewRowCollection.cs
- BuildProvider.cs
- UnknownWrapper.cs
- Serializer.cs
- CompModSwitches.cs
- EntityRecordInfo.cs