Vimで現在の日付・時刻を挿入する方法(strftime)
議事録やメモを書いているとき、Excelの「現在日時の挿入」ショートカットのような機能がVimにも欲しくなります。プラグインを入れなくても、標準のstrftime()関数を使えば、好きな書式で日付・時刻を挿入できます。
ただしこの関数は、書式の解釈をVim自身が担当しているわけではありません。どの指定子が使えるかも、曜日が日本語で出るか英語で出るかも、Vimの外側の事情で決まります。基本の挿入方法から、書式指定子の正体、ロケールとタイムゾーンの影響、保存時に更新日を書き換える設定までを、実際に打って確かめた結果とあわせてまとめます。確認はmacOS 26.4.1のVim 9.1.1752とNeovim 0.12.4、LANGがja_JP.UTF-8、プラグインを読まない状態(vim -Nu NONEとnvim -u NONE)で、2026-09-03に行いました。以下の出力はその日の実測値です。
目次(8項目)
基本の書き方
:put =strftime('%Y/%m/%d')
:putは式の評価結果をカーソル行の下に挿入するコマンドです。strftime()に書式文字列を渡せば、任意の形式で現在日時の文字列を作れます。
## 議事録
:put =strftime(...)## 議事録
2026/09/03
ここで効いてくるのが:putの性質です。受け取った文字列を必ず1行として置くので、文の途中には差し込めません。AAAからCCCまでが並ぶファイルの2行目にカーソルを置いて実行すると、日付は独立した3行目として入りました。上側に入れたいときは:put!を使います。
:put =strftime('%F') " カーソル行の下に1行として入る
:put! =strftime('%F') " カーソル行の上に1行として入る
文の途中に埋め込みたいときは、インサートモードで式レジスタを呼びます。Ctrl-rに続けて=を押すと画面下に式の入力欄が出て、Enterを押した瞬間に評価結果がカーソル位置へ流し込まれます。行は増えません。
Ctrl-r =strftime('%H:%M')<CR>
手元でCCCという行の末尾に now=と打ってからCtrl-r =を押し、strftime('%H:%M')を評価すると、その行はCCC now=16:44になりました。同じ関数でも:putなら行が増え、式レジスタなら増えません。式レジスタそのものの仕組み(評価されるタイミング、型ごとの挿入のされ方、履歴からの呼び戻し)はexpressionレジスタの解説に分けてあります。
3つ目の入口として、外部のdateコマンドを:r !dateで読み込む手もあります。ただしこちらは呼ぶたびにシェルのプロセスが起動します。同じ日付を100回作る処理をreltime()で計ったところ、strftime()が0.0005秒未満だったのに対し、system('date +%Y-%m-%d')は1秒前後かかりました。絶対値は同時に動いている処理の重さで振れますが、3000倍前後という開きは測り直しても変わりませんでした。1回あたり15ミリ秒ほどなので単発では気づきませんが、autocmdのように保存のたびに走る場所では効いてきます。書式の方言もシェル側のものになります。:r !そのものの使い方はシェルコマンドの出力をバッファに挿入する方法にまとめています。
書式を解釈しているのはVimではない
指定子を並べる前に、この関数の立ち位置を押さえておくと迷いが減ります。ヘルプは書式の解釈を外へ丸ごと投げていると明言します。
The accepted {format} depends on your system, thus this is not portable! See the manual page of the C function strftime() for the format. The maximum length of the result is 80 characters.
%Yや%dを日付に置き換えているのはVimではなく、OSに入っているCライブラリのstrftime()です。Vimは書式文字列をそのまま渡し、返ってきたものを返しているだけになります。使える指定子がOSごとに違うのはこのためで、同じVimでもmacOS・Linux・Windowsで結果が変わり得ます。手元で通った指定子が次のものです。
| 指定子 | 出力 | 補足 |
|---|---|---|
%F | 2026-09-03 | %Y-%m-%dと同じ |
%T | 16:42:28 | %H:%M:%Sと同じ |
%R | 16:42 | %H:%Mと同じ |
%s | 1788421348 | 1970年からの通算秒 |
%e | 「 3」 | 日。1桁のときは空白で詰める |
%-d | 3 | 日。0も空白も詰めない |
%j | 246 | 年内の通算日 |
%V | 36 | ISO 8601の週番号 |
%z | +0900 | UTCとの差 |
%Z | JST | タイムゾーンの略称 |
このうち%-dのようにハイフンを挟む書き方は、C標準ではなくライブラリ側の拡張です。macOSでは通りましたが、通らない環境がある前提で使うほうが安全になります。桁を揃えたいだけなら%dのままにしておくと、どこでも同じ結果が得られます。
対応していない指定子を渡してもエラーにはならず、そのまま素通りします。手元でstrftime('%N')を評価すると、返ってきたのはNという1文字でした。%だけが消えて後ろの文字が残るので、打ち間違えても止まってくれません。出力に日付らしくない文字が混ざっていたら、まず綴りを疑うと早く片づきます。
引用した一文には「結果は最大80文字」という記述もありますが、手元ではこれに当たりませんでした。%Yと空白を30回並べた書式を評価すると150文字がそのまま返り、Vim 9.1.1752とNeovim 0.12.4で同じ結果になりました。ここもCライブラリ側の都合が出る部分なので、環境を選べない場所で長い書式を使うならlen()で長さを見ておくと確実です。
そのまま使える書式のテンプレート
実際に打つ書式は数種類に落ち着きます。用途ごとに、手元で評価した結果を並べます。
| 用途 | 書式 | 出力 |
|---|---|---|
| 日付だけ | %F | 2026-09-03 |
| ISO 8601 | %FT%T%z | 2026-09-03T16:45:50+0900 |
| 日本語の年月日 | %Y年%-m月%-d日(%a) | 2026年9月3日(木) |
| Markdownの見出し | ## %F (%a) | ## 2026-09-03 (木) |
| ファイル名・ログ名 | %Y%m%d-%H%M%S | 20260903-164550 |
ファイル名向けの書式で区切りをハイフンだけにしているのは、:や/がパスの区切りとして解釈されるためです。%Y%m%d-%H%M%Sのように大きい単位から並べると辞書順と時系列が一致するので、lsで並べればそのまま古い順になります。
機械に読ませる前提でISO 8601を書くときは、末尾の%zを落とさないようにします。2026-09-03T16:45:50だけでは、その時刻が日本時間なのかUTCなのかが受け取った側に伝わりません。
日付を毎回手で打っていたころ、月が替わった直後の議事録を3本続けて前月の日付で作ったことがあります。見出しもファイル名も間違っていたので、後から日付で検索しても引っかからず、探すのに時間を取られました。マッピングに逃がしてからは打ち間違いが消え、日付だけの版と時刻まで入る版を別のキーに割り当ててからは、どちらを使うか考える手間もなくなりました。いまはメモを開いて最初に押すキーがこれになっています。
曜日名と時刻は環境で変わる
曜日を表す%aや%A、月名の%Bは、Cライブラリが見ているロケールで変わります。LANGがja_JP.UTF-8の手元では、何も設定しない状態で%aを評価すると木が返り、英語のThuにはなりません。Vimの中だけで切り替えるには:language timeを使います。
:language time C " 英語表記にする
:echo strftime('%A') " Thursday
:language time ja_JP.UTF-8 " 日本語表記に戻す
| ロケール | %A | %B |
|---|---|---|
指定なし(LANGのまま) | 木曜日 | 9月 |
C | Thursday | September |
ja_JP.UTF-8 | 木曜日 | 9月 |
en_US.UTF-8 | Thursday | September |
de_DE.UTF-8 | Donnerstag | September |
自分のPCでは日本語で出るのに、サーバーへ入って同じマッピングを使うと英語になる、という食い違いはここから来ています。機械が読む形で残したいなら、ロケールに依存しない%Fだけで組み立てるのが確実です。曜日を入れたいときは、設定ファイルで:language timeを明示しておくと環境をまたいでも揃います。
時刻のほうはタイムゾーンが効きます。$TZはVimの中から書き換えられて、書き換えた直後の呼び出しから結果が変わります。同じ瞬間に3つのタイムゾーンで評価すると、次のようになりました。
:let $TZ = 'UTC'
:echo strftime('%Y-%m-%d %H:%M %Z') " 2026-09-03 07:49 UTC
:let $TZ = 'America/New_York'
:echo strftime('%Y-%m-%d %H:%M %Z') " 2026-09-03 03:49 EDT
:let $TZ = 'Asia/Tokyo'
:echo strftime('%Y-%m-%d %H:%M %Z') " 2026-09-03 16:49 JST
変わるのは見え方だけで、指している瞬間は動いていません。この3回の切り替えを挟んでlocaltime()を呼び比べたところ、値は1788421758のまま一致していました。返しているのが1970年からの通算秒で、この数値にタイムゾーンという概念が入り込む余地がないためです。共有するログはUTC、手元のメモは現地時間、という使い分けが同じ関数のままできます。
いま以外の時刻を入れる
strftime()は2つ目の引数に時刻を秒で受け取ります。localtime()が現在時刻を秒で返すので、そこへ足し引きすれば任意の日付になります。手元のVim 9.1.1752で86400(1日ぶんの秒数)を足すと、翌日の日付が返りました。
:put =strftime('%Y/%m/%d', localtime() + 86400) " 明日
:put =strftime('%Y/%m/%d', localtime() - 86400) " 昨日
:put =strftime('%Y/%m', localtime() - 86400 * 30) " 30日前の年月
週次の議事録に次回の日付を入れる、締め切りを1週間後で書く、といった用途に向きます。ただし秒を数えているだけなので、月をまたぐ計算はずれます。30日前が先月とは限りませんし、夏時間のある地域では1日が86,400秒にならない日が年に2回あります。
秒で渡せるということは、ファイルの更新時刻も同じ書式で出せます。getftime()にパスを渡すと最終更新時刻が秒で返るので、そのまま第2引数に置けます。開いているファイル自身ならexpand('%')と組み合わせます。
:put =strftime('%F %T', getftime(expand('%'))) " このファイルの最終更新時刻
逆向きの変換にはstrptime()があります。書式と文字列を渡すと秒に直す関数で、手元ではstrptime('%Y-%m-%d %H:%M:%S', '2026-09-03 12:00:00')が1788404400を返しました。Vim 9.1.1752とNeovim 0.12.4のどちらにも実装されています。解釈できない文字列を渡してもエラーにはならず0が返るので、成否は戻り値で判定します。書式を別の書式へ変換したいときは、この2つを直列につなぎます。
:echo strftime('%Y年%-m月%-d日', strptime('%Y-%m-%d', '2026-01-05'))
" 2026年1月5日
マッピングとコマンド、保存時の自動更新
毎回打つのは手間なので、設定ファイルへマッピングしておきます。ノーマルモード用とインサートモード用を別のキーに割り当てると、書いている途中でも手を止めずに呼び出せます。
nnoremap <leader>dt :put =strftime('%Y/%m/%d')<CR>
inoremap <C-g>t <C-r>=strftime('%Y/%m/%d')<CR>
<leader>が実際にどのキーになるかはmapleaderの設定次第です。割り当ての決め方は<Leader>キーとmapleaderの設定で扱っています。名前で呼び出したいなら、ユーザー定義コマンドにする手もあります。
command! Today put =strftime('%Y/%m/%d')
command! Now put =strftime('%Y/%m/%d %H:%M:%S')
ユーザー定義コマンドの名前は大文字で始める決まりがあり、:Todまで打ってTabを押せば補完されます。マッピングと違って名前が残るので、しばらく使わなくても思い出せます。定義の細かい規則はユーザー定義コマンドの作り方にまとめています。
挿入した日付を手で直し忘れるのが嫌なら、保存時に書き換えさせます。ファイルの先頭数行に決まった書式の行を置いておき、BufWritePreでその行だけを置換する形が定番です。ただし、よく見かける次の書き方には落とし穴が2つあります。
autocmd BufWritePre *.md
\ silent! execute '1,5s/^updated: .*/updated: '
\ . strftime('%Y-%m-%d') . '/e'
1つ目はカーソルが飛ぶことです。20行のファイルの末尾で:wを押したところ、保存後のカーソルは置換した3行目へ移っていました。長い文書を書きながら何度も保存すると、そのたびに先頭へ引き戻されます。2つ目は、5行に満たないファイルで何も起きないことです。2行のファイルで試すと1,5が無効な範囲になり、silent!がそのエラーを握りつぶすため、updated:の行は古い日付のまま保存されました。
どちらも関数に切り出せば片づきます。winsaveview()で表示位置を控えてwinrestview()で戻し、範囲の終わりはline('$')と比べて小さいほうを採ります。そもそも先頭の数行だけを対象にしているのは、本文中に同じ書式の行があっても巻き込まないためです。
function! s:UpdateStamp() abort
let l:view = winsaveview()
let l:last = min([5, line('$')])
silent execute '1,' . l:last . 's/^updated: .*/updated: '
\ . strftime('%Y-%m-%d') . '/e'
call winrestview(l:view)
endfunction
autocmd BufWritePre *.md call s:UpdateStamp()
この形で同じ2つのファイルを保存し直すと、20行のファイルではカーソルが20行目に留まり、2行のファイルでもupdated:が当日の日付へ書き換わりました。末尾のeフラグは対象が1つも見つからないときにエラーを出さないためのもので、updated:の行を持たない3行のファイルを保存しても内容は変わらず、警告も出ませんでした。
まとめ
日付の挿入そのものはstrftime()ひとつで足ります。押さえておきたいのは、返ってくる文字列を決めているのがVimの外側だという点です。使える指定子はOSのCライブラリ、曜日名と月名はロケール、時刻はタイムゾーンが握っています。環境をまたいで同じ結果が欲しいなら%Fのように数字だけで組み立て、人が読むだけの場面に限って%aや%Bを混ぜる、という線を引いておくと後で困りません。