AzureでTwitterのBotを作ってて、この問題をうまく理解できてなくて数ヶ月間填まってたので、備忘録的に。
日付・時間を格納するDateTime型。これの比較時の罠。(知ってる人は知ってると思うけど)
結論から言うと、Date/DateTime型はミリ秒まで格納してるので、単純に型同士で比較すると、条件が成立しない場合がある。
特に数秒ごとに時間を判定する処理とか。たとえばBotとか、定期的に処理するサービスとか。
以下コード(ConsoleAppで実験)
- ただ単純にDateTimeで比較した場合。
Sub DateTimeSample01() '10秒後の時間を比較対象にする Dim TargetTime As DateTime = Now.AddSeconds(10) Do Console.WriteLine("CurrentTime:" & Now) Console.WriteLine("TargetTime :" & TargetTime) 'DateTime同士(正確にはDateTimeとDate)で比較 If TargetTime = Now Then Console.WriteLine("-- TargetTime=Now --") Console.ReadLine() Exit Do End If '1秒待つ System.Threading.Thread.Sleep(1000) Loop End Sub
結果:
CurrentTime:2012/12/22 0:04:00 TargetTime :2012/12/22 0:04:03 CurrentTime:2012/12/22 0:04:01 TargetTime :2012/12/22 0:04:03 CurrentTime:2012/12/22 0:04:02 TargetTime :2012/12/22 0:04:03 CurrentTime:2012/12/22 0:04:03 ← TargetTime :2012/12/22 0:04:03 ← CurrentTime:2012/12/22 0:04:04 TargetTime :2012/12/22 0:04:03 CurrentTime:2012/12/22 0:04:05 TargetTime :2012/12/22 0:04:03
時間を過ぎても -- TargetTime=Now --の表示がされないはず。そして永遠と条件が成立するまでループが続いていく。
- 実はミリ秒も格納してる証拠
ToStringの書式指定子に、Fを指定するとミリ秒まで出してくれます。FFFFFFFで秒以下の7桁まで表示。(カスタム DateTime 書式指定文字列 | Microsoft Docs)
Sub DateTimeSample02() '10秒後の時間を比較対象にする Dim TargetTime As DateTime = Now.AddSeconds(10) Do Console.WriteLine("CurrentTime:" & Now.ToString("yyyy/MM/dd HH:mm:ss.FFFFFFF")) Console.WriteLine("TargetTime :" & TargetTime.ToString("yyyy/MM/dd HH:mm:ss.FFFFFFF")) 'DateTime同士(正確にはDateTimeとDate)で比較 If TargetTime = Now Then Console.WriteLine("-- TargetTime=Now --") Console.ReadLine() Exit Do End If '1秒待つ System.Threading.Thread.Sleep(1000) Loop End Sub
結果:
CurrentTime:2012/12/22 00:06:48.0012582 TargetTime :2012/12/22 00:06:50.9918573 CurrentTime:2012/12/22 00:06:49.0023155 TargetTime :2012/12/22 00:06:50.9918573 CurrentTime:2012/12/22 00:06:50.0033728 ← TargetTime :2012/12/22 00:06:50.9918573 ← CurrentTime:2012/12/22 00:06:51.00443 TargetTime :2012/12/22 00:06:50.9918573 CurrentTime:2012/12/22 00:06:52.0054873 TargetTime :2012/12/22 00:06:50.9918573
コンソールにミリ秒が表示され、System.Threading.Thread.Sleep(1000)で1秒待ったとしても、完全に1秒じゃなくてミリ秒単位でズレが出てます。
- 解決方法はこちら
Sub DateTimeSample03() '10秒後の時間を比較対象にする Dim TargetTime As DateTime = Now.AddSeconds(10) Do Console.WriteLine("CurrentTime:" & Now.ToString("yyyy/MM/dd HH:mm:ss.FFFFFFF")) Console.WriteLine("TargetTime :" & TargetTime.ToString("yyyy/MM/dd HH:mm:ss.FFFFFFF")) 'フォーマットを指定して比較 If TargetTime.ToString("HH:mm:ss") = Now.ToString("HH:mm:ss") Then Console.WriteLine("-- TargetTime=Now --") Console.ReadLine() Exit Do End If '1秒待つ System.Threading.Thread.Sleep(1000) Loop End Sub
結果(あえてミリ秒まで表示させてます):
CurrentTime:2012/12/22 00:08:33.9413177 TargetTime :2012/12/22 00:08:34.9318024 CurrentTime:2012/12/22 00:08:34.9423749 TargetTime :2012/12/22 00:08:34.9318024 -- TargetTime=Now --
ToStringでフォーマットを指定し、その上で比較すれば大丈夫だよ。という話でした。