The Random Date Generator produces up to 1,000 uniformly sampled calendar dates from an inclusive YYYY-MM-DD range entirely in your browser, formatted as plain date strings you can paste straight into a PostgreSQL INSERT, COPY FROM, or generate_series call. Sampling is done on integer UTC day ordinals, so a leap day such as 2024-02-29 is as eligible as the days around it, and no calendar date is ever skipped or duplicated because of a local daylight-saving transition. The output is a pure date with no time and no timezone offset, which is exactly the shape a Postgres DATE column expects, and the inclusive boundaries mean a request for 2024-02-28 through 2024-03-01 is allowed to return February 28, February 29, or March 1 with no off-by-one surprise. The randomness comes from the browser Web Crypto getRandomValues API with rejection sampling rather than a modulo shortcut, so each date has the same number of underlying 32-bit source values and equal selection probability. Duplicate mode draws independently; unique mode runs a sparse partial Fisher-Yates selection without replacement and refuses any count larger than the inclusive range size instead of silently truncating.

generate random date in postgresql
Generate Random Dates for PostgreSQL the Easy Way

Why PostgreSQL Random Date Queries Drift Around Daylight Saving

The recipes most teams reach for first subtract a random fraction of a day count from a known timestamp:

SELECT current_date - (random() * 365)::int;

That pattern works well for DATE arithmetic because the right-hand side is already an integer number of days and Postgres does the calendar math for you. The trouble starts when the column is TIMESTAMP or TIMESTAMPTZ and the recipe is rewritten as now() - interval '1 day' * (random() * 30) or as date '2024-01-01' + (random() * 30) * interval '1 day'. Adding a whole number of intervals to a timestamp with time zone can land on a wall-clock hour that is not the one you started at, because Postgres stores the instant and renders it in the session's zone. Across a spring-forward or fall-back Sunday, a loop that adds 24 hours step by step can produce two timestamps on the same local date or skip a date entirely, which is exactly the bug a tester is trying to expose and the last one they want their fixture to contain.

Another common shortcut uses to_timestamp(random() * range). That returns a value with microsecond precision and a session-dependent timezone rendering, so the displayed date in psql may not match the date in an application reading the same column from a client in another zone. A safer pattern in pure SQL is to compute on integers and let Postgres build the date:

SELECT date '2024-01-01' + (random() * 366)::int;

That avoids the interval machinery, but it still relies on the database's internal pseudo-random number generator, which is not exposed as a seedable, auditable source and which can be hard to reproduce across environments. When the requirement is a stable list of uniformly sampled dates you can paste into a fixture file or compare across machines, generating the dates up front with a separate, well-defined tool is often faster and easier to reason about.

What the Browser-Based Random Date Generator Returns

The Random Date Generator is a single page tool that takes a start date, an end date, a count between 1 and 1,000, and a flag for whether repeats are allowed, then produces a list of dates in the same canonical YYYY-MM-DD shape Postgres uses in its DATE literal syntax. Both endpoints are eligible for selection, so a one-day range is valid and always returns that date. The list is drawn from the inclusive range with each date getting the same selection probability, and the implementation counts whole UTC days rather than adding hours, which is the source of the daylight-saving immunity the tool promises.

Calendar validation follows the Gregorian behavior implemented by the ECMAScript Date specification. Years divisible by four are normally leap years, century years are common unless divisible by 400, so 2000-02-29 and 2024-02-29 are valid input while 1900-02-29 and 2023-02-29 are rejected. Month and day overflow is also rejected: an entry such as 2025-04-31 cannot roll silently into May 1. The supported span is 0001-01-01 through 9999-12-31, and the implementation uses setUTCFullYear rather than the legacy two-digit-year interpretation in Date.UTC, so years 0001 through 0099 keep their literal meaning instead of being shifted into the twentieth century.

Input or scenarioTool behaviorReason
One-day range, any duplicates settingReturns that dateInclusive boundaries and at least one eligible day
Range crosses a daylight-saving transitionNo skipped or repeated datesSampling on UTC day ordinals, not local hour arithmetic
Unique mode, count larger than available daysReports an error, no resultSparse partial Fisher-Yates selection cannot draw more than available
Count = 0, blank, negative, or above 1,000Reports a clear error, no truncationHard cap is 1,000 dates per generation
Entry such as 2025-04-31Rejected as invalidStrict component check, no silent rollover

Generate a Random Date Range in Three Steps

  1. Pick a valid start date and end date. Both endpoints are inclusive, so the same date that appears in the start field can appear in the output. Use the strict YYYY-MM-DD shape; values such as 2024-02-30, 2025-04-31, or 2023-02-29 are rejected with a clear message rather than silently repaired. A single-day range is valid and will always produce that one date.
  2. Enter a count from 1 to 1,000 and decide on duplicates. A blank count, a fractional value, zero, a negative number, or anything above 1,000 produces an error explaining the allowed range and that nothing was truncated. Toggle duplicate mode on if the same date may appear more than once, and leave it off if you need distinct dates only.
  3. Generate the list, verify the displayed range and count, and copy the values you need. Each result is a plain YYYY-MM-DD string. Editing either endpoint, changing the count, or toggling duplicate mode clears the previous list and any prior error, so a result generated with earlier settings cannot remain on screen as if it matched the current controls.

