Timerクラス

最近の働き方改革で夜遅くまでオフィスで仕事をしない人は関係ない話なので、この記事は読まなくても大丈夫です。

 

経過時間をざっくり計算するために一番簡単な方法はVBAのTimer関数です。たいていの人がこんな感じで済ませていると思いますが、これだと対応できないケースがあります。 

Double dStart As Double
Double dEnd As Double

dStart = Timer
.....
.....
dEnd = Timer
Debug.Print "Elapsed: " & dEnd - dStart 

ローカル変数で宣言してるけど、複数計測したい場合に変数を個数分宣言しなきゃいけないのは煩雑。そもそもタイトルがTimerクラスってなってるしw

残念でした。不正解です。日付をまたいだケースに対応できないが正解です。Timer関数は午前0時からの経過秒数を返してきます。そのため夜0時をまたぐと上記のdEnd - dStartがマイナスになってしまいます。

 

例: 23:55PMにプログラムを走らせて、0:05に処理が終わった。
dStart = Timer '(23*60+55) *60  = 86100
dEnd = Timer     '5 * 60 = 300
Elapsed: -85800   '300 - 86100

日付が変わるまでオフィスで仕事をしない人は関係ないと言った理由がお分かりいただけただろうか。自宅のPCで何か走らせたい人は下のコードをコピペしてください。

グローバルモジュールで関数を定義してもいいですが、それだと1箇所でしか計測できません。そのためにクラスにしておくとよいでしょう。

 

 

Option Explicit

Private m_dStartTime    As Double
Private m_dEndTime      As Double
 
' ---------------------------------------------
'   タイマー
' ---------------------------------------------
Public Sub StartTimer()
    m_dStartTime = Timer
    m_dEndTime = Timer
End Sub

Public Function ElapsedTime() As Double
    m_dEndTime = Timer
 
    If m_dEndTime - m_dStartTime < 0 Then
        ElapsedTime = m_dEndTime + (86400 - m_dStartTime)
    Else
        ElapsedTime = m_dEndTime - m_dStartTime
    End If

End Function

メモ:Windows10でDLNAメディアサーバーを立ててみる

DLNAメディアサーバー

PS4メディアプレーヤーをインストールすると、Nasneにあるファイルを再生できるのはいいんですが、Windows10のファイルは再生できません。単純にメディアサーバーを動かせばいいわけで、今回はそのやり方を解説します。

 

DLNA (Digital Living Network Alliance)

家電、モバイル、およびパーソナルコンピュータ産業における異メーカー間の機器の相互接続を容易にするために2003年6月に結成された非営利業界団体。ソニー東芝パナソニック、日立制作、バッファロー、シャープ、アイ・オーデータなどの企業が参加しています。こんなものがあるとは知りませんでした。マイクロソフト独自の規格ではないというのはいいことです。

 

メディアサーバーの起動

エクスプローラの左側にあるネットワークを右クリック

▶プロパティをクリック

▶コントロールパネルが出てくる

▶左の『メディアストリーミングオプション』をクリック

▶『メディアストリーミングを有効にする』をクリック

▶メディアライブラリに名前を付けておく

 

ファイル保存場所の追加

エクスプローラの左側にあるネットワークをクリック

▶先ほどのメディアライブラリをクリック 

Windows Media Playerが起動する

▶音楽を右クリックして、『音楽ライブラリの管理』

▶追加ボタンをクリックして、MP3が置いてあるフォルダーを選択

▶ビデオを右クリックして『ビデオライブラリの管理』、

▶追加ボタンをクリックして、動画が置いてあるフォルダーを選択

 

バイスキャスト

Windows Media Playerエクスプローラの右クリックメニューにデバイスキャストというメニューがありますが、これはローカルエリアネットワークに接続されているスピーカーに音声出力できるという機能です。ソニーのMusic Center for PCが一番エレガントにこの機能を実装していますね。ハードディスクやNASに置いてあるMP3ファイルをサウンドバーに出力できるのはいいです。

 

 

 

 

 

Boolean表記とIIF関数

