Epoch converter

Convert a Unix timestamp in seconds or milliseconds to a readable UTC and local date. You can also turn a date and time into an epoch timestamp, with the source time zone kept explicit.

Loading the converter...

Is this timestamp seconds or milliseconds?

Unix time counts from January 1, 1970 at 00:00 UTC. A seconds value and a milliseconds value can describe the same instant: 1 second equals 1,000 milliseconds. Current timestamps commonly have 10 digits in seconds or 13 in milliseconds, but the length changes for dates farther from today. Use the unit selector to tell the epoch converter which value you have.

For example, 1700000000 seconds and 1700000000000 milliseconds both mean November 14, 2023 at 22:13:20 UTC. A negative timestamp is before the Unix epoch. Seconds can include up to three decimal places. Milliseconds use whole numbers.

One instant, different local dates

A Unix timestamp has no local time zone. Changing the display zone changes the clock and possibly the calendar date, while the timestamp stays the same. Read the local date and UTC offset together before copying a log time, deadline or event.

The ISO 8601 result ends in Z, meaning UTC. Copy seconds, milliseconds or ISO separately to match the system you are using. These calculations run in your browser and use its clock and time-zone rules.

Convert a date to a Unix timestamp

Switch to Date to timestamp, enter the local date and time, and select the zone where that clock time applies. You can include seconds and milliseconds. A repeated hour during a clock change needs an earlier or later choice. A skipped time needs a different local time.

For a meeting between two zones, use the time zone converter. For the name of the zone at your physical location, use my time zone.

Build an epoch converter

Convert an explicitly labeled Unix seconds or milliseconds value to an ISO instant locally. Send that ISO instant to Time for its UTC and destination clocks. For the reverse direction, Time converts a written local date from its source zone to UTC. Reject skipped or repeated times until the visitor chooses.

  1. Read the timestamp unit explicitly. Validate numeric precision and calendar range before multiplying or dividing by 1,000.
  2. Convert the timestamp to ISO 8601 locally, then ask Time for the same instant in the selected zone.
  3. For date to timestamp, send the source zone and local date/time with to=UTC and disambiguation=reject. Ask for an earlier/later occurrence when needed.
  4. Keep UTC/local dates, offsets and separately labeled seconds/milliseconds together. The pure browser helper below supports fractional seconds, milliseconds and explicit DST choices.

Installation steps are included in each example. Set PARSEAPI_KEY on your server. Swift and Kotlin use an app key restricted to your app ID.

// npm install @parseapi/sdk@1.14.0
import { parseAPI } from '@parseapi/sdk';

// Set PARSEAPI_KEY on your server.
const parse = parseAPI();

const raw = '0', unit = 'seconds'; // Choose 'milliseconds' for a millisecond input.
if (!['seconds', 'milliseconds'].includes(unit) || !/^-?\d+$/.test(raw)) throw new Error('Enter an integer timestamp and its unit.');
const milliseconds = Number(raw) * (unit === 'seconds' ? 1000 : 1);
if (!Number.isSafeInteger(milliseconds) || milliseconds < -62135596800000 || milliseconds > 253402300799999) throw new Error('Use calendar years 1-9999.');
const iso = new Date(milliseconds).toISOString();
const row = await parse.time('UTC', { at: iso, to: 'America/New_York' });
if (!row.to?.at) throw new Error('Conversion unavailable.');
// For a repeated time, ask the visitor, then change reject to earlier or later.
const reverse = await parse.time('America/New_York', {
  at: '2026-11-16T09:00:00', to: 'UTC', disambiguation: 'reject',
});
console.log({ seconds: milliseconds / 1000, milliseconds, iso, utc: row.at,
  timezone: row.to.timezone, local: row.to.at, offset: row.to.offset,
  reverseUnix: reverse.unix });

Browser timestamp and DST arithmetic

