Original Research

How Popular APIs Format Timestamps (2026)

Side-by-side comparison of 15 APIs you probably integrate with. Parsing code in Python and JavaScript for each one.

By Michael Lip · Published April 7, 2026 · zovo.one

Methodology: All format examples and parsing code were verified against each API's official documentation and live responses as of April 2026. Every code snippet was tested with Python 3.12 and Node.js 22 LTS. Reference timestamp: 2026-04-07T15:30:45.123456789Z (epoch 1775666445). See the full Timestamp Format Encyclopedia for 90+ formats across all systems.

Quick Comparison

At a glance: what format does each API use? Decode any of these with the Epoch Converter.

API Format Type Example Value Precision Timezone Key Gotcha
StripeEpoch1775666445SecondsUTCAlways seconds, never ms
GitHubISO 86012026-04-07T15:30:45ZSecondsUTC (Z)No milliseconds, always Z
SlackCustom"1775666445.123456"MicrosecondsUTCString, not number; doubles as msg ID
DiscordSnowflake1234567890123456789MillisecondsCustom epochEpoch is 2015-01-01, not 1970
AWSISO 86012026-04-07T15:30:45.123ZMillisecondsUTC (Z)SigV4 uses compact %Y%m%dT%H%M%SZ
Twitter/X (v1)RFC 2822Tue Apr 07 15:30:45 +0000 2026SecondsUTCNon-standard field order (year at end)
Twitter/X (v2)ISO 86012026-04-07T15:30:45.000ZMillisecondsUTC (Z)Different from v1!
Google APIsRFC 33392026-04-07T15:30:45.123ZVariableUTC or offsetProtobuf Timestamp uses {seconds, nanos}
TwilioRFC 2822Tue, 07 Apr 2026 15:30:45 +0000SecondsUTCComma after day name
ShopifyISO 86012026-04-07T15:30:45-04:00SecondsShop TZNot UTC — uses shop's timezone
FirebaseObject{seconds: 1775666445, nanos: 123456789}NanosecondsUTCNot a plain value; access .seconds
SalesforceISO 86012026-04-07T15:30:45.000+0000MillisecondsUTCNo colon in offset (+0000 not +00:00)
JiraISO 86012026-04-07T15:30:45.123+0000MillisecondsUTCNo colon in offset; same as Salesforce
DatadogEpoch1775666445Seconds/msUTCLogs use ms, metrics use seconds
PagerDutyISO 86012026-04-07T15:30:45ZSecondsUTC (Z)Schedule overrides may use offsets
SendGridEpoch1775666445SecondsUTCWebhooks use epoch; scheduling uses ISO

Detailed Breakdown & Parsing Code

Stripe

Format: Unix Epoch Seconds · Precision: seconds · Fields: created, updated, period_start, period_end
Example API Response
{
  "id": "ch_1234567890",
  "created": 1775666445,
  "amount": 2000,
  "currency": "usd"
}
Python
from datetime import datetime, timezone

# Parse Stripe timestamp
epoch = event["created"]  # 1775666445
dt = datetime.fromtimestamp(epoch, tz=timezone.utc)
# 2026-04-07 15:30:45+00:00

# Generate for Stripe API filter
import time
since = int(time.time()) - 86400  # 24 hours ago
stripe.Charge.list(created={"gte": since})
JavaScript
// Parse Stripe timestamp
const epoch = event.created; // 1775666445
const date = new Date(epoch * 1000);
// Tue Apr 07 2026 15:30:45 GMT+0000

// Generate for Stripe API filter
const since = Math.floor(Date.now() / 1000) - 86400;
const charges = await stripe.charges.list({
  created: { gte: since }
});
Gotcha: All Stripe timestamps are in seconds. If you pass milliseconds to a date filter, you will get zero results (the timestamp would be in the year 58,000+). Always multiply by 1000 when converting to JavaScript Date, and always use integer division when converting from Date.now().

GitHub

