Files
openobserve/web/src/utils/date.ts
T
c99cb0d516 fix(logs): logs finesse phase 1 — severity inference, auto-run, free-text FTS search, J/K, line permalinks (#15234)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Neha P <neha@openobserve.ai>
2026-10-12 16:50:32 +00:00

417 lines
13 KiB
TypeScript

// Copyright 2026 OpenObserve Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import { format as dfFormat, sub } from "date-fns";
import { formatInTimeZone } from "date-fns-tz";
import { DateTime as _DateTime } from "luxon";
import { gt } from "@/types/i18n";
// ---------------------------------------------------------------------------
// Duration helpers
// ---------------------------------------------------------------------------
// Parses the duration string and returns the number is seconds
export const parseDuration = (durationString: string) => {
const regex = /^(\d+)([smhdwMy])?$/;
const match = durationString.match(regex);
if (match) {
const value = parseInt(match[1]);
const unit = match[2];
if (unit === "s") {
return value;
} else if (unit === "m") {
return value * 60;
} else if (unit === "h") {
return value * 3600;
} else if (unit === "d") {
return value * 86400;
} else if (unit === "w") {
return value * 604800;
} else if (unit === "M") {
return value * 2592000;
} else if (unit === "y") {
return value * 31536000;
} else {
return value;
}
} else {
return 0;
}
};
export const generateDurationLabel = (seconds: number) => {
const units = [
{ label: "s", value: 1 },
{ label: "m", value: 60 },
{ label: "h", value: 3600 },
{ label: "d", value: 86400 },
{ label: "w", value: 604800 },
{ label: "M", value: 2592000 },
{ label: "y", value: 31536000 },
];
const duration = seconds;
let label = "";
for (let i = units.length - 1; i >= 0; i--) {
const unit = units[i];
if (duration >= unit.value) {
const unitDuration = Math.floor(duration / unit.value);
const remainingDuration = duration % unit.value;
if (remainingDuration === 0) {
label = `${unitDuration}${unit.label}`;
break;
} else {
label = `${duration}s`;
break;
}
}
}
if (label === "") {
label = gt("common.off");
}
return label;
};
export const getQueryParamsForDuration = (obj: any) => {
const params: any = {};
const tab = obj.tab;
const period = obj.relative.period;
const from = obj.absolute.date.from + " " + obj.absolute.startTime;
const to = obj.absolute.date.to + " " + obj.absolute.endTime;
if (tab === "relative") {
const labelsToUnitMapping: any = {
Minutes: "m",
Hours: "h",
Days: "d",
Weeks: "w",
Months: "M",
};
const periodValue = obj.relative.value;
const periodLabel = period.label;
const periodUnit = labelsToUnitMapping[periodLabel];
if (periodLabel === "Minutes" && periodUnit === "m") {
params["period"] = `${periodValue}${periodUnit}`;
} else if (periodLabel === "Hours" && periodUnit === "h") {
params["period"] = `${periodValue}${periodUnit}`;
} else if (periodLabel === "Days" && periodUnit === "d") {
params["period"] = `${periodValue}${periodUnit}`;
} else if (periodLabel === "Weeks" && periodUnit === "w") {
params["period"] = `${periodValue}${periodUnit}`;
} else if (periodLabel === "Months" && periodUnit === "M") {
params["period"] = `${periodValue}${periodUnit}`;
}
} else if (tab === "absolute") {
const fromTime = new Date(from).getTime();
const toTime = new Date(to).getTime();
params["from"] = `${fromTime}`;
params["to"] = `${toTime}`;
}
return params;
};
export const getDurationObjectFromParams = (params: any) => {
const obj = {
tab: "relative",
relative: {
period: { label: "Minutes", value: "Minutes" },
value: 15,
},
absolute: {
date: {
from: new Date().toLocaleDateString("en-ZA"),
to: new Date().toLocaleDateString("en-ZA"),
},
startTime: "00:00",
endTime: "23:59",
},
};
const period = params?.period?.match(/(\d+)([mhdwM])/);
const from = params?.from?.match(/(\d+)/);
const to = params?.to?.match(/(\d+)/);
if (period) {
const periodValue = period[1];
const periodUnit = period[2];
if (periodUnit === "m") {
obj.relative.period = { label: "Minutes", value: "Minutes" };
} else if (periodUnit === "h") {
obj.relative.period = { label: "Hours", value: "Hours" };
} else if (periodUnit === "d") {
obj.relative.period = { label: "Days", value: "Days" };
} else if (periodUnit === "w") {
obj.relative.period = { label: "Weeks", value: "Weeks" };
} else if (periodUnit === "M") {
obj.relative.period = { label: "Months", value: "Months" };
}
obj.relative.value = parseInt(periodValue);
} else if (from && to) {
const fromTime = parseInt(from[1]);
const toTime = parseInt(to[1]);
obj.tab = "absolute";
obj.absolute.date.from = new Date(fromTime).toLocaleDateString("en-ZA");
obj.absolute.date.to = new Date(toTime).toLocaleDateString("en-ZA");
const startTimeDateObj = new Date(fromTime);
obj.absolute.startTime = `${startTimeDateObj
.getHours()
.toString()
.padStart(2, "0")}:${startTimeDateObj.getMinutes().toString().padStart(2, "0")}`;
const toTimeDateObj = new Date(toTime);
obj.absolute.endTime = `${toTimeDateObj.getHours().toString().padStart(2, "0")}:${toTimeDateObj
.getMinutes()
.toString()
.padStart(2, "0")}`;
}
return obj;
};
export const RELATIVE_PERIOD_PATTERN = /(\d+)([smhdwM])/;
export const getConsumableRelativeTime = (period: string) => {
const periodString = period?.match(RELATIVE_PERIOD_PATTERN);
if (periodString) {
let periodValue: number = parseInt(periodString[1]);
let periodUnit: string = periodString[2];
// date-fns sub() supports weeks, but we convert for consistency with period string format
if (periodUnit == "w") {
periodUnit = "d";
periodValue = periodValue * 7;
}
const subtractObject: Record<string, number> = {};
subtractObject[getRelativePeriod(periodUnit).toLowerCase()] = periodValue;
const endTimeStamp = new Date();
const startTimeStamp = subtractRelativeTime(endTimeStamp, subtractObject);
return {
startTime: startTimeStamp.getTime() * 1000,
endTime: endTimeStamp.getTime() * 1000,
};
}
return undefined;
};
export const getRelativePeriod = (period: string) => {
const mapping: { [key: string]: string } = {
s: "Seconds",
m: "Minutes",
h: "Hours",
d: "Days",
w: "Weeks",
M: "Months",
};
return mapping[period];
};
export const isInvalidDate = (date: any) => {
return !(date && date instanceof Date && !isNaN(date.getTime()));
};
// ---------------------------------------------------------------------------
// Core formatting
// ---------------------------------------------------------------------------
/**
* Normalize legacy uppercase format tokens to date-fns (Unicode CLDR) tokens.
* Legacy formats use YYYY/DD whereas date-fns uses yyyy/dd.
*/
function normalizeFormat(format: string): string {
return format
.replace(/YYYY/g, "yyyy")
.replace(/DD/g, "dd")
.replace(/(?<!d)D(?!D)/g, "d")
.replace(/(?<!')T(?!')/g, "'T'")
.replace(/(?<!')Z(?!')/g, "X");
}
/**
* Format a value using date-fns format.
* Accepts a Date object, ISO string, or number (milliseconds since epoch).
*/
export function formatDate(value: number | string | Date, formatStr: string): string {
const input = typeof value === "string" ? new Date(value) : value;
return dfFormat(input, normalizeFormat(formatStr));
}
/**
* Format a microsecond timestamp (standard backend format) to a string.
* Converts microseconds to milliseconds internally.
*/
export function formatTimestamp(us: number, format: string): string {
return formatDate(us / 1000, format);
}
/**
* Format a microsecond timestamp in a specific IANA timezone.
* Mirrors formatTimestamp but renders in the given timezone instead of the
* browser's local zone, so the displayed value matches a timezone label.
*/
export function formatTimestampInTimezone(us: number, format: string, timezone: string): string {
return formatInTimeZone(us / 1000, timezone, normalizeFormat(format));
}
/**
* Format a nanosecond timestamp to a string.
* Converts nanoseconds to milliseconds internally.
*/
export function formatTimestampNs(ns: number, format: string): string {
return formatDate(ns / 1000000, format);
}
// ---------------------------------------------------------------------------
// Preset formatters — all accept microsecond timestamps
// ---------------------------------------------------------------------------
/** "YYYY-MM-DD HH:mm:ss" — standard readable datetime */
export const formatToReadable = (us: number): string => formatTimestamp(us, "YYYY-MM-DD HH:mm:ss");
/** "YYYY-MM-DD HH:mm:ss.SSS" — readable with milliseconds */
export const formatToDetailed = (us: number): string =>
formatTimestamp(us, "YYYY-MM-DD HH:mm:ss.SSS");
/** "YYYY-MM-DDTHH:mm:ssZ" — ISO 8601 compact */
export const formatToISO = (us: number): string => formatTimestamp(us, "YYYY-MM-DDTHH:mm:ssZ");
/** "MMM DD, YYYY HH:mm:ss.SSS Z" — human-readable with ms + tz */
export const formatToHuman = (us: number): string =>
formatTimestamp(us, "MMM DD, YYYY HH:mm:ss.SSS Z");
/** "MMM DD, YYYY HH:mm:ss Z" — human-readable with tz */
export const formatToHumanShort = (us: number): string =>
formatTimestamp(us, "MMM DD, YYYY HH:mm:ss Z");
/** "MMM D, YYYY" — date only (e.g. "May 19, 2026") */
export const formatToDateOnly = (us: number): string => formatTimestamp(us, "MMM D, YYYY");
/** "MMM DD, HH:mm" — month day + time, no year */
export const formatToTimeCompact = (us: number): string => formatTimestamp(us, "MMM DD, HH:mm");
// ---------------------------------------------------------------------------
// Relative time
// ---------------------------------------------------------------------------
/**
* Subtract a relative time period from a date.
*
* @example
* subtractRelativeTime(new Date(), { minutes: 15 }) // 15 min ago
* subtractRelativeTime(new Date(), { days: 7 }) // 7 days ago
*/
export function subtractRelativeTime(endDate: Date | number, period: Record<string, number>): Date {
return sub(endDate, period);
}
// ---------------------------------------------------------------------------
// Legacy / specific helpers
// ---------------------------------------------------------------------------
/** Default timestamp format. */
export const DATE_TIMESTAMP_FORMAT = "YYYY-MM-DDTHH:mm:ssZ";
/**
* Render a microsecond timestamp as a local-time string.
*
* Local time is deliberate: these render "created at" / "last triggered at"
* columns, which a user reads in their own timezone. The tests pin TZ=UTC for
* exactly that reason (see vitest.config.ts).
*/
export const convertUnixToDateFormat = (
unixMicroseconds: any,
format: string = DATE_TIMESTAMP_FORMAT,
) => {
try {
if (!unixMicroseconds) return "";
const unixSeconds = unixMicroseconds / 1e6;
const dateToFormat = new Date(unixSeconds * 1000);
const formattedDate = dateToFormat.toISOString();
return formatDate(formattedDate, format);
} catch (error) {
console.log("Error converting unix to date format");
return "";
}
};
/**
* @param {string} date - date in DD-MM-YYYY format
* @param {string} time - time in HH:MM 24hr format
* @param {string} timezone - timezone
*/
export const convertDateToTimestamp = (date: string, time: string, timezone: string) => {
try {
const browserTime = "Browser Time (" + Intl.DateTimeFormat().resolvedOptions().timeZone + ")";
const [day, month, year] = date.split("-");
const [hour, minute] = time.split(":");
const _date = {
year: Number(year),
month: Number(month),
day: Number(day),
hour: Number(hour),
minute: Number(minute),
};
if (timezone.toLowerCase() == browserTime.toLowerCase()) {
timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
}
// Create a DateTime instance from date and time, then set the timezone
const dateTime = _DateTime.fromObject(_date, { zone: timezone });
// Convert the DateTime to a Unix timestamp in milliseconds
const unixTimestampMillis = dateTime.toMillis();
return { timestamp: unixTimestampMillis * 1000, offset: dateTime.offset }; // timestamp in microseconds
} catch (error) {
console.log("Error converting date to timestamp");
return { timestamp: 0, offset: 0 };
}
};
export function getEffectiveTimeRange(dt: {
type: string;
relativeTimePeriod: string;
startTime: number;
endTime: number;
}): { startTime: number; endTime: number } {
if (dt.type === "relative" && dt.relativeTimePeriod) {
const rel = getConsumableRelativeTime(dt.relativeTimePeriod);
return {
startTime: rel?.startTime ?? dt.startTime,
endTime: rel?.endTime ?? dt.endTime,
};
}
return { startTime: dt.startTime, endTime: dt.endTime };
}