SmtpDateTime.cs source code in C# .NET

Source code for the .NET framework in C#

                        

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 IDictionary timeZoneOffsetLookup = 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

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK