【VBA】全角半角混在文字列からバイト単位で取り出したい

# はじめに
企業の総務部門や社労士事務所さんが使うシステムは、**人事労務**、**勤怠管理**、**給与計算**に大別されます。
同じシステムで纏めて処理することもあれば、ベンダーが異なるシステム同士をデータ連携させることもあります。
近年はソフトのクラウド化が進み、APIで連携できるようになったものの、まだCSVファイルでデータ受け渡しを行う現場が多勢です。

今回、人事労務システムの「**SmartHR**」から、給与計算システムの「**給与奉行**」に、連携（データ変換）するVBAマクロを組む機会があったので、少し解説してみます。所得控除の話も出てきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236222/407e8d67-15e2-3c9f-cdac-fa5781589d13.png)
ちなみにHRとはHuman Resourceの略で、直訳すると人材という意味になりますが、人事部という意味もあります。人事系のシステムやイベントにはHRを冠したものが多いです。**HRTech**は人事とITを掛け合わせた造語で、**FinTech**（金融×IT）、**EdTech**（教育×IT）、**AdTech**（広告×IT）と並ぶ、X-Techの造語になります。

# 実現方式
このデータ変換は、勤怠を締めてから給与計算を回すまでに基本一度だけ行えば良いので、API連携ではなく簡易なVBAマクロで処理します。
オービックビジネスコンサルタント社（OBC）のERPパッケージである奉行シリーズには「OBC受入形式」という汎用フォーマットがあり、この形式に変換できれば、奉行にインポート可能なようです。このときの文字コードはShift-JISです。

例えば下表のように、SmartHRの項目名▶奉行の受入記号の変換表を作成しておき、これに準じてマクロを組めばOKです。

| SmartHRの項目名 | 給与奉行の受入記号 | 給与奉行の項目名 | 変換方法 |
|:---------------|:------------------|:---------------|:-------|
|社員番号|EBAS001|社員番号|ゼロパディングで5桁
|姓(ヨミガナ)
名(ヨミガナ)|EBAS002|氏名(フリガナ)|姓と半角スペース区切りで結合
|姓
名|EBAS003|氏名|姓と半角スペース区切りで結合
|部署名|ECHA003|所属コード|所属マスタからコードに変換
|役職|ECHA005|役職コード|役職マスタからコードに変換
|雇用形態|EBAS005|雇用区分コード|1：正社員　2：契約社員<br>3：派遣社員　4：アルバイト<br>5：パート　6：役員
|性別|EBAS008|性別コード|0：男性　1：女性
|生年月日|EBAS010|生年月日|
|雇い入れ年月日|EBAS011|入社年月日|
|ビジネスネーム 姓(ヨミガナ)
ビジネスネーム 名(ヨミガナ)|EBAS026|職場氏名(フリガナ)|姓と半角スペース区切りで結合
|ビジネスネーム 姓
ビジネスネーム 名|EBAS027|職場氏名|姓と半角スペース区切りで結合
|住所(郵便番号)|EADD004|郵便番号|ハイフンを含む
|住所(都道府県)|EADD005|都道府県|12文字まで
|住所(市区町村)|EADD006|市区町村|24文字まで
|住所(丁目・番地)|EADD007|番地|30文字まで
|住所(建物名・部屋番号)|EADD008|マンション／ビル等|50文字まで
|住所(ヨミガナ)|EADD009|住所カナ|
|電話番号|EADD010|電話番号|7桁-5桁-5桁
|メールアドレス|EWOR002|社用e-Mail1|
|社用メールアドレス|EWOR004|社用e-Mail2|
|発令情報.職種|ECHA009|職種区分コード|職種区分マスタからコードに変換
|発令情報.採用区分|ECHA011|採用区分コード|採用区分マスタからコードに変換
|発令情報.等級|ECHA013|等級コード|等級マスタからコードに変換
|発令情報.事業所情報|ECHA015|事業所コード|事業所マスタからコードに変換
|発令情報.一般職管理職区分|EUNP002|給与体系コード|給与体系マスタからコードに変換
|給与支給形態|EUNP003|給与区分コード|0：月給　1：日給　2：時給
|税表区分.税表区分|ESAL003|課税区分コード|0：計算不要　1：甲欄<br>2：乙欄　3：丙欄<br>4：報酬　5：非居住者<br>6：課税不要
|配偶者 姓(ヨミガナ)
配偶者 名(ヨミガナ)|EFMM001|配偶者－フリガナ|姓と半角スペース区切りで結合
|配偶者 姓
配偶者 名|EFMM002|配偶者－氏名|姓と半角スペース区切りで結合
|配偶者 性別|EFMM003|配偶者－性別コード|0：男性　1：女性
|配偶者 生年月日|EFMM004|配偶者－生年月日|
|配偶者 同居・別居の別|EFMM009|配偶者－同居区分コード|0：対象外　1：同居
|配偶者 障害者区分|EFMM011|配偶者－障害者区分コード|0：対象外　1：一般障害者<br>2：特別障害者
|配偶者 社会保険の扶養状況|EFMM012|配偶者－健保扶養区分コード|0：未加入　1：加入
|配偶者 税法上の扶養状況|EFMM020|配偶者－扶養区分コード|0：控除対象外<br>1：源泉控除配偶
|家族1 姓(ヨミガナ)
家族1 名(ヨミガナ)|EFMD001|扶養親族1－フリガナ|
|家族1 姓
家族1 名|EFMD002|扶養親族1－氏名|
|家族1 性別|EFMD003|扶養親族1－性別コード|
|家族1 続柄|EFMD004|扶養親族1－続柄コード|
|家族1 生年月日|EFMD005|扶養親族1－生年月日|
|家族1 同居・別居の別|EFMD010|扶養親族1－同居区分コード|
|家族1 税法上の扶養状況|EFMD011|扶養親族1－扶養区分コード|0：控除対象外　1：一般扶養<br>2：特定扶養　3：老人扶養<br>4：老親等　9：年少扶養
|家族1 障害者区分|EFMD012|扶養親族1－障害者区分コード|0：対象外　1：一般障害者<br>2：特別障害者
|家族1 社会保険の扶養状況|EFMD013|扶養親族1－健保扶養区分コード|0：未加入　1：加入
|寡婦・ひとり親|ESED001|寡婦／ひとり親区分コード|0：対象外　1：寡婦<br>2：ひとり親
|障害者区分|ESED002|障害者区分コード|0：対象外　1：一般障害者<br>2：特別障害者
|勤労学生フラグ|ESED003|勤労学生区分コード|0：対象外　1：勤労学生
|基礎年金番号|ESOC001|基礎年金番号|
|健康保険の被保険者整理番号|ESOC005|健保証番号|
|給与振込口座 銀行コード|ESPY105|給与振込1－振込先銀行コード|
|給与振込口座 支店コード|ESPY106|給与振込1－振込先支店コード|
|給与振込口座 預金種別|ESPY107|給与振込1－預金種目コード|
|給与振込口座 口座番号|ESPY108|給与振込1－口座番号|
|給与振込口座 名義(カタカナ)|ESPY109|給与振込1－フリガナ|