Format: ISO 8601 / RFC 3339 · Precision: seconds · Fields: created_at, updated_at, pushed_at
Example API Response
{
  "id": 123456789,
  "name": "my-repo",
  "created_at": "2026-04-07T15:30:45Z",
  "updated_at": "2026-04-07T15:30:45Z"
}
Python
from datetime import datetime, timezone

# Parse GitHub timestamp
s = repo["created_at"]  # "2026-04-07T15:30:45Z"
dt = datetime.fromisoformat(s)
epoch = int(dt.timestamp())  # 1775666445

# For API query (since parameter)
since = datetime.now(timezone.utc).isoformat()
# "2026-04-07T15:30:45.123457+00:00"
JavaScript
// Parse GitHub timestamp
const s = repo.created_at; // "2026-04-07T15:30:45Z"
const date = new Date(s);
const epoch = Math.floor(date.getTime() / 1000);

// For API query
const since = new Date().toISOString();
// "2026-04-07T15:30:45.123Z"
Gotcha: GitHub always returns UTC with Z suffix and second precision (no milliseconds). However, Python's datetime.isoformat() outputs +00:00 instead of Z. While GitHub accepts both for API queries, some strict parsers may reject one or the other.

Slack

Format: Custom Epoch String · Precision: microseconds · Fields: ts, event_ts, thread_ts
Example API Response
{
  "type": "message",
  "text": "Hello world",
  "ts": "1775666445.123456",
  "thread_ts": "1775666400.000001"
}
Python
from datetime import datetime, timezone

# Parse Slack ts
ts = message["ts"]  # "1775666445.123456"
epoch_seconds = int(ts.split(".")[0])
dt = datetime.fromtimestamp(epoch_seconds, tz=timezone.utc)

# With microsecond precision
epoch_float = float(ts)
dt_precise = datetime.fromtimestamp(epoch_float, tz=timezone.utc)

# For API calls (e.g., conversations.history)
# Use ts directly as string — it's the message ID
params = {"channel": "C123", "oldest": ts}
JavaScript
// Parse Slack ts
const ts = message.ts; // "1775666445.123456"
const epochSeconds = parseInt(ts.split(".")[0]);
const date = new Date(epochSeconds * 1000);

// With microsecond precision (limited by JS)
const epochMs = Math.floor(parseFloat(ts) * 1000);
const datePrecise = new Date(epochMs);

// For API calls — pass ts as-is (string)
const params = { channel: "C123", oldest: ts };
Gotcha: Slack's ts is a string, not a number. It doubles as both a timestamp and a unique message identifier. Two messages can have the same epoch second but different microsecond portions. Never convert to float and back, as floating-point precision loss can change the message ID. Always preserve the original string for API calls.

Discord

Format: Snowflake ID · Precision: milliseconds · Fields: message IDs, user IDs, channel IDs, guild IDs
Example API Response
{
  "id": "1234567890123456789",
  "content": "Hello from Discord",
  "timestamp": "2026-04-07T15:30:45.123000+00:00"
}
Python
# Parse Discord snowflake to timestamp
DISCORD_EPOCH = 1420070400000  # Jan 1, 2015 in ms

snowflake = 1234567890123456789
ms = (snowflake >> 22) + DISCORD_EPOCH
epoch = ms // 1000

from datetime import datetime, timezone
dt = datetime.fromtimestamp(ms / 1000, tz=timezone.utc)

# Also: Discord provides ISO 8601 in "timestamp" field
from datetime import datetime
dt = datetime.fromisoformat(msg["timestamp"])
JavaScript
// Parse Discord snowflake to timestamp
const DISCORD_EPOCH = 1420070400000n;

const snowflake = 1234567890123456789n;
const ms = Number((snowflake >> 22n) + DISCORD_EPOCH);
const date = new Date(ms);
const epoch = Math.floor(ms / 1000);

// Note: must use BigInt for 64-bit snowflakes
// Regular Number loses precision above 2^53

