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
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 |
|---|---|---|---|---|---|
| Stripe | Epoch | 1775666445 | Seconds | UTC | Always seconds, never ms |
| GitHub | ISO 8601 | 2026-04-07T15:30:45Z | Seconds | UTC (Z) | No milliseconds, always Z |
| Slack | Custom | "1775666445.123456" | Microseconds | UTC | String, not number; doubles as msg ID |
| Discord | Snowflake | 1234567890123456789 | Milliseconds | Custom epoch | Epoch is 2015-01-01, not 1970 |
| AWS | ISO 8601 | 2026-04-07T15:30:45.123Z | Milliseconds | UTC (Z) | SigV4 uses compact %Y%m%dT%H%M%SZ |
| Twitter/X (v1) | RFC 2822 | Tue Apr 07 15:30:45 +0000 2026 | Seconds | UTC | Non-standard field order (year at end) |
| Twitter/X (v2) | ISO 8601 | 2026-04-07T15:30:45.000Z | Milliseconds | UTC (Z) | Different from v1! |
| Google APIs | RFC 3339 | 2026-04-07T15:30:45.123Z | Variable | UTC or offset | Protobuf Timestamp uses {seconds, nanos} |
| Twilio | RFC 2822 | Tue, 07 Apr 2026 15:30:45 +0000 | Seconds | UTC | Comma after day name |
| Shopify | ISO 8601 | 2026-04-07T15:30:45-04:00 | Seconds | Shop TZ | Not UTC — uses shop's timezone |
| Firebase | Object | {seconds: 1775666445, nanos: 123456789} | Nanoseconds | UTC | Not a plain value; access .seconds |
| Salesforce | ISO 8601 | 2026-04-07T15:30:45.000+0000 | Milliseconds | UTC | No colon in offset (+0000 not +00:00) |
| Jira | ISO 8601 | 2026-04-07T15:30:45.123+0000 | Milliseconds | UTC | No colon in offset; same as Salesforce |
| Datadog | Epoch | 1775666445 | Seconds/ms | UTC | Logs use ms, metrics use seconds |
| PagerDuty | ISO 8601 | 2026-04-07T15:30:45Z | Seconds | UTC (Z) | Schedule overrides may use offsets |
| SendGrid | Epoch | 1775666445 | Seconds | UTC | Webhooks use epoch; scheduling uses ISO |
Detailed Breakdown & Parsing Code
Stripe
{
"id": "ch_1234567890",
"created": 1775666445,
"amount": 2000,
"currency": "usd"
}
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})
// 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 }
});
GitHub
{
"id": 123456789,
"name": "my-repo",
"created_at": "2026-04-07T15:30:45Z",
"updated_at": "2026-04-07T15:30:45Z"
}
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"
// 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"
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
{
"type": "message",
"text": "Hello world",
"ts": "1775666445.123456",
"thread_ts": "1775666400.000001"
}
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}
// 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 };
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
{
"id": "1234567890123456789",
"content": "Hello from Discord",
"timestamp": "2026-04-07T15:30:45.123000+00:00"
}
# 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"])
// 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);
timestamp field in ISO 8601 format on message objects, which is easier to parse.AWS (General)
{
"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
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())
}]
)
// 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();
20260407T153045Z) without dashes or colons. DynamoDB TTL requires epoch seconds as a Number attribute. Always check the specific service documentation.Twitter/X
{
"created_at": "Tue Apr 07 15:30:45 +0000 2026",
"id": 1234567890123456789,
"text": "Hello from Twitter v1"
}
{
"data": {
"created_at": "2026-04-07T15:30:45.000Z",
"id": "1234567890123456789",
"text": "Hello from Twitter v2"
}
}
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
// 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);
Google APIs
// 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 }
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
// 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
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
{
"order": {
"id": 123456789,
"created_at": "2026-04-07T11:30:45-04:00",
"updated_at": "2026-04-07T11:30:45-04:00"
}
}
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
// 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"
-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
// Firestore document
{
"name": "My Document",
"createdAt": Timestamp {
seconds: 1775666445,
nanoseconds: 123456789
}
}
// REST API (JSON)
{
"fields": {
"createdAt": {
"timestampValue": "2026-04-07T15:30:45.123456789Z"
}
}
}
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")
// 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);
.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
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)
// 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);
Key Takeaways
- 9 of 15 APIs use ISO 8601 / RFC 3339 as their primary format
- 4 of 15 APIs use Unix epoch integers (Stripe, Datadog, SendGrid, partially Discord)
- Only Shopify returns non-UTC timestamps by default — every other API is UTC
- Slack is unique: its timestamp doubles as a message identifier (do not round or truncate)
- Discord and Twitter embed timestamps in snowflake IDs with custom epochs
- Salesforce and Jira omit the colon in the UTC offset (
+0000not+00:00) - Datadog is the most dangerous: metrics use seconds, logs use milliseconds, mixing them is silent
- Firebase wraps timestamps in objects — calling
JSON.stringifyon a Firestore Timestamp loses its type
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].