Parse common log timestamp formats
Apache's 25/Jul/2026:14:30:00 +0200, syslog's Jul 25 14:30:00 and 2026-07-25T14:30:00.123Z each come back as one ISO 8601 UTC time, with epoch milliseconds and the format that matched, so merging logs no longer means three sets of rules. Free in your browser.
- Free
- Runs in your browser
- No sign-up
- Your data stays on your device
Run — free
Worked example
- Text
- 2026-07-25T14:30:00.123Z INFO worker started
- Iso
- 2026-07-25T14:30:00.123Z
- Epoch ms
- 1,784,989,800,123
- Output format
- iso8601
Log files do not agree on how to write a timestamp. Your app emits ISO 8601, the host writes syslog, and the web server stamps everything in Apache's common log format, so merging three sources means writing the same parsing code yet again. One call replaces a pile of per-format regexes and timezone math.
Three formats, one normalized answer
Every log source picked its own convention decades ago and never moved. ISO 8601 stamps like 2026-07-25T14:30:00.123Z are unambiguous but only one of the formats you will meet in the wild. Syslog lines following RFC 3164 open with a stamp like Jul 25 14:30:00 — no year, no timezone, a two-space gap when the day is single-digit.
Apache and nginx access logs in the common log format use their own shape entirely: 25/Jul/2026:14:30:00 +0200, with slashes, colons and a numeric zone. The log timestamp parser accepts the leading timestamp of a line in any of the three and answers with a single normalized ISO 8601 UTC string, the epoch milliseconds, and the name of the format that matched. You stop branching on source type: whatever arrives, one field tells you when it happened.
Anything that does not start with a known timestamp is rejected with a clear error instead of a guessed date, so a malformed line fails loudly upstream rather than silently corrupting your analytics downstream.
What normalization actually means
Normalization is where the subtle bugs live, so the rules are stated plainly. An ISO 8601 input with an explicit offset, say 2026-07-25T16:30:00+02:00, is converted to UTC and returned as 2026-07-25T14:30:00.000Z — the same instant, one canonical spelling. An ISO stamp with no zone at all is assumed to be UTC, which is the least surprising default for server logs.
Apache's mandatory numeric zone is honored exactly. Fractional seconds are kept to millisecond precision. Calendar validation is strict: February 30th, a 25th hour or a 61st second are errors, not silent rollovers into the next day, because a log pipeline that accepts impossible dates is one that will eventually mislead you during an incident.
Built for pipelines, deterministic by design
The parser is a pure function: regular expressions to recognize the format, then integer calendar arithmetic to convert between civil dates and the epoch. It never reads the system clock, never calls the network and never depends on the timezone of the machine it runs on, which means the same input produces the same output in your tests today and in production a year from now. That determinism matters when you replay archived logs through a new pipeline and expect the reconstructed timeline to match the original byte for byte.
Typical uses are normalizing mixed-format archives before loading them into a search engine, aligning events from application, system and web-server logs onto one UTC timeline during incident analysis, and validating that a shipper is stamping lines in the format your collector expects.
What you can do with it
Merge logs from different sources
Normalize ISO, syslog and Apache stamps onto one UTC timeline before loading mixed archives into your log store.
Rebuild an incident timeline
Align application, system and web-server events chronologically when each layer stamped its lines differently.
Validate a log shipper's format
Check that the lines leaving your agents actually start with the timestamp format your collector expects, rejecting malformed lines early.
FAQ
What does it cost?
The same parser also runs free in your browser on this page.
Which timestamp formats are supported?
ISO 8601 (with or without fractional seconds and timezone offset), syslog per RFC 3164 such as 'Jul 25 14:30:00', and the Apache common log format such as '25/Jul/2026:14:30:00 +0200'.
What if a timestamp has no timezone?
It is assumed to be UTC. Stamps with an explicit offset are converted to UTC, so every answer comes back in the same normalized form.
What if the line does not start with a known timestamp?
Impossible dates like February 30 or hour 25 are rejected the same way.
Is my log data stored?
No. The line is parsed in memory and discarded; only the normalized timestamp is returned.
Related tools
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, by email and from Telegram — and soon from our app too.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/dev/timestamp-log-parse \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"text":"2026-07-25T14:30:00.123Z INFO worker started"}'const res = await fetch("https://api.kit.forhosting.com/dev/timestamp-log-parse", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"text": "2026-07-25T14:30:00.123Z INFO worker started"
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/dev/timestamp-log-parse",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"text": "2026-07-25T14:30:00.123Z INFO worker started"
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/dev/timestamp-log-parse", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"text":"2026-07-25T14:30:00.123Z INFO worker started"}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"text":"2026-07-25T14:30:00.123Z INFO worker started"}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/dev/timestamp-log-parse", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"text": "2026-07-25T14:30:00.123Z INFO worker started"
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "dev.timestamp_log_parse",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Limits
max_chars | 10000 |
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |
Technical notes
This API takes a log line, reads the timestamp at its start in any of the three formats, and returns it normalized to ISO 8601 UTC, together with the epoch milliseconds and the format it matched.
What normalization actually means
Syslog's missing year is the one place a guess is unavoidable: RFC 3164 simply carries none, so the parser fills it from the default_year parameter (2000 unless you pass another) and marks the result with year_assumed: true so the output itself admits the assumption.
Built for pipelines, deterministic by design
The same code runs free in your browser on this page — paste a line, see the result — and costs $0.002 per request when you automate it through the API, with no file or line retained after the answer is returned.
Technical questions
What happens with syslog lines that carry no year?
The year is filled from the default_year parameter (2000 unless you pass your own) and the result is marked with year_assumed: true so the assumption is visible in the output.