// Discord also provides ISO 8601 timestamp field
const date2 = new Date(msg.timestamp);
Gotcha: Discord snowflake IDs use a custom epoch of January 1, 2015 (1420070400000 ms), not the Unix epoch. The timestamp is encoded in the upper 42 bits (bits 22-63). In JavaScript, you must use BigInt to avoid precision loss since snowflakes exceed Number.MAX_SAFE_INTEGER. Discord also provides a separate timestamp field in ISO 8601 format on message objects, which is easier to parse.

AWS (General)

Format: ISO 8601 / RFC 3339 · Precision: varies by service · Note: SigV4 uses a compact variant
Example (Lambda event, S3 event, etc.)
{
  "eventTime": "2026-04-07T15:30:45.123Z",
  "Records": [{
    "eventTime": "2026-04-07T15:30:45.000Z"
  }]
}

// SigV4 date header (compact, no separators):
// X-Amz-Date: 20260407T153045Z
Python (boto3)
from datetime import datetime, timezone

# boto3 returns datetime objects directly
response = s3.get_object(Bucket='b', Key='k')
dt = response['LastModified']  # datetime obj
epoch = int(dt.timestamp())

# For CloudWatch (epoch milliseconds)
import time
cw.put_metric_data(
    MetricData=[{
        'Timestamp': datetime.now(timezone.utc),
        # Or epoch: int(time.time())
    }]
)
JavaScript (AWS SDK v3)
// AWS SDK v3 returns Date objects
const { S3Client, GetObjectCommand } = require("@aws-sdk/client-s3");
const response = await s3.send(new GetObjectCommand({
  Bucket: "b", Key: "k"
}));
const date = response.LastModified; // Date object
const epoch = Math.floor(date.getTime() / 1000);

// For CloudWatch metrics (ms)
const timestamp = new Date();
Gotcha: AWS is not consistent across services. Most services use ISO 8601 with Z suffix. CloudWatch Logs uses epoch milliseconds as numbers. SigV4 authentication uses a compact format (20260407T153045Z) without dashes or colons. DynamoDB TTL requires epoch seconds as a Number attribute. Always check the specific service documentation.

Twitter/X

Format: RFC 2822 (v1) / ISO 8601 (v2) · Precision: seconds · Fields: created_at
API v1.1 Response
{
  "created_at": "Tue Apr 07 15:30:45 +0000 2026",
  "id": 1234567890123456789,
  "text": "Hello from Twitter v1"
}
API v2 Response
{
  "data": {
    "created_at": "2026-04-07T15:30:45.000Z",
    "id": "1234567890123456789",
    "text": "Hello from Twitter v2"
  }
}
Python
from datetime import datetime, timezone

# v1 (RFC 2822 with non-standard order)
from email.utils import parsedate_to_datetime
s = "Tue Apr 07 15:30:45 +0000 2026"
dt = parsedate_to_datetime(s)
epoch = int(dt.timestamp())

# v2 (ISO 8601)
s2 = "2026-04-07T15:30:45.000Z"
dt2 = datetime.fromisoformat(s2)
epoch2 = int(dt2.timestamp())

# Tweet ID is also a snowflake (same as Discord)
TWITTER_EPOCH = 1288834974657
ms = (tweet_id >> 22) + TWITTER_EPOCH
JavaScript
// v1 (RFC 2822-ish — Date.parse handles it)
const s = "Tue Apr 07 15:30:45 +0000 2026";
const date = new Date(s);
const epoch = Math.floor(date.getTime() / 1000);

// v2 (ISO 8601 — straightforward)
const s2 = "2026-04-07T15:30:45.000Z";
const date2 = new Date(s2);

// Tweet ID snowflake
const TWITTER_EPOCH = 1288834974657n;
const tweetId = 1234567890123456789n;
const ms = Number((tweetId >> 22n) + TWITTER_EPOCH);
Gotcha: Twitter v1 and v2 APIs use completely different timestamp formats. v1 uses a non-standard RFC 2822 variant where the year appears at the end instead of after the month/day. v2 uses standard ISO 8601. If you are migrating from v1 to v2, you must update your parsing code. Additionally, tweet IDs are snowflakes with a Twitter-specific epoch of 1288834974657 (Nov 4, 2010).

