temporal-react-hook
Advanced tools
| import { useState } from 'react'; | ||
| import './DemoCard.css'; | ||
| import useTimeZoneOffset from '../../src/useTimeZoneOffset'; | ||
| const COMMON_ZONES = [ | ||
| 'UTC', | ||
| 'America/New_York', | ||
| 'America/Los_Angeles', | ||
| 'Europe/London', | ||
| 'Europe/Berlin', | ||
| 'Europe/Paris', | ||
| 'Asia/Tokyo', | ||
| 'Asia/Singapore', | ||
| 'Australia/Sydney', | ||
| ]; | ||
| export default function DemoUseTimeZoneOffset() { | ||
| const [zone, setZone] = useState<string>('UTC'); | ||
| const [customZone, setCustomZone] = useState<string>(''); | ||
| const [asMinutes, setAsMinutes] = useState<boolean>(false); | ||
| const offset = useTimeZoneOffset(zone, { asMinutes }); | ||
| const applyCustomZone = () => { | ||
| const trimmed = customZone.trim(); | ||
| if (trimmed) { | ||
| setZone(trimmed); | ||
| setCustomZone(''); | ||
| } | ||
| }; | ||
| return ( | ||
| <section className="demo-card"> | ||
| <h3>useTimeZoneOffset</h3> | ||
| <div className="demo-config-panel"> | ||
| <div className="demo-config-row"> | ||
| <label style={{ marginRight: 8 }}>Time Zone:</label> | ||
| <select | ||
| className="demo-select" | ||
| value={COMMON_ZONES.includes(zone) ? zone : 'custom'} | ||
| onChange={(e) => { | ||
| const val = e.target.value; | ||
| if (val !== 'custom') setZone(val); | ||
| }} | ||
| > | ||
| {COMMON_ZONES.map((z) => ( | ||
| <option key={z} value={z}>{z}</option> | ||
| ))} | ||
| <option value="custom">Custom…</option> | ||
| </select> | ||
| </div> | ||
| {(!COMMON_ZONES.includes(zone) || customZone) && ( | ||
| <div className="demo-config-row"> | ||
| <input | ||
| type="text" | ||
| value={customZone} | ||
| onChange={(e) => setCustomZone(e.target.value)} | ||
| placeholder="Enter IANA time-zone" | ||
| className="demo-input" | ||
| style={{ flex: 1, marginRight: 8 }} | ||
| /> | ||
| <button className="demo-button" onClick={applyCustomZone}>Apply</button> | ||
| </div> | ||
| )} | ||
| <div className="demo-config-row"> | ||
| <label> | ||
| <input | ||
| type="checkbox" | ||
| checked={asMinutes} | ||
| onChange={(e) => setAsMinutes(e.target.checked)} | ||
| />{' '} | ||
| Return as minutes | ||
| </label> | ||
| </div> | ||
| </div> | ||
| <div className="demo-output-panel"> | ||
| <div className="demo-config-row"> | ||
| <b>Current Offset:</b> | ||
| <span>{offset.toString()}</span> | ||
| </div> | ||
| </div> | ||
| <div className="demo-info-card"> | ||
| <div className="demo-description"> | ||
| <strong>Description:</strong> | ||
| <span> | ||
| Returns the current UTC offset for a given IANA time-zone and updates automatically on DST transitions. | ||
| </span> | ||
| </div> | ||
| <div className="demo-usage"> | ||
| <span> | ||
| <strong>Syntax:</strong> useTimeZoneOffset(zone?, options?)<br/> | ||
| <strong>Parameters:</strong><br/> | ||
| – zone: string | Temporal.TimeZoneLike — IANA zone, default system<br/> | ||
| – options: {'{'} asMinutes?: boolean; updateOnTick?: number {'}'}<br/> | ||
| <strong>Returns:</strong> string | number — formatted offset (e.g. "UTC+02:00") or minutes east of UTC<br/> | ||
| <strong>Example:</strong> | ||
| <code className="example-code"> | ||
| <pre style={{margin: 0}}>{`import { useTimeZoneOffset } from 'temporal-react-hook'; | ||
| // London offset as formatted string | ||
| const offsetStr = useTimeZoneOffset('Europe/London'); | ||
| // New York offset as minutes (negative west of UTC) | ||
| const nyMinutes = useTimeZoneOffset('America/New_York', { asMinutes: true }); | ||
| console.log(offsetStr); // "UTC+01:00" | ||
| console.log(nyMinutes); // -300 | ||
| `}</pre> | ||
| </code> | ||
| </span> | ||
| </div> | ||
| </div> | ||
| </section> | ||
| ); | ||
| } |
| import { useState, useEffect, useCallback } from 'react'; | ||
| import { Temporal } from '@js-temporal/polyfill'; | ||
| export interface TimeZoneOffsetOptions { | ||
| /** | ||
| * If true, the hook returns the offset as a signed number of minutes. | ||
| * If false (default), it returns a formatted string such as "UTC+04:00". | ||
| */ | ||
| asMinutes?: boolean; | ||
| /** | ||
| * Interval, in milliseconds, at which to re-evaluate the offset. | ||
| * Defaults to 60 000 ms (one minute) which is ample to capture DST jumps. | ||
| */ | ||
| updateOnTick?: number; | ||
| } | ||
| /** | ||
| * React hook that returns the current UTC offset for a given IANA time-zone and | ||
| * keeps it up-to-date as Daylight Saving Time transitions occur. | ||
| * | ||
| * @param zone IANA time-zone identifier. Defaults to the system zone. | ||
| * @param opts Behavioural options – see {@link TimeZoneOffsetOptions}. | ||
| * @returns Either a formatted offset string (e.g. "UTC+02:00") or a signed | ||
| * number of minutes east of UTC, depending on `opts.asMinutes`. | ||
| */ | ||
| export default function useTimeZoneOffset( | ||
| zone: string | Temporal.TimeZoneLike | undefined = Temporal.Now.timeZoneId(), | ||
| opts: TimeZoneOffsetOptions = {} | ||
| ): string | number { | ||
| const { asMinutes = false, updateOnTick = 60_000 } = opts; | ||
| const calcOffset = useCallback(() => { | ||
| const zdt = Temporal.Now.zonedDateTimeISO(zone as any); | ||
| const minutes = zdt.offsetNanoseconds / 60_000_000_000; | ||
| return asMinutes ? minutes : formatOffset(minutes); | ||
| }, [zone, asMinutes]); | ||
| const [offset, setOffset] = useState<string | number>(() => calcOffset()); | ||
| useEffect(() => { | ||
| setOffset(calcOffset()); // sync immediately when deps change | ||
| const id = window.setInterval(() => setOffset(calcOffset()), updateOnTick); | ||
| return () => window.clearInterval(id); | ||
| }, [calcOffset, updateOnTick]); | ||
| return offset; | ||
| } | ||
| function formatOffset(totalMinutes: number): string { | ||
| const sign = totalMinutes >= 0 ? '+' : '−'; // U+2212 minus sign for aesthetics | ||
| const abs = Math.abs(totalMinutes); | ||
| const hours = Math.floor(abs / 60) | ||
| .toString() | ||
| .padStart(2, '0'); | ||
| const minutes = (abs % 60).toString().padStart(2, '0'); | ||
| return `UTC${sign}${hours}:${minutes}`; | ||
| } |
+19
-0
@@ -8,2 +8,21 @@ # Changelog | ||
| ## [1.7.1] - 2025-06-17 | ||
| ### Added | ||
| - `useTimeZoneOffset` hook for retrieving the current UTC offset of any IANA time-zone with automatic updates on DST transitions. | ||
| - `DemoUseTimeZoneOffset` component demonstrating the hook with common and custom zones, string/minutes output, and info card documentation. | ||
| ## [1.7.0] - 2025-06-16 | ||
| ### Added | ||
| - `useDateTimeRange` hook and `DemoUseDateTimeRange` component:: | ||
| - Manages a range of `Temporal.PlainDateTime` values with guaranteed `start <= end` ordering. | ||
| - Options to clamp the end date to the current time (`clampToNow`). | ||
| - Option to specify `smallestUnit` ('day', 'hour', 'minute', 'second') for `length` calculation and `toArray` enumeration. | ||
| - Provides `setStart`, `setEnd` setters. | ||
| - Includes `shiftBy(duration)` method to move the entire range. | ||
| - Exposes `length` (number of `smallestUnit`s in range, inclusive). | ||
| - Exposes `contains(dateTime)` method to check if a `Temporal.PlainDateTime` is within the range. | ||
| - Exposes `toArray()` method to enumerate all `Temporal.PlainDateTime` instances in the range by the `smallestUnit`. | ||
| ## [1.6.15] - 2025-06-11 | ||
@@ -10,0 +29,0 @@ |
| import { useState, FC, useRef, useEffect } from "react"; | ||
| import DemoUseCurrentDateTime from "./components/DemoUseCurrentDateTime"; | ||
| import DemoUseTimeZone from "./components/DemoUseTimeZone"; | ||
| import DemoUseTimeZoneOffset from "./components/DemoUseTimeZoneOffset"; | ||
| import DemoUseDuration from "./components/DemoUseDuration"; | ||
@@ -25,2 +26,3 @@ import DemoUseRelativeTime from "./components/DemoUseRelativeTime"; | ||
| { key: "timezone", label: "useTimeZone", component: <DemoUseTimeZone /> }, | ||
| { key: "timezoneoffset", label: "useTimeZoneOffset", component: <DemoUseTimeZoneOffset /> }, | ||
| { key: "duration", label: "useDuration", component: <DemoUseDuration /> }, | ||
@@ -27,0 +29,0 @@ { key: "relativetime", label: "useRelativeTime", component: <DemoUseRelativeTime /> }, |
+1
-1
| { | ||
| "name": "temporal-react-hook", | ||
| "version": "1.7.0", | ||
| "version": "1.7.1", | ||
| "description": "A React library that provides hooks for handling date and time operations using the Temporal API", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
+3
-1
@@ -5,3 +5,3 @@ # temporal-react-hook | ||
| ## 🖥️ [Live Demo](https://vlad-grigoryan.github.io/temporal-react-hook/) | ||
| ## [Live Demo](https://vlad-grigoryan.github.io/temporal-react-hook/) | ||
@@ -40,2 +40,4 @@ ## Installation | ||
| | `useTemporalEndOf` | Get the end of a time unit (day, week, month, etc.) | | ||
| | `useTimeZoneOffset` | Get the current UTC offset for a time zone, auto-updates on DST | | ||
| | `useDateTimeRange` | Manage a date-time range with ordering, clamping, and helper functions | | ||
@@ -42,0 +44,0 @@ ## Running the Demo Locally |
+1
-0
@@ -18,1 +18,2 @@ export { default as useCurrentDateTime } from './useCurrentDateTime'; | ||
| export { default as useDateTimeRange } from './useDateTimeRange'; | ||
| export { default as useTimeZoneOffset } from './useTimeZoneOffset'; |
387453
1.96%65
3.17%9033
1.8%73
2.82%