Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

J2N.Text.StringFormatter: Added constructor overload for IFormatProvider #46

Merged
merged 2 commits into from
Dec 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@

<!-- Features in .NET 5.x and .NET 6.x only -->
<PropertyGroup Condition=" $(TargetFramework.StartsWith('net5.')) Or $(TargetFramework.StartsWith('net6.')) ">


<DefineConstants>$(DefineConstants);FEATURE_CULTUREINFO_PREDEFINEDONLY</DefineConstants>
<DefineConstants>$(DefineConstants);FEATURE_HASHSET_MODIFY_CONTINUEENUMERATION</DefineConstants>
<DefineConstants>$(DefineConstants);FEATURE_ICU</DefineConstants>

Expand Down Expand Up @@ -132,8 +133,6 @@
<!-- Features not in .NET Framework 4.0 and .NET Framework 4.5.2 (the target framework we use for testing .NET Framework 4.0) -->
<PropertyGroup Condition="'$(TargetFramework)' != 'net40' And '$(TargetFramework)' != 'net452'">

<DefineConstants>$(DefineConstants);FEATURE_CULTUREINFO_DEFAULTTHREADCURRENTCULTURE</DefineConstants>
<DefineConstants>$(DefineConstants);FEATURE_CULTUREINFO_DEFAULTTHREADCURRENTUICULTURE</DefineConstants>
<DefineConstants>$(DefineConstants);FEATURE_EXCEPTIONDISPATCHINFO</DefineConstants>
<DefineConstants>$(DefineConstants);FEATURE_IREADONLYCOLLECTIONS</DefineConstants>
<DefineConstants>$(DefineConstants);FEATURE_METHODIMPLOPTIONS_AGRESSIVEINLINING</DefineConstants>
Expand Down
85 changes: 59 additions & 26 deletions src/J2N/Text/StringFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,6 @@ public class StringFormatter : IFormatProvider, ICustomFormatter
/// </summary>
public static StringFormatter CurrentUICulture { get; } = new StringFormatter(CultureType.CurrentUICulture);

///// <summary>
///// Gets a <see cref="StringFormatter"/> that uses the default culture for threads in the current application domain to format values.
///// </summary>
//public static StringFormatter DefaultThreadCurrentCulture { get; } = new StringFormatter(CultureType.DefaultThreadCurrentCulture);

///// <summary>
///// Gets a <see cref="StringFormatter"/> that uses the default UI culture for threads in the current application domain to format values.
///// </summary>
//public static StringFormatter DefaultThreadCurrentUICulture { get; } = new StringFormatter(CultureType.DefaultThreadCurrentUICulture);

/// <summary>
/// Gets a <see cref="StringFormatter"/> that uses the invariant culture to format values.
/// This is the default setting in Java.
Expand All @@ -75,6 +65,8 @@ public class StringFormatter : IFormatProvider, ICustomFormatter
private CultureInfo? culture; // not readonly for deserialization
private readonly CultureType? cultureType;

private IFormatProvider? formatProvider; // NOTE: This needs to be [Serializable] to support serialization. Note on .NET Core serialization has been dropped on these implementations.

/// <summary>
/// Initializes a new instance of <see cref="StringFormatter"/>.
/// </summary>
Expand All @@ -84,6 +76,10 @@ public StringFormatter()

/// <summary>
/// Initializes a new instance of <see cref="StringFormatter"/> with the specified <paramref name="culture"/>.
/// <para/>
/// <b>NOTE:</b> This overload only supports serialization of built-in cultures. If you require serialization and have a custom implementation,
/// you will need to provide a serializable wrapper to the <see cref="StringFormatter.StringFormatter(IFormatProvider)"/> constructor. Note that
/// on .NET Core and newer .NET platforms serialization is not supported for <see cref="CultureInfo"/>, <see cref="NumberFormatInfo"/> and <see cref="DateTimeFormatInfo"/>.
/// </summary>
/// <param name="culture">A <see cref="CultureInfo"/> that specifies the culture-specific rules that will be used for formatting.</param>
/// <exception cref="ArgumentNullException">If <paramref name="culture"/> is <c>null</c>.</exception>
Expand All @@ -94,6 +90,21 @@ public StringFormatter(CultureInfo culture)
this.cultureSymbol = this.culture.Name.ToCharArray(); // For deserialization
}

/// <summary>
/// Initializes a new instance of <see cref="StringFormatter"/> with the specified <paramref name="formatProvider"/>.
/// <para/>
/// <b>NOTE:</b> If binary serialization is required, the type passed must be annotated with the <see cref="SerializableAttribute"/> and otherwise be
/// setup for serialization and deserialization. However, note that on .NET Core and newer .NET platforms serialization is not supported for
/// <see cref="CultureInfo"/>, <see cref="NumberFormatInfo"/> and <see cref="DateTimeFormatInfo"/>.
/// </summary>
/// <param name="formatProvider">An <see cref="IFormatProvider"/> that specifies the culture-specific rules that will be used for formatting.</param>
/// <exception cref="ArgumentNullException">If <paramref name="formatProvider"/> is <c>null</c>.</exception>
public StringFormatter(IFormatProvider formatProvider)
: this(CultureType.IFormatProvider)
{
this.formatProvider = formatProvider ?? throw new ArgumentNullException(nameof(formatProvider));
}