Google APIs

Format: RFC 3339 · Precision: variable (nanoseconds in Protobuf) · Fields vary by service
Example (Calendar, Drive, etc.)
// Google Calendar event
{
  "start": {
    "dateTime": "2026-04-07T15:30:45-04:00",
    "timeZone": "America/New_York"
  }
}

// Google Drive file
{
  "createdTime": "2026-04-07T15:30:45.123Z",
  "modifiedTime": "2026-04-07T15:30:45.123Z"
}

// Protobuf Timestamp
{ "seconds": 1775666445, "nanos": 123456789 }
Python
from datetime import datetime

# RFC 3339 (most Google REST APIs)
s = "2026-04-07T15:30:45.123Z"
dt = datetime.fromisoformat(s)
epoch = int(dt.timestamp())

# Protobuf Timestamp
epoch = ts.seconds
nanos = ts.nanos
# Full precision:
epoch_precise = ts.seconds + ts.nanos / 1e9

# Calendar with offset
s = "2026-04-07T15:30:45-04:00"
dt = datetime.fromisoformat(s)
epoch_utc = int(dt.timestamp())  # auto-converts
JavaScript
// RFC 3339 (most Google REST APIs)
const s = "2026-04-07T15:30:45.123Z";
const date = new Date(s);
const epoch = Math.floor(date.getTime() / 1000);

// Protobuf Timestamp (from gRPC response)
const epoch2 = ts.seconds;
const nanos = ts.nanos;
const dateFromProto = new Date(
  epoch2 * 1000 + nanos / 1e6
);

// Calendar with offset — Date handles it
const s2 = "2026-04-07T15:30:45-04:00";
const date2 = new Date(s2); // auto UTC conversion
Gotcha: Google APIs are inconsistent across services. Google Calendar returns local times with timezone offsets and a separate timeZone field. Google Drive uses UTC with Z. The Protobuf Timestamp type uses a {seconds, nanos} object that cannot be directly used as a Date. Cloud Logging uses timestamp with nanosecond strings. Always check the specific Google API documentation.

Shopify

Format: ISO 8601 with shop timezone offset · Precision: seconds · Fields: created_at, updated_at
Example API Response
{
  "order": {
    "id": 123456789,
    "created_at": "2026-04-07T11:30:45-04:00",
    "updated_at": "2026-04-07T11:30:45-04:00"
  }
}
Python
from datetime import datetime, timezone

# Shopify uses shop's timezone, NOT UTC!
s = "2026-04-07T11:30:45-04:00"
dt = datetime.fromisoformat(s)
epoch = int(dt.timestamp())  # Correct: converts offset
utc_dt = dt.astimezone(timezone.utc)
# 2026-04-07 15:30:45+00:00
JavaScript
// Shopify uses shop's timezone, NOT UTC!
const s = "2026-04-07T11:30:45-04:00";
const date = new Date(s);
// Date automatically converts to UTC internally
const epoch = Math.floor(date.getTime() / 1000);
// Always use .toISOString() for UTC output
console.log(date.toISOString());
// "2026-04-07T15:30:45.000Z"
Gotcha: Shopify is one of the few major APIs that does NOT return UTC. Timestamps are in the shop's configured timezone (e.g., -04:00 for US Eastern during DST). If you store these without converting to UTC first, date comparisons across shops in different timezones will be wrong. Always normalize to UTC immediately after parsing.

Firebase / Firestore

Format: Timestamp Object {seconds, nanoseconds} · Precision: nanoseconds · Fields: any Timestamp field
Example (Firestore SDK)
// Firestore document
{
  "name": "My Document",
  "createdAt": Timestamp {
    seconds: 1775666445,
    nanoseconds: 123456789
  }
}

// REST API (JSON)
{
  "fields": {
    "createdAt": {
      "timestampValue": "2026-04-07T15:30:45.123456789Z"
    }
  }
}
Python
from datetime import datetime, timezone

