マウントできなくなったHDDから、testdiskを使用して、ファイルをサルベージしてみた

USB接続で使用していた4TBのHDDが、突然使用できなくなりました。
マウントできないと・・・
f:id:Z1000S:20180702221411p:plain

根本的な原因は不明なのですが、ラベルが書き換わって、openSUSE-Leapとなっていましたし、
中途半端に、ddコマンドが実行されたような感じでしょうか?
openSUSEは、インストール用としてUSBメモリに書き込むために以前使ったことはありますが
現在は、内蔵HDDには残っていません。

f:id:Z1000S:20180702220751j:plain

主にバックアップ用として使用していたので
いろいろと大切なファイル(そう、あんなファイルやこんなファイル・・・)を保存していたので
困ってしまいました。

唯一救いだったのが、OSのインストールされたHDDではなく
外付けのHDDだったので、不要な書き込みによる上書きの心配が少なかったことでしょうか。

諦めてフォーマットしてしまおうかと思ってみたものの
少しくらいは悪あがきしてみて、駄目なら諦めようと調べてみると
testdiskというツールがあるらしく、それを使ってみることにした。

HDDの情報が書き換わったPC(Manjaro Linux)でサルベージするのも嫌だったので
別のPC(Antergos)で作業することに。

pacmanで検索すると、testdiskが見つかったので、即インストール。

端末から、コマンドを入力して開始。
f:id:Z1000S:20180702221033p:plain

最初にログの作成方法について聞いてきた。
初めてなので、新規にログを作成するので、「Create」が選択された状態でEnter
(ログは、自分のHOMEディレクトリに作成されました。 /home/z1000)
f:id:Z1000S:20180702221243p:plain

次に認識されたHDDのリストが表示されるので、対象となるHDDを選択して
[ Proceed ]を選択して、Enter
f:id:Z1000S:20180702221756p:plain

次は、パーティションテーブルの種類。
今回のLinuxの場合には、[ Intel ]を選べばOKなようです。
f:id:Z1000S:20180702222029p:plain

事前の準備が住んだところで、まずは分析
[ Analyse ]を選択して、Enter
f:id:Z1000S:20180702222150p:plain

現状で、OSが認識している状況が表示される(?)ようです。
[ Quick Search ]を選択して、Enter
f:id:Z1000S:20180702222245p:plain

そうすると、以前の状況らしきものが見えてきた。
f:id:Z1000S:20180702222553p:plain

キーボードから「P」を入力すると、ファイルリストが表示されるようです。
f:id:Z1000S:20180702222726p:plain
2018/7/3 修正
赤い文字のファイルやディレクトリは、復元できないものなのかもしれない。
赤い文字のファイルも復元はできるようです。


↑↓で、ディレクトリやファイルを選択でき、
←→で、ディレクトリの親や子に移動できるようです。
f:id:Z1000S:20180702223326j:plain

サルベージしたいファイルやディレクトリを選択し、
キーボードの「C」を押下すると、コピー先を聞いてきます。
f:id:Z1000S:20180702223621j:plain

あとは、移動先を選択し、Enterを押下すればコピーが始まります。
f:id:Z1000S:20180702223842j:plain

選択したファイルは、階層付きでコピーされました。
f:id:Z1000S:20180702224441j:plain

この調子で、他のファイルも・・・

2018/7/3 追記
USBメモリのファイルを削除して、同様に操作したら、
削除したファイルを復元できました。
復元されたファイルの所有者、グループはrootでした。
ファイル名によっては、元のファイル名を復元できない場合もあるようです。
(省略形のような名前で復元されたりする場合があるようです。)

ExcelのVBAで使えるDLLを、C++(Visual Studio 2017)で作る。・・・その2

次回予告までしておきながら、他にVBA関係の記事を書いていたこともあり、既に2ケ月以上が過ぎ、
「いつ頃までに、まとめられるかは、不明・・・」の記載通りになってしまった。

前回も書きましたけど、64bit版のVBAではどうなるかわかりませんので!!!

今回の内容は次の通り。

  1. 受け渡しするデータの型について
  2. 処理する値を渡せるようにすること
  3. 値渡しと参照渡しについて
  4. 処理した結果や値を返してもらえるようにすること
  5. 数値型の場合の受け渡しの例


受け渡しするデータの型について

とりあえず必要なのは、VBAC++のデータ型の対応。
VBA独自の型は、C++に渡せないし、逆にC++にしかない型は、VBAが受け取れない。
(条件付きであれば、例外もあるけど・・・)
VBAの主な型を中心に対応を見てみると次のような感じ。

VBAの型 C++の型 備考
Byte unsigned char
BYTE
Integer short
SHORT
Long int
long
INT
LONG
Single float
Double double
Boolean BOOL
C++の型 VBAの型 備考
HANDLE Long
char
CHAR
Byte 最上位ビットがOFFならばそのまま使用可
unsigned short
WORD
Integer 最上位ビットがOFFならばそのまま使用可
unsigned int
unsigned long
UINT
ULONG
DWORD
Long 最上位ビットがOFFならばそのまま使用可
VBAにあってC++にない型(抜粋)
Currency
Date
Decimal
String
Object
Variant
C++にあってVBAにない型(抜粋)
WCHAR
LONGLONG
ULONGLONG

VBAのString型に対応するC++の型はありません。
ちょっと特殊です。データの受け渡しにはchar型のポインタまたは、BSTR型を使用します。
これについては、次回(?)にでも・・・

処理する値を渡せるようにすること

DLLに何らかのデータ処理をしてもらうためには、必要なデータを渡してあげないと出来ません。
データはVBAの関数と同様にパラメータに引数を渡します。
ただ、C++VBAでは、構文が違うので予め覚えておく必要があります。

