3 // Copyright (c) Microsoft Corporation. All rights reserved.
6 // <OWNER>[....]</OWNER>
8 namespace System.Globalization {
11 using System.Diagnostics.Contracts;
12 using System.Globalization;
14 internal static class TimeSpanFormat {
16 [System.Security.SecuritySafeCritical] // auto-generated
17 private static String IntToString(int n, int digits) {
18 return ParseNumbers.IntToString(n, 10, digits, '0', 0);
21 internal static readonly FormatLiterals PositiveInvariantFormatLiterals = TimeSpanFormat.FormatLiterals.InitInvariant(false /*isNegative*/);
22 internal static readonly FormatLiterals NegativeInvariantFormatLiterals = TimeSpanFormat.FormatLiterals.InitInvariant(true /*isNegative*/);
24 internal enum Pattern {
33 // Actions: Main method called from TimeSpan.ToString
35 internal static String Format(TimeSpan value, String format, IFormatProvider formatProvider) {
36 if (format == null || format.Length == 0)
40 if (format.Length == 1) {
43 if (f == 'c' || f == 't' || f == 'T')
44 return FormatStandard(value, true, format, Pattern.Minimum);
45 if (f == 'g' || f == 'G') {
47 DateTimeFormatInfo dtfi = DateTimeFormatInfo.GetInstance(formatProvider);
50 format = dtfi.FullTimeSpanNegativePattern;
52 format = dtfi.FullTimeSpanPositivePattern;
54 pattern = Pattern.Minimum;
56 pattern = Pattern.Full;
58 return FormatStandard(value, false, format, pattern);
60 throw new FormatException(Environment.GetResourceString("Format_InvalidString"));
63 return FormatCustomized(value, format, DateTimeFormatInfo.GetInstance(formatProvider));
69 // Actions: Format the TimeSpan instance using the specified format.
71 private static String FormatStandard(TimeSpan value, bool isInvariant, String format, Pattern pattern) {
72 StringBuilder sb = StringBuilderCache.Acquire();
73 int day = (int)(value._ticks / TimeSpan.TicksPerDay);
74 long time = value._ticks % TimeSpan.TicksPerDay;
76 if (value._ticks < 0) {
80 int hours = (int)(time / TimeSpan.TicksPerHour % 24);
81 int minutes = (int)(time / TimeSpan.TicksPerMinute % 60);
82 int seconds = (int)(time / TimeSpan.TicksPerSecond % 60);
83 int fraction = (int)(time % TimeSpan.TicksPerSecond);
85 FormatLiterals literal;
88 literal = NegativeInvariantFormatLiterals;
90 literal = PositiveInvariantFormatLiterals;
93 literal = new FormatLiterals();
94 literal.Init(format, pattern == Pattern.Full);
96 if (fraction != 0) { // truncate the partial second to the specified length
97 fraction = (int)((long)fraction / (long)Math.Pow(10, DateTimeFormat.MaxSecondsFractionDigits - literal.ff));
100 // Pattern.Full: [-]dd.hh:mm:ss.fffffff
101 // Pattern.Minimum: [-][d.]hh:mm:ss[.fffffff]
103 sb.Append(literal.Start); // [-]
104 if (pattern == Pattern.Full || day != 0) { //
105 sb.Append(day); // [dd]
106 sb.Append(literal.DayHourSep); // [.]
108 sb.Append(IntToString(hours, literal.hh)); // hh
109 sb.Append(literal.HourMinuteSep); // :
110 sb.Append(IntToString(minutes, literal.mm)); // mm
111 sb.Append(literal.MinuteSecondSep); // :
112 sb.Append(IntToString(seconds, literal.ss)); // ss
113 if (!isInvariant && pattern == Pattern.Minimum) {
114 int effectiveDigits = literal.ff;
115 while (effectiveDigits > 0) {
116 if (fraction % 10 == 0) {
117 fraction = fraction / 10;
124 if (effectiveDigits > 0) {
125 sb.Append(literal.SecondFractionSep); // [.FFFFFFF]
126 sb.Append((fraction).ToString(DateTimeFormat.fixedNumberFormats[effectiveDigits - 1], CultureInfo.InvariantCulture));
129 else if (pattern == Pattern.Full || fraction != 0) {
130 sb.Append(literal.SecondFractionSep); // [.]
131 sb.Append(IntToString(fraction, literal.ff)); // [fffffff]
133 sb.Append(literal.End); //
135 return StringBuilderCache.GetStringAndRelease(sb);
144 // Actions: Format the TimeSpan instance using the specified format.
146 internal static String FormatCustomized(TimeSpan value, String format, DateTimeFormatInfo dtfi) {
148 Contract.Assert(dtfi != null, "dtfi == null");
150 int day = (int)(value._ticks / TimeSpan.TicksPerDay);
151 long time = value._ticks % TimeSpan.TicksPerDay;
153 if (value._ticks < 0) {
157 int hours = (int)(time / TimeSpan.TicksPerHour % 24);
158 int minutes = (int)(time / TimeSpan.TicksPerMinute % 60);
159 int seconds = (int)(time / TimeSpan.TicksPerSecond % 60);
160 int fraction = (int)(time % TimeSpan.TicksPerSecond);
165 StringBuilder result = StringBuilderCache.Acquire();
167 while (i < format.Length) {
172 tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
174 throw new FormatException(Environment.GetResourceString("Format_InvalidString"));
175 DateTimeFormat.FormatDigits(result, hours, tokenLen);
178 tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
180 throw new FormatException(Environment.GetResourceString("Format_InvalidString"));
181 DateTimeFormat.FormatDigits(result, minutes, tokenLen);
184 tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
186 throw new FormatException(Environment.GetResourceString("Format_InvalidString"));
187 DateTimeFormat.FormatDigits(result, seconds, tokenLen);
191 // The fraction of a second in single-digit precision. The remaining digits are truncated.
193 tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
194 if (tokenLen > DateTimeFormat.MaxSecondsFractionDigits)
195 throw new FormatException(Environment.GetResourceString("Format_InvalidString"));
197 tmp = (long)fraction;
198 tmp /= (long)Math.Pow(10, DateTimeFormat.MaxSecondsFractionDigits - tokenLen);
199 result.Append((tmp).ToString(DateTimeFormat.fixedNumberFormats[tokenLen - 1], CultureInfo.InvariantCulture));
203 // Displays the most significant digit of the seconds fraction. Nothing is displayed if the digit is zero.
205 tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
206 if (tokenLen > DateTimeFormat.MaxSecondsFractionDigits)
207 throw new FormatException(Environment.GetResourceString("Format_InvalidString"));
209 tmp = (long)fraction;
210 tmp /= (long)Math.Pow(10, DateTimeFormat.MaxSecondsFractionDigits - tokenLen);
211 int effectiveDigits = tokenLen;
212 while (effectiveDigits > 0) {
221 if (effectiveDigits > 0) {
222 result.Append((tmp).ToString(DateTimeFormat.fixedNumberFormats[effectiveDigits - 1], CultureInfo.InvariantCulture));
227 // tokenLen == 1 : Day as digits with no leading zero.
228 // tokenLen == 2+: Day as digits with leading zero for single-digit days.
230 tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
232 throw new FormatException(Environment.GetResourceString("Format_InvalidString"));
233 DateTimeFormat.FormatDigits(result, day, tokenLen, true);
237 StringBuilder enquotedString = new StringBuilder();
238 tokenLen = DateTimeFormat.ParseQuoteString(format, i, enquotedString);
239 result.Append(enquotedString);
242 // Optional format character.
243 // For example, format string "%d" will print day
244 // Most of the cases, "%" can be ignored.
245 nextChar = DateTimeFormat.ParseNextChar(format, i);
246 // nextChar will be -1 if we already reach the end of the format string.
247 // Besides, we will not allow "%%" appear in the pattern.
248 if (nextChar >= 0 && nextChar != (int)'%') {
249 result.Append(TimeSpanFormat.FormatCustomized(value, ((char)nextChar).ToString(), dtfi));
255 // This means that '%' is at the end of the format string or
256 // "%%" appears in the format string.
258 throw new FormatException(Environment.GetResourceString("Format_InvalidString"));
262 // Escaped character. Can be used to insert character into the format string.
263 // For example, "\d" will insert the character 'd' into the string.
265 nextChar = DateTimeFormat.ParseNextChar(format, i);
268 result.Append(((char)nextChar));
274 // This means that '\' is at the end of the formatting string.
276 throw new FormatException(Environment.GetResourceString("Format_InvalidString"));
280 throw new FormatException(Environment.GetResourceString("Format_InvalidString"));
284 return StringBuilderCache.GetStringAndRelease(result);
291 internal struct FormatLiterals {
292 internal String Start {
297 internal String DayHourSep {
302 internal String HourMinuteSep {
307 internal String MinuteSecondSep {
312 internal String SecondFractionSep {
317 internal String End {
322 internal String AppCompatLiteral;
329 private String[] literals;
332 /* factory method for static invariant FormatLiterals */
333 internal static FormatLiterals InitInvariant(bool isNegative) {
334 FormatLiterals x = new FormatLiterals();
335 x.literals = new String[6];
336 x.literals[0] = isNegative ? "-" : String.Empty;
341 x.literals[5] = String.Empty;
342 x.AppCompatLiteral = ":."; // MinuteSecondSep+SecondFractionSep;
347 x.ff = DateTimeFormat.MaxSecondsFractionDigits;
351 // For the "v1" TimeSpan localized patterns, the data is simply literal field separators with
352 // the constants guaranteed to include DHMSF ordered greatest to least significant.
353 // Once the data becomes more complex than this we will need to write a proper tokenizer for
354 // parsing and formatting
355 internal void Init(String format, bool useInvariantFieldLengths) {
356 literals = new String[6];
357 for (int i = 0; i < literals.Length; i++)
358 literals[i] = String.Empty;
365 StringBuilder sb = StringBuilderCache.Acquire();
366 bool inQuote = false;
370 for (int i = 0; i < format.Length; i++) {
374 if (inQuote && (quote == format[i])) {
375 /* we were in a quote and found a matching exit quote, so we are outside a quote now */
376 Contract.Assert(field >= 0 && field <= 5, "field >= 0 && field <= 5");
377 if (field >= 0 && field <= 5) {
378 literals[field] = sb.ToString();
383 return; // how did we get here?
387 /* we are at the start of a new quote block */
392 /* we were in a quote and saw the other type of quote character, so we are still in a quote */
396 Contract.Assert(false, "Unexpected special token '%', Bug in DateTimeFormatInfo.FullTimeSpan[Positive|Negative]Pattern");
400 i++; /* skip next character that is escaped by this backslash or percent sign */
406 Contract.Assert((field == 0 && sb.Length == 0) || field == 1,
407 "field == 0 || field == 1, Bug in DateTimeFormatInfo.FullTimeSpan[Positive|Negative]Pattern");
408 field = 1; // DayHourSep
414 Contract.Assert((field == 1 && sb.Length == 0) || field == 2,
415 "field == 1 || field == 2, Bug in DateTimeFormatInfo.FullTimeSpan[Positive|Negative]Pattern");
416 field = 2; // HourMinuteSep
422 Contract.Assert((field == 2 && sb.Length == 0) || field == 3,
423 "field == 2 || field == 3, Bug in DateTimeFormatInfo.FullTimeSpan[Positive|Negative]Pattern");
424 field = 3; // MinuteSecondSep
430 Contract.Assert((field == 3 && sb.Length == 0) || field == 4,
431 "field == 3 || field == 4, Bug in DateTimeFormatInfo.FullTimeSpan[Positive|Negative]Pattern");
432 field = 4; // SecondFractionSep
439 Contract.Assert((field == 4 && sb.Length == 0) || field == 5,
440 "field == 4 || field == 5, Bug in DateTimeFormatInfo.FullTimeSpan[Positive|Negative]Pattern");
446 sb.Append(format[i]);
451 Contract.Assert(field == 5);
452 AppCompatLiteral = MinuteSecondSep + SecondFractionSep;
454 Contract.Assert(0 < dd && dd < 3, "0 < dd && dd < 3, Bug in System.Globalization.DateTimeFormatInfo.FullTimeSpan[Positive|Negative]Pattern");
455 Contract.Assert(0 < hh && hh < 3, "0 < hh && hh < 3, Bug in System.Globalization.DateTimeFormatInfo.FullTimeSpan[Positive|Negative]Pattern");
456 Contract.Assert(0 < mm && mm < 3, "0 < mm && mm < 3, Bug in System.Globalization.DateTimeFormatInfo.FullTimeSpan[Positive|Negative]Pattern");
457 Contract.Assert(0 < ss && ss < 3, "0 < ss && ss < 3, Bug in System.Globalization.DateTimeFormatInfo.FullTimeSpan[Positive|Negative]Pattern");
458 Contract.Assert(0 < ff && ff < 8, "0 < ff && ff < 8, Bug in System.Globalization.DateTimeFormatInfo.FullTimeSpan[Positive|Negative]Pattern");
460 if (useInvariantFieldLengths) {
465 ff = DateTimeFormat.MaxSecondsFractionDigits;
468 if (dd < 1 || dd > 2) dd = 2; // The DTFI property has a problem. let's try to make the best of the situation.
469 if (hh < 1 || hh > 2) hh = 2;
470 if (mm < 1 || mm > 2) mm = 2;
471 if (ss < 1 || ss > 2) ss = 2;
472 if (ff < 1 || ff > 7) ff = 7;
474 StringBuilderCache.Release(sb);
476 } //end of struct FormatLiterals