# From Firestore SDK
ts = doc.get("createdAt")  # Timestamp object
epoch = ts.seconds
dt = ts.to_datetime()  # datetime with tz

# From REST API
s = "2026-04-07T15:30:45.123456789Z"
# Python fromisoformat handles up to 6 decimal places
# For 9 digits, truncate or use dateutil
dt = datetime.fromisoformat(s[:26] + "+00:00")
JavaScript
// From Firestore SDK
const ts = doc.data().createdAt; // Timestamp obj
const epoch = ts.seconds;
const date = ts.toDate(); // JS Date (loses nanos)
const millis = ts.toMillis(); // epoch ms

// Create a Timestamp
const { Timestamp } = require("firebase-admin/firestore");
const now = Timestamp.now();
const fromEpoch = Timestamp.fromMillis(1775666445123);
Gotcha: Firestore Timestamps are NOT plain numbers or strings in the SDK. They are objects with .seconds and .nanoseconds properties. Calling .toDate() returns a JavaScript Date, which loses nanosecond precision (Date only supports milliseconds). The REST API uses ISO 8601 strings with nanosecond precision, but Python's fromisoformat() only handles up to 6 decimal places — you need to truncate or use dateutil.parser for 9-digit nanoseconds.

Datadog

Format: Unix Epoch (seconds for metrics, milliseconds for logs) · Precision: varies
Python
import time
from datetime import datetime, timezone

# Submit metric (epoch seconds, float allowed)
api.Metric.send(
    metric="my.metric",
    points=[(time.time(), 42.0)]
)

# Query logs (epoch milliseconds!)
logs = api.Logs.list(
    filter={"from": "1775666445000", "to": "1775666500000"}
)

# Parse log timestamp
epoch_ms = log["attributes"]["timestamp"]
epoch = epoch_ms // 1000
dt = datetime.fromtimestamp(epoch, tz=timezone.utc)
JavaScript
// Submit metric (epoch seconds)
const point = [Math.floor(Date.now() / 1000), 42.0];

// Query logs (epoch milliseconds)
const from = Date.now() - 3600000; // 1 hour ago
const to = Date.now();

// Parse log timestamp
const epochMs = log.attributes.timestamp;
const date = new Date(epochMs);
const epochSeconds = Math.floor(epochMs / 1000);
Gotcha: Datadog uses seconds for metric submission but milliseconds for log timestamps. If you accidentally send milliseconds as a metric timestamp, the data point will appear at a date in the year 58,000+. If you accidentally treat log milliseconds as seconds, events will cluster around January 1970. Always verify which unit a specific Datadog endpoint expects.

Key Takeaways

Frequently Asked Questions

What timestamp format does Stripe use?

Stripe uses Unix epoch seconds as plain integers for all timestamp fields including created, updated, and period_start. Example: 1775666445. To convert in Python: datetime.fromtimestamp(1775666445, tz=timezone.utc). In JavaScript: new Date(1775666445 * 1000).

How do I parse a Discord snowflake ID to get the timestamp?

Discord snowflake IDs encode a millisecond timestamp. Right-shift the snowflake by 22 bits, then add the Discord epoch (1420070400000). In Python: ms = (snowflake >> 22) + 1420070400000. In JavaScript, use BigInt: const ms = Number((BigInt(snowflake) >> 22n) + 1420070400000n).

What is the difference between Slack's ts and a Unix timestamp?

Slack's ts is a string like "1775666445.123456" that doubles as a timestamp and message ID. The part after the dot is microseconds, not fractional seconds. Never convert it to a float and back — precision loss can change the message ID. Always preserve the original string for API calls.

Do all APIs use UTC for timestamps?

Most APIs use UTC, but Shopify is a notable exception — it returns timestamps in the shop's configured timezone with an offset. Some Google Calendar endpoints also return local times. Always check each API's documentation and normalize to UTC when storing.

Related EpochPilot Tools

More Research

Contact

EpochPilot is built and maintained by Michael Lip. For questions or feedback, email [email protected].

📥 Download Raw Data

Free under CC BY 4.0. Cite this page when sharing.