値渡しと参照渡しについて

値渡し
引数のアドレスをプロシージャに渡すのではなく引数の値を渡す方法。
VBAでは、ByValを指定する事により値渡しとなる。
参照渡し
引数の値をプロシージャに渡すのではなく引数のアドレスを渡す方法。
VBAでは、デフォルトで参照渡しである。明示的に指定する場合には、ByRefを指定する。

C++の場合

値渡し
データ型の後ろに変数を指定する。
void doAnything(int hoge); intデータ型で、hoge変数
値渡し
データ型の後ろに"*"を付けその後ろに変数を指定する。(ポインタですな。)
void doSomething(long* fuga); longデータ型で、fuga変数

処理した結果や値を返してもらえるようにすること

値を返してもらう方法は、大きく分けて2つ。
一つは、関数の復帰値による方法。
これは、VBAでいえば、ファンクションプロシージャによる復帰値で結果を得る方法と同じ。
C++の場合には、関数名の前に復帰値のデータ型を指定する。
復帰値の型 関数名(パラメータリスト)
もう一つは、渡したパラメータにデータを入れてもらい、返してもらう方法。
こちらは、VBAでいえば、パラメータにByRefを指定し、返してもらう方法と同じ。
どちらを使うかは、状況に応じて使い分ければよろしいかと。
場合によっては、両方というのもありです。

数値型の場合の受け渡しの例

VBAからLongの値を渡して、2倍した結果を返してもらう「GetNumberI」という関数と、
300倍した値を返してもらう「GetNumberI2」を作ってみます。
GetNumberIは、パラメータを値渡しして、復帰値で結果をもらいます。
GetNumberI2は、パラメータを参照渡しし、そのパラメータ値を変更して返してもらいます。

前回のファイルに、書き加えていきます。

まず、ヘッダファイル(AccessibleFromVBA.h)

#pragma once

extern "C"
{
#define ACCESSIBLEFROMVBA_API __declspec(dllexport) 

	ACCESSIBLEFROMVBA_API void WINAPI DoNothing();

	ACCESSIBLEFROMVBA_API int WINAPI GetNumberI(int i);

	ACCESSIBLEFROMVBA_API void WINAPI GetNumberI2(int* pi);
}

f:id:Z1000S:20180627100359j:plain

次に、ソースファイル(AccessibleFromVBA.cpp)

#include "stdafx.h"
#include "AccessibleFromVBA.h"


ACCESSIBLEFROMVBA_API void WINAPI DoNothing()
{
	return;
}

//int型の値を受け取って、int型の復帰値を返す(VBAのファンクションプロシージャ相当)
ACCESSIBLEFROMVBA_API int WINAPI GetNumberI(int i)
{
	return i * 2;
}

//int型の値を受け取って、内部で値を変更して返す(VBAでパラメータをByRef で受け渡しするイメージ)
ACCESSIBLEFROMVBA_API void WINAPI GetNumberI2(int* pi)
{
	*pi *= 300;

	return;
}

f:id:Z1000S:20180627100406j:plain

最後に、モジュール定義ファイル(AccessibleFromVBA.def)

LIBRARY AccessibleFromVba

EXPORTS
	DoNothing
	GetNumberI
	GetNumberI2

f:id:Z1000S:20180627100410j:plain
全て追加したら、プロジェクトをビルドします。

========== ビルド: 1 正常終了、0 失敗、0 更新不要、0 スキップ ==========

と表示されればOK。
f:id:Z1000S:20180627100414j:plain

呼び出すExcel側は、

Private Declare Function GetNumberI Lib "C:\Datas\MyDatas\Developer\VisualStudioComunity2017\DllForVBA\ForTest\AccessibleFromVBA.dll" (ByVal l As Long) As Long

Private Declare Sub GetNumberI2 Lib "C:\Datas\MyDatas\Developer\VisualStudioComunity2017\DllForVBA\ForTest\AccessibleFromVBA.dll" (ByRef l As Long)

呼び出してみる

Public Sub DllCallTest2()

    Dim lValue  As Long
    Dim lResult As Long

    lValue = 1000

    lResult = GetNumberI(lValue)

    Debug.Print "GetNumberI:", lValue, lResult

    Debug.Print ""

    Debug.Print "GetNumberI2(Before):", lValue

    Call GetNumberI2(lValue)

    Debug.Print "GetNumberI2(After ):", lValue

End Sub

実行結果は、

call DllCallTest2
GetNumberI: 1000 2000


GetNumberI2(Before): 1000
GetNumberI2(After ): 300000

f:id:Z1000S:20180713081606j:plain

当てにならない次回予告

とりあえず、数値の受け渡しは出来たので、次は、

  • 文字列
  • 配列
  • 構造体

の受け渡しあたりをまとめられたらいいなぁ~

VBAによる祝日判定および祝日取得(改正東京五輪・パラリンピック特別措置法 対応)

2018年6月13日、参院本会議で可決、成立しました。

これに伴って、2020年の祝日が移動するものが出てきたようなので、先日公開した祝日判定処理を更新しました。
また、2020年からは「体育の日」が「スポーツの日」になるそうなので、合わせて対応済みです。

まぁ、祝日の定義の部分を変えただけなんですけどね。
処理内容は全く変えないで、元になる祝日データだけいじればいいので、祝日の追加、廃止、変更等のメンテナンスは楽ですよ。

Option Explicit

'//////////////////////////////////////////////////
'                   概  要
'//////////////////////////////////////////////////
'
'用   途:祝日取得、確認用
'処理対象日:1948/7/20以降(2050年までは、春分の日、秋分の日確認済み)
'備   考:2020/2/23天皇誕生日対応済み
'作   成:2018/5/13
'改   定:2018/6/14 改正東京五輪・パラリンピック特別措置法(2018/6/13参議院本会議可決分)対応