IF-ELSE文で次のようなコードを書くときに5行も使うの嫌ですよね。

    Dim fFlag   As Boolean
    If buff = "EP9 sucks" Then
        fFlag = True
    Else
        fFlag = False
    End If

IIF関数なら1行で書けます。
    fFlag = IIf(buff = "EP9 sucks", True, False)

でも、ステートメントでBoolean評価してくれるって知ってましたか?。しかも、IIF関数よりも4倍早いです。
    fFlag = (buff = "EP9 sucks")

 

IIF関数はTrueパートとFalseパート両方とも実行されてしまうので、初期化されていないオブジェクトを参照するようなコードを書くとエラーになってしまいます。そういう場合は、おとなしくIF-ELSE構造にするか、実行時エラーにならないようにオブジェクトを初期化するとか、注意してください。

 

メモ:条件付き書式ダイアログのバグ

条件付き書式ダイアログで書式ルールを6個以上でクリックできなくなる、適用先が消えてしまうバグが発生します。

 

1.6個の書式ルールが定義されたダイアログ

f:id:gungnir46:20191220125901p:plain

 

2.6個目を表示しようとしてスクロールすると・・・・

f:id:gungnir46:20191220130011p:plain

一番下の適用先が表示されていません。

 

3.さらに上にスクロールすると・・・

f:id:gungnir46:20191220130059p:plain

もう何がなんだか。しかも適用先はクリックしても編集すらできない状態です。

 

 

条件をダブルクリックして編集画面を出しておいてOKボタンを押すと、適用先が編集できるようになるようです。わたしが使っているのはエクセル2016ですが、最新バージョンも同じバグがあるんでしょうか。

 

メモ:Windows10のクラウドクリップボード

マイクロソフトは知らないうちにこんなものを実装したようです。クラウドコンピューティングの定義が云々というのはこの際置いておきます。ただ、必要ない人にとってリソースを浪費するだけの機能なので、オフにしてすっきりしましょう。エクセルでコメント付きのセルを大量にコピーするとやたら時間がかかるのは仕方ないことかと諦めていましたが、こんなもののせいで遅くなっていたようです。

 

実際にどんなものか見るにはウィンドウズキー+Vでクリップボードの履歴が出てきます。Androidスマホの貼り付けで出てくるような画面ですね。

 

設定オフ

▶スタートボタン▶設定▶システム▶クリップボード

 

 

日本国民として知っておくべきこと:国民祝日

まずは技術的な話

Windows10のカレンダーアプリには、OutlookGmailの国民祝日があります。普通に考えてOutlookAPIで簡単に取れるだろうと思って、いろいろ調べてみたんですが、サンプル通りにやってもうまくいきません。

 

休日一覧をウェブから取ってくればいっか 

年に一度のイベントと思って手入力してもいいんですが、やっぱりそれではダサいのでもうちょっと調べてみたらGoogleカレンダーAPIで可能だということが分かりました。そしてこのAPIを使って自前でGithubを自動更新しているページを見つけたので、ここから休日を取ってくるコードを書いてみました。商用・非商用OKということなので問題ないと思います。

 

すべてはこれが始まりでした

もうひとつCalendar Service (http://calendar-service.net/api.php) というページも見つけましたが、この2つのページで日付の差異があることに気が付きました。普段は国民祝日なんて気にも留めていないし、日本は祝日が多すぎてよくないと思っているくらいなので、ましてやそれぞれの休みの意味とか背景などどうでもよく、休みなら仕方なく休むかという感じでした。

 

違いが発生した理由その1 天皇誕生日

2019年12月23日は天皇誕生日のはずでした。しかし、皇位継承元号も変わり、この日は平日に変更されました。そして、ヤフーファイナンスのトップページもこの有様です。ヤフーは12/23 13:20くらいまで株価を更新していませんでした。おそらく2階建てのネタになるでしょう。

f:id:gungnir46:20191223132011p:plain

しかしながら、わたしが勘違いした理由にはもう一つあって、それはOutlookの休日カレンダーです。左ペインにあるとおり、OutlookGmailの両方に日本の休日というイベントがあり、12/23を休日としているのはOutlookです。Gmailはこの日を平日扱いにしています。

 

f:id:gungnir46:20191223132849p:plain


さきほど紹介した2つのウェブページを見てみましょう。holidays-jp.github.ioの方は12/23は平日扱い、calendar-service.netは祝日となっています。そして、Outlookとcarendar-service.netは2020年12月23日も天皇誕生日らしいです。

 

違いが発生した理由その2 即位礼正殿の儀の行われる日

これも皇位継承に伴う例外的な祝日ですね。 

 

Outlook 平日扱い
Gmail 祝日扱い
calendar-service.net 平日扱い
holidays-jp.github.io 祝日扱い

 

違いが発生した理由その3 海の日

そもそも海の日ってなに?っていう話から。

国民の祝日に関する法律祝日法、昭和23年7月20日法律第178号)第2条は、「の恩恵に感謝するとともに、海洋国日本の繁栄を願う」ことを趣旨としている。国土交通省は「世界の国々の中で『海の日』を国民の祝日としている国は唯一日本だけ」[1]としている。

