The Labs \ Source Viewer \ SSCLI \ System \ DateTimeFormat

  1. // ==++==
  2. //
  3. //
  4. // Copyright (c) 2006 Microsoft Corporation. All rights reserved.
  5. //
  6. // The use and distribution terms for this software are contained in the file
  7. // named license.txt, which can be found in the root of this distribution.
  8. // By using this software in any fashion, you are agreeing to be bound by the
  9. // terms of this license.
  10. //
  11. // You must not remove this notice, or any other, from this software.
  12. //
  13. //
  14. // ==--==
  15. namespace System
  16. {
  17.     using System.Text;
  18.     using System.Threading;
  19.     using System.Globalization;
  20.     using ArrayList = System.Collections.ArrayList;
  21. /* 
  22.     Customized format patterns:
  23.     P.S. Format in the table below is the internal number format used to display the pattern.
  24.     Patterns  Format      Description                          Example
  25.     =========  ==========  ===================================== ========
  26.         "h"    "0"        hour (12-hour clock)w/o leading zero  3
  27.         "hh"    "00"        hour (12-hour clock)with leading zero 03
  28.         "hh*"  "00"        hour (12-hour clock)with leading zero 03
  29.         "H"    "0"        hour (24-hour clock)w/o leading zero  8
  30.         "HH"    "00"        hour (24-hour clock)with leading zero 08
  31.         "HH*"  "00"        hour (24-hour clock)                  08
  32.         "m"    "0"        minute w/o leading zero
  33.         "mm"    "00"        minute with leading zero
  34.         "mm*"  "00"        minute with leading zero
  35.         "s"    "0"        second w/o leading zero
  36.         "ss"    "00"        second with leading zero
  37.         "ss*"  "00"        second with leading zero
  38.         "f"    "0"        second fraction (1 digit)
  39.         "ff"    "00"        second fraction (2 digit)
  40.         "fff"  "000"      second fraction (3 digit)
  41.         "ffff"  "0000"      second fraction (4 digit)
  42.         "fffff" "00000"        second fraction (5 digit)
  43.         "ffffff"    "000000"    second fraction (6 digit)
  44.         "fffffff"  "0000000"  second fraction (7 digit)
  45.         "F"    "0"        second fraction (up to 1 digit)
  46.         "FF"    "00"        second fraction (up to 2 digit)
  47.         "FFF"  "000"      second fraction (up to 3 digit)
  48.         "FFFF"  "0000"      second fraction (up to 4 digit)
  49.         "FFFFF" "00000"        second fraction (up to 5 digit)
  50.         "FFFFFF"    "000000"    second fraction (up to 6 digit)
  51.         "FFFFFFF"  "0000000"  second fraction (up to 7 digit)
  52.         "t"                first character of AM/PM designator  A
  53.         "tt"                AM/PM designator                      AM
  54.         "tt*"              AM/PM designator                      PM
  55.         "d"    "0"        day w/o leading zero                  1
  56.         "dd"    "00"        day with leading zero                01
  57.         "ddd"              short weekday name (abbreviation)    Mon
  58.         "dddd"              full weekday name                    Monday
  59.         "dddd*"            full weekday name                    Monday
  60.        
  61.         "M"    "0"        month w/o leading zero                2
  62.         "MM"    "00"        month with leading zero              02
  63.         "MMM"              short month name (abbreviation)      Feb
  64.         "MMMM"              full month name                      Febuary
  65.         "MMMM*"            full month name                      Febuary
  66.      
  67.         "y"    "0"        two digit year (year % 100) w/o leading zero          0
  68.         "yy"    "00"        two digit year (year % 100) with leading zero          00
  69.         "yyy"  "D3"        year                                  2000
  70.         "yyyy"  "D4"        year                                  2000
  71.         "yyyyy" "D5"        year                                  2000
  72.         ...
  73.         "z"    "+0;-0"    timezone offset w/o leading zero      -8
  74.         "zz"    "+00;-00"  timezone offset with leading zero    -08
  75.         "zzz"  "+00;-00" for hour offset, "00" for minute offset  full timezone offset  -08:00
  76.         "zzz*"  "+00;-00" for hour offset, "00" for minute offset  full timezone offset  -08:00
  77.        
  78.         "K"    -Local      "zzz", e.g. -08:00
  79.               -Utc        "'Z'", representing UTC
  80.               -Unspecified ""             
  81.         "g*"                the current era name                  A.D.
  82.         ":"                time separator                        :
  83.         "/"                date separator                        /
  84.         "'"                quoted string                        'ABC' will insert ABC into the formatted string.
  85.         '"'                quoted string                        "ABC" will insert ABC into the formatted string.
  86.         "%"                used to quote a single pattern characters      E.g.The format character "%y" is to print two digit year.
  87.         "\"                escaped character                    E.g. '\d' insert the character 'd' into the format string.
  88.         other characters    insert the character into the format string.
  89.     Pre-defined format characters:
  90.         (U) to indicate Universal time is used.
  91.         (G) to indicate Gregorian calendar is used.
  92.    
  93.         Format              Description                            Real format                            Example
  94.         =========          =================================      ======================                  =======================
  95.         "d"                short date                              culture-specific                        10/31/1999
  96.         "D"                long data                              culture-specific                        Sunday, October 31, 1999
  97.         "f"                full date (long date + short time)      culture-specific                        Sunday, October 31, 1999 2:00 AM
  98.         "F"                full date (long date + long time)      culture-specific                        Sunday, October 31, 1999 2:00:00 AM
  99.         "g"                general date (short date + short time)  culture-specific                        10/31/1999 2:00 AM
  100.         "G"                general date (short date + long time)  culture-specific                        10/31/1999 2:00:00 AM
  101.         "m"/"M"            Month/Day date                          culture-specific                        October 31
  102. (G)    "o"/"O"            Round Trip XML                          "yyyy-MM-ddTHH:mm:ss.fffffffK"          1999-10-31 02:00:00.0000000Z
  103. (G)    "r"/"R"            RFC 1123 date,                          "ddd, dd MMM yyyy HH':'mm':'ss 'GMT'"  Sun, 31 Oct 1999 10:00:00 GMT
  104. (G)    "s"                Sortable format, based on ISO 8601.    "yyyy-MM-dd'T'HH:mm:ss"                1999-10-31T02:00:00
  105.                                                                     ('T' for local time)
  106.         "t"                short time                              culture-specific                        2:00 AM
  107.         "T"                long time                              culture-specific                        2:00:00 AM
  108. (G)    "u"                Universal time with sortable format,    "yyyy'-'MM'-'dd HH':'mm':'ss'Z'"        1999-10-31 10:00:00Z
  109.                             based on ISO 8601.
  110. (U)    "U"                Universal time with full                culture-specific                        Sunday, October 31, 1999 10:00:00 AM
  111.                             (long date + long time) format
  112.                             "y"/"Y"            Year/Month day                          culture-specific                        October, 1999
  113.     */   
  114.    
  115.     //This class contains only static members and does not require the serializable attribute.
  116.     static internal class DateTimeFormat
  117.     {
  118.        
  119.         internal const int MaxSecondsFractionDigits = 7;
  120.        
  121.         static internal char[] allStandardFormats = {'d', 'D', 'f', 'F', 'g', 'G', 'm', 'M', 'o', 'O',
  122.         'r', 'R', 's', 't', 'T', 'u', 'U', 'y', 'Y'};
  123.        
  124.         internal const string RoundtripFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.fffffffK";
  125.        
  126.         private const int DEFAULT_ALL_DATETIMES_SIZE = 132;
  127.        
  128.         private static string[] fixedNumberFormats = new string[] {"0", "00", "000", "0000", "00000", "000000", "0000000"};
  129.        
  130.         ////////////////////////////////////////////////////////////////////////////
  131.         //
  132.         // Format the positive integer value to a string and perfix with assigned
  133.         // length of leading zero.
  134.         //
  135.         // Parameters:
  136.         // value: The value to format
  137.         // len: The maximum length for leading zero.
  138.         // If the digits of the value is greater than len, no leading zero is added.
  139.         //
  140.         // Notes:
  141.         // The function can format to Int32.MaxValue.
  142.         //
  143.         ////////////////////////////////////////////////////////////////////////////
  144.         unsafe private static void FormatDigits(StringBuilder outputBuffer, int value, int len)
  145.         {
  146.             BCLDebug.Assert(value >= 0, "DateTimeFormat.FormatDigits(): value >= 0");
  147.            
  148.             // Limit the use of this function to be two-digits, so that we have the same behavior
  149.             // as RTM bits.
  150.             if (len > 2) {
  151.                 len = 2;
  152.             }
  153.            
  154.             char* buffer = stackalloc char[16];
  155.             char* p = buffer + 16;
  156.             int n = value;
  157.             do {
  158.                 *--p = (char)(n % 10 + '0');
  159.                 n /= 10;
  160.             }
  161.             while ((n != 0) && (p > buffer));
  162.            
  163.             int digits = (int)(buffer + 16 - p);
  164.            
  165.             //If the repeat count is greater than 0, we're trying
  166.             //to emulate the "00" format, so we have to prepend
  167.             //a zero if the string only has one character.
  168.             while ((digits < len) && (p > buffer)) {
  169.                 *--p = '0';
  170.                 digits++;
  171.             }
  172.             outputBuffer.Append(p, digits);
  173.         }
  174.        
  175.         private static void HebrewFormatDigits(StringBuilder outputBuffer, int digits)
  176.         {
  177.             outputBuffer.Append(HebrewNumber.ToString(digits));
  178.         }
  179.        
  180.         static int ParseRepeatPattern(string format, int pos, char patternChar)
  181.         {
  182.             int len = format.Length;
  183.             int index = pos + 1;
  184.             while ((index < len) && (format[index] == patternChar)) {
  185.                 index++;
  186.             }
  187.             return (index - pos);
  188.         }
  189.        
  190.         private static string FormatDayOfWeek(int dayOfWeek, int repeat, DateTimeFormatInfo dtfi)
  191.         {
  192.             BCLDebug.Assert(dayOfWeek >= 0 && dayOfWeek <= 6, "dayOfWeek >= 0 && dayOfWeek <= 6");
  193.             if (repeat == 3) {
  194.                 return (dtfi.GetAbbreviatedDayName((DayOfWeek)dayOfWeek));
  195.             }
  196.             // Call dtfi.GetDayName() here, instead of accessing DayNames property, because we don't
  197.             // want a clone of DayNames, which will hurt perf.
  198.             return (dtfi.GetDayName((DayOfWeek)dayOfWeek));
  199.         }
  200.        
  201.         private static string FormatMonth(int month, int repeatCount, DateTimeFormatInfo dtfi)
  202.         {
  203.             BCLDebug.Assert(month >= 1 && month <= 12, "month >=1 && month <= 12");
  204.             if (repeatCount == 3) {
  205.                 return (dtfi.GetAbbreviatedMonthName(month));
  206.             }
  207.             // Call GetMonthName() here, instead of accessing MonthNames property, because we don't
  208.             // want a clone of MonthNames, which will hurt perf.
  209.             return (dtfi.GetMonthName(month));
  210.         }
  211.        
  212.         //
  213.         // FormatHebrewMonthName
  214.         //
  215.         // Action: Return the Hebrew month name for the specified DateTime.
  216.         // Returns: The month name string for the specified DateTime.
  217.         // Arguments:
  218.         // time the time to format
  219.         // month The month is the value of HebrewCalendar.GetMonth(time).
  220.         // repeat Return abbreviated month name if repeat=3, or full month name if repeat=4
  221.         // dtfi The DateTimeFormatInfo which uses the Hebrew calendars as its calendar.
  222.         // Exceptions: None.
  223.         //
  224.        
  225. /* Note:
  226.             If DTFI is using Hebrew calendar, GetMonthName()/GetAbbreviatedMonthName() will return month names like this:           
  227.             1  Hebrew 1st Month
  228.             2  Hebrew 2nd Month
  229.             ..  ...
  230.             6  Hebrew 6th Month
  231.             7  Hebrew 6th Month II (used only in a leap year)
  232.             8  Hebrew 7th Month
  233.             9  Hebrew 8th Month
  234.             10  Hebrew 9th Month
  235.             11  Hebrew 10th Month
  236.             12  Hebrew 11th Month
  237.             13  Hebrew 12th Month
  238.             Therefore, if we are in a regular year, we have to increment the month name if moth is greater or eqaul to 7.           
  239.         */       
  240.         private static string FormatHebrewMonthName(DateTime time, int month, int repeatCount, DateTimeFormatInfo dtfi)
  241.         {
  242.             BCLDebug.Assert(repeatCount != 3 || repeatCount != 4, "repeateCount should be 3 or 4");
  243.             if (dtfi.Calendar.IsLeapYear(dtfi.Calendar.GetYear(time))) {
  244.                 // This month is in a leap year
  245.                 return (dtfi.internalGetMonthName(month, MonthNameStyles.LeapYear, (repeatCount == 3)));
  246.             }
  247.             // This is in a regular year.
  248.             if (month >= 7) {
  249.                 month++;
  250.             }
  251.             if (repeatCount == 3) {
  252.                 return (dtfi.GetAbbreviatedMonthName(month));
  253.             }
  254.             return (dtfi.GetMonthName(month));
  255.         }
  256.        
  257.         //
  258.         // The pos should point to a quote character. This method will
  259.         // get the string encloed by the quote character.
  260.         //
  261.         static internal int ParseQuoteString(string format, int pos, StringBuilder result)
  262.         {
  263.             //
  264.             // NOTE : pos will be the index of the quote character in the 'format' string.
  265.             //
  266.             int formatLen = format.Length;
  267.             int beginPos = pos;
  268.             char quoteChar = format[pos++];
  269.             // Get the character used to quote the following string.
  270.             bool foundQuote = false;
  271.             while (pos < formatLen) {
  272.                 char ch = format[pos++];
  273.                 if (ch == quoteChar) {
  274.                     foundQuote = true;
  275.                     break;
  276.                 }
  277.                 else if (ch == '\\') {
  278.                     // The following are used to support escaped character.
  279.                     // Escaped character is also supported in the quoted string.
  280.                     // Therefore, someone can use a format like "'minute:' mm\"" to display:
  281.                     // minute: 45"
  282.                     // because the second double quote is escaped.
  283.                     if (pos < formatLen) {
  284.                         result.Append(format[pos++]);
  285.                     }
  286.                     else {
  287.                         //
  288.                         // This means that '\' is at the end of the formatting string.
  289.                         //
  290.                         throw new FormatException(Environment.GetResourceString("Format_InvalidString"));
  291.                     }
  292.                 }
  293.                 else {
  294.                     result.Append(ch);
  295.                 }
  296.             }
  297.            
  298.             if (!foundQuote) {
  299.                 // Here we can't find the matching quote.
  300.                 throw new FormatException(String.Format(CultureInfo.CurrentCulture, Environment.GetResourceString("Format_BadQuote"), quoteChar));
  301.             }
  302.            
  303.             //
  304.             // Return the character count including the begin/end quote characters and enclosed string.
  305.             //
  306.             return (pos - beginPos);
  307.         }
  308.        
  309.         //
  310.         // Get the next character at the index of 'pos' in the 'format' string.
  311.         // Return value of -1 means 'pos' is already at the end of the 'format' string.
  312.         // Otherwise, return value is the int value of the next character.
  313.         //
  314.         private static int ParseNextChar(string format, int pos)
  315.         {
  316.             if (pos >= format.Length - 1) {
  317.                 return (-1);
  318.             }
  319.             return ((int)format[pos + 1]);
  320.         }
  321.        
  322.         //
  323.         // IsUseGenitiveForm
  324.         //
  325.         // Actions: Check the format to see if we should use genitive month in the formatting.
  326.         // Starting at the position (index) in the (format) string, look back and look ahead to
  327.         // see if there is "d" or "dd". In the case like "d MMMM" or "MMMM dd", we can use
  328.         // genitive form. Genitive form is not used if there is more than two "d".
  329.         // Arguments:
  330.         // format The format string to be scanned.
  331.         // index Where we should start the scanning. This is generally where "M" starts.
  332.         // tokenLen The len of the current pattern character. This indicates how many "M" that we have.
  333.         // patternToMatch The pattern that we want to search. This generally uses "d"
  334.         //
  335.         private static bool IsUseGenitiveForm(string format, int index, int tokenLen, char patternToMatch)
  336.         {
  337.             int i;
  338.             int repeat = 0;
  339.             //
  340.             // Look back to see if we can find "d" or "ddd"
  341.             //
  342.            
  343.             // Find first "d".
  344.             for (i = index - 1; i >= 0 && format[i] != patternToMatch; i--) {
  345.                 /*Do nothing here */            }
  346.             ;
  347.            
  348.             if (i >= 0) {
  349.                 // Find a "d", so look back to see how many "d" that we can find.
  350.                 while (--i >= 0 && format[i] == patternToMatch) {
  351.                     repeat++;
  352.                 }
  353.                 //
  354.                 // repeat == 0 means that we have one (patternToMatch)
  355.                 // repeat == 1 means that we have two (patternToMatch)
  356.                 //
  357.                 if (repeat <= 1) {
  358.                     return (true);
  359.                 }
  360.                 // Note that we can't just stop here. We may find "ddd" while looking back, and we have to look
  361.                 // ahead to see if there is "d" or "dd".
  362.             }
  363.            
  364.             //
  365.             // If we can't find "d" or "dd" by looking back, try look ahead.
  366.             //
  367.            
  368.             // Find first "d"
  369.             for (i = index + tokenLen; i < format.Length && format[i] != patternToMatch; i++) {
  370.                 /* Do nothing here */            }
  371.             ;
  372.            
  373.             if (i < format.Length) {
  374.                 repeat = 0;
  375.                 // Find a "d", so contine the walk to see how may "d" that we can find.
  376.                 while (++i < format.Length && format[i] == patternToMatch) {
  377.                     repeat++;
  378.                 }
  379.                 //
  380.                 // repeat == 0 means that we have one (patternToMatch)
  381.                 // repeat == 1 means that we have two (patternToMatch)
  382.                 //
  383.                 if (repeat <= 1) {
  384.                     return (true);
  385.                 }
  386.             }
  387.             return (false);
  388.         }
  389.        
  390.         //
  391.         // FormatCustomized
  392.         //
  393.         // Actions: Format the DateTime instance using the specified format.
  394.         //
  395.         private static string FormatCustomized(DateTime dateTime, string format, DateTimeFormatInfo dtfi)
  396.         {
  397.             Calendar cal = dtfi.Calendar;
  398.             StringBuilder result = new StringBuilder();
  399.             // This is a flag to indicate if we are format the dates using Hebrew calendar.
  400.            
  401.             bool isHebrewCalendar = (cal.ID == Calendar.CAL_HEBREW);
  402.             // This is a flag to indicate if we are formating hour/minute/second only.
  403.             bool bTimeOnly = true;
  404.            
  405.             int i = 0;
  406.             int tokenLen;
  407.             int hour12;
  408.            
  409.             while (i < format.Length) {
  410.                 char ch = format[i];
  411.                 int nextChar;
  412.                 switch (ch) {
  413.                     case 'g':
  414.                         tokenLen = ParseRepeatPattern(format, i, ch);
  415.                         result.Append(dtfi.GetEraName(cal.GetEra(dateTime)));
  416.                         break;
  417.                     case 'h':
  418.                         tokenLen = ParseRepeatPattern(format, i, ch);
  419.                         hour12 = dateTime.Hour % 12;
  420.                         if (hour12 == 0) {
  421.                             hour12 = 12;
  422.                         }
  423.                         FormatDigits(result, hour12, tokenLen);
  424.                         break;
  425.                     case 'H':
  426.                         tokenLen = ParseRepeatPattern(format, i, ch);
  427.                         FormatDigits(result, dateTime.Hour, tokenLen);
  428.                         break;
  429.                     case 'm':
  430.                         tokenLen = ParseRepeatPattern(format, i, ch);
  431.                         FormatDigits(result, dateTime.Minute, tokenLen);
  432.                         break;
  433.                     case 's':
  434.                         tokenLen = ParseRepeatPattern(format, i, ch);
  435.                         FormatDigits(result, dateTime.Second, tokenLen);
  436.                         break;
  437.                     case 'f':
  438.                     case 'F':
  439.                         tokenLen = ParseRepeatPattern(format, i, ch);
  440.                         if (tokenLen <= MaxSecondsFractionDigits) {
  441.                             long fraction = (dateTime.Ticks % Calendar.TicksPerSecond);
  442.                             fraction = fraction / (long)Math.Pow(10, 7 - tokenLen);
  443.                             if (ch == 'f') {
  444.                                 result.Append(((int)fraction).ToString(fixedNumberFormats[tokenLen - 1], CultureInfo.InvariantCulture));
  445.                             }
  446.                             else {
  447.                                 int effectiveDigits = tokenLen;
  448.                                 while (effectiveDigits > 0) {
  449.                                     if (fraction % 10 == 0) {
  450.                                         fraction = fraction / 10;
  451.                                         effectiveDigits--;
  452.                                     }
  453.                                     else {
  454.                                         break;
  455.                                     }
  456.                                 }
  457.                                 if (effectiveDigits > 0) {
  458.                                     result.Append(((int)fraction).ToString(fixedNumberFormats[effectiveDigits - 1], CultureInfo.InvariantCulture));
  459.                                 }
  460.                                 else {
  461.                                     // No fraction to emit, so see if we should remove decimal also.
  462.                                     if (result.Length > 0 && result[result.Length - 1] == '.') {
  463.                                         result.Remove(result.Length - 1, 1);
  464.                                     }
  465.                                 }
  466.                             }
  467.                         }
  468.                         else {
  469.                             throw new FormatException(Environment.GetResourceString("Format_InvalidString"));
  470.                         }
  471.                         break;
  472.                     case 't':
  473.                         tokenLen = ParseRepeatPattern(format, i, ch);
  474.                         if (tokenLen == 1) {
  475.                             if (dateTime.Hour < 12) {
  476.                                 if (dtfi.AMDesignator.Length >= 1) {
  477.                                     result.Append(dtfi.AMDesignator[0]);
  478.                                 }
  479.                             }
  480.                             else {
  481.                                 if (dtfi.PMDesignator.Length >= 1) {
  482.                                     result.Append(dtfi.PMDesignator[0]);
  483.                                 }
  484.                             }
  485.                            
  486.                         }
  487.                         else {
  488.                             result.Append((dateTime.Hour < 12 ? dtfi.AMDesignator : dtfi.PMDesignator));
  489.                         }
  490.                         break;
  491.                     case 'd':
  492.                         //
  493.                         // tokenLen == 1 : Day of month as digits with no leading zero.
  494.                         // tokenLen == 2 : Day of month as digits with leading zero for single-digit months.
  495.                         // tokenLen == 3 : Day of week as a three-leter abbreviation.
  496.                         // tokenLen >= 4 : Day of week as its full name.
  497.                         //
  498.                         tokenLen = ParseRepeatPattern(format, i, ch);
  499.                         if (tokenLen <= 2) {
  500.                             int day = cal.GetDayOfMonth(dateTime);
  501.                             if (isHebrewCalendar) {
  502.                                 // For Hebrew calendar, we need to convert numbers to Hebrew text for yyyy, MM, and dd values.
  503.                                 HebrewFormatDigits(result, day);
  504.                             }
  505.                             else {
  506.                                 FormatDigits(result, day, tokenLen);
  507.                             }
  508.                         }
  509.                         else {
  510.                             int dayOfWeek = (int)cal.GetDayOfWeek(dateTime);
  511.                             result.Append(FormatDayOfWeek(dayOfWeek, tokenLen, dtfi));
  512.                         }
  513.                         bTimeOnly = false;
  514.                         break;
  515.                     case 'M':
  516.                         //
  517.                         // tokenLen == 1 : Month as digits with no leading zero.
  518.                         // tokenLen == 2 : Month as digits with leading zero for single-digit months.
  519.                         // tokenLen == 3 : Month as a three-letter abbreviation.
  520.                         // tokenLen >= 4 : Month as its full name.
  521.                         //
  522.                         tokenLen = ParseRepeatPattern(format, i, ch);
  523.                         int month = cal.GetMonth(dateTime);
  524.                         if (tokenLen <= 2) {
  525.                             if (isHebrewCalendar) {
  526.                                 // For Hebrew calendar, we need to convert numbers to Hebrew text for yyyy, MM, and dd values.
  527.                                 HebrewFormatDigits(result, month);
  528.                             }
  529.                             else {
  530.                                 FormatDigits(result, month, tokenLen);
  531.                             }
  532.                         }
  533.                         else {
  534.                             if (isHebrewCalendar) {
  535.                                 result.Append(FormatHebrewMonthName(dateTime, month, tokenLen, dtfi));
  536.                             }
  537.                             else {
  538.                                 if ((dtfi.FormatFlags & DateTimeFormatFlags.UseGenitiveMonth) != 0 && tokenLen >= 4) {
  539.                                     result.Append(dtfi.internalGetMonthName(month, IsUseGenitiveForm(format, i, tokenLen, 'd') ? MonthNameStyles.Genitive : MonthNameStyles.Regular, false));
  540.                                 }
  541.                                 else {
  542.                                     result.Append(FormatMonth(month, tokenLen, dtfi));
  543.                                 }
  544.                             }
  545.                         }
  546.                         bTimeOnly = false;
  547.                         break;
  548.                     case 'y':
  549.                         // Notes about OS behavior:
  550.                         // y: Always print (year % 100). No leading zero.
  551.                         // yy: Always print (year % 100) with leading zero.
  552.                         // yyy/yyyy/yyyyy/... : Print year value. No leading zero.
  553.                        
  554.                         int year = cal.GetYear(dateTime);
  555.                         tokenLen = ParseRepeatPattern(format, i, ch);
  556.                         if (dtfi.HasForceTwoDigitYears) {
  557.                             FormatDigits(result, year, tokenLen <= 2 ? tokenLen : 2);
  558.                         }
  559.                         else if (cal.ID == Calendar.CAL_HEBREW) {
  560.                             HebrewFormatDigits(result, year);
  561.                         }
  562.                         else {
  563.                             if (tokenLen <= 2) {
  564.                                 FormatDigits(result, year % 100, tokenLen);
  565.                             }
  566.                             else {
  567.                                 string fmtPattern = "D" + tokenLen;
  568.                                 result.Append(year.ToString(fmtPattern, CultureInfo.InvariantCulture));
  569.                             }
  570.                         }
  571.                         bTimeOnly = false;
  572.                         break;
  573.                     case 'z':
  574.                         //
  575.                         // Output the offset of the timezone according to the system timezone setting.
  576.                         //
  577.                         tokenLen = ParseRepeatPattern(format, i, ch);
  578.                         TimeSpan offset;
  579.                         if (bTimeOnly && dateTime.Ticks < Calendar.TicksPerDay) {
  580.                             offset = TimeZone.CurrentTimeZone.GetUtcOffset(DateTime.Now);
  581.                         }
  582.                         else {
  583.                             if (dateTime.Kind == DateTimeKind.Utc) {
  584.                                 InvalidFormatForUtc(format, dateTime);
  585.                                 offset = TimeZone.CurrentTimeZone.GetUtcOffset(DateTime.SpecifyKind(dateTime, DateTimeKind.Local));
  586.                             }
  587.                             else {
  588.                                 offset = TimeZone.CurrentTimeZone.GetUtcOffset(dateTime);
  589.                             }
  590.                         }
  591.                        
  592.                         switch (tokenLen) {
  593.                             case 1:
  594.                                 result.Append((offset.Hours).ToString("+0;-0", CultureInfo.InvariantCulture));
  595.                                 break;
  596.                             case 2:
  597.                                 result.Append((offset.Hours).ToString("+00;-00", CultureInfo.InvariantCulture));
  598.                                 break;
  599.                             default:
  600.                                 if (offset.Ticks >= 0) {
  601.                                     result.Append(String.Format(CultureInfo.InvariantCulture, "+{0:00}:{1:00}", offset.Hours, offset.Minutes));
  602.                                 }
  603.                                 else {
  604.                                     // When the offset is negative, note that the offset.Minute is also negative.
  605.                                     // So use should use -offset.Minute to get the postive value.
  606.                                     result.Append(String.Format(CultureInfo.InvariantCulture, "-{0:00}:{1:00}", -offset.Hours, -offset.Minutes));
  607.                                 }
  608.                                 break;
  609.                         }
  610.                         break;
  611.                     case 'K':
  612.                         tokenLen = 1;
  613.                         // The objective of this format is to round trip the Kind value and preserve the time zone
  614.                         switch (dateTime.Kind) {
  615.                             case DateTimeKind.Local:
  616.                                 // This should output the local offset, e.g. "-07:00"
  617.                                 TimeSpan localOffset = TimeZone.CurrentTimeZone.GetUtcOffset(dateTime);
  618.                                 if (localOffset.Ticks >= 0) {
  619.                                     result.Append(String.Format(CultureInfo.InvariantCulture, "+{0:00}:{1:00}", localOffset.Hours, localOffset.Minutes));
  620.                                 }
  621.                                 else {
  622.                                     // When the offset is negative, note that the localOffset.Minute is also negative.
  623.                                     // So use should use -localOffset.Minute to get the postive value.
  624.                                     result.Append(String.Format(CultureInfo.InvariantCulture, "-{0:00}:{1:00}", -localOffset.Hours, -localOffset.Minutes));
  625.                                 }
  626.                                 break;
  627.                             case DateTimeKind.Utc:
  628.                                 // The 'Z' constant is a marker for a UTC date
  629.                                 result.Append("Z");
  630.                                 break;
  631.                             default:
  632.                                 // If the kind is unspecified, we output nothing here
  633.                                 break;
  634.                         }
  635.                         break;
  636.                     case ':':
  637.                         result.Append(dtfi.TimeSeparator);
  638.                         tokenLen = 1;
  639.                         break;
  640.                     case '/':
  641.                         result.Append(dtfi.DateSeparator);
  642.                         tokenLen = 1;
  643.                         break;
  644.                     case '\'':
  645.                     case '"':
  646.                         StringBuilder enquotedString = new StringBuilder();
  647.                         tokenLen = ParseQuoteString(format, i, enquotedString);
  648.                         result.Append(enquotedString);
  649.                         break;
  650.                     case '%':
  651.                         // Optional format character.
  652.                         // For example, format string "%d" will print day of month
  653.                         // without leading zero. Most of the cases, "%" can be ignored.
  654.                         nextChar = ParseNextChar(format, i);
  655.                         // nextChar will be -1 if we already reach the end of the format string.
  656.                         // Besides, we will not allow "%%" appear in the pattern.
  657.                         if (nextChar >= 0 && nextChar != (int)'%') {
  658.                             result.Append(FormatCustomized(dateTime, ((char)nextChar).ToString(), dtfi));
  659.                             tokenLen = 2;
  660.                         }
  661.                         else {
  662.                             //
  663.                             // This means that '%' is at the end of the format string or
  664.                             // "%%" appears in the format string.
  665.                             //
  666.                             throw new FormatException(Environment.GetResourceString("Format_InvalidString"));
  667.                         }
  668.                         break;
  669.                     case '\\':
  670.                         //
  671.                         nextChar = ParseNextChar(format, i);
  672.                         if (nextChar >= 0) {
  673.                             result.Append(((char)nextChar));
  674.                             tokenLen = 2;
  675.                         }
  676.                         else {
  677.                             //
  678.                             // This means that '\' is at the end of the formatting string.
  679.                             //
  680.                             throw new FormatException(Environment.GetResourceString("Format_InvalidString"));
  681.                         }
  682.                         break;
  683.                     default:
  684.                         result.Append(ch);
  685.                         tokenLen = 1;
  686.                         break;
  687.                 }
  688.                 i += tokenLen;
  689.             }
  690.             return (result.ToString());
  691.         }
  692.        
  693.         static internal string GetRealFormat(string format, DateTimeFormatInfo dtfi)
  694.         {
  695.             string realFormat = null;
  696.            
  697.             switch (format[0]) {
  698.                 case 'd':
  699.                     // Short Date
  700.                     realFormat = dtfi.ShortDatePattern;
  701.                     break;
  702.                 case 'D':
  703.                     // Long Date
  704.                     realFormat = dtfi.LongDatePattern;
  705.                     break;
  706.                 case 'f':
  707.                     // Full (long date + short time)
  708.                     realFormat = dtfi.LongDatePattern + " " + dtfi.ShortTimePattern;
  709.                     break;
  710.                 case 'F':
  711.                     // Full (long date + long time)
  712.                     realFormat = dtfi.FullDateTimePattern;
  713.                     break;
  714.                 case 'g':
  715.                     // General (short date + short time)
  716.                     realFormat = dtfi.GeneralShortTimePattern;
  717.                     break;
  718.                 case 'G':
  719.                     // General (short date + long time)
  720.                     realFormat = dtfi.GeneralLongTimePattern;
  721.                     break;
  722.                 case 'm':
  723.                 case 'M':
  724.                     // Month/Day Date
  725.                     realFormat = dtfi.MonthDayPattern;
  726.                     break;
  727.                 case 'o':
  728.                 case 'O':
  729.                     realFormat = RoundtripFormat;
  730.                     break;
  731.                 case 'r':
  732.                 case 'R':
  733.                     // RFC 1123 Standard
  734.                     realFormat = dtfi.RFC1123Pattern;
  735.                     break;
  736.                 case 's':
  737.                     // Sortable without Time Zone Info
  738.                     realFormat = dtfi.SortableDateTimePattern;
  739.                     break;
  740.                 case 't':
  741.                     // Short Time
  742.                     realFormat = dtfi.ShortTimePattern;
  743.                     break;
  744.                 case 'T':
  745.                     // Long Time
  746.                     realFormat = dtfi.LongTimePattern;
  747.                     break;
  748.                 case 'u':
  749.                     // Universal with Sortable format
  750.                     realFormat = dtfi.UniversalSortableDateTimePattern;
  751.                     break;
  752.                 case 'U':
  753.                     // Universal with Full (long date + long time) format
  754.                     realFormat = dtfi.FullDateTimePattern;
  755.                     break;
  756.                 case 'y':
  757.                 case 'Y':
  758.                     // Year/Month Date
  759.                     realFormat = dtfi.YearMonthPattern;
  760.                     break;
  761.                 default:
  762.                     throw new FormatException(Environment.GetResourceString("Format_InvalidString"));
  763.                     break;
  764.             }
  765.             return (realFormat);
  766.         }
  767.        
  768.         // Expand a pre-defined format string (like "D" for long date) to the real format that
  769.         // we are going to use in the date time parsing.
  770.         // This method also convert the dateTime if necessary (e.g. when the format is in Universal time),
  771.         // and change dtfi if necessary (e.g. when the format should use invariant culture).
  772.         //
  773.         private static string ExpandPredefinedFormat(string format, ref DateTime dateTime, ref DateTimeFormatInfo dtfi)
  774.         {
  775.             switch (format[0]) {
  776.                 case 'o':
  777.                 case 'O':
  778.                     // Round trip format
  779.                     dtfi = DateTimeFormatInfo.InvariantInfo;
  780.                     break;
  781.                 case 'r':
  782.                 case 'R':
  783.                     // RFC 1123 Standard
  784.                     if (dateTime.Kind == DateTimeKind.Local) {
  785.                         InvalidFormatForLocal(format, dateTime);
  786.                     }
  787.                     dtfi = DateTimeFormatInfo.InvariantInfo;
  788.                     break;
  789.                 case 's':
  790.                     // Sortable without Time Zone Info
  791.                     dtfi = DateTimeFormatInfo.InvariantInfo;
  792.                     break;
  793.                 case 'u':
  794.                     // Universal time in sortable format.
  795.                     if (dateTime.Kind == DateTimeKind.Local) {
  796.                         InvalidFormatForLocal(format, dateTime);
  797.                     }
  798.                     dtfi = DateTimeFormatInfo.InvariantInfo;
  799.                     break;
  800.                 case 'U':
  801.                     // Universal time in culture dependent format.
  802.                     // Universal time is always in Greogrian calendar.
  803.                     //
  804.                     // Change the Calendar to be Gregorian Calendar.
  805.                     //
  806.                     dtfi = (DateTimeFormatInfo)dtfi.Clone();
  807.                     if (dtfi.Calendar.GetType() != typeof(GregorianCalendar)) {
  808.                         dtfi.Calendar = GregorianCalendar.GetDefaultInstance();
  809.                     }
  810.                     dateTime = dateTime.ToUniversalTime();
  811.                     break;
  812.             }
  813.             format = GetRealFormat(format, dtfi);
  814.             return (format);
  815.         }
  816.        
  817.         static internal string Format(DateTime dateTime, string format, DateTimeFormatInfo dtfi)
  818.         {
  819.             if (format == null || format.Length == 0) {
  820.                 format = "G";
  821.                 if (dateTime.Ticks < Calendar.TicksPerDay) {
  822.                     //
  823.                     switch (dtfi.Calendar.ID) {
  824.                         case Calendar.CAL_JAPAN:
  825.                         case Calendar.CAL_TAIWAN:
  826.                         case Calendar.CAL_HIJRI:
  827.                         case Calendar.CAL_HEBREW:
  828.                         case Calendar.CAL_JULIAN:
  829.                             format = "s";
  830.                             break;
  831.                     }
  832.                 }
  833.             }
  834.            
  835.             if (format.Length == 1) {
  836.                 format = ExpandPredefinedFormat(format, ref dateTime, ref dtfi);
  837.             }
  838.            
  839.             return (FormatCustomized(dateTime, format, dtfi));
  840.         }
  841.        
  842.         static internal string[] GetAllDateTimes(DateTime dateTime, char format, DateTimeFormatInfo dtfi)
  843.         {
  844.             string[] allFormats = null;
  845.             string[] results = null;
  846.            
  847.             switch (format) {
  848.                 case 'd':
  849.                 case 'D':
  850.                 case 'f':
  851.                 case 'F':
  852.                 case 'g':
  853.                 case 'G':
  854.                 case 'm':
  855.                 case 'M':
  856.                 case 't':
  857.                 case 'T':
  858.                 case 'y':
  859.                 case 'Y':
  860.                     allFormats = dtfi.GetAllDateTimePatterns(format);
  861.                     results = new string[allFormats.Length];
  862.                     for (int i = 0; i < allFormats.Length; i++) {
  863.                         results[i] = Format(dateTime, allFormats[i], dtfi);
  864.                     }
  865.                     break;
  866.                 case 'U':
  867.                     DateTime universalTime = dateTime.ToUniversalTime();
  868.                     allFormats = dtfi.GetAllDateTimePatterns(format);
  869.                     results = new string[allFormats.Length];
  870.                     for (int i = 0; i < allFormats.Length; i++) {
  871.                         results[i] = Format(universalTime, allFormats[i], dtfi);
  872.                     }
  873.                     break;
  874.                 case 'r':
  875.                 case 'R':
  876.                 case 'o':
  877.                 case 'O':
  878.                 case 's':
  879.                 case 'u':
  880.                     //
  881.                     // The following ones are special cases because these patterns are read-only in
  882.                     // DateTimeFormatInfo.
  883.                     //
  884.                     results = new string[] {Format(dateTime, new string(new char[] {format}), dtfi)};
  885.                     break;
  886.                 default:
  887.                     throw new FormatException(Environment.GetResourceString("Format_InvalidString"));
  888.                     break;
  889.                
  890.             }
  891.             return (results);
  892.         }
  893.        
  894.         static internal string[] GetAllDateTimes(DateTime dateTime, DateTimeFormatInfo dtfi)
  895.         {
  896.             ArrayList results = new ArrayList(DEFAULT_ALL_DATETIMES_SIZE);
  897.            
  898.             for (int i = 0; i < allStandardFormats.Length; i++) {
  899.                 string[] strings = GetAllDateTimes(dateTime, allStandardFormats[i], dtfi);
  900.                 for (int j = 0; j < strings.Length; j++) {
  901.                     results.Add(strings[j]);
  902.                 }
  903.             }
  904.             string[] value = new string[results.Count];
  905.             results.CopyTo(0, value, 0, results.Count);
  906.             return (value);
  907.         }
  908.        
  909.         // This is a placeholder for an MDA to detect when the user is using a
  910.         // local DateTime with a format that will be interpreted as UTC.
  911.         static internal void InvalidFormatForLocal(string format, DateTime dateTime)
  912.         {
  913.         }
  914.        
  915.         // This is an MDA for cases when the user is using a local format with
  916.         // a Utc DateTime.
  917.         static internal void InvalidFormatForUtc(string format, DateTime dateTime)
  918.         {
  919.         }
  920.        
  921.        
  922.     }
  923. }

Developer Fusion