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 // private static readonly string[] INVARIANT_ERA_NAMES = {"A.D."};
63 static readonly string[] INVARIANT_SHORT_DAY_NAMES =
64 new string[7] { "Su", "Mo", "Tu", "We", "Th", "Fr", "Sa" };
65 private static DateTimeFormatInfo theInvariantDateTimeFormatInfo;
67 #pragma warning disable 169
68 #region Sync with object-internals.h
69 private bool m_isReadOnly;
70 private string amDesignator;
71 private string pmDesignator;
72 private string dateSeparator;
73 private string timeSeparator;
74 private string shortDatePattern;
75 private string longDatePattern;
76 private string shortTimePattern;
77 private string longTimePattern;
78 private string monthDayPattern;
79 private string yearMonthPattern;
80 private int firstDayOfWeek;
81 private int calendarWeekRule;
82 private string[] abbreviatedDayNames;
83 private string[] dayNames;
84 private string[] monthNames;
85 private string[] genitiveMonthNames;
86 private string[] abbreviatedMonthNames;
87 private string[] m_genitiveAbbreviatedMonthNames;
89 private string[] allShortDatePatterns;
90 private string[] allLongDatePatterns;
91 private string[] allShortTimePatterns;
92 private string[] allLongTimePatterns;
93 private string[] monthDayPatterns;
94 private string[] yearMonthPatterns;
95 private string[] shortestDayNames;
98 internal readonly CultureInfo culture;
101 // MS Serialization needs this
102 private string fullDateTimePattern;
103 private int nDataItem;
104 private bool m_useUserOverride;
105 private bool m_isDefaultCalendar;
106 private int CultureID;
107 private bool bUseCalendarInfo;
108 private string generalShortTimePattern;
109 private string generalLongTimePattern;
110 private string[] m_eraNames;
111 private string[] m_abbrevEraNames;
112 private string[] m_abbrevEnglishEraNames;
113 private string[] m_dateWords;
114 private int[] optionalCalendars;
115 private string[] leapYearMonthNames;
116 private DateTimeFormatFlags formatFlags;
117 private string m_name; // Unused, but MS.NET serializes this
118 #pragma warning restore 169
120 internal DateTimeFormatInfo (CultureInfo culture, bool read_only)
123 throw new ArgumentNullException ("culture");
125 this.culture = culture;
126 m_isReadOnly = read_only;
132 shortDatePattern = "MM/dd/yyyy";
133 longDatePattern = "dddd, dd MMMM yyyy";
134 shortTimePattern = "HH:mm";
135 longTimePattern = "HH:mm:ss";
136 monthDayPattern = "MMMM dd";
137 yearMonthPattern = "yyyy MMMM";
139 firstDayOfWeek = (int) DayOfWeek.Sunday;
140 calendarWeekRule = (int) CalendarWeekRule.FirstDay;
142 abbreviatedDayNames = INVARIANT_ABBREVIATED_DAY_NAMES;
143 dayNames = INVARIANT_DAY_NAMES;
144 abbreviatedMonthNames = INVARIANT_ABBREVIATED_MONTH_NAMES;
145 monthNames = INVARIANT_MONTH_NAMES;
146 m_genitiveAbbreviatedMonthNames = INVARIANT_ABBREVIATED_MONTH_NAMES;
147 genitiveMonthNames = INVARIANT_MONTH_NAMES;
148 shortestDayNames = INVARIANT_SHORT_DAY_NAMES;
151 public DateTimeFormatInfo ()
152 : this (CultureInfo.InvariantCulture, false)
156 public static DateTimeFormatInfo GetInstance(IFormatProvider provider)
158 if (provider != null) {
159 DateTimeFormatInfo dtfi;
160 dtfi = (DateTimeFormatInfo)provider.GetFormat(typeof(DateTimeFormatInfo));
168 public bool IsReadOnly {
174 public static DateTimeFormatInfo ReadOnly(DateTimeFormatInfo dtfi)
176 DateTimeFormatInfo copy = (DateTimeFormatInfo)dtfi.Clone();
177 copy.m_isReadOnly = true;
181 public object Clone ()
183 DateTimeFormatInfo clone = (DateTimeFormatInfo) MemberwiseClone();
184 // clone is not read only
185 clone.m_isReadOnly = false;
189 public object GetFormat(Type formatType)
191 return (formatType == GetType()) ? this : null;
194 public string GetAbbreviatedEraName (int era)
196 if (era < 0 || era >= Calendar.AbbreviatedEraNames.Length)
197 throw new ArgumentOutOfRangeException ("era", era.ToString ());
198 return Calendar.AbbreviatedEraNames [era];
201 public string GetAbbreviatedMonthName(int month)
203 if (month < 1 || month > 13) throw new ArgumentOutOfRangeException();
204 return abbreviatedMonthNames[month-1];
207 public int GetEra (string eraName)
210 throw new ArgumentNullException ();
211 string [] eras = Calendar.EraNames;
212 for (int i = 0; i < eras.Length; i++)
213 if (CultureInfo.InvariantCulture.CompareInfo
214 .Compare (eraName, eras [i],
215 CompareOptions.IgnoreCase) == 0)
216 return Calendar.Eras [i];
218 eras = Calendar.AbbreviatedEraNames;
219 for (int i = 0; i < eras.Length; i++)
220 if (CultureInfo.InvariantCulture.CompareInfo
221 .Compare (eraName, eras [i],
222 CompareOptions.IgnoreCase) == 0)
223 return Calendar.Eras [i];
228 public string GetEraName (int era)
230 if (era < 0 || era > Calendar.EraNames.Length)
231 throw new ArgumentOutOfRangeException ("era", era.ToString ());
232 return Calendar.EraNames [era - 1];
235 public string GetMonthName(int month)
237 if (month < 1 || month > 13) throw new ArgumentOutOfRangeException();
238 return monthNames[month-1];
241 internal string GetMonthGenitiveName (int month)
243 return genitiveMonthNames [month - 1];
246 public string[] AbbreviatedDayNames
248 get { return (string[]) RawAbbreviatedDayNames.Clone (); }
249 set { RawAbbreviatedDayNames = value; }
252 internal string[] RawAbbreviatedDayNames
256 return abbreviatedDayNames;
259 CheckDaysValue (value);
260 abbreviatedDayNames = (string[]) value.Clone();
264 public string[] AbbreviatedMonthNames
266 get { return (string[]) RawAbbreviatedMonthNames.Clone (); }
267 set { RawAbbreviatedMonthNames = value; }
270 internal string[] RawAbbreviatedMonthNames
274 return abbreviatedMonthNames;
277 CheckMonthsValue (value);
278 abbreviatedMonthNames = (string[]) value.Clone();
282 public string[] DayNames {
284 return (string[]) dayNames.Clone ();
287 CheckDaysValue (value);
288 dayNames = (string[]) value.Clone();
292 internal string[] RawDayNames {
298 public string[] MonthNames {
300 return (string[]) monthNames.Clone ();
303 CheckMonthsValue (value);
304 monthNames = (string[]) value.Clone();
308 internal string[] RawMonthNames {
315 public string[] AbbreviatedMonthGenitiveNames {
317 return (string[]) m_genitiveAbbreviatedMonthNames.Clone ();
320 CheckMonthsValue (value);
321 m_genitiveAbbreviatedMonthNames = value;
326 public string[] MonthGenitiveNames {
328 return (string[]) genitiveMonthNames.Clone ();
331 CheckMonthsValue (value);
332 genitiveMonthNames = value;
336 [MonoLimitation ("Only default calendar is supported")]
338 public string NativeCalendarName {
340 if (Calendar != culture.Calendar)
343 return culture.NativeCalendarName;
348 public string[] ShortestDayNames {
350 return (string[]) shortestDayNames.Clone ();
354 CheckDaysValue (value);
355 shortestDayNames = value;
359 public string AMDesignator {
364 if (IsReadOnly) throw new InvalidOperationException(MSG_READONLY);
365 if (value == null) throw new ArgumentNullException();
366 amDesignator = value;
370 public string PMDesignator {
375 if (IsReadOnly) throw new InvalidOperationException(MSG_READONLY);
376 if (value == null) throw new ArgumentNullException();
377 pmDesignator = value;
381 public string DateSeparator
385 return dateSeparator;
389 if (IsReadOnly) throw new InvalidOperationException(MSG_READONLY);
390 if (value == null) throw new ArgumentNullException();
391 dateSeparator = value;
395 public string TimeSeparator
399 return timeSeparator;
403 if (IsReadOnly) throw new InvalidOperationException(MSG_READONLY);
404 if (value == null) throw new ArgumentNullException();
405 timeSeparator = value;
409 public string LongDatePattern
413 return longDatePattern;
417 if (IsReadOnly) throw new InvalidOperationException(MSG_READONLY);
418 if (value == null) throw new ArgumentNullException();
419 longDatePattern = value;
423 public string ShortDatePattern
427 return shortDatePattern;
431 if (IsReadOnly) throw new InvalidOperationException(MSG_READONLY);
432 if (value == null) throw new ArgumentNullException();
433 shortDatePattern = value;
437 public string ShortTimePattern
441 return shortTimePattern;
445 if (IsReadOnly) throw new InvalidOperationException(MSG_READONLY);
446 if (value == null) throw new ArgumentNullException();
447 shortTimePattern = value;
451 public string LongTimePattern
455 return longTimePattern;
459 if (IsReadOnly) throw new InvalidOperationException(MSG_READONLY);
460 if (value == null) throw new ArgumentNullException();
461 longTimePattern = value;
465 public string MonthDayPattern
469 return monthDayPattern;
473 if (IsReadOnly) throw new InvalidOperationException(MSG_READONLY);
474 if (value == null) throw new ArgumentNullException();
475 monthDayPattern = value;
479 public string YearMonthPattern
483 return yearMonthPattern;
487 if (IsReadOnly) throw new InvalidOperationException(MSG_READONLY);
488 if (value == null) throw new ArgumentNullException();
489 yearMonthPattern = value;
493 public string FullDateTimePattern
496 return fullDateTimePattern ?? (longDatePattern + " " + longTimePattern);
500 if (IsReadOnly) throw new InvalidOperationException(MSG_READONLY);
501 if (value == null) throw new ArgumentNullException();
502 fullDateTimePattern = value;
506 public static DateTimeFormatInfo CurrentInfo
510 return Thread.CurrentThread.CurrentCulture.DateTimeFormat;
514 public static DateTimeFormatInfo InvariantInfo
517 if (theInvariantDateTimeFormatInfo == null) {
518 var tmp = new DateTimeFormatInfo (CultureInfo.InvariantCulture, true);
519 tmp.FillInvariantPatterns ();
520 theInvariantDateTimeFormatInfo = tmp;
523 return theInvariantDateTimeFormatInfo;
527 public DayOfWeek FirstDayOfWeek
531 return (DayOfWeek)firstDayOfWeek;
535 if (IsReadOnly) throw new InvalidOperationException(MSG_READONLY);
536 if ((int) value < 0 || (int) value > 6) throw new ArgumentOutOfRangeException();
537 firstDayOfWeek = (int)value;
541 public Calendar Calendar {
543 return calendar ?? culture.Calendar;
546 [MonoLimitation ("Only default calendar specific data are available")]
548 if (IsReadOnly) throw new InvalidOperationException(MSG_READONLY);
549 if (value == null) throw new ArgumentNullException();
554 public CalendarWeekRule CalendarWeekRule
558 return (CalendarWeekRule)calendarWeekRule;
562 if (IsReadOnly) throw new InvalidOperationException(MSG_READONLY);
563 calendarWeekRule = (int)value;
567 public string RFC1123Pattern {
569 return "ddd, dd MMM yyyy HH':'mm':'ss 'GMT'";
573 internal string RoundtripPattern {
575 return "yyyy'-'MM'-'dd'T'HH':'mm':'ss.fffffffK";
579 public string SortableDateTimePattern {
581 return "yyyy'-'MM'-'dd'T'HH':'mm':'ss";
585 public string UniversalSortableDateTimePattern {
587 return "yyyy'-'MM'-'dd HH':'mm':'ss'Z'";
591 // FIXME: Not complete depending on GetAllDateTimePatterns(char)")]
592 public string[] GetAllDateTimePatterns ()
594 return (string[]) GetAllDateTimePatternsInternal ().Clone ();
597 // Same as above, but with no cloning, because we know that
598 // clients are friendly
599 internal string [] GetAllDateTimePatternsInternal ()
601 FillAllDateTimePatterns ();
602 return all_date_time_patterns;
605 // Prevent write reordering
606 volatile string [] all_date_time_patterns;
608 void FillAllDateTimePatterns (){
610 if (all_date_time_patterns != null)
613 var al = new List<string> (16);
614 al.AddRange (GetAllRawDateTimePatterns ('d'));
615 al.AddRange (GetAllRawDateTimePatterns ('D'));
616 al.AddRange (GetAllRawDateTimePatterns ('f'));
617 al.AddRange (GetAllRawDateTimePatterns ('F'));
618 al.AddRange (GetAllRawDateTimePatterns ('g'));
619 al.AddRange (GetAllRawDateTimePatterns ('G'));
620 al.AddRange (GetAllRawDateTimePatterns ('m'));
621 al.AddRange (GetAllRawDateTimePatterns ('M'));
622 al.AddRange (GetAllRawDateTimePatterns ('o'));
623 al.AddRange (GetAllRawDateTimePatterns ('O'));
624 al.AddRange (GetAllRawDateTimePatterns ('r'));
625 al.AddRange (GetAllRawDateTimePatterns ('R'));
626 al.AddRange (GetAllRawDateTimePatterns ('s'));
627 al.AddRange (GetAllRawDateTimePatterns ('t'));
628 al.AddRange (GetAllRawDateTimePatterns ('T'));
629 al.AddRange (GetAllRawDateTimePatterns ('u'));
630 al.AddRange (GetAllRawDateTimePatterns ('U'));
631 al.AddRange (GetAllRawDateTimePatterns ('y'));
632 al.AddRange (GetAllRawDateTimePatterns ('Y'));
634 // all_date_time_patterns needs to be volatile to prevent
635 // reordering of writes here and still avoid any locking.
636 all_date_time_patterns = al.ToArray ();
640 // FIXME: We need more culture data in locale-builder
641 // Whoever put that comment, please expand.
643 public string[] GetAllDateTimePatterns (char format)
645 return (string[]) GetAllRawDateTimePatterns (format).Clone ();
648 internal string[] GetAllRawDateTimePatterns (char format)
653 if (allLongDatePatterns != null && allLongDatePatterns.Length > 0)
654 return allLongDatePatterns;
655 return new string [] {LongDatePattern};
657 if (allShortDatePatterns != null && allShortDatePatterns.Length > 0)
658 return allShortDatePatterns;
659 return new string [] {ShortDatePattern};
662 if (allLongTimePatterns != null && allLongTimePatterns.Length > 0)
663 return allLongTimePatterns;
664 return new string [] {LongTimePattern};
666 if (allShortTimePatterns != null && allShortTimePatterns.Length > 0)
667 return allShortTimePatterns;
668 return new string [] {ShortTimePattern};
672 if (monthDayPatterns != null && monthDayPatterns.Length > 0)
673 return monthDayPatterns;
674 return new string[] { MonthDayPattern };
678 if (yearMonthPatterns != null && yearMonthPatterns.Length > 0)
679 return yearMonthPatterns;
680 return new string[] { YearMonthPattern };
683 return new string[] { RFC1123Pattern };
686 return new string[] { RoundtripPattern };
688 return new string[] { SortableDateTimePattern };
690 return new string[] { UniversalSortableDateTimePattern };
693 // Following patterns are combinations of {Short|Long}Date + {Short|Long}Time. Patters can
694 // be null for non-readonly invariant culture
697 return allShortDatePatterns == null ?
698 new string [] { ShortDatePattern + " " + LongTimePattern } :
699 PopulateCombinedList (allShortDatePatterns, allLongTimePatterns);
701 return allShortDatePatterns == null ?
702 new string [] { ShortDatePattern + " " + ShortTimePattern } :
703 PopulateCombinedList (allShortDatePatterns, allShortTimePatterns);
704 case 'U': // The 'U' pattern strings are always the same as 'F' (only differs in assuming UTC or not.)
706 return allLongDatePatterns == null ?
707 new string [] { LongDatePattern + " " + ShortTimePattern } :
708 PopulateCombinedList (allLongDatePatterns, allLongTimePatterns);
710 return allLongDatePatterns == null ?
711 new string [] { LongDatePattern + " " + ShortTimePattern } :
712 PopulateCombinedList (allLongDatePatterns, allShortTimePatterns);
714 throw new ArgumentException ("Format specifier was invalid.");
717 public string GetDayName(DayOfWeek dayofweek)
719 int index = (int) dayofweek;
720 if (index < 0 || index > 6) throw new ArgumentOutOfRangeException();
721 return dayNames[index];
724 public string GetAbbreviatedDayName(DayOfWeek dayofweek)
726 int index = (int) dayofweek;
727 if (index < 0 || index > 6) throw new ArgumentOutOfRangeException();
728 return abbreviatedDayNames[index];
731 void FillInvariantPatterns ()
733 allShortDatePatterns = new string [] {"MM/dd/yyyy"};
734 allLongDatePatterns = new string [] {"dddd, dd MMMM yyyy"};
735 allLongTimePatterns = new string [] {"HH:mm:ss"};
736 allShortTimePatterns = new string [] {
742 monthDayPatterns = new string [] {"MMMM dd"};
743 yearMonthPatterns = new string [] {"yyyy MMMM"};
745 fullDateTimePattern = "dddd, dd MMMM yyyy HH:mm:ss";
748 static string [] PopulateCombinedList (string [] dates, string [] times)
750 string[] list = new string[dates.Length * times.Length];
752 foreach (string d in dates)
753 foreach (string t in times)
754 list[i++] = d + " " + t;
759 public string GetShortestDayName (DayOfWeek dayOfWeek)
761 int index = (int) dayOfWeek;
762 if (index < 0 || index > 6)
763 throw new ArgumentOutOfRangeException ("dayOfWeek");
765 return shortestDayNames [index];
769 public void SetAllDateTimePatterns (string [] patterns, char format)
771 if (patterns == null)
772 throw new ArgumentNullException ("patterns");
773 if (patterns.Length == 0)
774 throw new ArgumentException ("patterns", "The argument patterns must not be of zero-length");
780 yearMonthPatterns = patterns;
785 monthDayPatterns = patterns;
789 allLongDatePatterns = patterns;
792 allShortDatePatterns = patterns;
796 allLongTimePatterns = patterns;
799 allShortTimePatterns = patterns;
802 // note that any other formats are invalid (such as 'r', 'g', 'U')
803 throw new ArgumentException ("format", "Format specifier is invalid");
807 void CheckDaysValue (string[] value)
810 throw new InvalidOperationException (MSG_READONLY);
813 throw new ArgumentNullException ();
815 if (value.Length != 7)
816 throw new ArgumentException ("An array with exactly 7 elements is required");
818 int ni = Array.IndexOf (value, null);
820 throw new ArgumentNullException (string.Format ("Element at index {0} is null", ni));
823 void CheckMonthsValue (string[] value)
826 throw new InvalidOperationException (MSG_READONLY);
829 throw new ArgumentNullException ();
831 if (value.Length != 13)
832 throw new ArgumentException ("An array with exactly 13 elements is required");
834 int ni = Array.IndexOf (value, null);
836 throw new ArgumentNullException (string.Format ("Element at index {0} is null", ni));