// ==++== // // Copyright (c) Microsoft Corporation. All rights reserved. // // ==--== // [....] // namespace System.Globalization { using System.Text; using System; using System.Diagnostics.Contracts; using System.Globalization; using System.Collections; static class TimeSpanFormat { private static void IntToString(int n, int digits, String outStr) { ((int32)n).[Friend]ToString(outStr, digits); } protected static readonly FormatLiterals PositiveInvariantFormatLiterals = TimeSpanFormat.FormatLiterals.[Friend]InitInvariant(false /*isNegative*/) ~ _.Dispose(); protected static readonly FormatLiterals NegativeInvariantFormatLiterals = TimeSpanFormat.FormatLiterals.[Friend]InitInvariant(true /*isNegative*/) ~ _.Dispose(); protected enum Pattern { None = 0, Minimum = 1, Full = 2, } // // Format // // Actions: Main method called from TimeSpan.ToString // protected static Result Format(TimeSpan value, StringView format, IFormatProvider formatProvider, String outStr) { var format; if (format.IsNull || format.Length == 0) format = "c"; // standard formats if (format.Length == 1) { char8 f = format[0]; if (f == 'c' || f == 't' || f == 'T') { FormatStandard(value, true, format, Pattern.Minimum, outStr); return .Ok; } if (f == 'g' || f == 'G') { Pattern pattern; DateTimeFormatInfo dtfi = DateTimeFormatInfo.GetInstance(formatProvider); if ((int64)value < 0) format = dtfi.[Friend]FullTimeSpanNegativePattern; else format = dtfi.[Friend]FullTimeSpanPositivePattern; if (f == 'g') pattern = Pattern.Minimum; else pattern = Pattern.Full; return FormatStandard(value, false, format, pattern, outStr); } //throw new FormatException(Environment.GetResourceString("Format_InvalidString")); return .Err; } return FormatCustomized(value, format, DateTimeFormatInfo.GetInstance(formatProvider), outStr); } // // FormatStandard // // Actions: Format the TimeSpan instance using the specified format. // private static Result FormatStandard(TimeSpan value, bool isInvariant, StringView format, Pattern pattern, String outStr) { int32 day = (int32)((int64)value / TimeSpan.TicksPerDay); int64 time = (int64)value % TimeSpan.TicksPerDay; if ((int64)value < 0) { day = -day; time = -time; } int hours = (int)(time / TimeSpan.TicksPerHour % 24); int minutes = (int)(time / TimeSpan.TicksPerMinute % 60); int seconds = (int)(time / TimeSpan.TicksPerSecond % 60); int fraction = (int)(time % TimeSpan.TicksPerSecond); FormatLiterals literal; if (isInvariant) { if ((int64)value < 0) literal = NegativeInvariantFormatLiterals; else literal = PositiveInvariantFormatLiterals; } else { literal = FormatLiterals(); literal.[Friend]Init(format, pattern == Pattern.Full); } if (fraction != 0) { // truncate the partial second to the specified length fraction = (int)((int64)fraction / (int64)Math.Pow(10, DateTimeFormat.[Friend]MaxSecondsFractionDigits - literal.[Friend]ff)); } // Pattern.Full: [-]dd.hh:mm:ss.fffffff // Pattern.Minimum: [-][d.]hh:mm:ss[.fffffff] outStr.Append(literal.[Friend]Start); // [-] if (pattern == Pattern.Full || day != 0) { // day.ToString(outStr); // [dd] outStr.Append(literal.[Friend]DayHourSep); // [.] } // IntToString(hours, literal.[Friend]hh, outStr); // hh outStr.Append(literal.[Friend]HourMinuteSep); // : IntToString(minutes, literal.[Friend]mm, outStr); // mm outStr.Append(literal.[Friend]MinuteSecondSep); // : IntToString(seconds, literal.[Friend]ss, outStr); // ss if (!isInvariant && pattern == Pattern.Minimum) { int effectiveDigits = literal.[Friend]ff; while (effectiveDigits > 0) { if (fraction % 10 == 0) { fraction = fraction / 10; effectiveDigits--; } else { break; } } if (effectiveDigits > 0) { outStr.Append(literal.[Friend]SecondFractionSep); // [.FFFFFFF] (fraction).ToString(outStr, DateTimeFormat.[Friend]fixedNumberFormats[effectiveDigits - 1], CultureInfo.InvariantCulture); } } else if (pattern == Pattern.Full || fraction != 0) { outStr.Append(literal.[Friend]SecondFractionSep); // [.] IntToString(fraction, literal.[Friend]ff, outStr); // [fffffff] } // outStr.Append(literal.[Friend]End); // return .Ok; } // // FormatCustomized // // Actions: Format the TimeSpan instance using the specified format. // protected static Result FormatCustomized(TimeSpan value, StringView format, DateTimeFormatInfo dtfi, String result) { Contract.Assert(dtfi != null, "dtfi == null"); int day = (int)((int64)value / TimeSpan.TicksPerDay); int64 time = (int64)value % TimeSpan.TicksPerDay; if ((int64)value < 0) { day = -day; time = -time; } int hours = (int)(time / TimeSpan.TicksPerHour % 24); int minutes = (int)(time / TimeSpan.TicksPerMinute % 60); int seconds = (int)(time / TimeSpan.TicksPerSecond % 60); int fraction = (int)(time % TimeSpan.TicksPerSecond); int64 tmp = 0; int i = 0; int tokenLen = 0; while (i < format.Length) { char8 ch = format[i]; int nextChar; switch (ch) { case 'h': tokenLen = DateTimeFormat.[Friend]ParseRepeatPattern(format, i, ch); if (tokenLen > 2) //throw new FormatException(Environment.GetResourceString("Format_InvalidString")); return .Err; DateTimeFormat.[Friend]FormatDigits(result, hours, tokenLen); break; case 'm': tokenLen = DateTimeFormat.[Friend]ParseRepeatPattern(format, i, ch); if (tokenLen > 2) return .Err; //throw new FormatException(Environment.GetResourceString("Format_InvalidString")); DateTimeFormat.[Friend]FormatDigits(result, minutes, tokenLen); break; case 's': tokenLen = DateTimeFormat.[Friend]ParseRepeatPattern(format, i, ch); if (tokenLen > 2) return .Err; //throw new FormatException(Environment.GetResourceString("Format_InvalidString")); DateTimeFormat.[Friend]FormatDigits(result, seconds, tokenLen); break; case 'f': // // The fraction of a second in single-digit precision. The remaining digits are truncated. // tokenLen = DateTimeFormat.[Friend]ParseRepeatPattern(format, i, ch); if (tokenLen > DateTimeFormat.[Friend]MaxSecondsFractionDigits) return .Err; //throw new FormatException(Environment.GetResourceString("Format_InvalidString")); tmp = (int64)fraction; tmp /= (int64)Math.Pow(10, DateTimeFormat.[Friend]MaxSecondsFractionDigits - tokenLen); (tmp).ToString(result, DateTimeFormat.[Friend]fixedNumberFormats[tokenLen - 1], CultureInfo.InvariantCulture); break; case 'F': // // Displays the most significant digit of the seconds fraction. Nothing is displayed if the digit is zero. // tokenLen = DateTimeFormat.[Friend]ParseRepeatPattern(format, i, ch); if (tokenLen > DateTimeFormat.[Friend]MaxSecondsFractionDigits) return .Err; //throw new FormatException(Environment.GetResourceString("Format_InvalidString")); tmp = (int64)fraction; tmp /= (int64)Math.Pow(10, DateTimeFormat.[Friend]MaxSecondsFractionDigits - tokenLen); int effectiveDigits = tokenLen; while (effectiveDigits > 0) { if (tmp % 10 == 0) { tmp = tmp / 10; effectiveDigits--; } else { break; } } if (effectiveDigits > 0) { (tmp).ToString(result, DateTimeFormat.[Friend]fixedNumberFormats[effectiveDigits - 1], CultureInfo.InvariantCulture); } break; case 'd': // // tokenLen == 1 : Day as digits with no leading zero. // tokenLen == 2+: Day as digits with leading zero for single-digit days. // tokenLen = DateTimeFormat.[Friend]ParseRepeatPattern(format, i, ch); if (tokenLen > 8) return .Err; //throw new FormatException(Environment.GetResourceString("Format_InvalidString")); DateTimeFormat.[Friend]FormatDigits(result, day, tokenLen, true); break; case '\'': case '\"': //StringBuilder enquotedString = new StringBuilder(); tokenLen = DateTimeFormat.[Friend]ParseQuoteString(format, i, result); //result.Append(enquotedString); break; case '%': // Optional format character. // For example, format string "%d" will print day // Most of the cases, "%" can be ignored. nextChar = DateTimeFormat.[Friend]ParseNextChar(format, i); // nextChar will be -1 if we already reach the end of the format string. // Besides, we will not allow "%%" appear in the pattern. if (nextChar >= 0 && nextChar != (int)'%') { TimeSpanFormat.FormatCustomized(value, StringView((char8*)&nextChar, 1), dtfi, result); tokenLen = 2; } else { // // This means that '%' is at the end of the format string or // "%%" appears in the format string. // //throw new FormatException(Environment.GetResourceString("Format_InvalidString")); return .Err; } break; case '\\': // Escaped character. Can be used to insert character into the format string. // For example, "\d" will insert the character 'd' into the string. // nextChar = DateTimeFormat.[Friend]ParseNextChar(format, i); if (nextChar >= 0) { result.Append(((char8)nextChar)); tokenLen = 2; } else { // // This means that '\' is at the end of the formatting string. // //throw new FormatException(Environment.GetResourceString("Format_InvalidString")); return .Err; } break; default: //throw new FormatException(Environment.GetResourceString("Format_InvalidString")); return .Err; } i += tokenLen; } return .Ok; } protected struct FormatLiterals { String Start { get { return literals[0]; } } String DayHourSep { get { return literals[1]; } } String HourMinuteSep { get { return literals[2]; } } String MinuteSecondSep { get { return literals[3]; } } String SecondFractionSep { get { return literals[4]; } } String End { get { return literals[5]; } } String AppCompatLiteral; int dd; int hh; int mm; int ss; int ff; private String[] literals; private List ownedStrs; public void Dispose() mut { DeleteAndNullify!(literals); if (ownedStrs != null) { DeleteContainerAndItems!(ownedStrs); ownedStrs = null; } } String AddOwnedStr(String str) mut { if (ownedStrs == null) ownedStrs = new List(); ownedStrs.Add(str); return str; } /* factory method for static invariant FormatLiterals */ static FormatLiterals InitInvariant(bool isNegative) { FormatLiterals x = FormatLiterals(); x.literals = new String[6]; x.literals[0] = isNegative ? "-" : String.Empty; x.literals[1] = "."; x.literals[2] = ":"; x.literals[3] = ":"; x.literals[4] = "."; x.literals[5] = String.Empty; x.AppCompatLiteral = ":."; // MinuteSecondSep+SecondFractionSep; x.dd = 2; x.hh = 2; x.mm = 2; x.ss = 2; x.ff = DateTimeFormat.[Friend]MaxSecondsFractionDigits; return x; } // For the "v1" TimeSpan localized patterns, the data is simply literal field separators with // the constants guaranteed to include DHMSF ordered greatest to least significant. // Once the data becomes more complex than this we will need to write a proper tokenizer for // parsing and formatting void Init(StringView format, bool useInvariantFieldLengths) mut { literals = new String[6]; for (int i = 0; i < literals.Count; i++) literals[i] = ""; dd = 0; hh = 0; mm = 0; ss = 0; ff = 0; String sb = scope String(); bool inQuote = false; char8 quote = '\''; int field = 0; for (int i = 0; i < format.Length; i++) { switch (format[i]) { case '\'': case '\"': if (inQuote && (quote == format[i])) { /* we were in a quote and found a matching exit quote, so we are outside a quote now */ Contract.Assert(field >= 0 && field <= 5, "field >= 0 && field <= 5"); if (field >= 0 && field <= 5) { literals[field] = AddOwnedStr(new String(sb)); sb.Length = 0; inQuote = false; } else { return; // how did we get here? } } else if (!inQuote) { /* we are at the start of a new quote block */ quote = format[i]; inQuote = true; } else { /* we were in a quote and saw the other type of quote character, so we are still in a quote */ } break; case '%': Contract.Assert(false, "Unexpected special token '%', Bug in DateTimeFormatInfo.FullTimeSpan[Positive|Negative]Pattern"); sb.Append(format[i]); case '\\': if (!inQuote) { i++; /* skip next character that is escaped by this backslash or percent sign */ break; } sb.Append(format[i]); case 'd': if (!inQuote) { Contract.Assert((field == 0 && sb.Length == 0) || field == 1, "field == 0 || field == 1, Bug in DateTimeFormatInfo.FullTimeSpan[Positive|Negative]Pattern"); field = 1; // DayHourSep dd++; } break; case 'h': if (!inQuote) { Contract.Assert((field == 1 && sb.Length == 0) || field == 2, "field == 1 || field == 2, Bug in DateTimeFormatInfo.FullTimeSpan[Positive|Negative]Pattern"); field = 2; // HourMinuteSep hh++; } break; case 'm': if (!inQuote) { Contract.Assert((field == 2 && sb.Length == 0) || field == 3, "field == 2 || field == 3, Bug in DateTimeFormatInfo.FullTimeSpan[Positive|Negative]Pattern"); field = 3; // MinuteSecondSep mm++; } break; case 's': if (!inQuote) { Contract.Assert((field == 3 && sb.Length == 0) || field == 4, "field == 3 || field == 4, Bug in DateTimeFormatInfo.FullTimeSpan[Positive|Negative]Pattern"); field = 4; // SecondFractionSep ss++; } break; case 'f': case 'F': if (!inQuote) { Contract.Assert((field == 4 && sb.Length == 0) || field == 5, "field == 4 || field == 5, Bug in DateTimeFormatInfo.FullTimeSpan[Positive|Negative]Pattern"); field = 5; // End ff++; } break; default: sb.Append(format[i]); break; } } Contract.Assert(field == 5); AppCompatLiteral = AddOwnedStr(new String(MinuteSecondSep, SecondFractionSep)); Contract.Assert(0 < dd && dd < 3, "0 < dd && dd < 3, Bug in System.Globalization.DateTimeFormatInfo.FullTimeSpan[Positive|Negative]Pattern"); Contract.Assert(0 < hh && hh < 3, "0 < hh && hh < 3, Bug in System.Globalization.DateTimeFormatInfo.FullTimeSpan[Positive|Negative]Pattern"); Contract.Assert(0 < mm && mm < 3, "0 < mm && mm < 3, Bug in System.Globalization.DateTimeFormatInfo.FullTimeSpan[Positive|Negative]Pattern"); Contract.Assert(0 < ss && ss < 3, "0 < ss && ss < 3, Bug in System.Globalization.DateTimeFormatInfo.FullTimeSpan[Positive|Negative]Pattern"); Contract.Assert(0 < ff && ff < 8, "0 < ff && ff < 8, Bug in System.Globalization.DateTimeFormatInfo.FullTimeSpan[Positive|Negative]Pattern"); if (useInvariantFieldLengths) { dd = 2; hh = 2; mm = 2; ss = 2; ff = DateTimeFormat.[Friend]MaxSecondsFractionDigits; } else { if (dd < 1 || dd > 2) dd = 2; // The DTFI property has a problem. let's try to make the best of the situation. if (hh < 1 || hh > 2) hh = 2; if (mm < 1 || mm > 2) mm = 2; if (ss < 1 || ss > 2) ss = 2; if (ff < 1 || ff > 7) ff = 7; } } } //end of struct FormatLiterals } }