To make the steps concrete, take a leap-year range that exercises the inclusive boundary. The range 2024-02-28 through 2024-03-01 inclusive contains exactly 3 calendar dates: 2024-02-28, 2024-02-29, and 2024-03-01. Each is eligible for selection, so with count = 3 and unique mode on, the generator must return all three in some order; with count = 1 and duplicate mode on, any of the three is a valid single result; and with count = 4 in unique mode the generator reports an error rather than silently dropping one of them.

Loading the Generated Dates Into PostgreSQL

Once a list is in hand, the easiest path into Postgres is to wrap the values in a VALUES list. With three sample dates, an INSERT looks like this:

INSERT INTO orders (placed_on) VALUES ('2024-02-28'), ('2024-02-29'), ('2024-03-01');

For larger batches, generate_series turns the list into a row source without needing individual literals:

INSERT INTO events (event_date, kind)SELECT d, 'signup'FROM generate_series(date '2024-02-28', date '2024-03-01', interval '1 day') AS d;

If the generated list is already exactly the rows wanted, COPY FROM STDIN with a tab separator is the fastest bulk path:

COPY sample_dates (the_date) FROM stdin; 2024-02-28 2024-02-29 2024-03-01 \.

Because every result is a pure date with no time and no offset, there is no timezone conversion to apply before the row reaches a DATE column. For TIMESTAMP columns, the value can be cast as the_date::timestamp or appended with T00:00:00 on the application side; the date itself does not shift when a different session timezone reads it back.

Input Limits, Leap Days, and Calendar Validation

The tool caps each generation at 1,000 dates and refuses to silently truncate, so a count of 1,500 produces an error message rather than a short list. Unique mode adds a second cap: if the requested count is larger than the number of days in the inclusive range, the tool reports an error instead of returning fewer rows or quietly switching to duplicate mode. A one-day range is valid and always returns that date, but repeated results from that same range require duplicate mode to be enabled.

Input validation is strict. A YYYY-MM-DD string is parsed by setting its year, month, and day on a UTC Date object and reading the components back, which means an entry such as 2025-04-31 is rejected because the day component does not survive the round-trip. Years from 0001 through 9999 are supported, and the implementation deliberately avoids the legacy two-digit-year behavior of Date.UTC, so a year like 0099 stays 0099 rather than being silently rewritten as 1999. Leap-day behavior matches the Gregorian rule described in the ECMAScript specification, with century years treated as common unless divisible by 400.

The randomness itself comes from the browser's Web Crypto getRandomValues API through unsigned 32-bit words, and the mapping from those words to day positions uses rejection sampling to remove the modulo bias that a raw % would introduce when the range size does not divide 2^32 evenly. The result is that every date in the inclusive range has the same number of underlying 32-bit source values, so each is equally likely to appear. The tool does not use Math.random(), and for reproducible tests the recommended approach is to keep the generated list as a fixture file rather than to seek a seedable path through the tool itself.

Common Use Cases for Random Dates in Postgres

Test fixtures are the most common reason. A checkout-flow test might want ten order dates inside a single quarter, a scheduling test might want twenty distinct weekdays, and a subscription-renewal test might want the same date repeated across hundreds of customer rows. The duplicate-mode toggle covers the first and third cases directly, and unique mode handles the second by refusing a count larger than the available days rather than silently dropping the overflow.

Sample schedules and writing prompts are the next layer. A team planning a randomized exercise needs a list of dates that look natural, are uniformly spread inside the window, and contain no obviously artificial spacing; a writer drafting a backstory can use the same list as anchors for fictional events. Both cases benefit from inclusive boundaries because the start and end date are often more memorable than a midpoint.

Demonstrations and documentation benefit too. A README or slide deck that shows SELECT * FROM orders WHERE placed_on BETWEEN ... reads more cleanly when the example rows are real dates inside a real range, and being able to regenerate the example list in one click keeps the screenshots and the SQL in sync. None of these uses carries financial, legal, contest, security, or audit weight, so the tool's "use generated dates for ordinary utility work" guidance applies; for anything with that kind of consequence, an independently audited procedure with retained evidence is the right answer.

For a broader walk-through of inclusive-range sampling and the calendar math behind it, the How to Generate Random Dates From Any Range guide covers the same tool from a more general angle and is a useful companion when the goal is dates that are not bound to a Postgres workflow.

For a deeper look, see Is a Random IP Address Generator Safe to Use Online?.