none
Export-Csv での 日付書式について RRS feed

  • 質問

  • ODP.NETのOracleDataAdapterを使って、DataTableにSelect文の結果をFillしました。

    $dataTable | Export-Csv ... とやると、あっという間にCSVファイルにできてとても便利なのですが、

    OracleテーブルのDATE型の列(DataTable上はDateTime型?)が勝手にフォーマットされてしまいます。

    CSV利用側では、yyyy/MM/dd HH:mm:ss または yyyy/MM/dd のどちらかを要求しています。

    Export-Csvが参照している日付書式を一時的に変更するにはどうすれば良いのでしょうか。

    ネットを検索しましたが、見つかりませんでした。

    2014年1月21日 16:30

回答

  • 牟田口さん

    アドバイスありがとうございました。悩めるSE、Ticky4649です。
    いろいろと調べているうちに、なんとか光が見えてきました(^^)
    SQL*PlusのNLS_DATE_FORMATの書き換えとほぼ同じ方法です。

    まず、Export-Csv はどのように DateTime型をフォーマットするのかですが、
    書式を指定することができない以上、当然「DateTime.ToString()」を呼び出しているに
    違いないという仮定からスタートしました。
    そうすると、DateTime.ToString()は、現在のスレッドに設定されている CultureInfo
    を使用しているらしいことがわかってきました。
    このオブジェクトは[System.Threading.Thread]::CurrentThread.CurrentCulture
    で取得できます。さらにこの、CultureInfoの DateTimeFormat プロパティに
    DateTimeFormatInfoオブジェクトが設定されており、「DateTime.ToString()」はこの
    情報を参照するらしいことがわかりました。あとは、一本道でした。

    PS C:\TEMP> [System.Threading.Thread]::CurrentThread.CurrentCulture.DateTimeFormat

    AMDesignator                     : 午前
    Calendar                         : System.Globalization.GregorianCalendar
    DateSeparator                    : /
    FirstDayOfWeek                   : Sunday
    CalendarWeekRule                 : FirstDay
    FullDateTimePattern              : yyyy'年'M'月'd'日' H:mm:ss
    LongDatePattern                  : yyyy'年'M'月'd'日'
    LongTimePattern                  : H:mm:ss
    MonthDayPattern                  : M'月'd'日'
    PMDesignator                     : 午後
    RFC1123Pattern                   : ddd, dd MMM yyyy HH':'mm':'ss 'GMT'
    ShortDatePattern                 : yyyy/MM/dd
    ShortTimePattern                 : H:mm
    SortableDateTimePattern          : yyyy'-'MM'-'dd'T'HH':'mm':'ss
    TimeSeparator                    : :
    UniversalSortableDateTimePattern : yyyy'-'MM'-'dd HH':'mm':'ss'Z'
    YearMonthPattern                 : yyyy'年'M'月'
    AbbreviatedDayNames              : {日, 月, 火, 水...}
    ShortestDayNames                 : {日, 月, 火, 水...}
    DayNames                         : {日曜日, 月曜日, 火曜日, 水曜日...}
    AbbreviatedMonthNames            : {1, 2, 3, 4...}
    MonthNames                       : {1月, 2月, 3月, 4月...}
    IsReadOnly                       : False
    NativeCalendarName               : 西暦 (日本語)
    AbbreviatedMonthGenitiveNames    : {1, 2, 3, 4...}
    MonthGenitiveNames               : {1月, 2月, 3月, 4月...}

    DateTime.ToString() が使用するのはこのオブジェクトのプロパティの
     ・ShortDatePatternプロパティ(日付部分)
     ・LongTimePattern(時刻部分)
    のようなので、このプロパティの書式を変更することでうまくいきました。
    以下は、Get-Dateでテストしてますが、Export-Csvでも同じでした。

    PS C:\TEMP> (Get-Date).ToString()
    2014/01/22 14:50:23
    PS C:\TEMP> [System.Threading.Thread]::CurrentThread.CurrentCulture.DateTimeFormat.ShortDatePattern = "yy-MM-dd"
    PS C:\TEMP> (Get-Date).ToString()
    14-01-22 14:50:35
    PS C:\TEMP> [System.Threading.Thread]::CurrentThread.CurrentCulture.DateTimeFormat.LongTimePattern = ""
    PS C:\TEMP> (Get-Date).ToString()
    14-01-22

    時刻部分が不要な場合は、$nullやstring.Emptyを設定することで表示されなくなりますが、スペースが1つ付いてしまいます。
    まぁ、これぐらいは許容範囲内かと...
    変更した DateTimeFormat は使用後は元に戻しておかなくてはとも思いましたが、どうせ処理が終わったら
    現スレッド自体おさらばするわけだし、特にいいかなぁと。。。

    以上、報告でした。

    2014年1月22日 6:01

