DateTime==>日付リテラルの変換

タグの編集
投稿者 まっさん  (社会人) 投稿日時 2018/5/26 17:32:19
DateTime==>日付リテラルの変換で以下のような拡張メソッドを作成しました。
ここで時間部分のミリ秒以下の精度について疑問がでました。
以下によると「時刻の値はタイマー刻み、100 ナノ秒単位で測定されます」とあります。
https://msdn.microsoft.com/ja-jp/library/system.datetime(v=vs.110).aspx

100 ナノ秒=1/10000000秒だと思うので、時間の秒の位の小数点以下の精度は7桁になると
思うのですが、DateTime.Millisecondプロパティではミリ秒部分 (0 ~ 999)は3桁しかありません。

100 ナノ秒単位の時間を得るにはどうすればいいですか?
拡張メソッドも、もっと簡潔に書ける方法があれば、教えてください。

    ''' <summary> DateTimeを日付リテラル文字列(#MM/dd/yyyy HH:mm:ss.fff#)に変換</summary>
    ''' <param name="IsDayOnly">日付のみ:True 時間も含める:False[規定値]</param>
    <Runtime.CompilerServices.Extension()>
    Public Function ToStringリテラル(dt As DateTime, Optional IsDayOnly As Boolean = False) As String
        Dim year As String = dt.Year.ToString.PadLeft(4, "0"c)
        Dim month As String = dt.Month.ToString.PadLeft(2, "0"c)
        Dim day As String = dt.Day.ToString.PadLeft(2, "0"c)
        Dim hour As String = dt.Hour.ToString.PadLeft(2, "0"c)
        Dim minute As String = dt.Minute.ToString.PadLeft(2, "0"c)
        Dim second As String = dt.Second.ToString.PadLeft(2, "0"c)
        Dim millisecond As String = dt.Millisecond.ToString.PadLeft(3, "0"c)
        If IsDayOnly Then
            Return String.Format("#{0}/{1}/{2}#", month, day, year)
        Else
            Return String.Format("#{0}/{1}/{2} {3}:{4}:{5}.{6}#", month, day, year, hour, minute, second, millisecond)
            'Return String.Format("#{0}#", dt.ToString("G", CultureInfo.InvariantCulture))
        End If
    End Function

ここまで書いて一旦投稿後、読み返していて気付いたのですが時間と時刻の違いでしょうか?
msdnのURLでは「時刻の値は~」とありますし。。。
以下のようなTimeSpan型の変数を用意すれば秒の位の小数点以下の精度は7桁まで得られました。
でもこれでいいのか自信がありません。
 Dim dts As TimeSpan = dt.TimeOfDay
投稿者 まっさん  (社会人) 投稿日時 2018/5/26 22:37:43
すみません。
その後、DataGridViewにDataTable=>BindingSourceで日付列をバインドして
BindingSource.Filterプロパティ で確認しました。
結果、DateTimeはやはり100 ナノ秒単位の精度を持っていて、TimeOfDayプロパティで得られる
値でOKという結論に至りました。

どうも、お騒がせして申し訳ありません。
明日まで未解決にしておきますので何か考え違いがありましたら、ご指摘ください。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2018/5/27 14:45:56
> DateTime==>日付リテラルの変換で以下のような拡張メソッドを作成しました。
「#12/31/2000 12:34:56.789#」 形式の文字列を返す拡張メソッドでしょうか。

DataView の RowFilter プロパティ等では、秒未満のリテラルも指定できますが、
Visual Basic の日付リテラルは秒未満の表現をサポートしていないのでご注意を。
'Dim dt0 As Date = #12/31/2000 12:34:56.789#  'これはエラー 
Dim dt1 As Date = #12/31/2000 12:34:56#
Dim dt2 As Date = #12/31/2018 12:34:56 PM#
Dim dt3 As Date = #2000/12/31 12:34:56#  'VB2015以降のみ 



> DateTime.Millisecondプロパティではミリ秒部分 (0 ~ 999)は3桁しかありません。
文字通りの「ミリ秒」プロパティですので、そうなりますね。

㍉ は基礎となる単位の 10⁻³ 倍 (= 0.001 倍)の量
㌨ は基礎となる単位の 10⁻⁹ 倍 (= 0.000 000 001 倍)の量


