Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
MM/dd/yyyy とかちょっと
* CurrentCulture 依存怖い
* かと言って InvariantCulture は US 基準
* ヤードポンドの国は日付フォーマットも一味違うぜ

のコンボ回避用 InterpolatedStringHandler。
  • Loading branch information
ufcpp committed Aug 18, 2021
1 parent ce4e663 commit 930db8d
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 0 deletions.
7 changes: 7 additions & 0 deletions Demo/2021/Csharp10/Csharp10.sln
Expand Up @@ -39,6 +39,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ParamsDictionary", "Interpo
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImprovedInterpolatedStrings", "InterpolatedStrings\ImprovedInterpolatedStrings\ImprovedInterpolatedStrings.csproj", "{D524F574-482D-4AF3-BBF3-74DF47646ACF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InvariantGlobalization", "InterpolatedStrings\InvariantGlobalization\InvariantGlobalization.csproj", "{8F740CFB-BA24-4F1E-8531-852255B587CB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -101,6 +103,10 @@ Global
{D524F574-482D-4AF3-BBF3-74DF47646ACF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D524F574-482D-4AF3-BBF3-74DF47646ACF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D524F574-482D-4AF3-BBF3-74DF47646ACF}.Release|Any CPU.Build.0 = Release|Any CPU
{8F740CFB-BA24-4F1E-8531-852255B587CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8F740CFB-BA24-4F1E-8531-852255B587CB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8F740CFB-BA24-4F1E-8531-852255B587CB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8F740CFB-BA24-4F1E-8531-852255B587CB}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -120,6 +126,7 @@ Global
{67355A52-DF8C-40F4-95BC-2F1D80267DA2} = {A73FA307-E8C2-45BE-87C3-488C4E90E153}
{3F5FAC7F-5A7C-4F6A-97DA-941255FE9B5D} = {A73FA307-E8C2-45BE-87C3-488C4E90E153}
{D524F574-482D-4AF3-BBF3-74DF47646ACF} = {A73FA307-E8C2-45BE-87C3-488C4E90E153}
{8F740CFB-BA24-4F1E-8531-852255B587CB} = {A73FA307-E8C2-45BE-87C3-488C4E90E153}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {14B6A0CC-5203-434F-A0F8-1F7B1A66DE48}
Expand Down
@@ -0,0 +1,34 @@
using System.Globalization;
using System.Runtime.CompilerServices;

namespace InvariantGlobalization;

/// <summary>
/// Pass always <see cref="CultureInfo.InvariantCulture"/> to <see cref="DefaultInterpolatedStringHandler"/>.
/// </summary>
public static class Invariant
{
public static string Format(InterpolatedStringHandler handler) => handler.ToStringAndClear();
public static string Format(Span<char> initialBuffer, [InterpolatedStringHandlerArgument("initialBuffer")] InterpolatedStringHandler handler) => handler.ToStringAndClear();

[InterpolatedStringHandler]
public ref struct InterpolatedStringHandler
{
private DefaultInterpolatedStringHandler _inner;

public InterpolatedStringHandler(int literalLength, int formattedCount) => _inner = new(literalLength, formattedCount, CultureInfo.InvariantCulture);
public InterpolatedStringHandler(int literalLength, int formattedCount, Span<char> initialBuffer) => _inner = new(literalLength, formattedCount, CultureInfo.InvariantCulture, initialBuffer);

public void AppendLiteral(string value) => _inner.AppendLiteral(value);
public void AppendFormatted(ReadOnlySpan<char> value) => _inner.AppendFormatted(value);
public void AppendFormatted(ReadOnlySpan<char> value, int alignment = 0, string? format = null) => _inner.AppendFormatted(value, alignment, format);
public void AppendFormatted<T>(T value) => _inner.AppendFormatted(value);
public void AppendFormatted<T>(T value, string? format) => _inner.AppendFormatted(value, format);
public void AppendFormatted<T>(T value, int alignment) => _inner.AppendFormatted(value, alignment);
public void AppendFormatted<T>(T value, int alignment, string? format) => _inner.AppendFormatted(value, alignment, format);
public void AppendFormatted(object? value, int alignment = 0, string? format = null) => _inner.AppendFormatted(value, alignment, format);
public void AppendFormatted(string? value) => _inner.AppendFormatted(value);
public void AppendFormatted(string? value, int alignment = 0, string? format = null) => _inner.AppendFormatted(value, alignment, format);
public string ToStringAndClear() => _inner.ToStringAndClear();
}
}
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>

</Project>
@@ -0,0 +1,62 @@
using System.Globalization;
using System.Runtime.CompilerServices;

namespace InvariantGlobalization;

/// <summary>
/// In addition to <see cref="Invariant"/>, use ISO 8601 date format.
/// </summary>
/// <remarks>
/// <see cref="CultureInfo.InvariantCulture"/> uses USA format which is very unique.
///
/// see:
/// <a href="https://en.wikipedia.org/wiki/Calendar_date#Usage_map">Calendar date: Usage map</a>,
/// <a href="https://www.quora.com/Why-do-some-English-speakers-format-their-dates-as-M-D-Y-while-most-of-the-world-uses-either-D-M-Y-or-Y-M-D">Why do some English speakers format their dates as M/D/Y?</a>
/// </remarks>
public static class Iso8601
{
public static string Format(InterpolatedStringHandler handler) => handler.ToStringAndClear();
public static string Format(Span<char> initialBuffer, [InterpolatedStringHandlerArgument("initialBuffer")] InterpolatedStringHandler handler) => handler.ToStringAndClear();

[InterpolatedStringHandler]
public ref struct InterpolatedStringHandler
{
private DefaultInterpolatedStringHandler _inner;

public InterpolatedStringHandler(int literalLength, int formattedCount) => _inner = new(literalLength, formattedCount, CultureInfo.InvariantCulture);
public InterpolatedStringHandler(int literalLength, int formattedCount, Span<char> initialBuffer) => _inner = new(literalLength, formattedCount, CultureInfo.InvariantCulture, initialBuffer);

public void AppendLiteral(string value) => _inner.AppendLiteral(value);
public void AppendFormatted(ReadOnlySpan<char> value) => _inner.AppendFormatted(value);
public void AppendFormatted(ReadOnlySpan<char> value, int alignment = 0, string? format = null) => _inner.AppendFormatted(value, alignment, format);
public void AppendFormatted<T>(T value) => _inner.AppendFormatted(value);
public void AppendFormatted<T>(T value, string? format) => _inner.AppendFormatted(value, format);
public void AppendFormatted<T>(T value, int alignment) => _inner.AppendFormatted(value, alignment);
public void AppendFormatted<T>(T value, int alignment, string? format) => _inner.AppendFormatted(value, alignment, format);
public void AppendFormatted(object? value, int alignment = 0, string? format = null) => _inner.AppendFormatted(value, alignment, format);
public void AppendFormatted(string? value) => _inner.AppendFormatted(value);
public void AppendFormatted(string? value, int alignment = 0, string? format = null) => _inner.AppendFormatted(value, alignment, format);

public void AppendFormatted(TimeOnly value) => _inner.AppendFormatted(value, "O");
public void AppendFormatted(TimeOnly value, string? format) => _inner.AppendFormatted(value, format ?? "O");
public void AppendFormatted(TimeOnly value, int alignment) => _inner.AppendFormatted(value, alignment, "O");
public void AppendFormatted(TimeOnly value, int alignment, string? format) => _inner.AppendFormatted(value, alignment, format ?? "O");

public void AppendFormatted(DateOnly value) => _inner.AppendFormatted(value, "O");
public void AppendFormatted(DateOnly value, string? format) => _inner.AppendFormatted(value, format ?? "O");
public void AppendFormatted(DateOnly value, int alignment) => _inner.AppendFormatted(value, alignment, "O");
public void AppendFormatted(DateOnly value, int alignment, string? format) => _inner.AppendFormatted(value, alignment, format ?? "O");

public void AppendFormatted(DateTime value) => _inner.AppendFormatted(value, "O");
public void AppendFormatted(DateTime value, string? format) => _inner.AppendFormatted(value, format ?? "O");
public void AppendFormatted(DateTime value, int alignment) => _inner.AppendFormatted(value, alignment, "O");
public void AppendFormatted(DateTime value, int alignment, string? format) => _inner.AppendFormatted(value, alignment, format ?? "O");

public void AppendFormatted(DateTimeOffset value) => _inner.AppendFormatted(value, "O");
public void AppendFormatted(DateTimeOffset value, string? format) => _inner.AppendFormatted(value, format ?? "O");
public void AppendFormatted(DateTimeOffset value, int alignment) => _inner.AppendFormatted(value, alignment, "O");
public void AppendFormatted(DateTimeOffset value, int alignment, string? format) => _inner.AppendFormatted(value, alignment, format ?? "O");

public string ToStringAndClear() => _inner.ToStringAndClear();
}
}
@@ -0,0 +1,45 @@
using InvariantGlobalization;
using System.Globalization;

// CurrentCulture をあえてフランスに。
// 大陸ヨーロッパは小数点が , な事が多く。
Thread.CurrentThread.CurrentCulture = new CultureInfo("fr-fr");

// 12,345 になるわ、
// dd/MM/yyyy になるわ。
Console.WriteLine($@"{12.345}
{DateTimeOffset.Now}
{new DateOnly(2000, 1, 2)}
{new TimeOnly(11, 5, 30)}
");

// というか、CurrentCulture 取得はそれなりにコスト(ThreadStatic アクセスあり)かかるし、
// そもそも実行環境によって Format/Parse 結果が違うのが怖い。
// なので InvariantCulture にすると…
// 北米フォーマットになる。
// (IT 分野あるあるの「デフォルトは北米」。)
//
// MM/dd/yyyy とかちょっと…
// (英語圏でも Why do Americans? とか言われてるし、他のアメリカ大陸国からは It's only USA って言われてる。
// 元々英語の語順は変だし、イギリスも古くは MM/dd/yyyy だったけど周辺国の影響で更生されてそう。
// 元植民地が宗主国よりも変化に対して保守的になるのは結構あるあるらしい。)
Console.WriteLine(string.Create(CultureInfo.InvariantCulture, $@"{12.345}
{DateTimeOffset.Now}
{new DateOnly(2000, 1, 2)}
{new TimeOnly(11, 5, 30)}
"));

// まあとりあえず、常時 InvariantCulture を指定するオーバーロードを作る。
Console.WriteLine(Invariant.Format($@"{12.345}
{DateTimeOffset.Now}
{new DateOnly(2000, 1, 2)}
{new TimeOnly(11, 5, 30)}
"));

// で、さらに、日付フォーマットのデフォルトを O (ISO 8601 準拠)に変更。
// yyyy-MM-ddThh:mm:ss.fffffffK 相当。
Console.WriteLine(Iso8601.Format($@"{12.345}
{DateTimeOffset.Now}
{new DateOnly(2000, 1, 2)}
{new TimeOnly(11, 5, 30)}
"));

0 comments on commit 930db8d

Please sign in to comment.