epoch-browser.js
// Pure browser arithmetic. Time API calls take the resulting ISO instant.
/** Calendar helpers for the shared timezone tool. The API converts the selected instant. */
function isToolDate(value) {
    if (!/^\d{4}-\d{2}-\d{2}$/.test(value) || value.startsWith('0000'))
        return false;
    const date = new Date(`${value}T00:00:00Z`);
    return Number.isFinite(date.getTime()) && date.toISOString().slice(0, 10) === value;
}
function isToolTime(value) {
    return /^(?:[01]\d|2[0-3]):[0-5]\d$/.test(value);
}
function isToolTimezone(value) {
    if (!value || value.length > 64)
        return false;
    try {
        new Intl.DateTimeFormat('en-US', { timeZone: value });
        return true;
    }
    catch {
        return false;
    }
}
function wallFormatter(zone) {
    return new Intl.DateTimeFormat('en-CA', {
        timeZone: zone,
        year: 'numeric', month: '2-digit', day: '2-digit',
        hour: '2-digit', minute: '2-digit', second: '2-digit', hourCycle: 'h23',
    });
}
function wallParts(formatter, instant) {
    const parts = Object.fromEntries(formatter.formatToParts(instant).map((p) => [p.type, p.value]));
    return {
        date: `${parts.year.padStart(4, '0')}-${parts.month}-${parts.day}`,
        time: `${parts.hour}:${parts.minute}:${parts.second}`,
    };
}
/**
 * Find exact mappings, never normalize a missing hour into a different time.
 * Sample either side of the date to collect the offsets around an IANA change,
 * then round-trip each candidate. This also handles half-hour DST changes and
 * a whole skipped date. The user chooses when two mappings are possible.
 */
function resolveToolWallTime(date, time, zone) {
    if (!isToolDate(date) || !isToolTime(time) || !isToolTimezone(zone))
        return { kind: 'invalid', instants: [] };
    const wall = Date.parse(`${date}T${time}:00Z`);
    const formatter = wallFormatter(zone);
    const offsets = new Set();
    for (let hours = -48; hours <= 48; hours += 6) {
        const sample = wall + hours * 3600000;
        const seen = wallParts(formatter, sample);
        offsets.add(Date.parse(`${seen.date}T${seen.time}Z`) - sample);
    }
    const instants = [...offsets]
        .map((offset) => wall - offset)
        .filter((instant) => {
        if (!Number.isFinite(instant))
            return false;
        const seen = wallParts(formatter, instant);
        return seen.date === date && seen.time === `${time}:00`;
    })
        .sort((a, b) => a - b);
    if (!instants.length)
        return { kind: 'gap', instants: [] };
    return { kind: instants.length === 1 ? 'unique' : 'repeated', instants };
}