> 100 ナノ秒=1/10000000秒だと思うので、時間の秒の位の小数点以下の精度は7桁になると
その認識で正しいです。
たとえば下記のようにすると、秒未満の時刻を 小数点以下 7 桁目まで表示できます。
MsgBox(Now.ToString("ss.fffffff"))
Dim s1 As String = Now.ToString("#MM/dd/yyyy HH:mm:ss.fffffff#")
Dim s2 As String = String.Format("#{0:MM/dd/yyyy HH:mm:ss.fffffff}#", Now)
Dim s3 As String = Now.ToString("\#MM/dd/yyyy HH:mm:ss.fffffff\#", CultureInfo.InvariantCulture)
Dim s4 As String = String.Format(CultureInfo.InvariantCulture, "#{0:MM/dd/yyyy HH:mm:ss.fffffff}#", Now)
Dim s5 As String = $"#{Now:MM/dd/yyyy HH:mm:ss.fffffff}\#"  'VB2015以降 



> 100 ナノ秒単位の精度
その 100 ナノ秒刻みのカウンターが、『Ticks プロパティ』です。

蛇足ですが、DateTime 構造体は 64 bit の符号なし整数型のフィールドで管理されていて、
.NET 1.x まででは、62bit の Ticks 値 + 2 bit の未使用領域、
.NET 2.0 以降では、62bit の Ticks 値 + 2 bit の Kind 値という内部構成です。

ちなみに Kind はタイムゾーンを表す情報なのですが、
.NET 3.5 以降では、時差情報を持った DateTimeOffset 構造体が登場したこともあり、
実際に Kind 部が使われることは殆どありません。


