Fix to UriTemplate.Match to properly handle query parameters without a value. No...
[mono.git] / mcs / class / corlib / System / DateTimeUtils.cs
1 /*
2  * System.DateTimeUtils
3  *
4  *  Copyright (C) 2007 Novell, Inc (http://www.novell.com) 
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining
7  * a copy of this software and associated documentation files (the
8  * "Software"), to deal in the Software without restriction, including
9  * without limitation the rights to use, copy, modify, merge, publish,
10  * distribute, sublicense, and/or sell copies of the Software, and to
11  * permit persons to whom the Software is furnished to do so, subject to
12  * the following conditions:
13  * 
14  * The above copyright notice and this permission notice shall be
15  * included in all copies or substantial portions of the Software.
16  * 
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21  * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22  * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23  * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24  */
25
26 using System.Globalization;
27 using System.Text;
28
29 namespace System {
30         internal static class DateTimeUtils {
31                 public static int CountRepeat (string fmt, int p, char c)
32                 {
33                         int l = fmt.Length;
34                         int i = p + 1;
35                         while ((i < l) && (fmt [i] == c)) 
36                                 i++;
37                         
38                         return i - p;
39                 }
40
41                 public static unsafe void ZeroPad (StringBuilder output, int digits, int len)
42                 {
43                         // more than enough for an int
44                         char* buffer = stackalloc char [16];
45                         int pos = 16;
46                         
47                         do {
48                                 buffer [-- pos] = (char) ('0' + digits % 10);
49                                 digits /= 10;
50                                 len --;
51                         } while (digits > 0);
52                         
53                         while (len -- > 0)
54                                 buffer [-- pos] = '0';
55                         
56                         output.Append (new string (buffer, pos, 16 - pos));
57                 }
58
59                 static int ParseQuotedString (string fmt, int pos, StringBuilder output)
60                 {
61                         // pos == position of " or '
62                         
63                         int len = fmt.Length;
64                         int start = pos;
65                         char quoteChar = fmt [pos++];
66                         
67                         while (pos < len) {
68                                 char ch = fmt [pos++];
69                                 
70                                 if (ch == quoteChar)
71                                         return pos - start;
72                                 
73                                 if (ch == '\\') {
74                                         // C-Style escape
75                                         if (pos >= len)
76                                                 throw new FormatException("Un-ended quote");
77         
78                                         if (output != null)
79                                                 output.Append (fmt [pos++]);
80                                 } else {
81                                         if (output != null)
82                                                 output.Append (ch);
83                                 }
84                         }
85
86                         throw new FormatException("Un-ended quote");
87                 }
88
89                 public static string GetStandardPattern (char format, DateTimeFormatInfo dfi, out bool useutc, out bool use_invariant)
90                 {
91                         return GetStandardPattern (format, dfi, out useutc, out use_invariant, false);
92                 }
93
94                 public static string GetStandardPattern (char format, DateTimeFormatInfo dfi, out bool useutc, out bool use_invariant, bool date_time_offset)
95                 {
96                         String pattern;
97
98                         useutc = false;
99                         use_invariant = false;
100
101                         switch (format)
102                         {
103                         case 'd':
104                                 pattern = dfi.ShortDatePattern;
105                                 break;
106                         case 'D':
107                                 pattern = dfi.LongDatePattern;
108                                 break;
109                         case 'f':
110                                 pattern = dfi.LongDatePattern + " " + dfi.ShortTimePattern;
111                                 break;
112                         case 'F':
113                                 pattern = dfi.FullDateTimePattern;
114                                 break;
115                         case 'g':
116                                 pattern = dfi.ShortDatePattern + " " + dfi.ShortTimePattern;
117                                 break;
118                         case 'G':
119                                 pattern = dfi.ShortDatePattern + " " + dfi.LongTimePattern;
120                                 break;
121                         case 'm':
122                         case 'M':
123                                 pattern = dfi.MonthDayPattern;
124                                 break;
125                         case 'o':
126                         case 'O':
127                                 pattern = dfi.RoundtripPattern;
128                                 use_invariant = true;
129                                 break;
130                         case 'r':
131                         case 'R':
132                                 pattern = dfi.RFC1123Pattern;
133                                 if (date_time_offset)
134                                         useutc = true;
135                                 use_invariant = true;
136                                 break;
137                         case 's':
138                                 pattern = dfi.SortableDateTimePattern;
139                                 use_invariant = true;
140                                 break;
141                         case 't':
142                                 pattern = dfi.ShortTimePattern;
143                                 break;
144                         case 'T':
145                                 pattern = dfi.LongTimePattern;
146                                 break;
147                         case 'u':
148                                 pattern = dfi.UniversalSortableDateTimePattern;
149                                 if (date_time_offset)
150                                         useutc = true;
151                                 use_invariant = true;
152                                 break;
153                         case 'U':
154                                 if (date_time_offset)
155                                         pattern = null;
156                                 else {
157 //                                      pattern = dfi.LongDatePattern + " " + dfi.LongTimePattern;
158                                         pattern = dfi.FullDateTimePattern;
159                                         useutc = true;
160                                 }
161                                 break;
162                         case 'y':
163                         case 'Y':
164                                 pattern = dfi.YearMonthPattern;
165                                 break;
166                         default:
167                                 pattern = null;
168                                 break;
169 //                              throw new FormatException (String.Format ("Invalid format pattern: '{0}'", format));
170                         }
171
172                         return pattern;
173                 }
174
175                 public static string ToString (DateTime dt, string format, DateTimeFormatInfo dfi)
176                 {
177                         return ToString (dt, null, format, dfi);
178                 }
179
180                 public static string ToString (DateTime dt, TimeSpan? utc_offset, string format, DateTimeFormatInfo dfi)
181                 {
182                         // the length of the format is usually a good guess of the number
183                         // of chars in the result. Might save us a few bytes sometimes
184                         // Add + 10 for cases like mmmm dddd
185                         StringBuilder result = new StringBuilder (format.Length + 10);
186
187                         int i = 0;
188                         bool saw_day_specifier = false;
189
190                         while (i < format.Length) {
191                                 int tokLen;
192                                 bool omitZeros = false;
193                                 char ch = format [i];
194
195                                 switch (ch) {
196
197                                 //
198                                 // Time Formats
199                                 //
200                                 case 'h':
201                                         // hour, [1, 12]
202                                         tokLen = DateTimeUtils.CountRepeat (format, i, ch);
203
204                                         int hr = dt.Hour % 12;
205                                         if (hr == 0)
206                                                 hr = 12;
207
208                                         DateTimeUtils.ZeroPad (result, hr, tokLen == 1 ? 1 : 2);
209                                         break;
210                                 case 'H':
211                                         // hour, [0, 23]
212                                         tokLen = DateTimeUtils.CountRepeat (format, i, ch);
213                                         DateTimeUtils.ZeroPad (result, dt.Hour, tokLen == 1 ? 1 : 2);
214                                         break;
215                                 case 'm':
216                                         // minute, [0, 59]
217                                         tokLen = DateTimeUtils.CountRepeat (format, i, ch);
218                                         DateTimeUtils.ZeroPad (result, dt.Minute, tokLen == 1 ? 1 : 2);
219                                         break;
220                                 case 's':
221                                         // second [0, 29]
222                                         tokLen = DateTimeUtils.CountRepeat (format, i, ch);
223                                         DateTimeUtils.ZeroPad (result, dt.Second, tokLen == 1 ? 1 : 2);
224                                         break;
225                                 case 'F':
226                                         omitZeros = true;
227                                         goto case 'f';
228                                 case 'f':
229                                         // fraction of second, to same number of
230                                         // digits as there are f's
231
232                                         tokLen = DateTimeUtils.CountRepeat (format, i, ch);
233                                         if (tokLen > 7)
234                                                 throw new FormatException ("Invalid Format String");
235
236                                         int dec = (int)((long)(dt.Ticks % TimeSpan.TicksPerSecond) / (long) Math.Pow (10, 7 - tokLen));
237                                         int startLen = result.Length;
238                                         DateTimeUtils.ZeroPad (result, dec, tokLen);
239
240                                         if (omitZeros) {
241                                                 while (result.Length > startLen && result [result.Length - 1] == '0')
242                                                         result.Length--;
243                                                 // when the value was 0, then trim even preceding '.' (!) It is fixed character.
244                                                 if (dec == 0 && startLen > 0 && result [startLen - 1] == '.')
245                                                         result.Length--;
246                                         }
247
248                                         break;
249                                 case 't':
250                                         // AM/PM. t == first char, tt+ == full
251                                         tokLen = DateTimeUtils.CountRepeat (format, i, ch);
252                                         string desig = dt.Hour < 12 ? dfi.AMDesignator : dfi.PMDesignator;
253
254                                         if (tokLen == 1) {
255                                                 if (desig.Length >= 1)
256                                                         result.Append (desig [0]);
257                                         }
258                                         else
259                                                 result.Append (desig);
260
261                                         break;
262                                 case 'z':
263                                         // timezone. t = +/-h; tt = +/-hh; ttt+=+/-hh:mm
264                                         tokLen = DateTimeUtils.CountRepeat (format, i, ch);
265                                         TimeSpan offset = 
266                                                 utc_offset ?? 
267                                                 TimeZone.CurrentTimeZone.GetUtcOffset (dt);
268
269                                         if (offset.Ticks >= 0)
270                                                 result.Append ('+');
271                                         else
272                                                 result.Append ('-');
273
274                                         switch (tokLen) {
275                                         case 1:
276                                                 result.Append (Math.Abs (offset.Hours));
277                                                 break;
278                                         case 2:
279                                                 result.Append (Math.Abs (offset.Hours).ToString ("00"));
280                                                 break;
281                                         default:
282                                                 result.Append (Math.Abs (offset.Hours).ToString ("00"));
283                                                 result.Append (':');
284                                                 result.Append (Math.Abs (offset.Minutes).ToString ("00"));
285                                                 break;
286                                         }
287                                         break;
288                                 case 'K': // 'Z' (UTC) or zzz (Local)
289                                         tokLen = 1;
290
291                                         if (utc_offset != null || dt.Kind == DateTimeKind.Local) {
292                                                 offset = utc_offset ?? TimeZone.CurrentTimeZone.GetUtcOffset (dt);
293                                                 if (offset.Ticks >= 0)
294                                                         result.Append ('+');
295                                                 else
296                                                         result.Append ('-');
297                                                 result.Append (Math.Abs (offset.Hours).ToString ("00"));
298                                                 result.Append (':');
299                                                 result.Append (Math.Abs (offset.Minutes).ToString ("00"));
300                                         } else if (dt.Kind == DateTimeKind.Utc)
301                                                 result.Append ('Z');
302                                         break;
303                                 //
304                                 // Date tokens
305                                 //
306                                 case 'd':
307                                         // day. d(d?) = day of month (leading 0 if two d's)
308                                         // ddd = three leter day of week
309                                         // dddd+ full day-of-week
310                                         tokLen = DateTimeUtils.CountRepeat (format, i, ch);
311
312                                         if (tokLen <= 2)
313                                                 DateTimeUtils.ZeroPad (result, dfi.Calendar.GetDayOfMonth (dt), tokLen == 1 ? 1 : 2);
314                                         else if (tokLen == 3)
315                                                 result.Append (dfi.GetAbbreviatedDayName (dfi.Calendar.GetDayOfWeek (dt)));
316                                         else
317                                                 result.Append (dfi.GetDayName (dfi.Calendar.GetDayOfWeek (dt)));
318
319                                         saw_day_specifier = true;
320                                         break;
321                                 case 'M':
322                                         // Month.m(m?) = month # (with leading 0 if two mm)
323                                         // mmm = 3 letter name
324                                         // mmmm+ = full name
325                                         tokLen = DateTimeUtils.CountRepeat (format, i, ch);
326                                         int month = dfi.Calendar.GetMonth(dt);
327                                         if (tokLen <= 2)
328                                                 DateTimeUtils.ZeroPad (result, month, tokLen);
329                                         else if (tokLen == 3)
330                                                 result.Append (dfi.GetAbbreviatedMonthName (month));
331                                         else {
332                                                 // Handles MMMM dd format
333                                                 if (!saw_day_specifier) {
334                                                         for (int ii = i + 1; ii < format.Length; ++ii) {
335                                                                 ch = format [ii];
336                                                                 if (ch == 'd') {
337                                                                         saw_day_specifier = true;
338                                                                         break;
339                                                                 }
340
341                                                                 if (ch == '\'' || ch == '"') {
342                                                                         ii += ParseQuotedString (format, ii, null) - 1;
343                                                                 }
344                                                         }
345                                                 }
346
347                                                 // NOTE: .NET ignores quoted 'd' and reads it as day specifier but I think 
348                                                 // that's wrong
349                                                 result.Append (saw_day_specifier ? dfi.GetMonthGenitiveName (month) : dfi.GetMonthName (month));
350                                         }
351
352                                         break;
353                                 case 'y':
354                                         // Year. y(y?) = two digit year, with leading 0 if yy
355                                         // yyy+ full year with leading zeros if needed.
356                                         tokLen = DateTimeUtils.CountRepeat (format, i, ch);
357
358                                         if (tokLen <= 2)
359                                                 DateTimeUtils.ZeroPad (result, dfi.Calendar.GetYear (dt) % 100, tokLen);
360                                         else
361                                                 DateTimeUtils.ZeroPad (result, dfi.Calendar.GetYear (dt), tokLen);
362                                         break;
363
364                                 case 'g':
365                                         // Era name
366                                         tokLen = DateTimeUtils.CountRepeat (format, i, ch);
367                                         result.Append (dfi.GetEraName (dfi.Calendar.GetEra (dt)));
368                                         break;
369
370                                 //
371                                 // Other
372                                 //
373                                 case ':':
374                                         result.Append (dfi.TimeSeparator);
375                                         tokLen = 1;
376                                         break;
377                                 case '/':
378                                         result.Append (dfi.DateSeparator);
379                                         tokLen = 1;
380                                         break;
381                                 case '\'': case '"':
382                                         tokLen = ParseQuotedString (format, i, result);
383                                         break;
384                                 case '%':
385                                         if (i >= format.Length - 1)
386                                                 throw new FormatException ("% at end of date time string");
387                                         if (format [i + 1] == '%')
388                                                 throw new FormatException ("%% in date string");
389
390                                         // Look for the next char
391                                         tokLen = 1;
392                                         break;
393                                 case '\\':
394                                         // C-Style escape
395                                         if (i >= format.Length - 1)
396                                                 throw new FormatException ("\\ at end of date time string");
397
398                                         result.Append (format [i + 1]);
399                                         tokLen = 2;
400
401                                         break;
402                                 default:
403                                         // catch all
404                                         result.Append (ch);
405                                         tokLen = 1;
406                                         break;
407                                 }
408                                 i += tokLen;
409                         }
410                         return result.ToString ();
411                 }
412         
413         }
414 }