Skip to main content

The date generator

Use it when you need a date or a date-time — a birthday, a document date, an event timestamp — inside a range and printed in a specific format.

Dates run on TDC's own portable date runtime: a strict parser, a UTC calendar, and a localized formatter, with no dependency on moment.js. The same config is designed to produce the same dates in every implementation.

Example outputs below are illustrative — the exact values depend on the seed and can differ by core version. What stays fixed is the shape and the format.

At a glance

AttributeWhat it does
valuebirth, today, now, a single date, or a range START..END
rangeA range START..END — a newer spelling of the same idea
from / toThe two endpoints of a range, given separately
formatOutput format (see Formatting the output); default L
localen, es, ru, or zh-cn; inherited from <env> if omitted
oldest / youngestAge window in years for value="birth" (defaults 80 and 10)
precisionday, second, or millisecond

Use only one of value, range, or the from/to pair to describe a range — they are three spellings of the same thing. Give none of them and the range runs from 1970-01-01 to the current moment; see No bounds at all.

A random date in a range

Give value a range as START..END (both ends inclusive) and you get a random date drawn uniformly inside it.

<gen type="date" value="2020-01-01..2025-12-31" format="YYYY-MM-DD"/>
./run demo.tdc (5 rows)
2024-08-15
2022-02-04
2020-06-21
2025-10-04
2021-03-31

The endpoints are part of the window. A one-week range makes that easy to see — over enough rows, both 2024-06-01 and 2024-06-07 show up:

<gen type="date" value="2024-06-01..2024-06-07" format="YYYY-MM-DD"/>
./run demo.tdc (6 rows)
2024-06-06
2024-06-03
2024-06-01
2024-06-07
2024-06-02
2024-06-02

Input dates are parsed strictly, in one of these forms:

  • date: YYYY-MM-DD, YYYY.MM.DD, or YYYY/MM/DD;
  • date-time: YYYY-MM-DDTHH:mm, YYYY-MM-DDTHH:mm:ss, or YYYY-MM-DDTHH:mm:ss.SSS;
  • range: START..END.

Free text (June 6th, 06/06/24) is not accepted — use one of the strict forms above.

The range attribute — the same window, a newer spelling

range="START..END" means exactly what value="START..END" does. It reads better when value would otherwise look like a keyword, and it's the spelling shared with the date.range template.

<gen type="date" range="2020-01-01..2024-12-31" format="YYYY-MM-DD"/>
./run demo.tdc (5 rows)
2023-11-08
2021-09-30
2020-05-23
2024-10-19
2021-01-14

The format attribute only controls how the date is written — it never changes the window, and the window never depends on the format.

from and to — the endpoints given separately

When it's clearer to name the two ends separately, use from and to. This is the most natural spelling for date-time windows, where the range would otherwise be one long string.

<gen type="date"
from="2026-05-02T09:00:00"
to="2026-05-02T09:00:05"
format="YYYY-MM-DDTHH:mm:ss"/>
./run demo.tdc (5 rows)
2026-05-02T09:00:04
2026-05-02T09:00:01
2026-05-02T09:00:03
2026-05-02T09:00:00
2026-05-02T09:00:02

No bounds at all — the clock closes the range

A date generator given neither end still produces a date. The window runs from 1970-01-01 to the current moment, so the generator reads the clock even though nothing in the config mentions a date:

<gen type="date" format="YYYY-MM-DD"/>
./run demo.tdc — the same seed, a year apart
--now 2026-04-23    --now 2027-04-23
1972-06-01          1972-06-17
1994-11-20          1995-04-30
1972-05-06          1972-05-21

This is the easiest way to lose reproducibility by accident. Give the generator two ends, or pin the clock with --now.

One end is not a third option. from without to is an error (TDC150), and so is range="2020-01-01.." (TDC151). A range is both ends or neither.

A birthday with value="birth"

value="birth" produces a date of birth relative to the current date, bounded by an age window. youngest and oldest are ages in years (defaults 10 and 80), so youngest="18" oldest="65" gives working-age adults.

<gen type="date" value="birth" youngest="18" oldest="65" format="MM/DD/YYYY"/>
./run demo.tdc (5 rows)
07/05/1997
11/23/1985
02/14/2003
09/30/1971
06/18/1990

Why use it instead of a fixed range: the window follows "now", so the same config still produces plausible ages next year, with no dates to go back and edit. For a birthday tied to a whole synthetic person, the person.b_day template takes the same oldest/youngest/format attributes.

That is also why the dates move. The window slides forward with the clock, so the same seed gives a different birth date tomorrow — the age holds, the date does not. Where the output has to stay put — a snapshot test, a fixture, a bug report — pin the clock with --now.

today and now

value="today" is the current date; value="now" is the current date and time. Both read from the runtime clock, so they're the natural stamps for "generated on" or "as of" fields.

<gen type="date" value="today" format="LL"/>
<gen type="date" value="now" format="YYYY-MM-DDTHH:mm:ss.SSS"/>
./run demo.tdc
value="today" format="LL"                       April 23, 2026
value="now"   format="YYYY-MM-DDTHH:mm:ss.SSS"   2026-04-23T12:00:00.000