'//////////////////////////////////////////////////
'                   参照設定
'//////////////////////////////////////////////////

'Dictionary用
'Microsoft Scripting Runtime


'//////////////////////////////////////////////////
'                ユーザー定義型
'//////////////////////////////////////////////////

'月日固定の祝日情報
Private Type FixMD
    sMD         As String
    lBeginYear  As Long
    lEndYear    As Long
    sName       As String
End Type

'月週曜日固定の祝日情報
Private Type FixWN
    lMonth      As Long
    lNthWeek    As Long
    lDayOfWeek  As Long
    lBeginYear  As Long
    lEndYear    As Long
    sName       As String
End Type

'//////////////////////////////////////////////////
'                   定数
'//////////////////////////////////////////////////

'「国民の祝日に関する法律」施行年月日
Private Const BEGIN_DATE    As Date = #7/20/1948#

'「振替休日」施行年月日
Private Const TRANSFER_HOLIDAY1_BEGIN_DATE    As Date = #4/12/1973#
Private Const TRANSFER_HOLIDAY2_BEGIN_DATE    As Date = #1/1/2007#

'「国民の休日」施行年月日
Private Const NATIONAL_HOLIDAY_BEGIN_DATE       As Date = #12/27/1985#

'年上限
Private Const YEAR_MAX      As Long = 2050

'エラーコード(パラメータ異常)
Private Const ERROR_INVALID_PARAMETER   As Long = &H57


'//////////////////////////////////////////////////
'               Private変数
'//////////////////////////////////////////////////

'国民の祝日格納用ディクショナリ
'キー:年月日(DateTime型)
'値 :祝日名
Private dicHoliday_ As New Dictionary

Private lInitializedLastYear_   As Long


'++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
'++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
'
'                       祝日情報の定義
'
' 基本的な祝日情報は、以下の2つのメソッド内で定義する。
'  getNationalHolidayInfoMD
'  getNationalHolidayInfoWN
'
'++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
'++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

'//////////////////////////////////////////////////
'月日固定の祝日情報生成
'//////////////////////////////////////////////////
Private Sub getNationalHolidayInfoMD(ByRef uFixMD() As FixMD)

    Dim sFixMD(24)  As String   '祝日データを追加削除した場合、この配列要素数を変更すること
    Dim sResult()   As String
    Dim i           As Long

    '//////////////////////////////////////////////////
    '               月日固定の祝日
    '//////////////////////////////////////////////////
    '適用開始年について
    ' 元旦(1/1)
    ' 成人の日(1/15)
    ' 天皇誕生日(4/29)
    ' 憲法記念日(5/3)
    ' こどもの日(5/5)
    'の5つは、「国民の祝日に関する法律」施行年(1948年)に制定されているが
    '同法の施行が7/20であり、それ以前となるため、適用開始年を翌年(1949年)に補正してある。
    '
    '月日,適用開始年,適用終了年,名前
    '適用終了年;9999は、現在も適用中
    sFixMD(0) = "01/01,1949,9999,元日"          '適用開始年補正済み
    sFixMD(1) = "01/15,1949,1999,成人の日"      '適用開始年補正済み
    sFixMD(2) = "02/11,1967,9999,建国記念の日"
    sFixMD(3) = "02/23,2020,9999,天皇誕生日"    '適用開始年補正済み
    sFixMD(4) = "02/24,1989,1989,昭和天皇の大喪の礼"
    sFixMD(5) = "04/10,1959,1959,皇太子明仁親王の結婚の儀"
    sFixMD(6) = "04/29,1949,1988,天皇誕生日"    '適用開始年補正済み
    sFixMD(7) = "04/29,1989,2006,みどりの日"
    sFixMD(8) = "04/29,2007,9999,昭和の日"
    sFixMD(9) = "05/03,1949,9999,憲法記念日"    '適用開始年補正済み
    sFixMD(10) = "05/04,2007,9999,みどりの日"
    sFixMD(11) = "05/05,1949,9999,こどもの日"    '適用開始年補正済み
    sFixMD(12) = "06/09,1993,1993,皇太子徳仁親王の結婚の儀"
    sFixMD(13) = "07/20,1996,2002,海の日"
    sFixMD(14) = "07/23,2020,2020,海の日"
    sFixMD(15) = "07/24,2020,2020,スポーツの日"
    sFixMD(16) = "08/10,2020,2020,山の日"
    sFixMD(17) = "08/11,2016,2019,山の日"
    sFixMD(18) = "08/11,2021,9999,山の日"
    sFixMD(19) = "09/15,1966,2002,敬老の日"
    sFixMD(20) = "10/10,1966,1999,体育の日"
    sFixMD(21) = "11/03,1948,9999,文化の日"
    sFixMD(22) = "11/12,1990,1990,即位礼正殿の儀"
    sFixMD(23) = "11/23,1948,9999,勤労感謝の日"
    sFixMD(24) = "12/23,1989,2018,天皇誕生日"

    ReDim uFixMD(UBound(sFixMD))

    For i = 0 To UBound(sFixMD)
        sResult = Split(sFixMD(i), ",")

        uFixMD(i).sMD = sResult(0)
        uFixMD(i).lBeginYear = CLng(sResult(1))
        uFixMD(i).lEndYear = CLng(sResult(2))
        uFixMD(i).sName = sResult(3)
    Next i

End Sub

