[[20190625094537]] 『日時を配列に代入して出力すると0:00のデータが前』(う) ページの最後に飛ぶ

[ 初めての方へ | 一覧(最新更新順) | 全文検索 | 過去ログ ]

 

『日時を配列に代入して出力すると0:00のデータが前日になる』(う)

例えば、セルA1に「2019/1/1 20:00」と入力し、セルA2に「=A1+1/24」を入力し、セルA7までコピーします。
そうすると、セルは以下のようになります。

2019/1/1 20:00 (2019/1/1 20:00)
2019/1/1 21:00 (=A1+1/24)
2019/1/1 22:00 (=A2+1/24)
2019/1/1 23:00 (=A3+1/24)
2019/1/2 0:00 (=A4+1/24) ←注目
2019/1/2 1:00 (=A5+1/24)
2019/1/2 2:00 (=A6+1/24)

※()は入力値

これを、以下のように配列に一括で代入して、その配列をセルに出力すると以下のようになります。

 Sub 日付がおかしくなる()

    Dim var As Variant
    var = Range("A1:A7").Value
    Range("C1").Resize(UBound(var, 1)).Value = var

 End Sub

<結果>
2019/1/1 20:00
2019/1/1 21:00
2019/1/1 22:00
2019/1/1 23:00
2019/1/1 0:00 ←注目
2019/1/2 1:00
2019/1/2 2:00

上記結果のように2019/1/2 0:00 が、2019/1/1 0:00になってしまいます。
配列の中身をローカルウィンドウで確認した場合、#2019/01/02#と正しく入っています。

なお、「=A1+1/24」を「=$A$1+(ROW()-1)/24」で作成した時は正常に「2019/1/2 00:00」と表示されます。
また、以下のように、配列に代入した値をDouble型にした場合は正常に「2019/1/2 00:00」と表示されます。

 Sub 日付はおかしくならない()

    Dim var As Variant
    var = Range("A1:A7").Value

    For i = 1 To UBound(var, 1)
        var(i, 1) = CDbl(var(i, 1))
    Next

    Range("C1").Resize(UBound(var, 1)).Value = var

 End Sub

日付型を扱う場合の何かあるある的(?)なものがあるのでしょうか。

なお、上記は再現のために簡単にしていますが、実際には日時に対する複数のデータがあり、そのデータから計算した結果を最終的に別シートに一括出力するという処理を行っています。

< 使用 Excel:Excel2013、使用 OS:Windows7 >


 A列の表示形式を標準にするだけでも正しくコピーされるようだ。
(ねむねむ) 2019/06/25(火) 11:28

 たぶん小数の演算誤差の丸め方がDate型とDouble型で違うのでしょう。

 A5の値は、整数にすると「43467」で、
 実際、他のセルに「=A5=43467」なんて式を入れてみても「TRUE」が返りますし、
 A5をコピーして別セルに値だけ貼り付けても数式バー上は「43467」に見えます。
 でも、厳密には「43467」ではありません。

 =DELTA(A5,43467)

 で検算してみれば戻り値はゼロで「A5と43467は等しくない」となります。

 日付型としての扱いをやめるか、
 A2 =--TEXT(A1+1/24,"yyyy/mm/dd hh:mm")
 の様にして誤差を修正しておく。
 みたいな事が必要なのではないかと思います。

(白茶) 2019/06/25(火) 11:34


ねむねむさん

なるほど!

最初に表示形式を変更してから配列に入れれば、日付型ではなくDouble型で取り込まれるので大丈夫なのですね。こちらの方がスッキリしました。

Sub 日付がおかしくならない2()

    Dim var As Variant

    Range("A1:A7").NumberFormat = "標準"
    var = Range("A1:A7").Value
    Range("A1:A7").NumberFormat = "YYYY/M/D hh:mm"

    Range("D1").Resize(UBound(var, 1)).Value = var

End Sub

ありがとうございます!
(う) 2019/06/25(火) 11:35


白茶さん

日付を入力するのは自分とは限らないので、日時の入力での対応は難しいと考えていました。
また、データ件数が多い(時間データが1年分×数十項目)ので、一括で配列に入れてしまいたく、入れた後に対応するしかないかなと思っていました。

DELTA関数初めて知りました。
勉強になりました。

ありがとうございます!

(う) 2019/06/25(火) 11:45


頂いたヒントを元に以下でもいけることに気づきました。

 Sub 日付がおかしくならない3()

    Dim var As Variant

    var = Range("A1:A7").Value2

    Range("D1").Resize(UBound(var, 1)).Value = var
    Range("D1").Resize(UBound(var, 1)).NumberFormat = "YYYY/M/D hh:mm"

 End Sub

Value2を使えば、日付ではなくシリアル値をゲットできることを思い出しました。
この場合、出力結果もシリアル値になってしまうので、出力後に日時の表示形式に変更しました。

日時の場合は、Value2で取得し日付型は使用しないことにしました。
(う) 2019/06/25(火) 14:32


 これは小数演算誤差の問題と言うより、DATE型特有のバグです。

 演算誤差は、有効桁数15〜16桁辺りで常に発生している問題

 このバグは、日付が変わる辺りだけ、0.5秒レベルで起きる問題

 ※日付部分は繰り上がらず、時刻部分だけが繰り上がる(つまり0:00になる)状態でシート上の値に変換される問題
  結局、ほぼ1日分マイナスとなる。

(半平太) 2019/06/25(火) 16:18