Reading the clock is the point of both, and it is what makes them non-reproducible: the seed has no say in what "today" is. Pin the clock with --now and today and now return the instant you named, in every run.

precision — the step for date-time ranges

precision sets the smallest step a range moves in: day, second, or millisecond. For a five-second window stepped by whole seconds:

<gen type="date"
from="2026-05-02T09:00:00"
to="2026-05-02T09:00:05"
precision="second"
format="YYYY-MM-DDTHH:mm:ss"/>
./run demo.tdc (5 rows)
2026-05-02T09:00:04
2026-05-02T09:00:01
2026-05-02T09:00:03
2026-05-02T09:00:00
2026-05-02T09:00:02

When precision is omitted, the default follows the range type:

Range typeDefault stepWhat precision changes
date-only (YYYY-MM-DD)one dayrarely needed — the range already steps in whole days
date-timeone millisecondprecision="second" zeros the milliseconds

Use precision="second" whenever you want clean, human-looking timestamps instead of millisecond noise; use precision="millisecond" (the date-time default) when you need sub-second resolution.

Formatting the output

format is a template of tokens. It changes only how a date is written — the underlying value is untouched. The default is L (a locale-aware short date).

Here is every token, applied to one fixed moment: Tuesday, March 5, 2024, 09:04:07. The day and the month are single-digit on purpose, so the padded and unpadded pairs differ visibly:

TokenMeansExample
YYYY4-digit year2024
YY2-digit year24
MMMMfull month nameMarch
MMMshort month nameMar
MM2-digit month03
Mmonth, no leading zero3
DD2-digit day05
Dday, no leading zero5
ddddfull weekday nameTuesday
dddshort weekday nameTue
HH2-digit hour (24h)09
Hhour, no leading zero9
mm2-digit minute04
mminute, no leading zero4
ss2-digit second07
ssecond, no leading zero7
SSSmilliseconds000
ZUTC offset with a colon+00:00
ZZUTC offset without a colon+0000
ISOISO 8601 date2024-03-05
Llocale-aware short date03/05/2024
LLlocale-aware long dateMarch 5, 2024
LLLlocale-aware long date + timeMarch 5, 2024 09:04
LLLLthe same, with the weekdayTuesday, March 5, 2024 09:04

Month and weekday names, and the four L forms, follow the locale — see below. Everything else is the same in every language.

The same date under four formats — same seed, so the date is identical on each line and only the writing differs:

<gen type="date" value="2020-01-01..2024-12-31" format="YYYY-MM-DD"/>
<gen type="date" value="2020-01-01..2024-12-31" format="DD.MM.YYYY"/>
<gen type="date" value="2020-01-01..2024-12-31" format="DD MMM YYYY"/>
<gen type="date" value="2020-01-01..2024-12-31" format="LL"/>
./run demo.tdc
YYYY-MM-DD    DD.MM.YYYY    DD MMM YYYY    LL
2023-11-08    08.11.2023    08 Nov 2023    November 8, 2023
2021-09-30    30.09.2021    30 Sep 2021    September 30, 2021
2020-05-23    23.05.2020    23 May 2020    May 23, 2020

Literal text with brackets

Anything in square brackets is copied through verbatim, so you can wrap a date in fixed text:

<gen type="date" value="2024-03-15..2024-03-15" format="[date:] YYYY-MM-DD"/>
./run demo.tdc
date: 2024-03-15

Locale-aware formats: L and LL

L, LL, LLL and LLLL follow the locale, taken from the local attribute (or local on <env>) — as do the name tokens MMMM, MMM, dddd and ddd. The default locale is en. Numeric templates like YYYY-MM-DD and DD.MM.YYYY never depend on the locale — only on the template you write.

Here is the fixed date 2024-03-15 under format="LL", first in the default English locale, then re-rendered with local="ru" to show the localized month name and long-date form — a deliberate localization demo:

<gen type="date" value="2024-03-15..2024-03-15" format="LL"/> <!-- default en -->
<gen type="date" value="2024-03-15..2024-03-15" format="LL" local="ru"/> <!-- Russian -->
./run demo.tdc
default (en)   March 15, 2024
local="ru"     15 марта 2024 г.

L shifts the same way: 03/15/2024 in en, 15.03.2024 in ru.

Gotchas, in one place

  • Input dates are parsed strictly — use YYYY-MM-DD (or . / /), not free text.
  • Both range endpoints are inclusive.
  • value, range, and from/to are three spellings of one window — use whichever reads best.
  • L / LL change with local; YYYY-MM-DD and friends do not.
  • Date-only ranges step by day; date-time ranges by millisecond unless you set precision.
  • today, now, value="birth" and a generator with no bounds read the clock, so the seed alone does not reproduce them — pin the clock with --now.
  • format applies only to dates. On template identifiers (SSN, IBAN, phone…) it is an error — shape those with interpolation filters instead.

See also

  • format, range, and local in the attribute reference.
  • Templateperson.b_day and date.range share this same date formatting.