'//////////////////////////////////////////////////
'月週曜日固定の祝日情報生成
'//////////////////////////////////////////////////
Private Sub getNationalHolidayInfoWN(ByRef uFixWN() As FixWN)

    Dim sFixWN(5)   As String   '祝日データを追加削除した場合、この配列要素数を変更すること
    Dim sResult()   As String
    Dim i           As Long

    '//////////////////////////////////////////////////
    '               月週曜日固定の祝日
    '//////////////////////////////////////////////////
    '月,週,曜日,適用開始年,適用終了年,名前
    '曜日:日 1
    '   月 2
    '   火 3
    '   水 4
    '   木 5
    '   金 6
    '   土 7
    '適用終了年;9999は、現在も適用中
    sFixWN(0) = "01,2,2,2000,9999,成人の日"
    sFixWN(1) = "07,3,2,2003,2019,海の日"
    sFixWN(2) = "07,3,2,2021,9999,海の日"
    sFixWN(3) = "09,3,2,2003,9999,敬老の日"
    sFixWN(4) = "10,2,2,2000,2019,体育の日"
    sFixWN(5) = "10,2,2,2021,9999,スポーツの日"

    ReDim uFixWN(UBound(sFixWN))

    For i = 0 To UBound(sFixWN)
        sResult = Split(sFixWN(i), ",")

        uFixWN(i).lMonth = CLng(sResult(0))
        uFixWN(i).lNthWeek = CLng(sResult(1))
        uFixWN(i).lDayOfWeek = CLng(sResult(2))
        uFixWN(i).lBeginYear = CLng(sResult(3))
        uFixWN(i).lEndYear = CLng(sResult(4))
        uFixWN(i).sName = sResult(5)
    Next i
End Sub
'++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
'                       祝日情報の定義 ここまで
'++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Private Sub Class_Initialize()

    Set dicHoliday_ = New Dictionary

    lInitializedLastYear_ = &H80000000

    'デフォルトで、現在の5年後までデータを生成する
    InitializedLastYear = Year(Now) + 5

End Sub

Private Sub Class_Terminate()

    Set dicHoliday_ = Nothing

End Sub


'//////////////////////////////////////////////////
'指定日が国民の祝日(休日)か?
'//////////////////////////////////////////////////
Public Function isNationalHoliday(ByVal dtDate As Date) As Boolean

    Dim dtDateW As Date

    '時分秒データを切り捨てる
    dtDateW = DateSerial(Year(dtDate), Month(dtDate), Day(dtDate))

    If dtDateW < BEGIN_DATE Then
        Err.Raise ERROR_INVALID_PARAMETER, "isNationalHoliday", Format$(dtDateW, "yyyy/mm/dd") & "は、適用範囲外です。"

        Exit Function
    ElseIf Year(dtDateW) > YEAR_MAX Then
        Err.Raise ERROR_INVALID_PARAMETER, "isNationalHoliday", Format$(YEAR_MAX + 1, "yyyy年") & "以降は、適用範囲外です。"

        Exit Function
    ElseIf Year(dtDateW) > InitializedLastYear Then
        Err.Raise ERROR_INVALID_PARAMETER, "isNationalHoliday", Format$(dtDateW, "yyyy年") & "は、データが生成されていないため、判定できません。" _
                            & vbCrLf & "reInitializeメソッドで対象年を設定後、再度確認してみて下さい。"

        Exit Function
    End If

    isNationalHoliday = dicHoliday_.Exists(dtDateW)

End Function

'//////////////////////////////////////////////////
'指定日が国民の祝日(休日)か?そうであれば、その祝日名を合わせて返す
'//////////////////////////////////////////////////
Public Function isNationalHoliday2(ByVal dtDate As Date, ByRef sHolidayName As String) As Boolean

    Dim dtDateW As Date

    '時分秒データを切り捨てる
    dtDateW = DateSerial(Year(dtDate), Month(dtDate), Day(dtDate))

    isNationalHoliday2 = isNationalHoliday(dtDateW)

    sHolidayName = getNationalHolidayName(dtDateW)

End Function

'//////////////////////////////////////////////////
'指定年の祝日を配列に格納して返す
'//////////////////////////////////////////////////
Public Function getNationalHolidays(ByVal lYear As Long, ByRef dtHolidays() As Date) As Long

    Dim dtHolidaysW()   As Date
    Dim lHolidays       As Long
    Dim i As Long

    lHolidays = 0
    ReDim dtHolidaysW(lHolidays)

    For i = 0 To dicHoliday_.Count - 1
        If Year(dicHoliday_.Keys(i)) = lYear Then
            ReDim Preserve dtHolidaysW(lHolidays)

            dtHolidaysW(lHolidays) = dicHoliday_.Keys(i)

            lHolidays = lHolidays + 1
        End If
    Next i

    '昇順並べ替え
    Call qSort(dtHolidaysW, 0, UBound(dtHolidaysW))

    Erase dtHolidays
    dtHolidays = dtHolidaysW

    getNationalHolidays = lHolidays

End Function

'//////////////////////////////////////////////////
'指定日の祝日名を返す
'//////////////////////////////////////////////////
Public Function getNationalHolidayName(ByVal dtHoliday As Date) As String

    Dim dtDateW As Date

    '時分秒データを切り捨てる
    dtDateW = DateSerial(Year(dtHoliday), Month(dtHoliday), Day(dtHoliday))

    If isNationalHoliday(dtDateW) = True Then
        getNationalHolidayName = dicHoliday_.Item(dtDateW)
    End If

End Function

'//////////////////////////////////////////////////
'何年までの祝日データが生成されているか
'//////////////////////////////////////////////////
Public Property Get InitializedLastYear() As Long

    InitializedLastYear = lInitializedLastYear_

End Property

