4 * Copyright (C) 2007 Novell, Inc (http://www.novell.com)
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:
14 * The above copyright notice and this permission notice shall be
15 * included in all copies or substantial portions of the Software.
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.
26 using System.Globalization;
30 internal static class DateTimeUtils {
31 public static int CountRepeat (string fmt, int p, char c)
35 while ((i < l) && (fmt [i] == c))
41 public static unsafe void ZeroPad (StringBuilder output, int digits, int len)
43 // more than enough for an int
44 char* buffer = stackalloc char [16];
48 buffer [-- pos] = (char) ('0' + digits % 10);
54 buffer [-- pos] = '0';
56 output.Append (new string (buffer, pos, 16 - pos));
59 static int ParseQuotedString (string fmt, int pos, StringBuilder output)
61 // pos == position of " or '
65 char quoteChar = fmt [pos++];
68 char ch = fmt [pos++];
76 throw new FormatException("Un-ended quote");
79 output.Append (fmt [pos++]);
86 throw new FormatException("Un-ended quote");
89 public static string GetStandardPattern (char format, DateTimeFormatInfo dfi, out bool useutc, out bool use_invariant)
91 return GetStandardPattern (format, dfi, out useutc, out use_invariant, false);
94 public static string GetStandardPattern (char format, DateTimeFormatInfo dfi, out bool useutc, out bool use_invariant, bool date_time_offset)
99 use_invariant = false;
104 pattern = dfi.ShortDatePattern;
107 pattern = dfi.LongDatePattern;
110 pattern = dfi.LongDatePattern + " " + dfi.ShortTimePattern;
113 pattern = dfi.FullDateTimePattern;
116 pattern = dfi.ShortDatePattern + " " + dfi.ShortTimePattern;
119 pattern = dfi.ShortDatePattern + " " + dfi.LongTimePattern;
123 pattern = dfi.MonthDayPattern;
127 pattern = dfi.RoundtripPattern;
128 use_invariant = true;
132 pattern = dfi.RFC1123Pattern;
133 if (date_time_offset)
135 use_invariant = true;
138 pattern = dfi.SortableDateTimePattern;
139 use_invariant = true;
142 pattern = dfi.ShortTimePattern;
145 pattern = dfi.LongTimePattern;
148 pattern = dfi.UniversalSortableDateTimePattern;
149 if (date_time_offset)
151 use_invariant = true;
154 if (date_time_offset)
157 // pattern = dfi.LongDatePattern + " " + dfi.LongTimePattern;
158 pattern = dfi.FullDateTimePattern;
164 pattern = dfi.YearMonthPattern;
169 // throw new FormatException (String.Format ("Invalid format pattern: '{0}'", format));
175 public static string ToString (DateTime dt, string format, DateTimeFormatInfo dfi)
177 return ToString (dt, null, format, dfi);
180 public static string ToString (DateTime dt, TimeSpan? utc_offset, string format, DateTimeFormatInfo dfi)
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);
188 bool saw_day_specifier = false;
190 while (i < format.Length) {
192 bool omitZeros = false;
193 char ch = format [i];
202 tokLen = DateTimeUtils.CountRepeat (format, i, ch);
204 int hr = dt.Hour % 12;
208 DateTimeUtils.ZeroPad (result, hr, tokLen == 1 ? 1 : 2);
212 tokLen = DateTimeUtils.CountRepeat (format, i, ch);
213 DateTimeUtils.ZeroPad (result, dt.Hour, tokLen == 1 ? 1 : 2);
217 tokLen = DateTimeUtils.CountRepeat (format, i, ch);
218 DateTimeUtils.ZeroPad (result, dt.Minute, tokLen == 1 ? 1 : 2);
222 tokLen = DateTimeUtils.CountRepeat (format, i, ch);
223 DateTimeUtils.ZeroPad (result, dt.Second, tokLen == 1 ? 1 : 2);
229 // fraction of second, to same number of
230 // digits as there are f's
232 tokLen = DateTimeUtils.CountRepeat (format, i, ch);
234 throw new FormatException ("Invalid Format String");
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);
241 while (result.Length > startLen && result [result.Length - 1] == '0')
243 // when the value was 0, then trim even preceding '.' (!) It is fixed character.
244 if (dec == 0 && startLen > 0 && result [startLen - 1] == '.')
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;
255 if (desig.Length >= 1)
256 result.Append (desig [0]);
259 result.Append (desig);
263 // timezone. t = +/-h; tt = +/-hh; ttt+=+/-hh:mm
264 tokLen = DateTimeUtils.CountRepeat (format, i, ch);
267 TimeZone.CurrentTimeZone.GetUtcOffset (dt);
269 if (offset.Ticks >= 0)
276 result.Append (Math.Abs (offset.Hours));
279 result.Append (Math.Abs (offset.Hours).ToString ("00"));
282 result.Append (Math.Abs (offset.Hours).ToString ("00"));
284 result.Append (Math.Abs (offset.Minutes).ToString ("00"));
288 case 'K': // 'Z' (UTC) or zzz (Local)
291 if (utc_offset != null || dt.Kind == DateTimeKind.Local) {
292 offset = utc_offset ?? TimeZone.CurrentTimeZone.GetUtcOffset (dt);
293 if (offset.Ticks >= 0)
297 result.Append (Math.Abs (offset.Hours).ToString ("00"));
299 result.Append (Math.Abs (offset.Minutes).ToString ("00"));
300 } else if (dt.Kind == DateTimeKind.Utc)
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);
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)));
317 result.Append (dfi.GetDayName (dfi.Calendar.GetDayOfWeek (dt)));
319 saw_day_specifier = true;
322 // Month.m(m?) = month # (with leading 0 if two mm)
323 // mmm = 3 letter name
325 tokLen = DateTimeUtils.CountRepeat (format, i, ch);
326 int month = dfi.Calendar.GetMonth(dt);
328 DateTimeUtils.ZeroPad (result, month, tokLen);
329 else if (tokLen == 3)
330 result.Append (dfi.GetAbbreviatedMonthName (month));
332 // Handles MMMM dd format
333 if (!saw_day_specifier) {
334 for (int ii = i + 1; ii < format.Length; ++ii) {
337 saw_day_specifier = true;
341 if (ch == '\'' || ch == '"') {
342 ii += ParseQuotedString (format, ii, null) - 1;
347 // NOTE: .NET ignores quoted 'd' and reads it as day specifier but I think
349 result.Append (saw_day_specifier ? dfi.GetMonthGenitiveName (month) : dfi.GetMonthName (month));
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);
359 DateTimeUtils.ZeroPad (result, dfi.Calendar.GetYear (dt) % 100, tokLen);
361 DateTimeUtils.ZeroPad (result, dfi.Calendar.GetYear (dt), tokLen);
366 tokLen = DateTimeUtils.CountRepeat (format, i, ch);
367 result.Append (dfi.GetEraName (dfi.Calendar.GetEra (dt)));
374 result.Append (dfi.TimeSeparator);
378 result.Append (dfi.DateSeparator);
382 tokLen = ParseQuotedString (format, i, result);
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");
390 // Look for the next char
395 if (i >= format.Length - 1)
396 throw new FormatException ("\\ at end of date time string");
398 result.Append (format [i + 1]);
410 return result.ToString ();