I’ve known that it is possible to create snippets in Visual Studio Code. Recently, I discovered a few tweaks to help me when taking notes. Here, I share a snippet for inserting the date and time in the format of my choosing in a Markdown note.

Workspace Configuration

By default, hitting Control ^+Space opens a snippet picker in VS Code. The snippets will be those relevant to the currently active document type.

The first thing I changed was the setting to use Tab to convert a snippet prefix into the full prefix and skip the snippet window altogether. I wanted this to apply to only my notes folder, which I open as a workspace, so in the notes folder’s configuration .vscode/settings.json:

{
    "editor.tabCompletion": true,
}

Snippet To Insert Time and Date

For the snippet itself, I wanted now followed by Tab to be replaced with the current date and time in the format 3:33pm 7 Jul 22. That’s just the way I’ve been typing dates and times.

Variables

The good thing is, since version 1.2 (January 2018), VS Code provides variables with the date and time. Bad thing is, there isn’t a way to flexibly format them. Crazy thing is, I worked around it!

From the latest documentation, I could use these variables in the snippet using ${variable} or $variable syntax:

Variable Value Example
$CURRENT_HOUR 24-hour format current hour 15
$CURRENT_MINUTE Two-digit current minute 33
$CURRENT_DATE Two-digit day 07
$CURRENT_MONTH_NAME_SHORT Three letter month Jul
$CURRENT_YEAR_SHORT Two-digit year 22

But:

  • I want to strip leading zeros from the day, and
  • I want to change a 24-hour time into 12-hour with am/pm suffix

Snippet JSON

Here’s the snippet code. I save the snippet as .vscode/md.code-snippets in my workspace folder, which limits the snippet to only markdown files (specified by the scope below) in workspace folders.

{
    "Now": {
        "prefix": "now",
        "description": "Insert a short time and date",
        "scope": "markdown",
        "body": [
            "${CURRENT_HOUR/(13)|(14)|(15)|(16)|(17)|(18)|(19)|(20)|(21)|(22)|(23)|0(.)/${1:+1}${2:+2}${3:+3}${4:+4}${5:+5}${6:+6}${7:+7}${8:+8}${9:+9}${10:+10}${11:+11}$12/}:$CURRENT_MINUTE${CURRENT_HOUR/(0.|10|11)/${1:?am:pm}/} ${CURRENT_DATE/0(.)/$1/} $CURRENT_MONTH_NAME_SHORT $CURRENT_YEAR_SHORT",
        ]
    }
}

Feel free to change the prefix now to @now or something more unique if you wish.

Break Down

Variable output can be altered by way of a Regular Expression (regex) with capture groups (in brackets) and then performing a substitution (using numbered captured groups). Regexs are in the format $variable/regex/substitution/options - I used no options, so there is nothing after the last /.

For clarity, let me break down the body above (which is all in one line, and the empty line is a space character):

  • I’ll explain the third regex block first, $CURRENT_DATE,
  • Then explain $CURRENT_HOUR which is where the complexity lies,
  • And finally explain $CURRENT MINUTE.
${
  CURRENT_HOUR
  /
  (13)|(14)|(15)|(16)|(17)|(18)|(19)|(20)|(21)|(22)|(23)|0(.)
  /
  ${1:+1}${2:+2}${3:+3}${4:+4}${5:+5}${6:+6}${7:+7}${8:+8}${9:+9}${10:+10}${11:+11}$12
  /
}
:
$CURRENT_MINUTE
${
  CURRENT_HOUR
  /
  (0.|10|11)
  /
  ${1:?am:pm}
  /
}

${
  CURRENT_DATE
  /
  0(.)
  /
  $1
  /
}

$CURRENT_MONTH_NAME_SHORT

$CURRENT_YEAR_SHORT

Strip leading zero from $CURRENT_DATE

Easy enough: the regular expression 0(.) with match a zero followed by any character and the substitution $1 is first match itself in brackets. I could’ve been more explicit with 0([1-9]), but why bother.

Convert $CURRENT_HOUR to 12-hour time

Since I can’t use a regex for math, what is needed is multiple capture groups and many, many substitutions.

This is hard, took me a while... because the documentation only offers a EBNF Grammar without any explanation (screenshot below)! This unrelated snippet on Stack Overflow and lots of testing helped me to understand.

Visual Studio Code Snippet Documentation in Extended Backus-Naur form

Looking at the three lines after the /pascalcase conversion, I think I worked it out as follows:

  • ${1:+a} = if there is a match, replace with "a"
  • ${1:?a:b} = if there is a match, replace with "a", but if there is no match, output "b" as the “default value”
  • ${1:b} or ${1:-b} = if there is no match, output "b" as the “default value”

Remember that this is a replacement, i.e. if it does not match, then nothing is replaced, and the rest of the string remains as-is. So: check for match in each (13)|(14)|..., and if the first group matches, then replace it with ${1:+1} (i.e. 13 will be come 1), but if the second group matches, replace with ${2:+2}, and so on.

The last group |0(.) simply matches hours with a leading zero, and just like for current date above, strips the first 0.

Actually, this format is the same as Bash’s shell parameter expansion. So the easiest way to think about it is that $1: is just a test “if parameter is unset or null,” rather than if there is a match...

Add am/pm prefix to CURRENT_HOUR

Using the if-then-else syntax (second above), (0.|10|11) will match the morning hours from 00 to 11 and, and the substitution ${1:?am:pm} will replace the match with am or the entire output will be pm. Honestly, many expressions work here, (0.|1[01]) or (0[0-9]|1[0-2]) but maybe simple is faster.

I did wonder if it is possible to combine this with the previous and reduce an expensive regex, but that’s for someone smarter than me...

Conclusion

I always over-complicate things, don’t I? Good luck of figuring out your own complex use case!

14 Jul 22: Whoops missed out one case, for when the hour starts with 0.