'//////////////////////////////////////////////////
'指定年までの祝日データを生成させる(YEAR_MAX以下)
' 外部からの要求は、reInitializeで行うことが出来る
'//////////////////////////////////////////////////
Private Property Let InitializedLastYear(ByVal lInitializedLastYear As Long)

    If lInitializedLastYear < lInitializedLastYear_ Then
        '要求された最終年が初期化済みの年より前ならば、処理しない
        Exit Property
    ElseIf lInitializedLastYear > YEAR_MAX Then
        lInitializedLastYear = YEAR_MAX
    End If

    Call initDictionary(lInitializedLastYear)

    lInitializedLastYear_ = lInitializedLastYear

End Property

'//////////////////////////////////////////////////
'指定年までの祝日データを生成させる
'//////////////////////////////////////////////////
Public Sub reInitialize(ByVal lLastYear As Long)

    InitializedLastYear = lLastYear

End Sub

'//////////////////////////////////////////////////
'Dictionaryへ祝日情報を格納
'//////////////////////////////////////////////////
Private Sub initDictionary(ByVal lLastYear As Long)

    Dim uFixMD()    As FixMD
    Dim uFixWN()    As FixWN

    '月日固定の祝日情報
    Call getNationalHolidayInfoMD(uFixMD)

    '月週曜日固定の祝日情報
    Call getNationalHolidayInfoWN(uFixWN)

    'Dictionaryへ追加
    Call add2Dictionary(lLastYear, uFixMD, uFixWN)

End Sub

'//////////////////////////////////////////////////
'祝日情報をDictionaryへ格納
'//////////////////////////////////////////////////
Private Sub add2Dictionary(ByVal lLastYear As Long, ByRef uFixMD() As FixMD, ByRef uFixWN() As FixWN)

    Dim lInitializedLastYear    As Long
    Dim lBeginYear          As Long
    Dim lEndYear            As Long
    Dim dtHoliday           As Date
    Dim lAddedDays          As Long
    Dim dtAdded()           As Date
    Dim existsHoliday       As Boolean
    Dim lYear               As Long
    Dim i                   As Long

    '初期化済みの最終年を取得
    lInitializedLastYear = InitializedLastYear

    If lInitializedLastYear < Year(BEGIN_DATE) Then
        '施工年より前ならば、施工年を開始年とする
        lBeginYear = Year(BEGIN_DATE)
    Else
        '施工年以後なら、初期化済みの翌年を開始年とする
        lBeginYear = lInitializedLastYear + 1
    End If

    lEndYear = lLastYear

    For lYear = lBeginYear To lEndYear
        '年間の祝日格納用配列クリア
        lAddedDays = 0
        ReDim dtAdded(lAddedDays)

        '月日固定の祝日
        For i = 0 To UBound(uFixMD)
            '適用期間のみを対象とする
            If uFixMD(i).lBeginYear <= lYear And uFixMD(i).lEndYear >= lYear Then
                dtHoliday = CDate(CStr(lYear) & "/" & uFixMD(i).sMD)

                dicHoliday_.Add dtHoliday, uFixMD(i).sName

                ReDim Preserve dtAdded(lAddedDays)
                dtAdded(lAddedDays) = dtHoliday
                lAddedDays = lAddedDays + 1
            End If
        Next i

        '月週曜日固定の祝日
        For i = 0 To UBound(uFixWN)
            '適用期間のみを対象とする
            If uFixWN(i).lBeginYear <= lYear And uFixWN(i).lEndYear >= lYear Then
                dtHoliday = getNthWeeksDayOfWeek(lYear, uFixWN(i).lMonth, uFixWN(i).lNthWeek, uFixWN(i).lDayOfWeek)

                dicHoliday_.Add dtHoliday, uFixWN(i).sName

                ReDim Preserve dtAdded(lAddedDays)
                dtAdded(lAddedDays) = dtHoliday
                lAddedDays = lAddedDays + 1
            End If
        Next i

        '春分の日
        dtHoliday = getVernalEquinoxDay(lYear)
        dicHoliday_.Add dtHoliday, "春分の日"

        ReDim Preserve dtAdded(lAddedDays)
        dtAdded(lAddedDays) = dtHoliday
        lAddedDays = lAddedDays + 1

        '秋分の日
        dtHoliday = getAutumnalEquinoxDay(lYear)
        dicHoliday_.Add dtHoliday, "秋分の日"

        ReDim Preserve dtAdded(lAddedDays)
        dtAdded(lAddedDays) = dtHoliday
        lAddedDays = lAddedDays + 1

        '振替休日
        For i = 0 To lAddedDays - 1
            existsHoliday = existsSubstituteHoliday(dtAdded(i), dtHoliday)

            If existsHoliday = True Then
                dicHoliday_.Add dtHoliday, "振替休日"
            End If
        Next i

        '国民の休日
        For i = 0 To lAddedDays - 1
            existsHoliday = existsNationalHoliday(dtAdded(i), dtHoliday)

            If existsHoliday = True Then
                dicHoliday_.Add dtHoliday, "国民の休日"
            End If
        Next i

        Erase dtAdded
    Next lYear

End Sub

'//////////////////////////////////////////////////
'振替休日の有無
' 祝日(dtDate)に対する振替休日の有無(ある場合は、dtSubstituteHolidayに代入される)
'//////////////////////////////////////////////////
Private Function existsSubstituteHoliday(ByVal dtDate As Date, ByRef dtSubstituteHoliday As Date) As Boolean

    Dim dtNextDay   As Date

    existsSubstituteHoliday = False

    If dicHoliday_.Exists(dtDate) = False Then
        'dtDateが祝日でなければ終了
        Exit Function
    End If

    '適用期間のみを対象とする
    If dtDate >= TRANSFER_HOLIDAY1_BEGIN_DATE And dtDate < TRANSFER_HOLIDAY2_BEGIN_DATE Then
        If Weekday(dtDate) = vbSunday Then
            '祝日が日曜日であれば、翌日(月曜日)が振替休日
            dtSubstituteHoliday = DateAdd("d", 1, dtDate)

            existsSubstituteHoliday = True
        End If
    ElseIf dtDate >= TRANSFER_HOLIDAY2_BEGIN_DATE Then
        '「国民の祝日」が日曜日に当たるときは、その日後においてその日に最も近い「国民の祝日」でない日を休日とする
        If Weekday(dtDate) = vbSunday Then
            dtNextDay = DateAdd("d", 1, dtDate)

            '直近の祝日でない日を取得
            Do Until dicHoliday_.Exists(dtNextDay) = False
                dtNextDay = DateAdd("d", 1, dtNextDay)
            Loop

            dtSubstituteHoliday = dtNextDay

            existsSubstituteHoliday = True
        End If
    End If

