2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ゼロ埋めの slice(-2) は100分を「00分」にする? padStart との違いと、マイナス・小数・月のずれ

2
Posted at

時刻を 09:05、番号を No.0007 のように、桁をそろえて表示したい。そんな「ゼロ埋め」には、よく次の2つの書き方が使われます。

const n = 5;
("0" + n).slice(-2);         // "05"
String(n).padStart(2, "0");  // "05"

1桁の数字なら、どちらも同じ結果です。ところが、桁があふれたとき、マイナスや小数のとき、日付の月を埋めるときには、エラーにならないまま結果がずれる場面がありました。この記事では、Node.js v24.14.1 で実際に動かした結果をもとに整理します。結果は、コードのまとまりごとに .mjs ファイルに保存して node で実行し、console.log で出した値をコメントに書いています。

桁があふれると、slice(-2) は上の桁を黙って捨てる

padStart(2, "0") は「2文字に足りなければ、左に "0" を足す」という意味です。もとから2文字以上あれば、何もしません。一方、slice(-2) は「後ろから2文字だけ取り出す」なので、3桁になると先頭が消えます。

String(123).padStart(2, "0");  // "123"
("0" + 123).slice(-2);         // "23"

秒数を「分:秒」にする処理で比べました。100分を超えると、違いがはっきり出ます。

const sec = 6005; // 100分5秒

`${String(Math.floor(sec / 60)).padStart(2, "0")}:${String(sec % 60).padStart(2, "0")}`;
// "100:05"

`${("0" + Math.floor(sec / 60)).slice(-2)}:${("0" + (sec % 60)).slice(-2)}`;
// "00:05"

slice(-2) 版は、100分5秒を 00:05 と表示しました。エラーは出ないので、短い動画や練習用のデータで試しているうちは気づきにくいところです。桁があふれる可能性があるなら、padStart のほうが安全です。

数値のまま padStart を呼ぶと TypeError

padStart は文字列のメソッドです。数値のまま呼ぶとエラーになりました。

const n = 5;
n.padStart(2, "0");
// TypeError: n.padStart is not a function(Node.js での表示)

String(n).padStart(2, "0");   // "05"
`${n}`.padStart(2, "0");      // "05"

先に String() かテンプレートリテラルで文字列にしてから呼びます。

マイナスと小数は、思った形にならない

padStart は、文字列の中身を数字として見ていません。左に "0" を足すだけなので、マイナスの記号や小数点も1文字として数えます。

String(-5).padStart(3, "0");    // "0-5"
String(5.5).padStart(2, "0");   // "5.5"
String(5.25).padStart(4, "0");  // "5.25"

-5 は "-05" ではなく、記号の前に 0 が入った "0-5" になりました。

小数は、整数の部分だけを2桁にしたくても、小数点と小数の桁まで数えます。5.5 は幅2では何も足されず、5.25 は幅4でも整数の部分が埋まりませんでした。必要な幅が、小数の桁数によって変わります。

気温や前日比のように、マイナスになりうる数を埋めるときは、符号と数字を分けて埋めます。

const n = -5;
(n < 0 ? "-" : "") + String(Math.abs(n)).padStart(2, "0");  // "-05"

Intl.NumberFormat の minimumIntegerDigits(整数部の最低桁数)を使う方法もあります。

const nf = new Intl.NumberFormat("ja-JP", { minimumIntegerDigits: 2 });
nf.format(5);     // "05"
nf.format(-5);    // "-05"
nf.format(5.25);  // "05.25"
nf.format(1234);  // "1,234"

マイナスはそのまま "-05" になり、小数も整数の部分だけが2桁になりました。一方で、1234 には3桁ごとのカンマが入りました。番号のように、カンマを入れたくない場面では useGrouping: false を足します。

new Intl.NumberFormat("ja-JP", { minimumIntegerDigits: 2, useGrouping: false }).format(1234);
// "1234"

もう1つ、何も指定しないと小数は3桁までに丸められました。小数の桁をそのまま残したいときは、maximumFractionDigits で桁数を指定します。

nf.format(1.23456);  // "01.235"
new Intl.NumberFormat("ja-JP", { minimumIntegerDigits: 2, maximumFractionDigits: 5 }).format(1.23456);
// "01.23456"

値が undefined や null でも、エラーにならない

String(undefined).padStart(2, "0");  // "undefined"
String(null).padStart(2, "0");       // "null"

どちらも2文字以上あるので、そのまま返りました。データがまだ届いていないときに、画面に undefined と出る原因になります。埋める前に、値があるかを確かめます。

const n = null;
n == null ? "--" : String(n).padStart(2, "0");  // "--"

n == null は、null と undefined の両方で true になる書き方です。理由は 「==は使うな」の唯一の例外。x == null だけは使っていい理由 で書きました。