半平太さん

DATE型のバグなのですね。。。
バグということが分かりスッキリしました。

ありがとうございます!
(う) 2019/06/25(火) 16:47


 >これは小数演算誤差の問題と言うより、DATE型特有のバグです。

 ちょっと不正確だったような気がします。

 DATE型のバグと言うより、
 RangeオブジェクトのValueプロパティ(=プログラムの一種)が、DATE型を引数に受け取った時、
 次の日付までに0.5秒以内の不足があるものは正しく繰り上げ処理できないバグ、と言った方がよさそうです。

 また、小数演算誤差も絡んではいます。

 A5セルの日付は、2019/1/2の値よりわずかに小さいです。

 A6セル =(43467-A5)/0.5^37 とすると

  行  _______A_______
   1  2019/1/1 20:00 
   2  2019/1/1 21:00 
   3  2019/1/1 22:00 
   4  2019/1/1 23:00 
   5  2019/1/2  0:00  
   6    1

 つまり、A5セルは 0.5の37乗だけ 2019/1/2より小さい。

 幾ら小さくても、0.5秒以内の不足には違いないので
 次の日への繰り上がり処理が行われるハズだが、
 Valueプロパティによる馬鹿げた変換処理が行われる。

(半平太) 2019/06/25(火) 22:49


半平太さん

詳細なご説明ありがとうございます。

時刻は少数値で持っている関係上、少数誤差により気づかない値の違いがあるだろうと思い、比較する時はFormatや日付・時刻関数、秒(10進)に変換等で合わせるようにしていましたが、そんなところにバグがあるとは思いもしませんでした。

とても勉強になりました。

(う) 2019/06/26(水) 10:00


    Function TO_DATE(v As Double) As Date
        TO_DATE = CDate(v)
    End Function

    Sub Test2()
        [A:C].NumberFormatLocal = "yyyy/mm/dd hh:mm:ss.000"
        [A1] = "2019/6/26 23:59:59.49"
        [A2] = "=A1+1/24/60/60/1000"
        [A2:A20].FillDown
        [B1:B20].FormulaR1C1 = "=TO_DATE(RC[-1])"
        [C1:C20] = [B1:B20].Value
        [A:C].Columns.AutoFit
    End Sub

 >Valueプロパティによる馬鹿げた変換処理
 おお! ホントだー!

 Date型に罪は無かったのですね。
 半平太さんありがとうございました。(便乗して御礼)

 負のゼロはゼロに変換してくれるのに
 こんな大きな落とし穴があったとは...

(白茶) 2019/06/26(水) 11:25


半平太さんから教えていた詳細を以下を元に試してみたところ、配列に代入するときはValueでも、セルに出力する時に Value2 にすると正しく出力されました。

 Sub 日付がおかしくならない4()

    Dim var As Variant

    var = Range("A1:C7").Value

    Range("D1").Resize(UBound(var, 1)).Value2 = var
    Range("D1").Resize(UBound(var, 1)).NumberFormat = "YYYY/M/D hh:mm"

 End Sub

Rangeの日時データを扱う時は、Value2を使用するよう留意しようと思います。

みなさま、ありがとうございました。
(う) 2019/06/26(水) 11:32


白茶さんの確認プログラムを実行してみて、Date型とValueの結果が一目瞭然ですごく分かりやすく確認できました。

今の私には「Valueプロパティの不具合であれば、配列には2019/1/2と入っていたのだから、出力する時にValueプロパティを使わなければいいのかも?」くらいの発想しかできませんでした。

DELTA関数といい、こういった確認をする術・発想力も身に付けていきたいと思いました。

ありがとうございました。
(う) 2019/06/26(水) 13:15


何度もすみません。

この内容を実際のプログラムに適用したところ、気づいた事があったので追記しておきます。

実際のプログラムは、出力結果をジャグ配列で作成し、最後にWorksheetFunction.Indexで2次元配列に変換していたのですが、WorksheetFunction.Index実行後に同現象が発生していました。

配列の型が、Index前はDate型、Index後はString型に変わっていました。
こちらに記載した再現プログラムでも、配列にValueプロパティでセット後、イミディエイトウィンドウで
「?worksheetfunction.Index(var,5,1)」を実行したところ、「2019/1/1 0:00:00」(正しくは2019/1/2)と返ってきました。
ワークシート関数だからかなぁと思っています。

そんなわけで、出力する時にValue2にするだけでいいやと安易に考えていたのですが、結局、配列に入れる時点でValue2にしておき、Data型は使わないという結論になりました。
本当は誤差が無くなるようプログラムで再セットするのがベストだとは思いますが。。。

以上、ご報告まで。
(う) 2019/06/26(水) 15:54


 >WorksheetFunction.Index実行後に同現象が発生していました。 

 こちらでも確認しました。

 こんなのでも、発生しますね。
    ↓   
  Debug.Print Application.Text(var(5, 1), "yyyy/m/d hh:mm") '2019/1/1 00:00

  Debug.Print Format(var(5, 1), "yyyy/m/d hh:nn")           '2019/1/2 00:00
   ↑
  これはならない。不思議。

  近づきたくない型ですね。

  うさん、ご報告ありがとうございました。

(半平太) 2019/06/26(水) 16:32


コメント返信:

[ 一覧(最新更新順) ]


YukiWiki 1.6.7 Copyright (C) 2000,2001 by Hiroshi Yuki. Modified by kazu.