End Function

'//////////////////////////////////////////////////
'国民の休日の有無
' 祝日(dtDate)に対す国民の休日の有無(ある場合は、dtNationalHolidayに代入される)
'//////////////////////////////////////////////////
Private Function existsNationalHoliday(ByVal dtDate As Date, ByRef dtNationalHoliday As Date) As Boolean

    Dim dtBaseDay   As Date
    Dim dtNextDay   As Date

    existsNationalHoliday = False

    If dicHoliday_.Exists(dtDate) = False Then
        'dtDateが祝日でなければ終了
        Exit Function
    End If

    '適用期間のみを対象とする
    If dtDate >= NATIONAL_HOLIDAY_BEGIN_DATE Then
        dtBaseDay = DateAdd("d", 1, dtDate)

        '直近の祝日でない日を取得
        Do Until dicHoliday_.Exists(dtBaseDay) = False
            dtBaseDay = DateAdd("d", 1, dtBaseDay)
        Loop

        '日曜日であれば対象外
        If Weekday(dtBaseDay) <> vbSunday Then
            dtNextDay = DateAdd("d", 1, dtBaseDay)

            '翌日が祝日であれば対象
            If dicHoliday_.Exists(dtNextDay) = True Then
                existsNationalHoliday = True

                dtNationalHoliday = dtBaseDay
            End If
        End If
    End If

End Function

'//////////////////////////////////////////////////
'月の第N W曜日の日時を取得
'//////////////////////////////////////////////////
Private Function getNthWeeksDayOfWeek(ByVal lYear As Long, _
                                      ByVal lMonth As Long, _
                                      ByVal lNth As Long, _
                                      ByVal lDayOfWeek As VbDayOfWeek) As Date

    Dim dt1stDate       As Date
    Dim lDayOfWeek1st   As Long
    Dim lOffset         As Long

    '指定年月の1日を取得
    dt1stDate = DateSerial(lYear, lMonth, 1)

    '1日の曜日を取得
    lDayOfWeek1st = Weekday(dt1stDate)

    '指定日へのオフセットを取得
    lOffset = lDayOfWeek - lDayOfWeek1st

    If lDayOfWeek1st > lDayOfWeek Then
        lOffset = lOffset + 7
    End If

    lOffset = lOffset + 7 * (lNth - 1)

    getNthWeeksDayOfWeek = DateAdd("d", lOffset, dt1stDate)

End Function

'//////////////////////////////////////////////////
'春分の日を取得
'//////////////////////////////////////////////////
Private Function getVernalEquinoxDay(ByVal lYear As Long) As Date

    Dim lDay    As Long

    lDay = Int(20.8431 + 0.242194 * (lYear - 1980) - Int((lYear - 1980) / 4))

    getVernalEquinoxDay = DateSerial(lYear, 3, lDay)

End Function

'//////////////////////////////////////////////////
'秋分の日を取得
'//////////////////////////////////////////////////
Private Function getAutumnalEquinoxDay(ByVal lYear As Long) As Date

    Dim lDay    As Long

    lDay = Int(23.2488 + 0.242194 * (lYear - 1980) - Int((lYear - 1980) / 4))

    getAutumnalEquinoxDay = DateSerial(lYear, 9, lDay)

End Function

Private Sub qSort(ByRef dtHolidays() As Date, ByVal lLeft As Long, ByVal lRight As Long)

    Dim dtCenter    As Date
    Dim dtTemp      As Date
    Dim i           As Long
    Dim j           As Long

    If lLeft < lRight Then
        dtCenter = dtHolidays((lLeft + lRight) \ 2)

        i = lLeft - 1
        j = lRight + 1

        Do While (True)
            i = i + 1
            Do While (dtHolidays(i) < dtCenter)
                i = i + 1
            Loop

            j = j - 1
            Do While (dtHolidays(j) > dtCenter)
                j = j - 1
            Loop

            If i >= j Then
                Exit Do
            End If

            dtTemp = dtHolidays(i)
            dtHolidays(i) = dtHolidays(j)
            dtHolidays(j) = dtTemp
        Loop

        Call qSort(dtHolidays, lLeft, i - 1)
        Call qSort(dtHolidays, j + 1, lRight)
    End If

End Sub

2019年から2021年の7月から10月の祝日を出力した結果が以下の通り。

----- 2019 -----
2019/07/15 海の日
2019/08/11 山の日
2019/08/12 振替休日
2019/09/16 敬老の日
2019/09/23 秋分の日
2019/10/14 体育の日

----- 2020 -----
2020/07/23 海の日
2020/07/24 スポーツの日
2020/08/10 山の日
2020/09/21 敬老の日
2020/09/22 秋分の日

----- 2021 -----
2021/07/19 海の日
2021/08/11 山の日
2021/09/20 敬老の日
2021/09/23 秋分の日
2021/10/11 スポーツの日

VBAによる祝日判定および祝日取得

2018/6/14 追記
この記事には、2018/6/13の改正東京五輪パラリンピック特別措置法が参院本会議での可決に伴って、更新された記事があります。
z1000s.hatenablog.com