> 100 ナノ秒単位の時間を得るにはどうすればいいですか?
文字列では無く数値として得たいなら、たとえば下記のいずれかを使ってみるとか。
Dim s1 As String = dt.ToString("fffffff"
Dim s2 As String = dt.ToString("ss.fffffff"
Dim nanoSec As Integer = CInt(dt.Ticks Mod 10000000L) * 100
Dim sec As Decimal = ((dt.Ticks Mod 10000000L) / 10000000D) + dt.Second
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2018/5/27 14:55:54
> 拡張メソッドも、もっと簡潔に書ける方法があれば、教えてください。
VB2015 以降であればこう書けます。
ついでに、小数点以下 7 桁まで指定してみました。

<System.Runtime.CompilerServices.Extension()> _
Public Function ToStringリテラル(dt As DateTime, Optional IsDayOnly As Boolean = FalseAs String
    Dim fmt As System.IFormattable = $"#{dt:MM/dd/yyyy HH:mm:ss.fffffff}#"
    If IsDayOnly Then fmt = $"#{dt:MM/dd/yyyy}#"
    Return fmt.ToString(Nothing, System.Globalization.CultureInfo.InvariantCulture)
End Function


上記では、OS の地域設定が和暦モードになっていた場合のことを考え、
念のために IFormattable.ToString でインバリアント カルチャーを明示しています。

地域設定がスレッドカルチャ依存で良いなら、さらに短く書けます。
Return If(IsDayOnly, $"#{dt:MM\/dd\/yyyy}#", $"#{dt:MM\/dd\/yyyy HH\:mm\:ss\.fffffff}#")

スレッドのカルチャーを切り替える方法は、下記をご覧ください。
http://smdn.jp/programming/netfx/locale/0_abstract/


なお、$ で始まる文字列構文は VB2015 以降でしか使えませんが、
VB2013 以下でも、前回のサンプルに書いたように、
String.Format メソッド または DateTime の ToString メソッドで同じことができます。


おまけで、処理系ごとの日付型の精度について。

[.NET Framework] DateTime / 8 バイト
 100 ナノ秒精度
 西暦0001年01月01日 00:00:00.0000000~西暦9999年12月31日 23:59:59.9999999
[OLE] Date Variant / 8 バイト
 1 秒精度 (秒未満も保持できるが精度は保証されない)
 西暦100年01月01日 00:00:00~西暦9999年12月31日 23:59:59
[SQL Server] SmallDateTime / 4 バイト
 1 分精度 (29.999 秒以上切り上げ、29.998 秒以下切り捨てで丸められる)
 西暦1900年01月01日 00:00~西暦2079年06月06日 23:59
[SQL Server] datetime / 8 バイト
 1/300 ミリ秒精度 (秒未満は小数点以下 3 桁で、.xx0/.xx3/.xx7 に丸められる)
 西暦1753年01月01日 00:00:00.000~西暦9999年12月31日 23:59:59.997
[SQL Server] datetime2(0~7) / 6~8 バイト
 1 秒精度~100 ナノ秒精度
 西暦0001年01月01日 00:00:00.0000000~西暦9999年12月31日 23:59:59.9999999
[Oracle] DATE / 7 バイト
 1 秒精度
 紀元前4712年01月01日 00:00:00~西暦9999年12月31日 23:59:59
[Oracle] TIMESTAMP(0~9) / 7~11 バイト
 1 秒精度~1 ナノ秒精度
 紀元前4712年01月01日 00:00:00~西暦9999年12月31日 23:59:59
[MySQL 5.6.4 以降] DATE / 3 バイト
 1 日精度
 西暦1000年01月01日~西暦9999年12月31日
[MySQL 5.6.4 以降] DATETIME / 5~8 バイト
 1 秒精度~1 マイクロ秒精度 / 月や日部に 00 を許容
 西暦1000年01月01日 00:00:00.000000~西暦9999年12月31日 23:59:59.999999
[PostgreSQL] date / 4 バイト
 1 日精度
 4713 BC~5874897 AD
[PostgreSQL] timestamp(0~6) / 8 バイト
 1 秒精度~1 マイクロ秒精度
 4713 BC~294276 AD (紀元前4714年11月23日 23:59:59~西暦294277年01月09日 04:00:55)
[ファイルシステム] 作成日時
 NTFS : 100 ナノ秒精度 / UTC
 ReFS : 100 ナノ秒精度 / UTC
 FAT  : 10 秒精度 / ローカルタイム
 FAT32: 10 秒精度 / ローカルタイム
 exFAT: 10 ミリ秒精度 / ローカルタイム または UTC(Vista SP2 以降)
[ファイルシステム] 更新日時
 NTFS : 100 ナノ秒精度 / UTC
 ReFS : 100 ナノ秒精度 / UTC
 FAT  : 2 秒精度 / ローカルタイム
 FAT32: 2 秒精度 / ローカルタイム
 exFAT: 10 ミリ秒精度 / ローカルタイム または UTC(Vista SP2 以降)
[ファイルシステム] アクセス日時
 NTFS : 100 ナノ秒精度 / UTC
 ReFS : 100 ナノ秒精度 / UTC
 FAT  : 1 日精度 / ローカルタイム
 FAT32: 1 日精度 / ローカルタイム
 exFAT: 2 秒精度 / ローカルタイム または UTC(Vista SP2 以降)
投稿者 まっさん  (社会人) 投稿日時 2018/5/27 16:25:04
魔界の仮面弁士様、前回に引き続き、返信ありがとうございます。

Ticksプロパティの存在は知っていたので今回の質問をする前にも試したのですが小数点7桁まで
取得できなかったので、あきらめた経緯があります。ごみコードなので消去してしまいましたが、多分
以下のようだったと思います。
  Dim dt As DateTime = DateTime.Now
  Console.WriteLine(dt.Ticks / 1000000000 * 100)


"fffffff"カスタム書式は今回の件を調査していて以下のURLで初めて知りました。
このURLは過去に何度も見ているのに、日付にこのような精度は必要ないと思いスルーしていたものと思います。
https://docs.microsoft.com/ja-jp/dotnet/standard/base-types/custom-date-and-time-format-strings#the-fffffff-custom-format-specifier
このようなカスタム書式があるなら、DateTime.ToString(String, IFormatProvider)メソッドで十分で、
わざわざ拡張メソッドを作る必要もなかったと思っています。でも、昨夜色々調査して、せっかく作ったので........
VB2015以降のコードを書いてもらったのに、最初に自分の環境を書かなくて申し訳ありません。
自分はVB2010です。新しい構文は試せませんが、今後アップデートした時、参考にさせていただきます。
一応、以下が自分なりに考えた現時点での最終コードです。
  Public Enum enumDateFormat
        Fullf7 '"MM/dd/yyyy HH:mm:ss.fffffff" 
        Fullf3 '"MM/dd/yyyy HH:mm:ss.fff" 
        DayAndTime '"MM/dd/yyyy HH:mm:ss" 
        DayOnly '"MM/dd/yyyy" 
    End Enum

    <Runtime.CompilerServices.Extension()>
    Public Function ToStringリテラル(dt As DateTime, format As enumDateFormat) As String
        Select Case format
            Case enumDateFormat.Fullf7
                Return dt.ToString("\#MM/dd/yyyy HH:mm:ss.fffffff\#", CultureInfo.InvariantCulture)
            Case enumDateFormat.Fullf3
                Return dt.ToString("\#MM/dd/yyyy HH:mm:ss.fff\#", CultureInfo.InvariantCulture)
            Case enumDateFormat.DayAndTime
                Return dt.ToString("\#MM/dd/yyyy HH:mm:ss\#", CultureInfo.InvariantCulture)
            Case enumDateFormat.DayOnly
                Return dt.ToString("\#MM/dd/yyyy\#", CultureInfo.InvariantCulture)
            Case Else
                Return String.Empty
        End Select
    End Function



投稿者 まっさん  (社会人) 投稿日時 2018/5/27 21:23:56
魔界の仮面弁士様、ありがとうございますした。
今後共、よろしくお願いします。

これにて解決とさせて頂きます。