3c1f479b9d
Former-commit-id: 806294f5ded97629b74c85c09952f2a74fe182d9
515 lines
13 KiB
C#
515 lines
13 KiB
C#
//
|
|
// CultureData.cs
|
|
//
|
|
// Authors:
|
|
// Marek Safar <marek.safar@gmail.com>
|
|
//
|
|
// Copyright (C) 2015 Xamarin Inc (http://www.xamarin.com)
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining
|
|
// a copy of this software and associated documentation files (the
|
|
// "Software"), to deal in the Software without restriction, including
|
|
// without limitation the rights to use, copy, modify, merge, publish,
|
|
// distribute, sublicense, and/or sell copies of the Software, and to
|
|
// permit persons to whom the Software is furnished to do so, subject to
|
|
// the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be
|
|
// included in all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
//
|
|
|
|
using System;
|
|
using System.Diagnostics.Contracts;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Runtime.InteropServices;
|
|
using System.Runtime.CompilerServices;
|
|
|
|
namespace System.Globalization
|
|
{
|
|
[StructLayout (LayoutKind.Sequential)]
|
|
class CultureData
|
|
{
|
|
#region Sync with object-internals.h
|
|
// Time
|
|
private String sAM1159; // (user can override) AM designator
|
|
private String sPM2359; // (user can override) PM designator
|
|
private String sTimeSeparator;
|
|
private volatile String[] saLongTimes; // (user can override) time format
|
|
private volatile String[] saShortTimes; // short time format
|
|
|
|
// Calendar specific data
|
|
private int iFirstDayOfWeek; // (user can override) first day of week (gregorian really)
|
|
private int iFirstWeekOfYear; // (user can override) first week of year (gregorian really)
|
|
#endregion
|
|
private volatile int[] waCalendars; // all available calendar type(s). The first one is the default calendar
|
|
|
|
// Store for specific data about each calendar
|
|
private CalendarData[] calendars; // Store for specific calendar data
|
|
|
|
// Language
|
|
private String sISO639Language; // ISO 639 Language Name
|
|
|
|
readonly string sRealName;
|
|
|
|
bool bUseOverrides;
|
|
|
|
// TODO: should query runtime with culture name for a list of culture's calendars
|
|
int calendarId;
|
|
|
|
private CultureData (string name)
|
|
{
|
|
this.sRealName = name;
|
|
}
|
|
|
|
static CultureData s_Invariant;
|
|
|
|
public static CultureData Invariant {
|
|
get {
|
|
if (s_Invariant == null) {
|
|
var invariant = new CultureData ("");
|
|
|
|
// Language
|
|
invariant.sISO639Language = "iv"; // ISO 639 Language Name
|
|
|
|
// Time
|
|
invariant.sAM1159 = "AM"; // AM designator
|
|
invariant.sPM2359 = "PM"; // PM designator
|
|
invariant.sTimeSeparator = ":";
|
|
invariant.saLongTimes = new String[] { "HH:mm:ss" }; // time format
|
|
invariant.saShortTimes = new String[] { "HH:mm", "hh:mm tt", "H:mm", "h:mm tt" }; // short time format
|
|
|
|
// Calendar specific data
|
|
invariant.iFirstDayOfWeek = 0; // first day of week
|
|
invariant.iFirstWeekOfYear = 0; // first week of year
|
|
invariant.waCalendars = new int[] { (int)CalendarId.GREGORIAN }; // all available calendar type(s). The first one is the default calendar
|
|
|
|
// Store for specific data about each calendar
|
|
invariant.calendars = new CalendarData[CalendarData.MAX_CALENDARS];
|
|
invariant.calendars[0] = CalendarData.Invariant;
|
|
|
|
Interlocked.CompareExchange (ref s_Invariant, invariant, null);
|
|
}
|
|
|
|
return s_Invariant;
|
|
}
|
|
}
|
|
|
|
public static CultureData GetCultureData (string cultureName, bool useUserOverride)
|
|
{
|
|
try {
|
|
var ci = new CultureInfo (cultureName, useUserOverride);
|
|
return ci.m_cultureData;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public static CultureData GetCultureData (string cultureName, bool useUserOverride, int datetimeIndex, int calendarId, string iso2lang)
|
|
{
|
|
if (string.IsNullOrEmpty (cultureName))
|
|
return Invariant;
|
|
|
|
var cd = new CultureData (cultureName);
|
|
cd.fill_culture_data (datetimeIndex);
|
|
cd.bUseOverrides = useUserOverride;
|
|
cd.calendarId = calendarId;
|
|
cd.sISO639Language = iso2lang;
|
|
return cd;
|
|
}
|
|
|
|
[MethodImplAttribute (MethodImplOptions.InternalCall)]
|
|
extern void fill_culture_data (int datetimeIndex);
|
|
|
|
public CalendarData GetCalendar (int calendarId)
|
|
{
|
|
// arrays are 0 based, calendarIds are 1 based
|
|
int calendarIndex = calendarId - 1;
|
|
|
|
// Have to have calendars
|
|
if (calendars == null)
|
|
{
|
|
calendars = new CalendarData[CalendarData.MAX_CALENDARS];
|
|
}
|
|
|
|
var calendarData = calendars[calendarIndex];
|
|
if (calendarData == null) {
|
|
calendarData = new CalendarData (sRealName, calendarId, bUseOverrides);
|
|
calendars [calendarIndex] = calendarData;
|
|
}
|
|
|
|
return calendarData;
|
|
}
|
|
|
|
internal String[] LongTimes {
|
|
get {
|
|
return saLongTimes;
|
|
}
|
|
}
|
|
|
|
internal String[] ShortTimes {
|
|
get {
|
|
return saShortTimes;
|
|
}
|
|
}
|
|
|
|
internal String SISO639LANGNAME {
|
|
get {
|
|
return sISO639Language;
|
|
}
|
|
}
|
|
|
|
internal int IFIRSTDAYOFWEEK {
|
|
get {
|
|
return iFirstDayOfWeek;
|
|
}
|
|
}
|
|
|
|
internal int IFIRSTWEEKOFYEAR {
|
|
get {
|
|
return iFirstWeekOfYear;
|
|
}
|
|
}
|
|
|
|
internal String SAM1159 {
|
|
get {
|
|
return sAM1159;
|
|
}
|
|
}
|
|
|
|
internal String SPM2359 {
|
|
get {
|
|
return sPM2359;
|
|
}
|
|
}
|
|
|
|
internal String TimeSeparator {
|
|
get {
|
|
return sTimeSeparator;
|
|
}
|
|
}
|
|
|
|
internal int[] CalendarIds {
|
|
get {
|
|
if (this.waCalendars == null) {
|
|
// Need this specialization because GetJapaneseCalendarDTFI/GetTaiwanCalendarDTFI depend on
|
|
// optional calendars
|
|
switch (sISO639Language) {
|
|
case "ja":
|
|
waCalendars = new int[] { calendarId, Calendar.CAL_JAPAN };
|
|
break;
|
|
case "zh":
|
|
waCalendars = new int[] { calendarId, Calendar.CAL_TAIWAN };
|
|
break;
|
|
default:
|
|
waCalendars = new int [] { calendarId };
|
|
break;
|
|
}
|
|
}
|
|
|
|
return waCalendars;
|
|
}
|
|
}
|
|
|
|
internal String CultureName {
|
|
get {
|
|
return sRealName;
|
|
}
|
|
}
|
|
|
|
internal String SCOMPAREINFO {
|
|
get {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
#region from reference sources
|
|
|
|
// Are overrides enabled?
|
|
internal bool UseUserOverride
|
|
{
|
|
get
|
|
{
|
|
return this.bUseOverrides;
|
|
}
|
|
}
|
|
|
|
// Native calendar names. index of optional calendar - 1, empty if no optional calendar at that number
|
|
internal String CalendarName(int calendarId)
|
|
{
|
|
// Get the calendar
|
|
return GetCalendar(calendarId).sNativeName;
|
|
}
|
|
|
|
// All of our era names
|
|
internal String[] EraNames(int calendarId)
|
|
{
|
|
Contract.Assert(calendarId > 0, "[CultureData.saEraNames] Expected Calendar.ID > 0");
|
|
|
|
return this.GetCalendar(calendarId).saEraNames;
|
|
}
|
|
|
|
internal String[] AbbrevEraNames(int calendarId)
|
|
{
|
|
Contract.Assert(calendarId > 0, "[CultureData.saAbbrevEraNames] Expected Calendar.ID > 0");
|
|
|
|
return this.GetCalendar(calendarId).saAbbrevEraNames;
|
|
}
|
|
|
|
internal String[] AbbreviatedEnglishEraNames(int calendarId)
|
|
{
|
|
Contract.Assert(calendarId > 0, "[CultureData.saAbbrevEraNames] Expected Calendar.ID > 0");
|
|
|
|
return this.GetCalendar(calendarId).saAbbrevEnglishEraNames;
|
|
}
|
|
|
|
// (user can override default only) short date format
|
|
internal String[] ShortDates(int calendarId)
|
|
{
|
|
return GetCalendar(calendarId).saShortDates;
|
|
}
|
|
|
|
// (user can override default only) long date format
|
|
internal String[] LongDates(int calendarId)
|
|
{
|
|
return GetCalendar(calendarId).saLongDates;
|
|
}
|
|
|
|
// (user can override) date year/month format.
|
|
internal String[] YearMonths(int calendarId)
|
|
{
|
|
return GetCalendar(calendarId).saYearMonths;
|
|
}
|
|
|
|
// day names
|
|
internal string[] DayNames(int calendarId)
|
|
{
|
|
return GetCalendar(calendarId).saDayNames;
|
|
}
|
|
|
|
// abbreviated day names
|
|
internal string[] AbbreviatedDayNames(int calendarId)
|
|
{
|
|
// Get abbreviated day names for this calendar from the OS if necessary
|
|
return GetCalendar(calendarId).saAbbrevDayNames;
|
|
}
|
|
|
|
// The super short day names
|
|
internal string[] SuperShortDayNames(int calendarId)
|
|
{
|
|
return GetCalendar(calendarId).saSuperShortDayNames;
|
|
}
|
|
|
|
// month names
|
|
internal string[] MonthNames(int calendarId)
|
|
{
|
|
return GetCalendar(calendarId).saMonthNames;
|
|
}
|
|
|
|
// Genitive month names
|
|
internal string[] GenitiveMonthNames(int calendarId)
|
|
{
|
|
return GetCalendar(calendarId).saMonthGenitiveNames;
|
|
}
|
|
|
|
// month names
|
|
internal string[] AbbreviatedMonthNames(int calendarId)
|
|
{
|
|
return GetCalendar(calendarId).saAbbrevMonthNames;
|
|
}
|
|
|
|
// Genitive month names
|
|
internal string[] AbbreviatedGenitiveMonthNames(int calendarId)
|
|
{
|
|
return GetCalendar(calendarId).saAbbrevMonthGenitiveNames;
|
|
}
|
|
|
|
// Leap year month names
|
|
// Note: This only applies to Hebrew, and it basically adds a "1" to the 6th month name
|
|
// the non-leap names skip the 7th name in the normal month name array
|
|
internal string[] LeapYearMonthNames(int calendarId)
|
|
{
|
|
return GetCalendar(calendarId).saLeapYearMonthNames;
|
|
}
|
|
|
|
// month/day format (single string, no override)
|
|
internal String MonthDay(int calendarId)
|
|
{
|
|
return GetCalendar(calendarId).sMonthDay;
|
|
}
|
|
|
|
// Date separator (derived from short date format)
|
|
internal String DateSeparator(int calendarId)
|
|
{
|
|
return GetDateSeparator(ShortDates(calendarId)[0]);
|
|
}
|
|
|
|
// NOTE: this method is used through reflection by System.Globalization.CultureXmlParser.ReadDateElement()
|
|
// and breaking changes here will not show up at build time, only at run time.
|
|
static private String GetDateSeparator(String format)
|
|
{
|
|
// Date format separator (ie: / in 9/1/03)
|
|
//
|
|
// We calculate this from the provided short date
|
|
//
|
|
|
|
//
|
|
// Find the date separator so that we can pretend we know SDATE.
|
|
//
|
|
return GetSeparator(format, "dyM");
|
|
}
|
|
|
|
private static string GetSeparator(string format, string timeParts)
|
|
{
|
|
int index = IndexOfTimePart(format, 0, timeParts);
|
|
|
|
if (index != -1)
|
|
{
|
|
// Found a time part, find out when it changes
|
|
char cTimePart = format[index];
|
|
|
|
do
|
|
{
|
|
index++;
|
|
} while (index < format.Length && format[index] == cTimePart);
|
|
|
|
int separatorStart = index;
|
|
|
|
// Now we need to find the end of the separator
|
|
if (separatorStart < format.Length)
|
|
{
|
|
int separatorEnd = IndexOfTimePart(format, separatorStart, timeParts);
|
|
if (separatorEnd != -1)
|
|
{
|
|
// From [separatorStart, count) is our string, except we need to unescape
|
|
return UnescapeNlsString(format, separatorStart, separatorEnd - 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
return String.Empty;
|
|
}
|
|
|
|
private static int IndexOfTimePart(string format, int startIndex, string timeParts)
|
|
{
|
|
Contract.Assert(startIndex >= 0, "startIndex cannot be negative");
|
|
Contract.Assert(timeParts.IndexOfAny(new char[] { '\'', '\\' }) == -1, "timeParts cannot include quote characters");
|
|
bool inQuote = false;
|
|
for (int i = startIndex; i < format.Length; ++i)
|
|
{
|
|
// See if we have a time Part
|
|
if (!inQuote && timeParts.IndexOf(format[i]) != -1)
|
|
{
|
|
return i;
|
|
}
|
|
switch (format[i])
|
|
{
|
|
case '\\':
|
|
if (i + 1 < format.Length)
|
|
{
|
|
++i;
|
|
switch (format[i])
|
|
{
|
|
case '\'':
|
|
case '\\':
|
|
break;
|
|
default:
|
|
--i; //backup since we will move over this next
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case '\'':
|
|
inQuote = !inQuote;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Unescape a NLS style quote string
|
|
//
|
|
// This removes single quotes:
|
|
// 'fred' -> fred
|
|
// 'fred -> fred
|
|
// fred' -> fred
|
|
// fred's -> freds
|
|
//
|
|
// This removes the first \ of escaped characters:
|
|
// fred\'s -> fred's
|
|
// a\\b -> a\b
|
|
// a\b -> ab
|
|
//
|
|
// We don't build the stringbuilder unless we find a ' or a \. If we find a ' or a \, we
|
|
// always build a stringbuilder because we need to remove the ' or \.
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////
|
|
static private String UnescapeNlsString(String str, int start, int end)
|
|
{
|
|
Contract.Requires(str != null);
|
|
Contract.Requires(start >= 0);
|
|
Contract.Requires(end >= 0);
|
|
StringBuilder result = null;
|
|
|
|
for (int i = start; i < str.Length && i <= end; i++)
|
|
{
|
|
switch (str[i])
|
|
{
|
|
case '\'':
|
|
if (result == null)
|
|
{
|
|
result = new StringBuilder(str, start, i - start, str.Length);
|
|
}
|
|
break;
|
|
case '\\':
|
|
if (result == null)
|
|
{
|
|
result = new StringBuilder(str, start, i - start, str.Length);
|
|
}
|
|
++i;
|
|
if (i < str.Length)
|
|
{
|
|
result.Append(str[i]);
|
|
}
|
|
break;
|
|
default:
|
|
if (result != null)
|
|
{
|
|
result.Append(str[i]);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (result == null)
|
|
return (str.Substring(start, end - start + 1));
|
|
|
|
return (result.ToString());
|
|
}
|
|
|
|
#endregion
|
|
|
|
|
|
static internal String[] ReescapeWin32Strings(String[] array)
|
|
{
|
|
return array;
|
|
}
|
|
|
|
static internal String ReescapeWin32String(String str)
|
|
{
|
|
return str;
|
|
}
|
|
}
|
|
}
|