internal StringFormatter(CultureType cultureType)
{
this.cultureType = cultureType;
Expand All @@ -102,6 +113,7 @@ internal StringFormatter(CultureType cultureType)
/// <summary>
/// Gets the culture of the current instance.
/// </summary>
[Obsolete("Store the CultureInfo in your subclass from the CultureInfo constructor. Note that .NET doesn't provide a reliable way to get from IFormatProvider > CultureInfo, so this will return the current cultue when using the IFormatProvider constructor.")]
protected virtual CultureInfo Culture
{
get
Expand All @@ -116,14 +128,31 @@ protected virtual CultureInfo Culture
return CultureInfo.CurrentCulture;
case CultureType.CurrentUICulture:
return CultureInfo.CurrentUICulture;
#if FEATURE_CULTUREINFO_DEFAULTTHREADCURRENTCULTURE
case CultureType.DefaultThreadCurrentCulture:
return CultureInfo.DefaultThreadCurrentCulture ?? CultureInfo.CurrentCulture;
#endif
#if FEATURE_CULTUREINFO_DEFAULTTHREADCURRENTUICULTURE
case CultureType.DefaultThreadCurrentUICulture:
return CultureInfo.DefaultThreadCurrentUICulture ?? CultureInfo.CurrentUICulture;
#endif
default:
return CultureInfo.CurrentCulture;
}
}
}

/// <summary>
/// Gets the <see cref="IFormatProvider"/> of the current instance.
/// </summary>
private IFormatProvider FormatProvider
{
get
{
switch (cultureType)
{
case CultureType.IFormatProvider:
return formatProvider!;
case CultureType.CustomCulture:
return culture!;
case CultureType.InvariantCulture:
return CultureInfo.InvariantCulture;
case CultureType.CurrentCulture:
return CultureInfo.CurrentCulture;
case CultureType.CurrentUICulture:
return CultureInfo.CurrentUICulture;
default:
return CultureInfo.CurrentCulture;
}
Expand All @@ -134,10 +163,9 @@ internal enum CultureType
{
CurrentCulture,
CurrentUICulture,
DefaultThreadCurrentCulture,
DefaultThreadCurrentUICulture,
InvariantCulture,
CustomCulture
CustomCulture,
IFormatProvider
}

/// <summary>
Expand All @@ -150,9 +178,9 @@ internal enum CultureType
if (typeof(ICustomFormatter).Equals(formatType))
return this;
if (typeof(NumberFormatInfo).Equals(formatType))
return NumberFormatInfo.GetInstance(Culture);
return NumberFormatInfo.GetInstance(FormatProvider);
if (typeof(DateTimeFormatInfo).Equals(formatType))
return DateTimeFormatInfo.GetInstance(Culture);
return DateTimeFormatInfo.GetInstance(FormatProvider);

return null;
}
Expand Down Expand Up @@ -250,10 +278,15 @@ internal void OnDeserializedMethod(System.Runtime.Serialization.StreamingContext
{
// We only need to deserialize custom cultures. Note that if it is not a built-in
// culture, this will fail.
// J2N TODO: On newer .NET platforms, there is an overload that accepts a predefinedOnly parameter
// that when set to false allows retrieving made-up cultures. Need to investigate.
if (cultureType == CultureType.CustomCulture)
this.culture = CultureInfo.GetCultureInfo(new string(this.cultureSymbol!));
{
this.culture = CultureInfo.GetCultureInfo(new string(this.cultureSymbol!)
#if FEATURE_CULTUREINFO_PREDEFINEDONLY
, predefinedOnly: true // We only support predefined cultures for serialization. End users must provide their own serializable IFormatProvider implementation for other types.
#endif
);
}

}
#endif

Expand Down
10 changes: 10 additions & 0 deletions tests/J2N.Tests/Text/TestStringFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,16 @@ public void TestDecimalPlaces_Float()
public void TestDecimalPlaces_Double()
{
assertEquals("22.0", string.Format(StringFormatter.InvariantCulture, "{0}", 22d));

assertEquals("22,0", string.Format(new StringFormatter(new CultureInfo("fr-FR")), "{0}", 22d));
}

[Test]
public void TestIFormatProvider_Double()
{
assertEquals("22.0", string.Format((IFormatProvider)StringFormatter.InvariantCulture, "{0}", 22d));

assertEquals("22,0", string.Format(new StringFormatter((IFormatProvider)new CultureInfo("fr-FR")), "{0}", 22d));
}

[Test]
Expand Down