日付の月は、+1 を忘れると「00月」になる

Date の getMonth() は、1月を 0 として返します。

const d = new Date(2026, 0, 5, 9, 3, 7); // 2026年1月5日 9:03:07

d.getMonth();                              // 0
String(d.getMonth()).padStart(2, "0");     // "00"(+1 を忘れた)
String(d.getMonth() + 1).padStart(2, "0"); // "01"

`${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
// "2026-01-05"

Date の月まわりで、1月31日の1か月後が3月3日になる話は 1月31日の「1か月後」が3月3日になる — Date の月の繰り上がりと月末の扱い で書きました。

日付と時刻なら、toLocaleDateString / toLocaleTimeString に "2-digit" を指定して、埋めてもらう方法もあります。

d.toLocaleDateString("ja-JP", { year: "numeric", month: "2-digit", day: "2-digit" });
// "2026/01/05"
d.toLocaleTimeString("ja-JP", { hour: "2-digit", minute: "2-digit", second: "2-digit" });
// "09:03:07"
d.toLocaleTimeString("ja-JP", { hour: "2-digit" });
// "09時"

時だけを指定すると、"09時" のように「時」が付きました(どれも Node.js v24.14.1 で確かめた出力です)。区切りの記号や文字は地域の設定(ここでは "ja-JP")で決まるので、形を決め打ちで使うなら、使う環境で出力を一度確かめます。たくさんの日付をまとめて整形するときの速さは、toLocaleString を2万回呼ぶと遅い? Node.js と WebKit で Intl.DateTimeFormat 使い回しと実測 で比べました。

ゼロ埋めすると、文字列の並べ替えがそろう

ファイル名や番号を文字列のまま並べ替えると、10 が 2 より前に来ます。文字列は、1文字目から順に比べるためです。

["img10.png", "img2.png", "img1.png"].sort();
// [ 'img1.png', 'img10.png', 'img2.png' ]

["img10.png", "img02.png", "img01.png"].sort();
// [ 'img01.png', 'img02.png', 'img10.png' ]

"9" < "10";   // false
"09" < "10";  // true

桁をそろえると、文字列のままでも数の順に並びました。ただし、埋めた桁数より桁の多い番号(2桁で埋めたところに100番など)が混ざると、また崩れます。

["img10.png", "img02.png", "img100.png", "img11.png", "img01.png"].sort();
// [ 'img01.png', 'img02.png', 'img10.png', 'img100.png', 'img11.png' ]

2桁で埋めているところに img100.png が入ると、img10.png と img11.png の間に並びました。最初の章の「桁があふれる」と同じで、いちばん大きい番号の桁数で埋める必要があります。名前を変えられないときは、localeCompare の numeric: true で、数字の部分を数として比べられます。

["img10.png", "img2.png", "img1.png"].sort((a, b) =>
  a.localeCompare(b, undefined, { numeric: true })
);
// [ 'img1.png', 'img2.png', 'img10.png' ]

padStart の細かいところ

"5".padStart(3);          // "  5"(第2引数を省くと半角スペース)
"5".padStart(3, "");      // "5"(空の文字列だと何も足さない)
"5".padStart(6, "ab");    // "ababa5"(足りない分だけ繰り返して切る)
"abc".padEnd(6, ".");     // "abc..."(右に足すのは padEnd)

全角の数字や絵文字も、文字数の数え方は length と同じです。

"5".padStart(2, "0");    // "05"(全角の5は1文字)
"😀".padStart(3, "0");    // "0😀"(😀は length が2)

😀 は見た目は1文字ですが、length は 2 なので、"0" は1つしか足されませんでした。絵文字の length がずれる理由は、絵文字を含む文字列の length が思った数にならない理由と、正しい文字数の数え方 で書きました。

AI が書いたゼロ埋めを見るときの確認ポイント

  • ("0" + n).slice(-2) の形になっていて、3桁以上になる値(分・番号・金額など)を渡していないか
  • マイナスになりうる値を、そのまま padStart していないか
  • 小数を埋めるときに、幅を小数の桁まで含めて考えているか(小数の桁数がばらつくなら Intl.NumberFormat を使う)
  • 並べ替えのためにゼロ埋めするなら、いちばん大きい番号の桁数で埋めているか
  • getMonth() に + 1 があるか
  • 埋める前に、値が undefined や null でないかを確かめているか

未経験者向けの講座を運営しています

未経験から Next.js + Supabase + Claude Code で Webアプリを公開するまで を、全24セッションで体系化した教材です。Claude Code を学習パートナーにする CLAUDE.md と学習モード(learner / developer)の設計までセットで含みます。

※ 中上級者には易しすぎる内容なので、初心者の知り合いへの紹介や社内研修の参考としてどうぞ。

2
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?