制定当初は7月20日であったが、2003年(平成15年)に改正された祝日法ハッピーマンデー制度により、7月の第3月曜日となった。

(Wikipediaより抜粋)

 

これでいくと2020年の海の日は7月第3月曜日にぶつけて7/20になるはずでした。ところが、2020年は東京オリンピック開催で3連休にすると都内が渋滞するので例外的に7/23に変更することになったらしいです。

2020年令和2年)は東京五輪・パラリンピック特措法により、東京オリンピックの開会式の前日に当たる7月23日(木曜日)に変更される[6]
(Wikipediaより抜粋)

 

一番の問題はこの例外処理に対応しているのはどのカレンダーということです。今回でもOutlookとcalendar-service.netはNG。

Outlook 7月20日
Gmail 7月23日
calendar-service.net 7月20日
holidays-jp.github.io 7月23日

 

2019年の祝日を総チェック

そろそろ分かってきましたが、Outlookとcalendar-serviceはデータソースとしてはダメです。普段は気にも留めていなかった祝日にここまで時間をかけたのは生まれて初めてのことですが、この際なので徹底的に調べてみることにしましょう。

 

  Outlook Gmail calendar-service holidays-jp
04/30 祝日 X O X O
05/01 天皇の即位の日 X O X O
05/02 祝日 X O X O
10/22 即位礼正殿の儀 X O X O
12/23 平日 X O X O

 

 

もはやVBAの技術論ではなく日本国民として知っておくべきチコちゃんネタになってしまいましたが、Outlookの国民祝日カレンダーは使うべきではありません。いますぐ外しましょう。そして、Gmailとholidays-jpに賛辞を贈ることにします。 

 

https://holidays-jp.github.io/api/v1/2020/date.json

出力結果:

{
"2020-01-01": "元日",
"2020-01-13": "成人の日",
"2020-02-11": "建国記念の日",
"2020-02-23": "天皇誕生日",
"2020-02-24": "天皇誕生日 振替休日",
"2020-03-20": "春分の日",
"2020-04-29": "昭和の日",
"2020-05-03": "憲法記念日",
"2020-05-04": "みどりの日",
"2020-05-05": "こどもの日",
"2020-05-06": "憲法記念日 振替休日",
"2020-07-23": "海の日",
"2020-07-24": "体育の日",
"2020-08-10": "山の日",
"2020-09-21": "敬老の日",
"2020-09-22": "秋分の日",
"2020-11-03": "文化の日",
"2020-11-23": "勤労感謝の日"
}

 

Private Sub TestHarness()

    Debug.Print IsHoliday("2019/5/2")

    UpdateHolidays 2019

End Sub

