Update Reference Sources to .NET Framework 4.6.1
[mono.git] / mcs / class / referencesource / mscorlib / system / globalization / timespanformat.cs
1 // ==++==
2 // 
3 //   Copyright (c) Microsoft Corporation.  All rights reserved.
4 // 
5 // ==--==
6 // <OWNER>[....]</OWNER>
7 // 
8 namespace System.Globalization {
9     using System.Text;
10     using System;
11     using System.Diagnostics.Contracts;
12     using System.Globalization;
13
14     internal static class TimeSpanFormat {
15
16         [System.Security.SecuritySafeCritical]  // auto-generated
17         private static String IntToString(int n, int digits) {
18             return ParseNumbers.IntToString(n, 10, digits, '0', 0);
19         }
20
21         internal static readonly FormatLiterals PositiveInvariantFormatLiterals  = TimeSpanFormat.FormatLiterals.InitInvariant(false /*isNegative*/);
22         internal static readonly FormatLiterals NegativeInvariantFormatLiterals  = TimeSpanFormat.FormatLiterals.InitInvariant(true  /*isNegative*/);
23
24         internal enum Pattern {
25             None    = 0,
26             Minimum = 1,
27             Full    = 2,
28         }  
29
30         //
31         //  Format
32         //
33         //  Actions: Main method called from TimeSpan.ToString
34         // 
35         internal static String Format(TimeSpan value, String format, IFormatProvider formatProvider) {
36             if (format == null || format.Length == 0)
37                 format = "c";
38
39             // standard formats
40             if (format.Length == 1) {               
41                 char f = format[0];
42
43                 if (f == 'c' || f == 't' || f == 'T')
44                     return FormatStandard(value, true, format, Pattern.Minimum);
45                 if (f == 'g' || f == 'G') {
46                     Pattern pattern;
47                     DateTimeFormatInfo dtfi = DateTimeFormatInfo.GetInstance(formatProvider);
48
49                     if (value._ticks < 0)
50                         format = dtfi.FullTimeSpanNegativePattern;
51                     else
52                         format = dtfi.FullTimeSpanPositivePattern;
53                     if (f == 'g')
54                         pattern = Pattern.Minimum;
55                     else
56                         pattern = Pattern.Full;
57                   
58                     return FormatStandard(value, false, format, pattern);
59                 }
60                 throw new FormatException(Environment.GetResourceString("Format_InvalidString"));
61             }
62
63             return FormatCustomized(value, format, DateTimeFormatInfo.GetInstance(formatProvider));
64         }
65
66         //
67         //  FormatStandard
68         //
69         //  Actions: Format the TimeSpan instance using the specified format.
70         // 
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;
75
76             if (value._ticks < 0) {
77                 day = -day;
78                 time = -time;
79             }
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);
84
85             FormatLiterals literal;
86             if (isInvariant) {
87                 if (value._ticks < 0)
88                     literal = NegativeInvariantFormatLiterals;
89                 else
90                     literal = PositiveInvariantFormatLiterals;
91             }
92             else {
93                 literal = new FormatLiterals();
94                 literal.Init(format, pattern == Pattern.Full);
95             }
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));
98             }
99
100             // Pattern.Full: [-]dd.hh:mm:ss.fffffff
101             // Pattern.Minimum: [-][d.]hh:mm:ss[.fffffff] 
102
103             sb.Append(literal.Start);                           // [-]
104             if (pattern == Pattern.Full || day != 0) {          //
105                 sb.Append(day);                                 // [dd]
106                 sb.Append(literal.DayHourSep);                  // [.]
107             }                                                   //
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;
118                         effectiveDigits--;
119                     }
120                     else {
121                         break;
122                     }
123                 }
124                 if (effectiveDigits > 0) {
125                     sb.Append(literal.SecondFractionSep);           // [.FFFFFFF]
126                     sb.Append((fraction).ToString(DateTimeFormat.fixedNumberFormats[effectiveDigits - 1], CultureInfo.InvariantCulture));
127                 }
128             }
129             else if (pattern == Pattern.Full || fraction != 0) {
130                 sb.Append(literal.SecondFractionSep);           // [.]
131                 sb.Append(IntToString(fraction, literal.ff));   // [fffffff]
132             }                                                   //
133             sb.Append(literal.End);                             //
134
135             return StringBuilderCache.GetStringAndRelease(sb);
136         }
137
138
139
140
141         //
142         //  FormatCustomized
143         //
144         //  Actions: Format the TimeSpan instance using the specified format.
145         // 
146         internal static String FormatCustomized(TimeSpan value, String format, DateTimeFormatInfo dtfi) {                      
147
148             Contract.Assert(dtfi != null, "dtfi == null");
149
150             int day = (int)(value._ticks / TimeSpan.TicksPerDay);
151             long time = value._ticks % TimeSpan.TicksPerDay;
152
153             if (value._ticks < 0) {
154                 day = -day;
155                 time = -time;
156             }
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);
161
162             long tmp = 0;
163             int i = 0;
164             int tokenLen;
165             StringBuilder result = StringBuilderCache.Acquire();
166             
167             while (i < format.Length) {
168                 char ch = format[i];
169                 int nextChar;
170                 switch (ch) {
171                     case 'h':
172                         tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
173                         if (tokenLen > 2)
174                             throw new FormatException(Environment.GetResourceString("Format_InvalidString"));
175                         DateTimeFormat.FormatDigits(result, hours, tokenLen);
176                         break;
177                     case 'm':
178                         tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
179                         if (tokenLen > 2)
180                             throw new FormatException(Environment.GetResourceString("Format_InvalidString"));
181                         DateTimeFormat.FormatDigits(result, minutes, tokenLen);
182                         break;
183                     case 's':
184                         tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
185                         if (tokenLen > 2)
186                             throw new FormatException(Environment.GetResourceString("Format_InvalidString"));
187                         DateTimeFormat.FormatDigits(result, seconds, tokenLen);
188                         break;
189                     case 'f':
190                         //
191                         // The fraction of a second in single-digit precision. The remaining digits are truncated. 
192                         //
193                         tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
194                         if (tokenLen > DateTimeFormat.MaxSecondsFractionDigits)
195                             throw new FormatException(Environment.GetResourceString("Format_InvalidString"));
196
197                         tmp = (long)fraction;
198                         tmp /= (long)Math.Pow(10, DateTimeFormat.MaxSecondsFractionDigits - tokenLen);
199                         result.Append((tmp).ToString(DateTimeFormat.fixedNumberFormats[tokenLen - 1], CultureInfo.InvariantCulture));
200                         break;
201                     case 'F':
202                         //
203                         // Displays the most significant digit of the seconds fraction. Nothing is displayed if the digit is zero.
204                         //
205                         tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
206                         if (tokenLen > DateTimeFormat.MaxSecondsFractionDigits)
207                             throw new FormatException(Environment.GetResourceString("Format_InvalidString"));
208
209                         tmp = (long)fraction;
210                         tmp /= (long)Math.Pow(10, DateTimeFormat.MaxSecondsFractionDigits - tokenLen);
211                         int effectiveDigits = tokenLen;
212                         while (effectiveDigits > 0) {
213                             if (tmp % 10 == 0) {
214                                 tmp = tmp / 10;
215                                 effectiveDigits--;
216                             }
217                             else {
218                                 break;
219                             }
220                         }
221                         if (effectiveDigits > 0) {
222                             result.Append((tmp).ToString(DateTimeFormat.fixedNumberFormats[effectiveDigits - 1], CultureInfo.InvariantCulture));
223                         }
224                         break;
225                     case 'd':
226                         //
227                         // tokenLen == 1 : Day as digits with no leading zero.
228                         // tokenLen == 2+: Day as digits with leading zero for single-digit days.
229                         //
230                         tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
231                         if (tokenLen > 8)
232                             throw new FormatException(Environment.GetResourceString("Format_InvalidString"));
233                         DateTimeFormat.FormatDigits(result, day, tokenLen, true);
234                         break;
235                     case '\'':
236                     case '\"':
237                         StringBuilder enquotedString = new StringBuilder();
238                         tokenLen = DateTimeFormat.ParseQuoteString(format, i, enquotedString); 
239                         result.Append(enquotedString);
240                         break;
241                     case '%':
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));
250                             tokenLen = 2;
251                         }
252                         else
253                         {
254                             //
255                             // This means that '%' is at the end of the format string or
256                             // "%%" appears in the format string.
257                             //
258                             throw new FormatException(Environment.GetResourceString("Format_InvalidString"));
259                         }
260                         break;
261                     case '\\':
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.
264                         //
265                         nextChar = DateTimeFormat.ParseNextChar(format, i);
266                         if (nextChar >= 0)
267                         {
268                             result.Append(((char)nextChar));
269                             tokenLen = 2;
270                         } 
271                         else
272                         {
273                             //
274                             // This means that '\' is at the end of the formatting string.
275                             //
276                             throw new FormatException(Environment.GetResourceString("Format_InvalidString"));
277                         }
278                         break;
279                     default:
280                         throw new FormatException(Environment.GetResourceString("Format_InvalidString"));
281                 }
282                 i += tokenLen;
283             }
284             return StringBuilderCache.GetStringAndRelease(result);
285
286         }
287
288
289
290
291         internal struct FormatLiterals {
292             internal String Start {
293                 get {
294                     return literals[0];
295                 }
296             }
297             internal String DayHourSep {
298                 get {
299                     return literals[1];
300                 }
301             }
302             internal String HourMinuteSep {
303                 get {
304                     return literals[2];
305                 }
306             }
307             internal String MinuteSecondSep {
308                 get {
309                     return literals[3];
310                 }
311             }
312             internal String SecondFractionSep {
313                 get {
314                     return literals[4];
315                 }
316             }
317             internal String End {
318                 get {
319                     return literals[5];
320                 }
321             }
322             internal String AppCompatLiteral;
323             internal int dd;
324             internal int hh;
325             internal int mm;
326             internal int ss;
327             internal int ff;  
328
329             private String[] literals;
330
331
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;
337                 x.literals[1] = ".";
338                 x.literals[2] = ":";
339                 x.literals[3] = ":";
340                 x.literals[4] = ".";
341                 x.literals[5] = String.Empty;  
342                 x.AppCompatLiteral = ":."; // MinuteSecondSep+SecondFractionSep;       
343                 x.dd = 2;
344                 x.hh = 2;
345                 x.mm = 2;
346                 x.ss = 2;
347                 x.ff = DateTimeFormat.MaxSecondsFractionDigits;
348                 return x;
349             }
350
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;
359                 dd = 0;
360                 hh = 0;
361                 mm = 0;
362                 ss = 0;
363                 ff = 0;
364
365                 StringBuilder sb = StringBuilderCache.Acquire();
366                 bool inQuote = false;
367                 char quote = '\'';
368                 int  field = 0;
369
370                 for (int i = 0; i < format.Length; i++) {
371                     switch (format[i]) {
372                         case '\'':
373                         case '\"':
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();
379                                     sb.Length = 0;
380                                     inQuote = false;
381                                 }
382                                 else {                                   
383                                     return; // how did we get here?
384                                 }
385                             }
386                             else if (!inQuote) {
387                                 /* we are at the start of a new quote block */
388                                 quote = format[i];
389                                 inQuote = true;
390                             }
391                             else {
392                                 /* we were in a quote and saw the other type of quote character, so we are still in a quote */
393                             }
394                             break;
395                         case '%':
396                             Contract.Assert(false, "Unexpected special token '%', Bug in DateTimeFormatInfo.FullTimeSpan[Positive|Negative]Pattern");
397                             goto default;
398                         case '\\':
399                             if (!inQuote) {
400                                 i++; /* skip next character that is escaped by this backslash or percent sign */
401                                 break;
402                             }
403                             goto default;
404                         case 'd':
405                             if (!inQuote) {
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
409                                 dd++;
410                             }
411                             break;
412                         case 'h':
413                             if (!inQuote) {
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
417                                 hh++;
418                             }
419                             break;
420                         case 'm':
421                             if (!inQuote) {
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
425                                 mm++;
426                             }
427                             break;
428                         case 's':
429                             if (!inQuote) {
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
433                                 ss++;
434                             }
435                             break;
436                         case 'f':
437                         case 'F':
438                             if (!inQuote) {
439                                 Contract.Assert((field == 4 && sb.Length == 0) || field == 5,
440                                                 "field == 4 || field == 5, Bug in DateTimeFormatInfo.FullTimeSpan[Positive|Negative]Pattern");
441                                 field = 5; // End
442                                 ff++;
443                             }
444                             break;
445                         default:
446                             sb.Append(format[i]);
447                             break;
448                     }
449                 }
450
451                 Contract.Assert(field == 5);
452                 AppCompatLiteral = MinuteSecondSep + SecondFractionSep;
453
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");
459
460                 if (useInvariantFieldLengths) {
461                     dd = 2;
462                     hh = 2;
463                     mm = 2;
464                     ss = 2;
465                     ff = DateTimeFormat.MaxSecondsFractionDigits;
466                 }
467                 else {
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;
473                 }
474                 StringBuilderCache.Release(sb);
475             }
476         } //end of struct FormatLiterals
477     }
478 }