none
PCの日付設定が和暦になっていると、DateTime.ToStringを行うと区切り文字が「年」になってしまう RRS feed

  • 質問

  • お世話になります。

    VisualStudio2017、C#で開発をしております。


    問題が発生する環境の前提条件として以下の条件があります。

    日付のデータ形式が以下のようになっている

    • カレンダー:和暦
    • 日付(短い形式):「平成○○年○月○日」


    この条件の時に、プログラムのターゲットフレームワークを .NETFramework2.0~3.5 にすると、以下のコードで出力結果がおかしくなります。

    static void Main(string[] args)
    {
        var culture = CultureInfo.CreateSpecificCulture("ja-JP");
        culture.DateTimeFormat.Calendar = new GregorianCalendar();
    
        DateTime date = new DateTime(2000, 1, 1);
        Console.WriteLine(date.ToString("yyyy/MM/dd", culture)); // 「2000年01年01」 と表示されてしまう
        Console.Read();
    }

    Win10 Pro(x64)、Win7(x86) 二つの環境で試してみましたが、どちらも同じ挙動を行います。

    DateTimeFormat.DateSeparatorがなぜか「年」になってしまっているため、このような挙動になっています。


    ちなみにターゲットフレームワークが.Net4.0以上の場合は「2000/01/01」と正常に表示されます。

    不可解なのが、Calendarをセットする前に以下のようにDateSeparatorにアクセスをすると正常な表示になります。

    static void Main(string[] args)
    {
        var culture = CultureInfo.CreateSpecificCulture("ja-JP");
        var dummy = culture.DateTimeFormat.DateSeparator; //何の意味もないアクセスするためだけの処理
        culture.DateTimeFormat.Calendar = new GregorianCalendar();
     
        DateTime date = new DateTime(2000, 1, 1);
        Console.WriteLine(date.ToString("yyyy/MM/dd", culture)); // 「2000/01/01」 と正常に表示
        Console.Read();
    }

    これは.NET3.5系のバグなのでしょうか?何か根本的な解決方法や情報がありましたらご教授いただければ幸いです。

    ちなみにターゲットを.NET4以上にあげるのは現状難しいです・・・^^;


    • 編集済み Kinenows 2019年8月29日 7:29
    2019年8月29日 7:28