Public Function IsHoliday(pDate As String) As Boolean

    Dim iDay    As Long

    IsHoliday = True
    iDay = Weekday(pDate)
    If iDay = vbSunday Or iDay = vbSaturday Then Exit Function

    Dim vMatch As Variant
    On Error GoTo Unmatch
    vMatch = Application.WorksheetFunction.Match(_
Clng(CDate(pDate)), wksCalendar.Range("A:A"), 0)
IsHoliday = (vMatch > 0) Exit Function Unmatch: IsHoliday = False End Function Public Sub UpdateHolidays(pYear As Long) Dim sURL As String Dim buff As String Dim oStream As Stream Dim sDate As String Dim xCell As Excel.Range ' ---- Download Holidays sURL = "https://holidays-jp.github.io/api/v1/" & pYear & "/date.json" buff = modUtil.GetHTMLText(sURL) Set oStream = New Stream oStream.Buffer = buff Set xCell = wksCalendar.Range("A1") ' ---- Update calendar worksheet Do Until oStream.EOF buff = oStream.ReadLine If InStr(buff, ":") > 0 Then sDate = parseString(buff, """", """") xCell.Value = "'" & Format(sDate, "YYYY/MM/DD") Set xCell = xCell.Offset(1, 0) End If Loop End Sub Private Function parseString(pBuff As String, pKey1 As String, pKey2 As String) As String Dim iPos1 As Long Dim iPos2 As Long iPos1 = InStr(pBuff, pKey1) iPos2 = InStr(iPos1 + 1, pBuff, pKey2) If iPos1 + iPos2 = 0 Then parseString = "" Else iPos1 = iPos1 + Len(pKey1) parseString = Mid(pBuff, iPos1, iPos2 - iPos1) End If End Function

いまさらRangeオブジェクト: Value、Text、メンバー指定なし

RangeオブジェクトのディフォルトのプロパティはValueです。Textではありません。この2つのプロパティの違いはちゃんと理解しておいた方がよいです。

    Dim xCell   As Excel.Range

    Set xCell = Sheet1.Range("A1")
    xCell = Now
    xCell.NumberFormatLocal = "[$-ja-JP-x-gannen]ggge""年""m""月""d""日"";@"

    Debug.Print xCell
    Debug.Print xCell.Value
    Debug.Print xCell.Tex

実行結果:

2019/12/11 13:42:18
2019/12/11 13:42:18
令和元年12月11日

xCellには今日の日付をセットして、表示形式を和暦にしてあります。xCellとxCell.Valueは西暦を返していますが、xCell.Textは令和になっています。つまり、Textは書式付きの値を返します。あと、Textは読み取り専用です。

 

普通に考えて.Valueよりも.Textの方が時間がかかるだろうと予想できますが、どのくらい違うのか計測してみました。 

Value指定
Private Sub test1()

    Dim xCell   As Excel.Range
    Dim i       As Long
    Dim buff    As String

    Set xCell = Sheet1.Range("A10")
  
    StartTimer
    For i = 1 To 10000
        buff = xCell.Offset(0, 1).Value
    Next
    Debug.Print "Test1: " & ElapsedTime

End Sub
Textプロパティ
Private Sub test2()

    Dim xCell   As Excel.Range
    Dim i       As Long
    Dim buff    As String

    Set xCell = Sheet1.Range("A10")
  
    StartTimer
    For i = 1 To 10000
        buff = xCell.Offset(0, 1).Text
    Next
    Debug.Print "Test2: " & ElapsedTime

End Sub

 

プロパティ指定なしと.Value指定の違いはほとんどありませんが、.Value指定の方が誤差の範囲程度で早いです。気になるtest1とtest2の結果ですが、test2はtest1の4倍時間がかかるという結果が出ました。

 

結論

  • プロパティ指定なしにするくらいなら.Valueプロパティを指定した方がいい。
  • .Textプロパティを使うことは避けて、VBAのコードでフォーマット変換したほうが早い。

 

 

.Textはセルに表示されている形式で返してきます。ここで問題になるのはDate/TimeとNumberです。この2つのデータタイプはセル幅が狭くなると、"#"と表示されてしまいます。

 

Time Number String
11:30 9,999,999,999 This is a test
11:35    
11:40    
11:50    
11:55    

 

セル幅を狭くすると、このようになります。

Time Number String
## ##### This is a test
##    
##    
##    
##    

 

? Sheet1.Range("A5").Text

#

? Sheet1.Range("B5").Text

 #

? Sheet1.Range("C5").Text

 This is a test

 

.Textプロパティにこんな実行時エラーがあるとはプロ泣かせです。