すべての返信

  • $dataTable | Select-Object name,number,@{Name="date";Expression={$_.ToString("yyyy/MM/dd HH:mm:ss")}} | Export-Csv 

    のように、Select-Objectコマンドレットを併用し、集計プロパティを利用してはいかがでしょうか。

    ※テーブルにname,number,dateという列が存在し、このうちdate列がDateTime型であると仮定しています。


    2014年1月21日 18:44
    モデレータ
  • 牟田口さん、レスありがとうございました。ポケットリファレンス愛用しています(^^)

    説明不足で申し訳ありません。
    いま作ろうとしている機能は、呼び出し側よりSQL文とDATE型の日付書式などを引数として受け取り
    SQL文の実行結果をCSVにして返すというものです。

    つまり、SQL文にどのような列が含まれているか、どの列がDATE型かということが事前にわからないのです。
    SQL文は複数のテーブルやビューがJOINされていたり、列の別名が付けられていたりするので、オラクルの
    ディクショナリ情報(USER_TAB_COLUMNSなど)を参照するわけにもいきません。
    それこそ、動的にGetSchema()を出して、DataTableのメタデータにアクセスしなければならないのですが、
    そこまでやってしまうと便利な Export-Csv を使う意味がなくなってしまいます(というか、それでできれば
    まだいいのですが...できるのかなぁ?)。

    SELECT文の結果をCSVにするには、Oracleの場合SQL*Plusでもできるわけですが、
     ALTER SESSION SET NLS_DATE_FORMAT='YYYY/MM/DD HH24:MI:SS';
    と一発やっておけば、SQL*Plusはその書式にしたがってDATE型をフォーマットしてくれます。
    (DATE型の列ごとに書式を変える予定はありません。DATE型の全列が指定の書式で出力されれば十分です)

    ただ、SQL*Plusを使うと、ヘッダー行をどうやって動的に出すかとか、列データの中に「"」が含まれていたり
    すると「""」に編集し直したりしなければならず、かなり面倒になります。
    Export-Csvはこのあたりの編集を自動でやってくれるようなのでこれからどんどん利用していきたいのですが、
    DATE型の書式指定ができないようなので、「う~ん、何だかなぁ」状態で悩んでおります。
    Oracle SQL DeveloperのCSVエクスポート機能を使えば希望のCSVができるのですが、GUIアプリなので
    定型処理(バッチ処理)に向いていません。どれもこれも「帯に短し、襷に長し」なんです。

    「SQL*PlusのNLS_DATE_FORMATの一時的な変更」相当のことをやらせて簡便にExport-Csvを利用したいというのが
    こちらの希望です(Oracleを知っているという前提になってしまい申し訳ありません)。

     

    2014年1月22日 1:02
  • # 拙著ご愛読ありがとうございます

    それでは列名を指定するのではなく、値がDateTime型であるかどうかを逐一判定する方式はどうでしょうか。

    $dataTable | foreach {
        $propertyNames = @($_.PSObject.Properties.Name)
        $out = @{}
        foreach($n in $propertyNames)
        {
            $value = $_.$n
            if( $value -is [DateTime])
            {
                $out[$n] = $value.tostring("yyyy/MM/dd HH:mm:ss")
            }
            else
            {
                $out[$n] = $value
            }
        }
        [pscustomobject]$out
    }| Export-Csv ... 

    うーん、正直いまいちですね。

    DateTime型に対しUpdate-TypeDataコマンドレットでToStringメソッド定義を上書きとかできないかとも思いましたが…

    2014年1月22日 4:37
    モデレータ
  • 牟田口さん

    アドバイスありがとうございました。悩めるSE、Ticky4649です。
    いろいろと調べているうちに、なんとか光が見えてきました(^^)
    SQL*PlusのNLS_DATE_FORMATの書き換えとほぼ同じ方法です。

    まず、Export-Csv はどのように DateTime型をフォーマットするのかですが、
    書式を指定することができない以上、当然「DateTime.ToString()」を呼び出しているに
    違いないという仮定からスタートしました。
    そうすると、DateTime.ToString()は、現在のスレッドに設定されている CultureInfo
    を使用しているらしいことがわかってきました。
    このオブジェクトは[System.Threading.Thread]::CurrentThread.CurrentCulture
    で取得できます。さらにこの、CultureInfoの DateTimeFormat プロパティに
    DateTimeFormatInfoオブジェクトが設定されており、「DateTime.ToString()」はこの
    情報を参照するらしいことがわかりました。あとは、一本道でした。

    PS C:\TEMP> [System.Threading.Thread]::CurrentThread.CurrentCulture.DateTimeFormat

    AMDesignator                     : 午前
    Calendar                         : System.Globalization.GregorianCalendar
    DateSeparator                    : /
    FirstDayOfWeek                   : Sunday
    CalendarWeekRule                 : FirstDay
    FullDateTimePattern              : yyyy'年'M'月'd'日' H:mm:ss
    LongDatePattern                  : yyyy'年'M'月'd'日'
    LongTimePattern                  : H:mm:ss
    MonthDayPattern                  : M'月'd'日'
    PMDesignator                     : 午後
    RFC1123Pattern                   : ddd, dd MMM yyyy HH':'mm':'ss 'GMT'
    ShortDatePattern                 : yyyy/MM/dd
    ShortTimePattern                 : H:mm
    SortableDateTimePattern          : yyyy'-'MM'-'dd'T'HH':'mm':'ss
    TimeSeparator                    : :
    UniversalSortableDateTimePattern : yyyy'-'MM'-'dd HH':'mm':'ss'Z'
    YearMonthPattern                 : yyyy'年'M'月'
    AbbreviatedDayNames              : {日, 月, 火, 水...}
    ShortestDayNames                 : {日, 月, 火, 水...}
    DayNames                         : {日曜日, 月曜日, 火曜日, 水曜日...}
    AbbreviatedMonthNames            : {1, 2, 3, 4...}
    MonthNames                       : {1月, 2月, 3月, 4月...}
    IsReadOnly                       : False
    NativeCalendarName               : 西暦 (日本語)
    AbbreviatedMonthGenitiveNames    : {1, 2, 3, 4...}
    MonthGenitiveNames               : {1月, 2月, 3月, 4月...}

    DateTime.ToString() が使用するのはこのオブジェクトのプロパティの
     ・ShortDatePatternプロパティ(日付部分)
     ・LongTimePattern(時刻部分)
    のようなので、このプロパティの書式を変更することでうまくいきました。
    以下は、Get-Dateでテストしてますが、Export-Csvでも同じでした。

    PS C:\TEMP> (Get-Date).ToString()
    2014/01/22 14:50:23
    PS C:\TEMP> [System.Threading.Thread]::CurrentThread.CurrentCulture.DateTimeFormat.ShortDatePattern = "yy-MM-dd"
    PS C:\TEMP> (Get-Date).ToString()
    14-01-22 14:50:35
    PS C:\TEMP> [System.Threading.Thread]::CurrentThread.CurrentCulture.DateTimeFormat.LongTimePattern = ""
    PS C:\TEMP> (Get-Date).ToString()
    14-01-22

    時刻部分が不要な場合は、$nullやstring.Emptyを設定することで表示されなくなりますが、スペースが1つ付いてしまいます。
    まぁ、これぐらいは許容範囲内かと...
    変更した DateTimeFormat は使用後は元に戻しておかなくてはとも思いましたが、どうせ処理が終わったら
    現スレッド自体おさらばするわけだし、特にいいかなぁと。。。

    以上、報告でした。

    2014年1月22日 6:01
  • なるほど、スレッドのCultureInfoを書き換えることでDateTimeのデフォルト書式を変更できるのですね。

    当方はDateTime.ToString()をスクリプトメソッドに強制置換する方針でやってみたのですが、

    Update-TypeData -TypeName DateTime -MemberType ScriptMethod -MemberName ToString -Value {$this.PSBase.ToString("yy-MM-dd")} -Force

    とすると(Get-Date).ToString()は確かに反映するのですが、Export-Csvでは反映されませんでした。おそらく内部的にPSで拡張する前のメソッドを呼ぶためなんだと思います。

    2014年1月22日 6:26
    モデレータ
  • 牟田口さん

    当方、PowerShellは2カ月ほどの初心者でして、ご提示いただいた方法は、正直何をやっているかわからない状態です(^^;;;

    今後、内容がわかるレベルになるべく精進していきたいと思います。ありがとうございました。

    2014年1月22日 6:56
  • 再度、追加質問です。
    $dataTable | Export-Csv で、DateTime型の列を綺麗に「yyyy/MM/dd」形式で出すには
    どのようにすればよいでしょうか。前の投稿で

    >時刻部分が不要な場合は、$nullやstring.Emptyを設定することで表示されなくなりますが、
    >スペースが1つ付いてしまいます。まぁ、これぐらいは許容範囲内かと...
    と思っていたのですが、利用側に「だっせぇ~」と言われてしまいました(DATE型が結構ある
    ので、後ろの空白)がことのほか目立ってしまっているようです。

    DateTime.ToString() [引数なし]は、標準書式として「G」を使用するらしく
    書式指定文字列として、DateTimeFormatInfoオブジェクトの
    ①ShortDatePattern + ②" " + ③LongDatePattern
    を使ってフォーマットします(前の投稿で書いた内容です)。
    ③を無理矢理 "" に設定しても、②の部分のスペースが残ってしまう理屈です。
    標準書式「G」の代わりに、「d」を指定することができれば、時刻部分なしの「yyyy/MM/dd」
    が空白なしで取得できるものと思われるのですが、この「G」をどこから取得しているのか
    わかりません(ハードコードされてたら無理なんでしょうけども)。

    まぁ、できたCSVを再度読み込んで、空白を削除するという手もあるとは思いますが、
    もっとスマートにやりたいなぁと...

    2014年1月26日 14:13
  • Ticky4649 さま よろしく。

    CSV利用側では、yyyy/MM/dd HH:mm:ss または yyyy/MM/dd のどちらかを要求 > 確認です。
    yyyy/M/d ではなく、yyyy/MM/dd ですよね。
    それなら、日付の桁数は固定なので、次では如何ですか。

    (get-date).tostring().substring(0,10)


    PS C:\Users\UserName> (get-date)
    
    2014年1月27日 10:32:46
    
    PS C:\Users\UserName> (get-date).tostring()
    2014/01/27 10:32:59
    PS C:\Users\UserName> (get-date).tostring().substring(0,10)
    2014/01/27
    PS C:\Users\UserName> (get-date).tostring().substring(0,10)+";"
    2014/01/27;
    

    • 編集済み ShiroYuki_Mot 2014年1月27日 1:57 画面イメージ挿入
    2014年1月27日 1:40
  • ShiroYuki_Motさん

    レスありがとうございます。
    前の話題に追加質問したので前提がわかりにくくなってしまい申し訳ありません。

    DateTime.ToString() が自分で呼び出せるのであれば、引数を指定したり、いくらでも
    加工はできるのですが、今回のポイントは Export-Csv が内部的に呼び出している
    DateTime.ToString() に対して、デフォルトの書式を変更できないかという点です。

    SELECT文の実行結果を $dataTable に格納し、$dataTable | Export-Csv ... とやると、
    簡単にCSVが作れるのはとても便利なのですが、$dataTableに入っている DateTime型の
    列データが、Export-Csvによって、勝手にフォーマットされてしまう(具体的には、
    yyyy/MM/dd H:mm:ss)ため、Export-Csv の呼出し前に何らかの設定変更を行い、こちらの
    指定した形式で出力されるように制御できないかということなのです。

    たとえば、時刻部分は不要なので、

    ... ,"2014/01/27", ...

    というCSVを作ってもらいたいのに

    ... ,"2014/01/27 13:00:00", ...

    といったCSVができてしまうわけです。これに対し、今までの話の流れでは、Export-Csv
    を呼び出す前に、カレントスレッドのCurrentCulture.DateTimeFormat を
    ShortDatePattern = "yyyy/MM/DD"
    LongDatePattern  = ""
    と書き換えておけば、それなりのものができるのですが、下のように

    ... ,"2014/01/27 ", ...

    後ろに余計なスペースが入り込んでしまうわけです。これを何とかスマートなやり方で
    できないかということなんです。
    できたCSVを再度読んでパターンマッチして削除するなどということをせずにです。

    わかりにくくて申し訳ありません。

    2014年1月27日 4:50
  • Ticky4649 さま 早合点で、大変、失礼致しました。

    内部的に呼ばれる .Tostring に対して、設定を変えて日付のみを取得させるには という事だったのですね。
    オーバーライドが頭を過ぎりますが、既に、牟田口大介 さまが試されて、 NO ! の様で ... 。

    ShortDatePattern や LongDatePattern ではなく、直接 dateTimeInfo.FullDateTimePattern = "yyyy/MM/dd" とか、
    でも駄目ですね。
    難しいですね。 失礼致しました。

    後思い付くのは、SELECT文で DateTime から Date (String) に変換しておく位しか思い浮かびません。
    お騒がせしました。

    2014年1月27日 7:07
  • ShiroYuki_Motさん

    わたしも、スペースが付くのがいやだったらSELECT文でTO_CHAR()してフォーマットを切ってくれと言ってます。

    結構、SELECT * でSQL文を作る人が多くて...

    何か進展がありましたらよろしくお願いします。

    2014年1月27日 15:00