Excelで祝日判定をしたい」というと、「祝日データを用意して、それを・・・」とお決まりの文言が出てくるのが一般的です。
では、その祝日データはどうすれば用意できるのでしょうか?

ここのように、期間を指定して生成してくれるようなところもあるようです。

が、必要なデータが変わるたびにサイトにアクセスしてというのも面倒です。
となれば、「自分で作ってしまえ作れるようにしてしまえ」となりました。

以上



???普通ならそうはならない???
まぁ、いいじゃないですか。私がそう思ったんだから。
それに、ここで終わったら、ただの落書きで終わってしましますよ。


そろそろ本題に。

まず、処理するに当たって祝日を分類してみる。カッコ内は例。

  1. 月、日が固定のもの(元日:1月1日)
  2. 月、第N ○曜日が固定のもの(成人の日:1月第2月曜日)
  3. 月は固定だが、日が可変のもの(春分の日:3月19〜22日)
  4. 月、日が定まっておらず、他の祝日などに依存するもの(振替休日、国民の休日

1、2項は簡単に求められる。
3項は、計算式があるのでこれも問題なし。
4項は、他の祝日に依存するため、単独では求められない。予め1〜3項の祝日がわかっている必要がある。(或いは、その都度依存する日の状態を確認するか。)

では具体的にどうすれば求められるか、簡単(?)かもしれない解説。
前提条件として、データはDate型で保持するものとします。

1.月、日が固定のもの

VBA では、DateSerial 関数を使用すると、年、月、日を指定してDate 型の値を取得できます。

DateSerial( year, month, day )

DateSerial 関数については、こちら

2.月、第N ○曜日が固定のもの

指定された月の1日の曜日がわかれば、1日以降の最初の指定された曜日(第1○曜日)へのオフセット値(何日補正すればよいか)がわかります。
それがわかれば、7の倍数を加算することで求められます。

まず、指定月の1日を求めるのは、1項で登場したDateSerial 関数

DateSerial( year, month, 1 )

と日に「1」を指定すればOK。

次にその曜日
曜日は、Weekday 関数を使用します。

Weekday( date , [ firstdayofweek ] )

dateに、上で求めた1日の日付データを渡します。
firstdayofweekは省略可能です。今回は使用しません。
そうすると、指定した日付によって1〜7の値が返ってきます。

定数 説明
vbSunday 1 日曜日
vbMonday 2 月曜日
vbTuesday 3 火曜日
vbWednesday 4 水曜日
vbThursday 5 木曜日
vbFriday 6 金曜日
vbSaturday 7 土曜日

これを書いている2018年5月の場合を考えてみます。
第2週までのカレンダーは、以下の通りです。

1 2 3 4 5
6 7 8 9 10 11 12

最初の曜日までの日数は、以下のようになります。

曜日 指定曜日の日付 1日から指定曜日までの日数
日曜日 6 5
月曜日 7 6
火曜日 1 0
水曜日 2 1
木曜日 3 2
金曜日 4 3
土曜日 5 4

で、曜日を使って「指定曜日までの日数」をどうやって求めるかというと
前述の曜日の定数を使って「指定曜日」 から 「1日の曜日」を引きます。
ただし、「指定曜日」が「1日の曜日」より前の場合、結果が負の値となるのでその場合には7を加えます。

5月1日は火曜日なので、vbTuesday ===> 3
例えば、第1金曜日なら、vbFriday ===> 6
なので、6 - 3 = 3 となります。

第1日曜日なら、vbSunday ===> 1
なので、1 - 3 = -2 となりますが、結果が負なので、+7 して
-2 + 7 = 5 となります。

この値が求められれば、次は1日の日付に加算を行います。
日付の加減算は、DateAdd 関数を使用します。

DateAdd(interval, number, date)

intervalには、日数の計算を行うので、"d" を指定します。
numberには、上記の計算で求めた値を指定します。
dateには、1日の日付データを指定します。

最初の曜日であれば、これで良いのですが2回め以降の曜日の場合、更に7の倍数を加算する必要があります。
第2曜日であれば、+7
第3曜日であれば、+14
第4曜日であれば、+21
第5曜日であれば、+28
となるので、第N曜日の場合
(N ー 1)*7
を加算すれば良いことになります。

最終的には、以下のようなプロシージャにより求められます。

Public Function getNthWeeksDayOfWeek(ByVal lYear As Long, _
                                      ByVal lMonth As Long, _
                                      ByVal lNth As Long, _
                                      ByVal lDayOfWeek As VbDayOfWeek) As Date

    Dim dt1stDate       As Date
    Dim lDayOfWeek1st   As Long
    Dim lOffset         As Long

    '指定年月の1日を取得
    dt1stDate = DateSerial(lYear, lMonth, 1)

    '1日の曜日を取得
    lDayOfWeek1st = Weekday(dt1stDate)

    '指定日へのオフセットを取得
    lOffset = lDayOfWeek - lDayOfWeek1st

    If lDayOfWeek1st > lDayOfWeek Then
        lOffset = lOffset + 7
    End If

    lOffset = lOffset + 7 * (lNth - 1)

    getNthWeeksDayOfWeek = DateAdd("d", lOffset, dt1stDate)

End Function

ただし、N >= 5を指定した場合、日付が翌月になる場合がありますので、注意が必要です。

Weekday 関数については、こちら
DateAdd 関数については、こちら

3.月は固定だが、日が可変のもの

これは、春分の日秋分の日が該当。
Wikipediaによると
春分の日は、3月19~22日のいずれか。
秋分の日は、22~24日のいずれか。
となっている。
一般に、

'春分の日
Int(20.8431 + 0.242194 * (- 1980)) - Int((- 1980) / 4)
'秋分の日
Int(23.2488 + 0.242194 * (- 1980)) - Int((- 1980) / 4)

という計算で求められるようです。
1980年以降に限定すれば、以下の内容でもOK。
というか、こうしたかったんだけれど、1979年以前の場合、
演算子の結果が、Fix 関数と同等になり、Int 関数とは結果が変わってしまうため
If で振り分ける等の必要が生じてしまうので、上記の式をそのまま使用することにしました。

'春分の日
Int(20.8431 + 0.242194 * (- 1980)) - (- 1980) \ 4
'秋分の日
Int(23.2488 + 0.242194 * (- 1980)) - (- 1980) \ 4

ちなみに、¥演算子を使用した場合は、内部で整数演算が行われるが、
/演算子の場合、整数同士の演算であっても、内部では浮動小数点数で演算が行われます。

4.月日が定まっておらず、他の祝日などに依存するもの

4.1 振替休日

1973年の国民の祝日に関する法律が改正されたことにより制定された。これにより祝日(国民の祝日)が日曜日の場合、その翌日となる月曜日が振替休日となった(ハッピーマンデー制度の法律と同じ)。1973年の天皇誕生日(4月29日)が日曜日で、同年4月30日が最初の適用日となった。

当初は祝日が2日以上連続することがなかったため、「国民の祝日の日曜日の翌日の月曜日」としていた。しかし2005年の国民の祝日に関する法律の改正(2007年施行)で、4月29日が「昭和の日」となり、「みどりの日」が4月29日から5月4日へ変更。5月3日から5月5日まで祝日が3日連続することになり、その直後の国民の祝日の日曜日の翌日の月曜日以降の国民の祝日でない祝日の翌日」を休日とすることと改められ振替先が月曜日固定ではなくなった。

振替休日 - Wikipedia

ということなので、
祝日が日曜日であった場合、適用された年から判断し、その翌日または、それ以降で最初の祝日でない日を求めればOK。
2007年以降では、当然ながらこの日が月曜日とは限りません。
2015年5月3日の憲法記念日の振替休日は、6日の水曜日でした。
 5月3日(日):憲法記念日
 5月4日(月):みどりの日
 5月5日(火):こどもの日
 5月6日(水):5月3日の振替休日

4.2 国民の休日

前後が祝日である平日は、国民の休日となり、休日となる。

国民の休日 - Wikipedia

こちらは、翌々日まで確認が必要なので、要件は振替休日より複雑。

  1. 最初の祝日が月曜日から金曜日の間にあること。
    1. 最初の祝日が日曜日の場合、翌日は振替休日となるので、2項を満足しない。
    2. 最初の祝日が土曜日の場合、翌日は日曜日となるので、平日ではない。
  2. 最初の祝日の翌日が、平日であること。(祝日でなく、休日でもないこと。)
  3. 最初の祝日の翌々日が、祝日であること。

これらの条件を満たした時、最初の祝日の翌日が「国民の休日」となる。

2項、3項の祝日は、年によって日付が変動する祝日であり、「移動祝日」または「移動祝祭日」と呼ぶそうです。

最終的なコード

VBAのクラスとして作成しました。

続きを読む

秀丸で、選択行の行頭に文字を挿入するマクロ

先日、人力検索はてなで、
q.hatena.ne.jp
に回答して、ベストアンサーを頂いたのですが
ベースになった物があって、C++のソースを編集する際に、

  • 行コメントの追加
  • 既存の行を改修してコメント化

するために以前作ったもの(下記)でした。
(今回の件で、ちょっとしたバグを発見して修正したけど・・・)

あちらにアップしたものは、コメント削除したものなので、わかりにくかったかも。

$comment_prefix = "//";

//範囲選択されているか
if ( selecting == 1 )
{
	//範囲選択されていたら解除
	escape;

	//選択開始行
	#TargetLine = seltopy + 1;

	//選択終了行
	if ( selendx == 0 )
	{
		//選択終了行のカーソル位置が行先頭なら、その行はコメント化しない
		#EndLine    = selendy;
	}
	else
	{
		#EndLine    = selendy + 1;
	}

	//選択開始行に移動
	movetolineno 1, seltopy + 1;
}
else
{
	//選択開始行
	#TargetLine = lineno;

	//選択終了行
	#EndLine    = lineno;
}

//最終行に移動
gofileend;

//最終行取得
#FileEndLine = lineno;

//元の行に戻る
movetolineno 1, #TargetLine;

while ( #TargetLine != #EndLine + 1 )
{
	//行先頭に移動
	golinetop;

	//コメント化
	insert $comment_prefix;

	//処理行をインクリメント
	#TargetLine = #TargetLine + 1;

	if ( #TargetLine > #FileEndLine )
	{
		break;
	}

	//1行下へ移動
	movetolineno 1, #TargetLine;
}

LINEのバックアップがGoogleドライブに見当たらない?

スマホの機種変更をしようとしていた知人から、
「LINEのバックアップをGoogleドライブに保存したけど、ファイルが見当たらない。このまま移行作業を進めてもいいのか?」
と相談されました。
LINEの設定画面には、バックアップ日時が表示されているし、
再度バックアップすると、日時が更新されるので、
バックアップデータは、アップロードされているような雰囲気。

自分のスマホでバックアップして、Googleドライブを覗いてみても確かに見当たらない。
隠しファイルを表示させても見えない。
試しにPCのブラウザから、ドライブを確認してみても見当たらない。

で、探したら見つけました。
PCのブラウザで、Googleドライブを表示させ、「設定」-「アプリの管理」を表示させると、「非表示のアプリデータ」として保存されていました。
f:id:Z1000S:20180508233154j:plain
見えないと、本当に保存されているのか不安ですよね。
とりあえず解決ということで・・・