diff --git a/components/seo/TimestampConverterSEO.tsx b/components/seo/TimestampConverterSEO.tsx new file mode 100644 index 0000000..0c547a7 --- /dev/null +++ b/components/seo/TimestampConverterSEO.tsx @@ -0,0 +1,115 @@ +import CodeExample from "../CodeExample"; + +export default function TimestampConverterSEO() { + return ( +
+
+

+ Effortlessly convert timestamps between different timezones with this + free online tool. Whether you're coordinating events across + regions, analyzing logs, or building global applications, our + timestamp timezone converter makes it easy to translate any date and + time from one timezone to another. +

+
+ +
+

Key Features:

+ +
+ +
+

How to Use the Timestamp Timezone Converter:

+

Converting a timestamp between timezones is simple:

+ +

+ Your timestamp is now accurately converted and ready to use or share + across different regions. +

+
+ +
+

Why Convert Timestamps Between Timezones?

+

+ Timezone conversion is essential for global teams, distributed + systems, and anyone working with international data. Converting + timestamps ensures accurate scheduling, reporting, and analysis + regardless of where your users or systems are located. +

+
+ +
+

How to Convert Timestamps Between Timezones in Code:

+

+ Need to perform timezone conversions in your own JavaScript or + TypeScript projects? Here's a sample using luxon: +

+
+ +
+ {jsCodeExample} +
+ +
+

FAQs:

+ +
+
+ ); +} + +const jsCodeExample = `import { DateTime } from "luxon"; + +function convertTimezone(timestamp: string, fromZone: string, toZone: string) { + // Parse timestamp as ISO string or epoch milliseconds + const dt = DateTime.fromMillis(Number(timestamp), { zone: fromZone }); + if (!dt.isValid) throw new Error("Invalid timestamp or timezone"); + return dt.setZone(toZone).toFormat("yyyy-LL-dd HH:mm:ss ZZZZ"); +} + +// Example: +// convertTimezone("1680307200000", "UTC", "America/New_York"); +`; diff --git a/components/utils/tools-list.ts b/components/utils/tools-list.ts index ef41923..0ba9ee1 100644 --- a/components/utils/tools-list.ts +++ b/components/utils/tools-list.ts @@ -107,6 +107,12 @@ export const tools = [ "Resize images while maintaining aspect ratio and choose between PNG and JPEG formats with our free tool.", link: "/utilities/image-resizer", }, + { + title: "Timezone Converter", + description: + "Convert timestamps between different timezones with ease. Perfect for scheduling events across regions and analyzing logs.", + link: "/utilities/timezone-converter", + }, { title: "JWT Parser", description: diff --git a/pages/utilities/timezone-converter.test.tsx b/pages/utilities/timezone-converter.test.tsx new file mode 100644 index 0000000..2d72247 --- /dev/null +++ b/pages/utilities/timezone-converter.test.tsx @@ -0,0 +1,148 @@ +import React from "react"; +import { render, fireEvent } from "@testing-library/react"; +import TimezoneComparer from "./timezone-converter"; + +function convertTime(inputTime: string, fromTz: string, toTz: string): string { + if (!inputTime) return ""; + const match = inputTime.match(/^(\d{4}):(\d{2}):(\d{2}) (\d{2}):(\d{2})$/); + if (!match) return "Invalid time format"; + const [, yearStr, monthStr, dayStr, hourStr, minuteStr] = match; + const year = Number(yearStr); + const month = Number(monthStr); + const day = Number(dayStr); + const hours = Number(hourStr); + const minutes = Number(minuteStr); + if ( + isNaN(year) || + isNaN(month) || + isNaN(day) || + isNaN(hours) || + isNaN(minutes) || + month < 1 || + month > 12 || + day < 1 || + day > 31 || + hours < 0 || + hours > 23 || + minutes < 0 || + minutes > 59 + ) + return "Invalid time format"; + + let date: Date; + if (fromTz === "UTC") { + date = new Date(Date.UTC(year, month - 1, day, hours, minutes, 0)); + } else { + const inputIso = `${yearStr}-${monthStr}-${dayStr}T${hourStr}:${minuteStr}:00`; + const utcMillis = Date.parse( + new Date( + new Intl.DateTimeFormat("en-US", { + timeZone: fromTz, + year: "numeric", + month: "2-digit", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + hour12: false, + }) + .formatToParts(new Date(inputIso)) + .map((p) => p.value) + .join("") + ).toISOString() + ); + date = new Date(utcMillis); + } + + const formatter = new Intl.DateTimeFormat("en-US", { + year: "numeric", + month: "2-digit", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + hour12: false, + timeZone: toTz, + }); + const parts = formatter.formatToParts(date); + const yearPart = parts.find((p) => p.type === "year"); + const monthPart = parts.find((p) => p.type === "month"); + const dayPart = parts.find((p) => p.type === "day"); + const hourPart = parts.find((p) => p.type === "hour"); + const minutePart = parts.find((p) => p.type === "minute"); + if (!yearPart || !monthPart || !dayPart || !hourPart || !minutePart) + return "Conversion error"; + let hourOut = hourPart.value; + if (hourOut === "24") hourOut = "00"; + return `${yearPart.value}:${monthPart.value}:${dayPart.value} ${hourOut}:${minutePart.value}`; +} + +describe("convertTime", () => { + test("UTC to UTC, same time and date", () => { + expect(convertTime("2024:06:01 12:00", "UTC", "UTC")).toBe( + "2024:06:01 12:00" + ); + }); + + test("UTC to New York, date and time format", () => { + const nyTime = convertTime("2024:06:01 12:00", "UTC", "America/New_York"); + expect(/^\d{4}:\d{2}:\d{2} \d{2}:\d{2}$/.test(nyTime)).toBe(true); + }); + + test("New York to Tokyo, date and time format", () => { + const tokyoTime = convertTime( + "2024:06:01 08:00", + "America/New_York", + "Asia/Tokyo" + ); + expect(/^\d{4}:\d{2}:\d{2} \d{2}:\d{2}$/.test(tokyoTime)).toBe(true); + }); + + test("Invalid hour", () => { + expect(convertTime("2024:06:01 25:00", "UTC", "UTC")).toBe( + "Invalid time format" + ); + }); + + test("Invalid minute", () => { + expect(convertTime("2024:06:01 12:60", "UTC", "UTC")).toBe( + "Invalid time format" + ); + }); + + test("Invalid date", () => { + expect(convertTime("2024:13:01 12:00", "UTC", "UTC")).toBe( + "Invalid time format" + ); + expect(convertTime("2024:06:32 12:00", "UTC", "UTC")).toBe( + "Invalid time format" + ); + }); + + test("Empty input", () => { + expect(convertTime("", "UTC", "UTC")).toBe(""); + }); + + test("UTC midnight to Tokyo, date and time format", () => { + const tokyoTime = convertTime("2024:06:01 00:00", "UTC", "Asia/Tokyo"); + expect(/^\d{4}:\d{2}:\d{2} \d{2}:\d{2}$/.test(tokyoTime)).toBe(true); + }); + + test("Handles leap year", () => { + expect(convertTime("2024:02:29 12:00", "UTC", "UTC")).toBe( + "2024:02:29 12:00" + ); + }); + + test("Handles single digit months and days", () => { + expect(convertTime("2024:01:09 09:05", "UTC", "UTC")).toBe( + "2024:01:09 09:05" + ); + }); + + test("Invalid format", () => { + expect(convertTime("2024-06-01 12:00", "UTC", "UTC")).toBe( + "Invalid time format" + ); + expect(convertTime("12:00", "UTC", "UTC")).toBe("Invalid time format"); + }); +}); diff --git a/pages/utilities/timezone-converter.tsx b/pages/utilities/timezone-converter.tsx new file mode 100644 index 0000000..889c8cf --- /dev/null +++ b/pages/utilities/timezone-converter.tsx @@ -0,0 +1,196 @@ +import { useState } from "react"; +import { Card } from "@/components/ds/CardComponent"; +import { Button } from "@/components/ds/ButtonComponent"; +import { Label } from "@/components/ds/LabelComponent"; +import PageHeader from "@/components/PageHeader"; +import Header from "@/components/Header"; +import { CMDK } from "@/components/CMDK"; +import Meta from "@/components/Meta"; +import CallToActionGrid from "@/components/CallToActionGrid"; +import TimestampConverterSEO from "@/components/seo/TimestampConverterSEO"; + +const timezones = [ + "UTC", + "America/New_York", + "Europe/London", + "Asia/Kolkata", + "Asia/Tokyo", + "Australia/Sydney", + "Europe/Berlin", + "America/Los_Angeles", +]; + +function convertTime(inputTime: string, fromTz: string, toTz: string): string { + if (!inputTime) return ""; + const match = inputTime.match(/^(\d{4}):(\d{2}):(\d{2}) (\d{2}):(\d{2})$/); + if (!match) return "Invalid time format"; + const [, yearStr, monthStr, dayStr, hourStr, minuteStr] = match; + const year = Number(yearStr); + const month = Number(monthStr); + const day = Number(dayStr); + const hours = Number(hourStr); + const minutes = Number(minuteStr); + if ( + isNaN(year) || + isNaN(month) || + isNaN(day) || + isNaN(hours) || + isNaN(minutes) || + month < 1 || + month > 12 || + day < 1 || + day > 31 || + hours < 0 || + hours > 23 || + minutes < 0 || + minutes > 59 + ) + return "Invalid time format"; + + let date: Date; + if (fromTz === "UTC") { + date = new Date(Date.UTC(year, month - 1, day, hours, minutes, 0)); + } else { + const inputIso = `${yearStr}-${monthStr}-${dayStr}T${hourStr}:${minuteStr}:00`; + const utcMillis = Date.parse( + new Date( + new Intl.DateTimeFormat("en-US", { + timeZone: fromTz, + year: "numeric", + month: "2-digit", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + hour12: false, + }) + .formatToParts(new Date(inputIso)) + .map((p) => p.value) + .join("") + ).toISOString() + ); + date = new Date(utcMillis); + } + + const formatter = new Intl.DateTimeFormat("en-US", { + year: "numeric", + month: "2-digit", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + hour12: false, + timeZone: toTz, + }); + const parts = formatter.formatToParts(date); + const yearPart = parts.find((p) => p.type === "year"); + const monthPart = parts.find((p) => p.type === "month"); + const dayPart = parts.find((p) => p.type === "day"); + const hourPart = parts.find((p) => p.type === "hour"); + const minutePart = parts.find((p) => p.type === "minute"); + if (!yearPart || !monthPart || !dayPart || !hourPart || !minutePart) + return "Conversion error"; + let hourOut = hourPart.value; + if (hourOut === "24") hourOut = "00"; + return `${yearPart.value}:${monthPart.value}:${dayPart.value} ${hourOut}:${minutePart.value}`; +} + +export default function TimezoneComparer() { + const userTz = + typeof window !== "undefined" + ? Intl.DateTimeFormat().resolvedOptions().timeZone + : "UTC"; + + const getDefaultInputTime = () => { + const now = new Date(); + const yyyy = now.getFullYear(); + const mm = String(now.getMonth() + 1).padStart(2, "0"); + const dd = String(now.getDate()).padStart(2, "0"); + const hh = String(now.getHours()).padStart(2, "0"); + const min = String(now.getMinutes()).padStart(2, "0"); + return `${yyyy}:${mm}:${dd} ${hh}:${min}`; + }; + + const [fromTz, setFromTz] = useState(userTz); + const [toTz, setToTz] = useState("UTC"); + const [inputTime, setInputTime] = useState(getDefaultInputTime()); + const [outputTime, setOutputTime] = useState(""); + + const handleConvert = () => { + setOutputTime(convertTime(inputTime, fromTz, toTz)); + }; + + return ( +
+ +
+ + +
+ +
+ +
+ +
+ + + + + + setInputTime(e.target.value)} + maxLength={16} + />{" "} + +
+
+ + +
+
+
+
+ +
+ +
+ ); +}