/** One instant, with the date and offset kept beside the local clock. */
function formatToolClock(instant, timezone, locale = 'en-US') {
    const parts = Object.fromEntries(new Intl.DateTimeFormat('en-US', {
        timeZone: timezone, year: 'numeric', month: '2-digit', day: '2-digit', era: 'short',
        hour: '2-digit', minute: '2-digit', second: '2-digit', hourCycle: 'h23',
    }).formatToParts(instant).map(part => [part.type, part.value]));
    if (parts.era !== 'AD' || Number(parts.year) > 9999)
        throw new RangeError('The local date is outside years 1-9999.');
    const isoDate = `${parts.year.padStart(4, '0')}-${parts.month}-${parts.day}`;
    const wallTime = `${parts.hour}:${parts.minute}:${parts.second}`;
    const date = new Intl.DateTimeFormat(locale, { timeZone: timezone, weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' }).format(instant);
    const time = new Intl.DateTimeFormat(locale, { timeZone: timezone, hour: 'numeric', minute: '2-digit', second: '2-digit' }).format(instant);
    const zoneName = new Intl.DateTimeFormat(locale, { timeZone: timezone, timeZoneName: 'long' }).formatToParts(instant).find(part => part.type === 'timeZoneName')?.value ?? timezone;
    const offset = new Intl.DateTimeFormat('en-US', { timeZone: timezone, timeZoneName: 'longOffset' }).formatToParts(instant).find(part => part.type === 'timeZoneName')?.value.replace(/^GMT/, 'UTC') ?? 'UTC';
    return { date, time, zoneName, offset, isoDate, wallTime, summary: `${date}, ${time}, ${zoneName} (${timezone}, ${offset})` };
}

const MIN_INSTANT = Date.parse('0001-01-01T00:00:00Z');
const MAX_INSTANT = Date.parse('9999-12-31T23:59:59.999Z');
/** Explicit units, exact millisecond precision, and no exponent/hex coercion. */
function parseToolEpoch(raw, unit) {
    const value = raw.trim();
    if (!value)
        return { error: 'Enter a Unix timestamp.' };
    if (!(unit === 'seconds' ? /^-?\d+(?:\.\d{1,3})?$/ : /^-?\d+$/).test(value)) {
        return { error: unit === 'seconds' ? 'Enter seconds as a number with up to three decimal places.' : 'Enter milliseconds as a whole number.' };
    }
    const number = Number(value);
    const instant = unit === 'seconds' ? Math.round(number * 1000) : number;
    if (!Number.isSafeInteger(instant) || instant < MIN_INSTANT || instant > MAX_INSTANT)
        return { error: 'That timestamp is outside the supported calendar years 1-9999. Check whether the value is seconds or milliseconds.' };
    return { instant };
}
function toolEpochFormats(instant) {
    return { seconds: String(instant / 1000), milliseconds: String(instant), iso: new Date(instant).toISOString() };
}
/** Reuse the existing explicit DST choice, retaining entered seconds and milliseconds. */
function resolveEpochWallTime(date, time, timezone) {
    const match = time.match(/^((?:[01]\d|2[0-3]):[0-5]\d)(?::([0-5]\d)(?:\.(\d{1,3}))?)?$/);
    if (!match)
        return { kind: 'invalid', instants: [] };
    const base = resolveToolWallTime(date, match[1], timezone);
    if (base.kind === 'invalid' || base.kind === 'gap')
        return base;
    const seconds = Number(match[2] ?? 0);
    const milliseconds = Number((match[3] ?? '').padEnd(3, '0'));
    const candidates = base.instants.map(value => value + seconds * 1000 + milliseconds);
    const instants = candidates.filter(value => {
        if (value < MIN_INSTANT || value > MAX_INSTANT)
            return false;
        try {
            const clock = formatToolClock(value, timezone);
            return clock.isoDate === date && clock.wallTime === `${match[1]}:${String(seconds).padStart(2, '0')}`;
        }
        catch {
            return false;
        }
    });
    return instants.length ? { kind: instants.length > 1 ? 'repeated' : 'unique', instants } : { kind: candidates.some(value => value < MIN_INSTANT || value > MAX_INSTANT) ? 'range' : 'gap', instants: [] };
}

Time API documentationTime zone converter

What is Unix epoch time?

Unix time counts seconds from January 1, 1970 at 00:00 UTC. Zero is that instant. A negative value is earlier. A milliseconds timestamp counts thousandths of a second from the same starting point.

Is my timestamp in seconds or milliseconds?

Check the system that produced it and select the matching unit. Current timestamps commonly have 10 digits in seconds or 13 in milliseconds, but digit length is only a clue. This converter shows the selected interpretation and lets you correct it.

Does changing the time zone change the Unix timestamp?

No. The timestamp identifies one instant. Another zone can display a different clock time and even a different calendar date for that same instant.

How do I convert a date to a Unix timestamp?

Select Date to timestamp, enter the local date and time, then select the zone where that time applies. The results include Unix seconds, milliseconds and ISO 8601 in UTC. You can include seconds and up to three fractional digits.

Why can the same local time have two timestamps?

When clocks move backward, a local time can occur twice with different UTC offsets. Choose the earlier or later occurrence to get the intended timestamp. A time skipped when clocks move forward needs a different local time.

What does the Z in an ISO timestamp mean?

Z means UTC. For example, 2023-11-14T22:13:20.000Z is a complete UTC instant. It is the same instant as 1700000000 Unix seconds or 1700000000000 milliseconds.

What precision and date range are supported?

Seconds can have up to three decimal places. Milliseconds must be whole numbers. The form supports calendar years 1-9999 and rejects out-of-range values. Calculations use your browser's time-zone rules and do not count leap seconds as extra Unix seconds.