SMSカウンターとは?
SMSカウンターは、メッセージに含まれる文字数を計算し、エンコーディングタイプ(GSM 7ビットまたはUCS-2)を検出し、メッセージがいくつのSMSセグメントに分割されるかを判断するツールです。モバイルキャリアはメッセージ単位ではなくセグメント単位で課金するため、これは重要です。単一SMSの制限を超えると、警告なしにコストが2倍または3倍になる可能性があります。
通常の文字カウンターとは異なり、SMSカウンターはGSM 03.38で定義されたショートメッセージサービスの技術的制約を考慮します。テキストの長さだけでなく、含まれる文字と、それらがセルラーネットワークを介した送信のためにどのようにエンコードされる必要があるかを考慮します。
GSM 7ビットエンコーディングの仕組み
GSM 03.38仕様は、各7ビットでエンコードできる128文字のデフォルトアルファベットを定義しています。これには、大文字と小文字のラテン文字、数字、一般的な句読点、いくつかの特殊記号が含まれます。各文字が8ビットではなく7ビットのみを使用するため、1つのSMSは最大160文字まで収容できます。計算式は(140バイト × 8ビット)/ 7ビット/文字 です。
GSM 7ビット基本文字セット:
| カテゴリ | 文字 |
|---|---|
| 文字 | A–Z, a–z |
| 数字 | 0–9 |
| 句読点 | @, !, ", #, $, %, &, ', (, ), *, +, ,, -, ., / |
| 記号 | :, ;, <, =, >, ?, ¡, ¿, ¤, £, ¥, §, ¨, ©, ®, ´, `, ^, ~, ¯ |
| 特殊 | スペース、改行、復帰 |
| 拡張 | ^, {, }, , [, ~, ], , €(エスケープバイトが必要) |
^、{、}、[、]、~、|、€ などの拡張GSM 7ビット文字は、文字の前にエスケープコード(0x1B)が必要です。このエスケープバイトはメッセージ内の容量を消費するため、各拡張文字は160文字の制限に対して実質的に2文字としてカウントされます。
UCS-2エンコーディング:7ビットでは不十分な場合
メッセージにGSM 7ビットアルファベット以外の文字(絵文字、カーリークォート、é や ü などのアクセント付き文字、— などのダッシュ、または任意のUnicode記号)が含まれる場合、メッセージ全体がUCS-2(16ビット)エンコーディングに切り替わります。UCS-2モードでは、各文字が16ビットを占有するため、単一SMSの容量が160文字から70文字に減少します。
UCS-2エンコーディングを強制する文字:
- 絵文字とピクトグラフ(
😊、🚀、❤️) - カーリークォート(
""、'') - 長いダッシュ(
—、–) - GSM基本セットにないアクセント付き文字(
ě、ř、č) $、£、¥、€以外の通貨記号- 矢印(
→、←、↑、↓) - 数学記号(
∑、∫、√、∞) - ほとんどのCJK(中国語、日本語、韓国語)文字
メッセージに1つでもUCS-2文字が含まれると、メッセージ全体が16ビットエンコーディングを使用します。ハイブリッドモードはありません。すべて7ビットまたはすべて16ビットのいずれかです。
マルチパートSMSとセグメント制限
メッセージが単一SMSの制限(GSM 160文字またはUCS-2 70文字)を超えると、キャリアはメッセージを複数のセグメントに分割します。各セグメントには再構成用の6バイトのユーザーデータヘッダー(UDH)が含まれ、利用可能なペイロード容量が減少します。
| エンコーディング | 単一SMS | マルチパート(セグメントあたり) |
|---|---|---|
| GSM 7ビット | 160文字 | 153文字 |
| UCS-2 | 70文字 | 67文字 |
計算の仕組み:
GSM 7ビット マルチパート:
セグメントあたりの文字数 = (140 - 6) × 8 / 7 = 153
UCS-2 マルチパート:
セグメントあたりの文字数 = (140 - 6) / 2 = 67
GSM 7ビットエンコーディングの200文字メッセージは2つのセグメント(153 + 47)に分割されます。絵文字を含む150文字メッセージは3つのUCS-2セグメント(67 + 67 + 16)に分割されます。ほとんどのキャリアはセグメントごとに課金するため、これらの制限を理解することはコスト管理に役立ちます。
コード例
PHP:SMSセグメントをカウント
function countSmsSegments(string $text): array {
$gsm7 = '@£$¥èéùìòÇØøÅåΔ_ΦΓΛΩΠΨΣΘΞÆæßÉ !"#¤%&\'()*+,-./0123456789:;<=>?¡ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÑܧ¿abcdefghijklmnopqrstuvwxyzäöñüà';
$gsm7ext = ['^', '{', '}', '\\', '[', '~', ']', '|', '€'];
$isUcs2 = false;
$extCount = 0;
for ($i = 0; $i < mb_strlen($text); $i++) {
$char = mb_substr($text, $i, 1);
if (in_array($char, $gsm7ext)) {
$extCount++;
} elseif (mb_strpos($gsm7, $char) === false) {
$isUcs2 = true;
break;
}
}
if ($isUcs2) {
$maxPerSegment = 70;
$segments = (int)ceil(mb_strlen($text) / $maxPerSegment);
// マルチパートは制限を減らす
if ($segments > 1) {
$maxPerSegment = 67;
$segments = (int)ceil(mb_strlen($text) / $maxPerSegment);
}
return ['encoding' => 'UCS-2', 'segments' => $segments, 'chars_per_segment' => $maxPerSegment];
}
$effectiveLength = mb_strlen($text) + $extCount;
$maxPerSegment = 160;
$segments = (int)ceil($effectiveLength / $maxPerSegment);
if ($segments > 1) {
$maxPerSegment = 153;
$segments = (int)ceil($effectiveLength / $maxPerSegment);
}
return ['encoding' => 'GSM 7-bit', 'segments' => $segments, 'chars_per_segment' => $maxPerSegment];
}
JavaScript:SMSエンコーディングを検出
const GSM7_BASIC = new Set(
'@£$¥èéùìòÇØøÅåΔ_ΦΓΛΩΠΨΣΘΞÆæßÉ !"#¤%&\'()*+,-./0123456789:;<=>?¡' +
'ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÑܧ¿abcdefghijklmnopqrstuvwxyzäöñüà'.split('')
);
const GSM7_EXT = new Set(['^', '{', '}', '\\', '[', '~', ']', '|', '€']);
function detectSmsEncoding(text) {
let extCount = 0;
for (const char of text) {
if (GSM7_EXT.has(char)) {
extCount++;
} else if (!GSM7_BASIC.has(char)) {
return { encoding: 'UCS-2', extCount: 0 };
}
}
return { encoding: 'GSM 7-bit', extCount };
}
const result = detectSmsEncoding('Hello World! €50');
console.log(result); // { encoding: 'GSM 7-bit', extCount: 1 }
Python:SMSパーツを計算
def sms_parts(text: str) -> dict:
gsm7_basic = set('@£$¥èéùìòÇØøÅåΔ_ΦΓΛΩΠΨΣΘΞÆæßÉ !"#¤%&\'()*+,-./0123456789:;<=>?¡ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÑܧ¿abcdefghijklmnopqrstuvwxyzäöñüà')
gsm7_ext = {'^', '{', '}', '\\', '[', '~', ']', '|', '€'}
is_ucs2 = False
ext_count = 0
for char in text:
if char in gsm7_ext:
ext_count += 1
elif char not in gsm7_basic:
is_ucs2 = True
break
if is_ucs2:
limit = 70 if len(text) <= 70 else 67
parts = (len(text) + limit - 1) // limit
return {'encoding': 'UCS-2', 'parts': parts, 'chars_per_part': limit}
effective = len(text) + ext_count
limit = 160 if effective <= 160 else 153
parts = (effective + limit - 1) // limit
return {'encoding': 'GSM 7-bit', 'parts': parts, 'chars_per_part': limit}
print(sms_parts('Hello World')) # 1 part, GSM 7-bit
print(sms_parts('Hello 😊 World')) # UCS-2 encoding triggered
SMSエンコーディングが開発者にとって重要な理由
SMS通知を送信するアプリケーション(二要素認証コード、予約リマインダー、マーケティングキャンペーンなど)を構築する場合、SMSエンコーディングを理解することで、配信可能性とコストの両方を制御できます。
開発者向けの重要なポイント:
- 予期しないマルチパート課金を避けるために、送信前に常にメッセージ長を検証する
- 可能な場合はUCS-2文字を削除または置換して、メッセージをGSM 7ビットに保つ
€や[]などの拡張文字でテストする — これらは基本GSM文字とは異なる圧縮方法になる- マルチパート容量を見積もる際は、6バイトのUDHオーバーヘッドを考慮する
- 開発とテスト中は専用のSMSカウンターツールの使用を検討する
オンラインツール
Help2CodeのSMSカウンターは、リアルタイムの文字カウント、エンコーディング検出、セグメントプレビューを提供します。メッセージを貼り付けると、GSM 7ビットとUCS-2エンコーディングのどちらを使用しているか、必要なセグメント数、各セグメントの正確な区切り位置が即座に表示されます。色分けされたエンコーディングプレビューは、どの文字がUnicodeモードをトリガーし、どの文字が7ビットで安全かを示します。
結論
SMSカウンターは、プログラムでテキストメッセージを送信する人、または予期しないキャリア課金を避けたい人にとって不可欠なユーティリティです。GSM 7ビットとUCS-2エンコーディングの違いを理解し、マルチパートセグメンテーションの仕組みを知ることで、よりコスト効果の高いSMSアプリケーションを作成し、配信問題をより迅速にデバッグできます。本番環境で送信する前に、SMSカウンターツールを使用してメッセージをテストしてください。