# 業務用語解説
先述の表には、所得控除に関する用語が沢山出てきました。
業務システム開発に関わる**アプリケーションエンジニア**にとってはクライアント業務の基礎的な勉強は避けられませんし、サラリーマンで年末調整する方も、フリーランスで確定申告する方も、所得控除は知らないと損する話です。
一緒に学んでいきましょう。

## 所得控除について
会社の利益に対してかかる法人税と違い、個人の所得に対してかかる所得税では、個人の事情が考慮され、扶養控除や医療費控除などの**所得控除**が設けられています。
所得税は、所得（収入-経費=利益）に税率を乗じて計算されますが、この所得から控除できる金額のことを所得控除というわけです。（個人事業主の場合は売上高から必要経費を差し引いた利益がそのまま自分の所得となり、これを事業所得といいます）
所得控除には、基礎控除、配偶者控除、扶養控除などの種類があります。

### 扶養控除とは
**扶養**とは親族から経済的援助を受けることをいいます。「妻が夫の扶養に入る」という使い方をしますよね。
一般に「親族」というと親戚すべての人が含まれますが、法律上の親族という場合には、配偶者、6親等内の**血族**（血が繋がっている人。養子縁組は法的な血縁関係が生じる）、3親等内の**姻族**（配偶者の血族）に限られます。
扶養を受けている者（被扶養者）は、収入が一定金額を超えると扶養から外れて、税金の負担が大きくなります。

扶養控除は、その年12月31日現況で、いくつかの要件を満たす扶養親族がいる場合に控除できます。
要件には、16歳以上とか、配偶者以外の親族とか、納税者と生計を一にしているとか、他にも色々あります。

扶養親族には、次の種類があります。

- **特定扶養親族**
その年12月31日の年齢が19歳以上23歳未満の人です。
つまり、2021年分なら、生年月日が1999年1月2日～2003年1月1日の人を指します。

- **老人扶養親族**
その年12月31日の年齢が70歳以上の人です。
つまり、2021年分なら、生年月日が1952年1月1日以前の人を指します。
なお、日時などを定める際に法律で用いられる「以前」は、その基準時点を含んでそれより前という意味です。

- **同居老親**
老人扶養親族のうち、納税者と同居している人です。

- **年少扶養**
こども手当（現在は児童手当）に移行したため、年少扶養親族（16歳未満）に対する扶養控除は廃止されました。
ただし、住民税については非課税限度額という制度があり、16歳未満の子供の有無が税額計算に関わってくるため、扶養控除等申告書の住民税に関する事項には記入が必要です。
2021年分なら、生年月日が2006年1月2日以後の人を指します。

