2 // System.Globalization.DateTimeFormatInfo.cs
5 // Martin Weindel (martin.weindel@t-online.de)
6 // Marek Safar (marek.safar@gmail.com)
8 // (C) Martin Weindel (martin.weindel@t-online.de)
9 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
10 // Copyright (C) 2012 Xamarin Inc (http://www.xamarin.com)
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 using System.Collections.Generic;
34 using System.Runtime.InteropServices;
35 using System.Threading;
37 namespace System.Globalization
40 enum DateTimeFormatFlags {
50 [StructLayout (LayoutKind.Sequential)]
51 public sealed class DateTimeFormatInfo : ICloneable, IFormatProvider
53 const string MSG_READONLY = "This instance is read only";
54 private static readonly string[] INVARIANT_ABBREVIATED_DAY_NAMES
55 = new string[7] { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
56 private static readonly string[] INVARIANT_DAY_NAMES
57 = new string[7] { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" };
58 private static readonly string[] INVARIANT_ABBREVIATED_MONTH_NAMES
59 = new string[13] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "" };
60 private static readonly string[] INVARIANT_MONTH_NAMES
61 = new string[13] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" };
62 static readonly string[] INVARIANT_SHORT_DAY_NAMES =
63 new string[7] { "Su", "Mo", "Tu", "We", "Th", "Fr", "Sa" };
64 static readonly string[] INVARIANT_ERA_NAMES = { "A.D." };
65 static readonly string[] INVARIANT_ABBREVIATED_ERA_NAMES = { "AD" };
67 private static DateTimeFormatInfo theInvariantDateTimeFormatInfo;
69 #pragma warning disable 169
70 #region Sync with object-internals.h
71 private bool m_isReadOnly;
72 private string amDesignator;
73 private string pmDesignator;
74 private string dateSeparator;
75 private string timeSeparator;
76 private string shortDatePattern;
77 private string longDatePattern;
78 private string shortTimePattern;
79 private string longTimePattern;
80 private string monthDayPattern;
81 private string yearMonthPattern;
82 private int firstDayOfWeek;
83 private int calendarWeekRule;
84 private string[] abbreviatedDayNames;
85 private string[] dayNames;
86 private string[] monthNames;
87 private string[] genitiveMonthNames;
88 private string[] abbreviatedMonthNames;
89 private string[] m_genitiveAbbreviatedMonthNames;
91 private string[] allShortDatePatterns;
92 private string[] allLongDatePatterns;
93 private string[] allShortTimePatterns;
94 private string[] allLongTimePatterns;
95 private string[] monthDayPatterns;
96 private string[] yearMonthPatterns;
97 private string[] shortestDayNames;
100 internal readonly CultureInfo culture;
103 // MS Serialization needs this
104 private string fullDateTimePattern;
105 private int nDataItem;
106 private bool m_useUserOverride;
107 private bool m_isDefaultCalendar;
108 private int CultureID;
109 private bool bUseCalendarInfo;
110 private string generalShortTimePattern;
111 private string generalLongTimePattern;
112 private string[] m_eraNames;
113 private string[] m_abbrevEraNames;
114 private string[] m_abbrevEnglishEraNames;
115 private string[] m_dateWords;
116 private int[] optionalCalendars;
117 private string[] leapYearMonthNames;
118 private DateTimeFormatFlags formatFlags;
119 private string m_name; // Unused, but MS.NET serializes this
120 #pragma warning restore 169
122 internal DateTimeFormatInfo (CultureInfo culture, bool read_only)
125 throw new ArgumentNullException ("culture");
127 this.culture = culture;
128 m_isReadOnly = read_only;
134 shortDatePattern = "MM/dd/yyyy";
135 longDatePattern = "dddd, dd MMMM yyyy";
136 shortTimePattern = "HH:mm";
137 longTimePattern = "HH:mm:ss";
138 monthDayPattern = "MMMM dd";
139 yearMonthPattern = "yyyy MMMM";
141 firstDayOfWeek = (int) DayOfWeek.Sunday;
142 calendarWeekRule = (int) CalendarWeekRule.FirstDay;
144 abbreviatedDayNames = INVARIANT_ABBREVIATED_DAY_NAMES;
145 dayNames = INVARIANT_DAY_NAMES;
146 abbreviatedMonthNames = INVARIANT_ABBREVIATED_MONTH_NAMES;
147 monthNames = INVARIANT_MONTH_NAMES;
148 m_genitiveAbbreviatedMonthNames = INVARIANT_ABBREVIATED_MONTH_NAMES;
149 genitiveMonthNames = INVARIANT_MONTH_NAMES;
150 shortestDayNames = INVARIANT_SHORT_DAY_NAMES;
153 public DateTimeFormatInfo ()
154 : this (CultureInfo.InvariantCulture, false)
158 public static DateTimeFormatInfo GetInstance(IFormatProvider provider)
160 if (provider != null) {
161 DateTimeFormatInfo dtfi;
162 dtfi = (DateTimeFormatInfo)provider.GetFormat(typeof(DateTimeFormatInfo));
170 public bool IsReadOnly {
176 public static DateTimeFormatInfo ReadOnly(DateTimeFormatInfo dtfi)
178 DateTimeFormatInfo copy = (DateTimeFormatInfo)dtfi.Clone();
179 copy.m_isReadOnly = true;
183 public object Clone ()
185 DateTimeFormatInfo clone = (DateTimeFormatInfo) MemberwiseClone();
186 // clone is not read only
187 clone.m_isReadOnly = false;
191 public object GetFormat(Type formatType)
193 return (formatType == GetType()) ? this : null;
196 public string GetAbbreviatedEraName (int era)
198 if (era < 0 || era > AbbreviatedEraNames.Length)
199 throw new ArgumentOutOfRangeException ("era", era.ToString ());
200 if (era == Calendar.CurrentEra)
201 era = EraNames.Length;
202 return AbbreviatedEraNames [era - 1];
205 public string GetAbbreviatedMonthName(int month)
207 if (month < 1 || month > 13) throw new ArgumentOutOfRangeException();
208 return abbreviatedMonthNames[month-1];
211 public int GetEra (string eraName)
214 throw new ArgumentNullException ();
215 string [] eras = EraNames;
216 for (int i = 0; i < eras.Length; i++)
217 if (CultureInfo.InvariantCulture.CompareInfo
218 .Compare (eraName, eras [i],
219 CompareOptions.IgnoreCase) == 0)
220 return Calendar.Eras [i];
222 eras = AbbreviatedEraNames;
223 for (int i = 0; i < eras.Length; i++)
224 if (CultureInfo.InvariantCulture.CompareInfo
225 .Compare (eraName, eras [i],
226 CompareOptions.IgnoreCase) == 0)
227 return Calendar.Eras [i];
232 public string GetEraName (int era)
234 if (era < 0 || era > EraNames.Length)
235 throw new ArgumentOutOfRangeException ("era", era.ToString ());
236 if (era == Calendar.CurrentEra)
237 era = EraNames.Length;
238 return EraNames [era - 1];
241 internal string[] EraNames {
243 if (m_eraNames != null)
246 // TODO: Should use Calendar.ID to initialize calendar specific era names
247 // from current culture. We don't have such data yet
248 m_eraNames = INVARIANT_ERA_NAMES;
253 internal string[] AbbreviatedEraNames {
255 if (m_abbrevEraNames != null)
256 return m_abbrevEraNames;
258 // TODO: Should use Calendar.ID to initialize calendar specific era names
259 // from current culture. We don't have such data yet
260 m_abbrevEraNames = INVARIANT_ABBREVIATED_ERA_NAMES;
261 return m_abbrevEraNames;
265 public string GetMonthName(int month)
267 if (month < 1 || month > 13) throw new ArgumentOutOfRangeException();
268 return monthNames[month-1];
271 internal string GetMonthGenitiveName (int month)
273 return genitiveMonthNames [month - 1];
276 public string[] AbbreviatedDayNames
278 get { return (string[]) RawAbbreviatedDayNames.Clone (); }
279 set { RawAbbreviatedDayNames = value; }
282 internal string[] RawAbbreviatedDayNames
286 return abbreviatedDayNames;
289 CheckDaysValue (value);
290 abbreviatedDayNames = (string[]) value.Clone();
294 public string[] AbbreviatedMonthNames
296 get { return (string[]) RawAbbreviatedMonthNames.Clone (); }
297 set { RawAbbreviatedMonthNames = value; }
300 internal string[] RawAbbreviatedMonthNames
304 return abbreviatedMonthNames;
307 CheckMonthsValue (value);
308 abbreviatedMonthNames = (string[]) value.Clone();
312 public string[] DayNames {
314 return (string[]) dayNames.Clone ();
317 CheckDaysValue (value);
318 dayNames = (string[]) value.Clone();
322 internal string[] RawDayNames {
328 public string[] MonthNames {
330 return (string[]) monthNames.Clone ();
333 CheckMonthsValue (value);
334 monthNames = (string[]) value.Clone();
338 internal string[] RawMonthNames {
345 public string[] AbbreviatedMonthGenitiveNames {
347 return (string[]) m_genitiveAbbreviatedMonthNames.Clone ();
350 CheckMonthsValue (value);
351 m_genitiveAbbreviatedMonthNames = value;
356 public string[] MonthGenitiveNames {
358 return (string[]) genitiveMonthNames.Clone ();
361 CheckMonthsValue (value);
362 genitiveMonthNames = value;
366 [MonoLimitation ("Only default calendar is supported")]
368 public string NativeCalendarName {
370 if (Calendar != culture.Calendar)
373 return culture.NativeCalendarName;
378 public string[] ShortestDayNames {
380 return (string[]) shortestDayNames.Clone ();
384 CheckDaysValue (value);
385 shortestDayNames = value;
389 public string AMDesignator {
394 if (IsReadOnly) throw new InvalidOperationException(MSG_READONLY);
395 if (value == null) throw new ArgumentNullException();
396 amDesignator = value;
400 public string PMDesignator {
405 if (IsReadOnly) throw new InvalidOperationException(MSG_READONLY);
406 if (value == null) throw new ArgumentNullException();
407 pmDesignator = value;
411 public string DateSeparator
415 return dateSeparator;
419 if (IsReadOnly) throw new InvalidOperationException(MSG_READONLY);
420 if (value == null) throw new ArgumentNullException();
421 dateSeparator = value;
425 public string TimeSeparator
429 return timeSeparator;
433 if (IsReadOnly) throw new InvalidOperationException(MSG_READONLY);
434 if (value == null) throw new ArgumentNullException();
435 timeSeparator = value;
439 public string LongDatePattern
443 return longDatePattern;
447 if (IsReadOnly) throw new InvalidOperationException(MSG_READONLY);
448 if (value == null) throw new ArgumentNullException();
449 longDatePattern = value;
453 public string ShortDatePattern
457 return shortDatePattern;
461 if (IsReadOnly) throw new InvalidOperationException(MSG_READONLY);
462 if (value == null) throw new ArgumentNullException();
463 shortDatePattern = value;
467 public string ShortTimePattern
471 return shortTimePattern;
475 if (IsReadOnly) throw new InvalidOperationException(MSG_READONLY);
476 if (value == null) throw new ArgumentNullException();
477 shortTimePattern = value;
481 public string LongTimePattern
485 return longTimePattern;
489 if (IsReadOnly) throw new InvalidOperationException(MSG_READONLY);
490 if (value == null) throw new ArgumentNullException();
491 longTimePattern = value;
495 public string MonthDayPattern
499 return monthDayPattern;
503 if (IsReadOnly) throw new InvalidOperationException(MSG_READONLY);
504 if (value == null) throw new ArgumentNullException();
505 monthDayPattern = value;
509 public string YearMonthPattern
513 return yearMonthPattern;
517 if (IsReadOnly) throw new InvalidOperationException(MSG_READONLY);
518 if (value == null) throw new ArgumentNullException();
519 yearMonthPattern = value;
523 public string FullDateTimePattern
526 return fullDateTimePattern ?? (longDatePattern + " " + longTimePattern);
530 if (IsReadOnly) throw new InvalidOperationException(MSG_READONLY);
531 if (value == null) throw new ArgumentNullException();
532 fullDateTimePattern = value;
536 public static DateTimeFormatInfo CurrentInfo
540 return Thread.CurrentThread.CurrentCulture.DateTimeFormat;
544 public static DateTimeFormatInfo InvariantInfo
547 if (theInvariantDateTimeFormatInfo == null) {
548 var tmp = new DateTimeFormatInfo (CultureInfo.InvariantCulture, true);
549 tmp.FillInvariantPatterns ();
550 theInvariantDateTimeFormatInfo = tmp;
553 return theInvariantDateTimeFormatInfo;
557 public DayOfWeek FirstDayOfWeek
561 return (DayOfWeek)firstDayOfWeek;
565 if (IsReadOnly) throw new InvalidOperationException(MSG_READONLY);
566 if ((int) value < 0 || (int) value > 6) throw new ArgumentOutOfRangeException();
567 firstDayOfWeek = (int)value;
571 public Calendar Calendar {
573 return calendar ?? culture.Calendar;
576 [MonoLimitation ("Only default calendar specific data are available")]
579 throw new InvalidOperationException(MSG_READONLY);
581 throw new ArgumentNullException();
583 if (calendar == value)
586 // TODO: Should use only ids
587 var calendars = culture.OptionalCalendars;
588 for (int i = 0; i < calendars.Length; ++i) {
589 if (calendars [i].ID != value.ID)
594 m_abbrevEraNames = null;
597 throw new ArgumentOutOfRangeException ("Invalid calendar");
601 public CalendarWeekRule CalendarWeekRule
605 return (CalendarWeekRule)calendarWeekRule;
609 if (IsReadOnly) throw new InvalidOperationException(MSG_READONLY);
610 calendarWeekRule = (int)value;
614 public string RFC1123Pattern {
616 return "ddd, dd MMM yyyy HH':'mm':'ss 'GMT'";
620 internal string RoundtripPattern {
622 return "yyyy'-'MM'-'dd'T'HH':'mm':'ss.fffffffK";
626 public string SortableDateTimePattern {
628 return "yyyy'-'MM'-'dd'T'HH':'mm':'ss";
632 public string UniversalSortableDateTimePattern {
634 return "yyyy'-'MM'-'dd HH':'mm':'ss'Z'";
638 // FIXME: Not complete depending on GetAllDateTimePatterns(char)")]
639 public string[] GetAllDateTimePatterns ()
641 return (string[]) GetAllDateTimePatternsInternal ().Clone ();
644 // Same as above, but with no cloning, because we know that
645 // clients are friendly
646 internal string [] GetAllDateTimePatternsInternal ()
648 FillAllDateTimePatterns ();
649 return all_date_time_patterns;
652 // Prevent write reordering
653 volatile string [] all_date_time_patterns;
655 void FillAllDateTimePatterns (){
657 if (all_date_time_patterns != null)
660 var al = new List<string> (16);
661 al.AddRange (GetAllRawDateTimePatterns ('d'));
662 al.AddRange (GetAllRawDateTimePatterns ('D'));
663 al.AddRange (GetAllRawDateTimePatterns ('f'));
664 al.AddRange (GetAllRawDateTimePatterns ('F'));
665 al.AddRange (GetAllRawDateTimePatterns ('g'));
666 al.AddRange (GetAllRawDateTimePatterns ('G'));
667 al.AddRange (GetAllRawDateTimePatterns ('m'));
668 al.AddRange (GetAllRawDateTimePatterns ('M'));
669 al.AddRange (GetAllRawDateTimePatterns ('o'));
670 al.AddRange (GetAllRawDateTimePatterns ('O'));
671 al.AddRange (GetAllRawDateTimePatterns ('r'));
672 al.AddRange (GetAllRawDateTimePatterns ('R'));
673 al.AddRange (GetAllRawDateTimePatterns ('s'));
674 al.AddRange (GetAllRawDateTimePatterns ('t'));
675 al.AddRange (GetAllRawDateTimePatterns ('T'));
676 al.AddRange (GetAllRawDateTimePatterns ('u'));
677 al.AddRange (GetAllRawDateTimePatterns ('U'));
678 al.AddRange (GetAllRawDateTimePatterns ('y'));
679 al.AddRange (GetAllRawDateTimePatterns ('Y'));
681 // all_date_time_patterns needs to be volatile to prevent
682 // reordering of writes here and still avoid any locking.
683 all_date_time_patterns = al.ToArray ();
687 // FIXME: We need more culture data in locale-builder
688 // Whoever put that comment, please expand.
690 public string[] GetAllDateTimePatterns (char format)
692 return (string[]) GetAllRawDateTimePatterns (format).Clone ();
695 internal string[] GetAllRawDateTimePatterns (char format)
700 if (allLongDatePatterns != null && allLongDatePatterns.Length > 0)
701 return allLongDatePatterns;
702 return new string [] {LongDatePattern};
704 if (allShortDatePatterns != null && allShortDatePatterns.Length > 0)
705 return allShortDatePatterns;
706 return new string [] {ShortDatePattern};
709 if (allLongTimePatterns != null && allLongTimePatterns.Length > 0)
710 return allLongTimePatterns;
711 return new string [] {LongTimePattern};
713 if (allShortTimePatterns != null && allShortTimePatterns.Length > 0)
714 return allShortTimePatterns;
715 return new string [] {ShortTimePattern};
719 if (monthDayPatterns != null && monthDayPatterns.Length > 0)
720 return monthDayPatterns;
721 return new string[] { MonthDayPattern };
725 if (yearMonthPatterns != null && yearMonthPatterns.Length > 0)
726 return yearMonthPatterns;
727 return new string[] { YearMonthPattern };
730 return new string[] { RFC1123Pattern };
733 return new string[] { RoundtripPattern };
735 return new string[] { SortableDateTimePattern };
737 return new string[] { UniversalSortableDateTimePattern };
740 // Following patterns are combinations of {Short|Long}Date + {Short|Long}Time. Patters can
741 // be null for non-readonly invariant culture
744 return allShortDatePatterns == null ?
745 new string [] { ShortDatePattern + " " + LongTimePattern } :
746 PopulateCombinedList (allShortDatePatterns, allLongTimePatterns);
748 return allShortDatePatterns == null ?
749 new string [] { ShortDatePattern + " " + ShortTimePattern } :
750 PopulateCombinedList (allShortDatePatterns, allShortTimePatterns);
751 case 'U': // The 'U' pattern strings are always the same as 'F' (only differs in assuming UTC or not.)
753 return allLongDatePatterns == null ?
754 new string [] { LongDatePattern + " " + ShortTimePattern } :
755 PopulateCombinedList (allLongDatePatterns, allLongTimePatterns);
757 return allLongDatePatterns == null ?
758 new string [] { LongDatePattern + " " + ShortTimePattern } :
759 PopulateCombinedList (allLongDatePatterns, allShortTimePatterns);
761 throw new ArgumentException ("Format specifier was invalid.");
764 public string GetDayName(DayOfWeek dayofweek)
766 int index = (int) dayofweek;
767 if (index < 0 || index > 6) throw new ArgumentOutOfRangeException();
768 return dayNames[index];
771 public string GetAbbreviatedDayName(DayOfWeek dayofweek)
773 int index = (int) dayofweek;
774 if (index < 0 || index > 6) throw new ArgumentOutOfRangeException();
775 return abbreviatedDayNames[index];
778 void FillInvariantPatterns ()
780 allShortDatePatterns = new string [] {"MM/dd/yyyy"};
781 allLongDatePatterns = new string [] {"dddd, dd MMMM yyyy"};
782 allLongTimePatterns = new string [] {"HH:mm:ss"};
783 allShortTimePatterns = new string [] {
789 monthDayPatterns = new string [] {"MMMM dd"};
790 yearMonthPatterns = new string [] {"yyyy MMMM"};
792 fullDateTimePattern = "dddd, dd MMMM yyyy HH:mm:ss";
795 static string [] PopulateCombinedList (string [] dates, string [] times)
797 string[] list = new string[dates.Length * times.Length];
799 foreach (string d in dates)
800 foreach (string t in times)
801 list[i++] = d + " " + t;
806 public string GetShortestDayName (DayOfWeek dayOfWeek)
808 int index = (int) dayOfWeek;
809 if (index < 0 || index > 6)
810 throw new ArgumentOutOfRangeException ("dayOfWeek");
812 return shortestDayNames [index];
816 public void SetAllDateTimePatterns (string [] patterns, char format)
818 if (patterns == null)
819 throw new ArgumentNullException ("patterns");
820 if (patterns.Length == 0)
821 throw new ArgumentException ("patterns", "The argument patterns must not be of zero-length");
827 yearMonthPatterns = patterns;
832 monthDayPatterns = patterns;
836 allLongDatePatterns = patterns;
839 allShortDatePatterns = patterns;
843 allLongTimePatterns = patterns;
846 allShortTimePatterns = patterns;
849 // note that any other formats are invalid (such as 'r', 'g', 'U')
850 throw new ArgumentException ("format", "Format specifier is invalid");
854 void CheckDaysValue (string[] value)
857 throw new InvalidOperationException (MSG_READONLY);
860 throw new ArgumentNullException ();
862 if (value.Length != 7)
863 throw new ArgumentException ("An array with exactly 7 elements is required");
865 int ni = Array.IndexOf (value, null);
867 throw new ArgumentNullException (string.Format ("Element at index {0} is null", ni));
870 void CheckMonthsValue (string[] value)
873 throw new InvalidOperationException (MSG_READONLY);
876 throw new ArgumentNullException ();
878 if (value.Length != 13)
879 throw new ArgumentException ("An array with exactly 13 elements is required");
881 int ni = Array.IndexOf (value, null);
883 throw new ArgumentNullException (string.Format ("Element at index {0} is null", ni));