すべての返信

  • そもそも DateTime 型に対する書式文字 "/"ロケール依存であり、必ずしもスラッシュの出力を意味しません。あくまでも DateTimeFormatInfo.DateSeparator プロパティの値を表示するためのものであるはずです。

    同様に、書式文字 ":"ロケール依存であり、コロンではなく DateTimeFormatInfo.TimeSeparator を表すものです。

    確実に「/」が出力されるようにしたいのであれば、下記のように書かなければなりません。

    // Console.WriteLine(date.ToString("yyyy/MM/dd", culture));
    Console.WriteLine(date.ToString("yyyy\\/MM\\/dd", culture));
    Console.WriteLine(date.ToString(@"yyyy\/MM\/dd", culture));
    2019年8月29日 7:46
  • 魔界の仮面弁士さんご回答ありがとうございます。

    書式文字の "/" がロケール依存であることは重々承知しております。

    今回の問題で明らかにしたい点としては DateTimeFormatInfo.DateSeparator の値が異常になっているという点です。

    分かりにくい説明で申し訳ありません^^;

    .Net4では正常な動作を行うため、何か情報はないかと助けを求めている次第です。
    2019年8月29日 8:08
  • var dummy = culture.DateTimeFormat.DateSeparator; //何の意味もないアクセスするためだけの処理
    

    上記については、DateTimeFormat プロパティにアクセスするだけで回避されます。

    var dummy = culture.DateTimeFormat;

    しかし本質的には、書式指定を見直すべき案件であると思います。

    また、.NET バージョンによって結果が異なっているのは、.NET Framework のバージョンによって、DateTimeFormat プロパティの初期値の構築手続きに差異があるためです。

    ※下記はイメージコードであり、実際のものとは多少異なります。

    // System.Globalization.CultureInfo
    public virtual DateTimeFormatInfo DateTimeFormat
    {
      get
      {
        if (this.dateTimeInfo == null)
        {
          DateTimeFormatInfo dateTimeFormatInfo;
          #if CLR4
            dateTimeFormatInfo = new DateTimeFormatInfo(this.m_cultureData, this.Calendar);
          #else
            dateTimeFormatInfo = new DateTimeFormatInfo(this.m_cultureTableRecord, CultureInfo.GetLangID(this.cultureID), this.Calendar);
          #endif
          dateTimeFormatInfo.m_isReadOnly = this.m_isReadOnly;
          Thread.MemoryBarrier();
          this.dateTimeInfo = dateTimeFormatInfo;
        }
        return this.dateTimeInfo;
      }
      set
      {
        #if CLR4
          if (value == null)
          {
            throw new ArgumentNullException("value", Environment.GetResourceString("ArgumentNull_Obj"));
          }
          this.VerifyWritable();
        #else
          this.VerifyWritable();
          if (value == null)
          {
            throw new ArgumentNullException("value", Environment.GetResourceString("ArgumentNull_Obj"));
          }
        #endif
        this.dateTimeInfo = value;
      }
    }

    手元に資料が無いので確認できませんが、カルチャ周りについては、CLR1 から CLR2 の段階でも仕様変更があったはず…。

    2019年8月29日 8:24
  • とりあえず下記の場合「.NET Framework 2.0 ~ 3.5」+ 「culture1 パターン」においてのみ "年" で出力されることになるようです。

    //var cultureX = CultureInfo.CreateSpecificCulture("ja-JP");
    ////var dummy = culture.DateTimeFormat;
    //cultureX.DateTimeFormat.Calendar = new GregorianCalendar();
    
    var culture0 = new CultureInfo("ja-JP", true) { DateTimeFormat = { Calendar = new GregorianCalendar(), DateSeparator = "/" } };
    var culture1 = new CultureInfo("ja-JP", true) { DateTimeFormat = { Calendar = new GregorianCalendar() } };
    var culture2 = new CultureInfo("ja-JP", false) { DateTimeFormat = { Calendar = new GregorianCalendar() } };
    var culture3 = new CultureInfo("ja-JP", true) { DateTimeFormat = { Calendar = new JapaneseCalendar() } };
    var culture4 = new CultureInfo("ja-JP", false) { DateTimeFormat = { Calendar = new JapaneseCalendar() } };
    
    DateTime date = new DateTime(2000, 1, 1);
    foreach (var culture in new[] { culture0, culture1, culture2, culture3, culture4 })
    {
        Console.WriteLine("---{0} : '{1}'", culture.DateTimeFormat.NativeCalendarName, culture.DateTimeFormat.DateSeparator);
        Console.WriteLine(date.ToString("yyyy/MM/dd", culture));
        // Console.WriteLine(date.ToString("yyyy\\/MM\\/dd", culture));
        // Console.WriteLine(date.ToString(@"yyyy\/MM\/dd", culture));
    }
    

    .Net4では正常な動作を行うため、何か情報はないかと助けを求めている次第です。

    対策として挙げられるのは今のところ、以下の 4 つですね。

    1. DateSeparator に依存せぬよう、「/」書式を 「\/」書式に改める。
    2. DateSeparator が「/」となるような CultureInfo を利用する。
    3. コードは修正せず、.NET Framework バージョンのみ変更する。
    4. コントロールパネルの地域設定で和暦モードを指定しないようにする。

    自分としては 1 を推奨しますが、次点で 2 かな…? 3 や 4 は問題の先送りでしか無いので、個人的には避けたいところです。

    2019年8月29日 9:35
  • 引き続きの調査ありがとうございます。

    現時点での解決方法としては、「2」の CultureInfo 側をどうにかする方法で凌ぐしかないようですね。

    「¥/」に全て変更するとしても DateSeparator を明示的に「-」で変更する製品などもあり、基幹部分に組み込むとコードとして汎用さに欠けてしまうため採用しづらいです。

    何はともあれ調査などお時間取って頂き誠にありがとうございます!今後の参考に是非させていただきます!

    2019年8月30日 6:16