ということで、税法上の扶養では、生年月日と同居区分から、扶養区分（OBC受入形式のEFMD011）を求めることができます。

```vb:扶養区分を求める
' 生年月日が入力されているか？
If 生年月日 <> "" Then
    誕生日 = Format(生年月日, "yyyy/mm/dd")

    ' 年少扶養か？
    If 誕生日 >= "2006/01/02" Then
        扶養区分 = "年少扶養"
    Else
        ' 老人扶養親族か？
        If 誕生日 <= "1952/01/01" Then
            ' 同居しているか？
            If 同居フラグ = "同居" Then
                扶養区分 = "老親等"
            Else
                扶養区分 = "老人扶養"
            End If
        Else
            ' 特定扶養親族か？
            If 誕生日 >= "1999/01/02" And 誕生日 <= "2003/01/01" Then
                扶養区分 = "特定扶養"
            Else
                扶養区分 = "一般扶養"
            End If
        End If
    End If
End If
```

ところで、扶養控除には「**税法上の扶養**」と「**社会保険上の扶養**」があるのをご存知でしたか？
「税法上の扶養」は、家計を主に支える方が住民税や所得税の控除を受けて、納めるべき税金を低く抑えられるというものです。「給与所得者の扶養控除等(異動)申告書」に記載する「源泉控除対象配偶者」もこれと同じです。
「社会保険上の扶養」は、家計を主に支える方の勤務先の健康保険や厚生年金の被扶養者になることで、自分で社会保険料を納めなくても良いというものです。

### 配偶者控除とは
納税者に控除対象配偶者がいる場合に受けられる控除です。内縁関係は含まれません。
これにもいくつかの要件があります。
控除対象配偶者でありながら年間の合計所得金額が48万円を超えているために配偶者控除を受けられない場合、配偶者**特別**控除が受けられます。

なお、給与計算システムの配偶者の設定では、

- 源泉控除対象配偶者かどうかのチェック。（チェックすると扶養親族にカウントされる）
- 同一生計配偶者かどうかのチェック。（チェックしないと控除対象配偶者ということ）

が入れられるようになっていて、この設定を間違えると正しく年末調整できません。

同一生計配偶者は配偶者控除を使えませんが、その配偶者が障害者の場合、障害者控除が受けられます。

## 他に混同しがちな用語
寡婦（夫）とひとり親の違いは婚姻歴の有無です。寡婦（夫）は離婚や死別によるものでしたが、ひとり親は未婚です。子供の貧困を救済するため、令和2年から未婚のひとり親も控除を受けられるようになりました。

# 文字数制限問題
システムの話に戻ります。

テキスト型の項目では、最大文字数の制限が設けられているのが普通です。
この場合、単位が、文字数なのかバイト数なのかを仕様書から読み解くことになります。
日本語などの **DBCS** では、全角1文字を2バイトでカウントするため、文字種（全角／半角）の違いで最大文字数が変わるようならバイト数単位と考えて良いです。

給与奉行の「番地」は30文字までの制限がありました。文字種の言及は無いので、全角半角に関係なく30文字を超えてはならないようです。この場合、VBAの`Left`関数で先頭から30文字を取り出せば良いです。31文字目以降は`Mid`関数で取り出し、次項目の「マンション／ビル等」に突っ込みます。

全角半角混在文字列からバイト単位で取り出す場合は注意が必要です。全角文字は2バイトなので分割位置によっては文字化けが起こるからです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236222/c9fdf3a4-422e-f166-3579-ed3d1f34180e.png)
そこで、バイト単位の文字列分割に利用できる汎用のサブプロシージャを作成しました。

```vb
Public Sub JustRightSplit(mixStr As String, cutPos As Integer, firstStr As String, secondStr As String)
    Dim s As String, n As Integer
    
    s = StrConv(mixStr, vbFromUnicode)
    If LenB(s) > cutPos Then
        n = (LenB(StrConv(LeftB(s, cutPos), vbUnicode)) = LenB(StrConv(LeftB(s, 1 + cutPos), vbUnicode)))
        firstStr = StrConv(LeftB(s, cutPos + n), vbUnicode)
        secondStr = StrConv(MidB(s, 1 + cutPos + n), vbUnicode)
    Else
        firstStr = mixStr
        secondStr = ""
    End If
End Sub
```

考え方としては、今どきのWindowsが内部で使っているUnicode（UTF-16）を既定のコードページ（つまりShift-JIS）に変換し、`LenB`関数と`LeftB`関数を使ってバイト単位で計算しています。

使い方は次の通りです。

- 第一引数：分割したい文字列
- 第二引数：取り出したいバイト数
- 第三引数：分割後文字列の前半を格納する変数
- 第四引数：分割後文字列の後半を格納する変数

```
Dim a As String, b As String
    
JustRightSplit "あいAうBCえお", 10, a, b
    
Debug.Print a
Debug.Print b
```

```:出力
あいAうBC
えお
```

半端なバイト数でも丁度良い位置で分割してくれます。