I have a project that uses dates a lot, and I want to print the dates in YYYY-MM-DD format, e.g. 2020-06-01 (not 2020-6-1). I’m also using Real Date and Time by Ron Newcomb, so the year, month, and day are separate values. My code is starting to be full of lines like:

say "[player's year]-[if player's numerical month is less than 10]0[end if][player's numerical month]-[if player's day is less than 10]0[end if][player's day]"

I could define a new say phrase, but it’s not always the current date being printed, so there would have to be one such phrase for every set of variables that might be used. This seems awfully cumbersome. Is there a better way to do it?

To say (year - number) - (month - number) - (day - number) with leading zeroes:
say "[year]-[if month is less than 10]0[end if][month]-[if day is less than 10]0[end if][day]".
When play begins:
say "It is now [player's year - player's numerical month - player's day with leading zeroes]."

You can also specify a multipart unit, which by default will have leading zeroes. See Writing with Inform §15.14-15:

Include Real Date and Time by Ron Newcomb.
A ymd-date is a kind of value. 2000-12-31 specifies a ymd-date with parts date-year and date-month and date-day. [Prefixing with "date" to avoid conflict with the "month" kind in Ron's extension.]
Table of Numerical Month Equivalents
input numerical equivalent
January 1
February 2
March 3
April 4
May 5
June 6
July 7
August 8
September 9
October 10
November 11
December 12
To decide which number is the numerical equivalent of (brumaire - a month):
choose a row with an input of brumaire in the table of numerical month equivalents;
decide on the numerical equivalent entry.
Lab is a room.
When play begins:
let fake player-month be April;
let fake player-year be 2025;
let fake player-day be 1;
say "Current date is [ymd-date with date-year part fake player-year date-month part numerical equivalent of fake player-month date-day part fake player-day]."

The substitution is a bit of a mess to read, because the syntax for specifying multipart units is like “date-year part 2020 date-month part 5 date-day part 12” and they all get munched together, but it does seem to work. It might be better to wrap it up in a say phrase though. (I didn’t wind up using the real date because it wasn’t grabbing the player’s month correctly, but if you’ve got that working then this should work.)

Anyway, this is probably more work than Juhana’s solution, but leading zeroes can be automated in this way.

Another way to mess with dates, if you have some special long-living dates to manipulate, you can treat them as objects, which then lets you attach some fancy behaviors to them:

Include Real Date and Time by Ron Newcomb.
A date is a kind of object. A date has a number called year. A date has a month. A date has a number called day.
The Ides of March is a date. The year is 2046. The month of the Ides is March. The day is 15.
To say (d - date): say "[year of d]-[month of d]-[if day of d < 10]0[end if][day of d]";
Dusty Storeroom is a room. "Scrawled in dust on the floor are the words, 'Beware, beware [Ides of March]'."

(This is not quite as you asked without some extra work, because it’s using the name of the month, as that’s how the extension defines things. But you can store it as a number instead and perform a conversion as needed, similar to the above answers.)

(Also I forgot multi-part values were a thing, which is probably a cleaner solution if you don’t need the extra power that comes with objects, especially as that power does come with a price tag.)

Or as a more general solution for printing zero-prefixed numbers not necessarily related to dates, you can have:

To say (n - number) padded to (z - number) digits:
repeat with i running from 1 to z - 1:
let lim be (10 to the power i) to the nearest whole number;
if n is less than lim, say "0";
say n.
Dusty Storeroom is a room. "Scrawled in dust on the floor is the numbers '[42 padded to 3 digits]', '[42 padded to 4 digits]', and '[1234 padded to 5 digits]'."

(This requires Glulx, but can be simplified to multiple separate rules if there’s only a small number of digit lengths you’re looking for.)

Note that this only requires Glulx because the “to the power” phrase uses floating-point opcodes. Since in this case you only need integers, you can avoid using floating-point and it’ll work on the Z-machine too:

To decide what number is (X - a number) to the integer power (Y - a number):
let Z be one;
repeat with K running from 1 to Y:
let Z be Z times X;
decide on Z.

Though on the Z-machine all integers are sixteen-bit signed, so you know you’ll never need more than five digits (since the largest possible value is 32767). Which means you can just hardcode the checks too:

To say (N - a number) padded to (K - a number) places:
if N is less than 10000 and K is at least five: say "0";
if N is less than 1000 and K is at least four: say "0";
if N is less than 100 and K is at least three: say "0";
if N is less than 10 and K is at least two: say "0";
say N.

…though I suppose you should probably check for negative numbers too, for completeness.

To say (N - a number) padded to (K - a number) places with optional minus sign:
if N is less than zero:
let K be K minus one;
let N be zero minus N;
say "-[N padded to K places]";
otherwise:
say N padded to K places.

Modify as needed if you don’t want the minus sign to take the place of a digit, or if you want a plus sign on positive numbers, or whatever.

To everyone who responded, thanks for the suggestions! For the record, the solution I’m going with for now is the simple one:

To say (n - a number) with two digits:
if n is less than 10, say "0";
say n.
Say "The date is [player's year]-[player's numerical month with two digits]-[player's day with two digits]."

But I’ll keep the other possibilities in mind if I have to deal with more complex left-pad issues later

(Edit: My apologies for bumping this thread. I made an inconsequential edit just to remove the spurious syntax highlighting that Discourse was adding at one point, and didn’t realize the thread would go to the top.)