feat(timestamp): 实现时间戳与日期时间转换功能
- 新增日期时间转时间戳组件 DatetimeToTimestamp - 新增时间戳转日期时间组件 TimestampToDatetime - 新增当前时间戳显示组件 TimestampExecution - 重构 TimestampPage 页面,拆分功能到独立组件 - 扩展 timeUtils 工具函数,支持时区处理和格式化 - 添加时区选择器和时间单位切换功能 - 优化时间戳转换逻辑,支持毫秒和秒单位切换 - 改进 App.css 样式宽度设置
This commit is contained in:
+1
-1
@@ -3,7 +3,7 @@
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
font-family: Arial, sans-serif;
|
||||
width: 300px;
|
||||
width: 350px;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import {formatWithDate, formatWithZone, TimezoneOptions} from "../utils/timeUtils";
|
||||
import {useMemo, useState} from "react";
|
||||
|
||||
export function DatetimeToTimestamp() {
|
||||
const [dateValue, setDateValue] = useState(() => formatWithZone(Date.now()));
|
||||
const [selectedZone, setSelectedZone] = useState('Asia/Shanghai');
|
||||
const [result, setResult] = useState('');
|
||||
const [unit, setUnit] = useState('milliseconds');
|
||||
const timeZoneList = useMemo(() => ['America/New_York', 'America/Chicago', 'America/Denver', 'America/Los_Angeles', 'America/Anchorage', 'America/Honolulu', 'Europe/London', 'Europe/Paris', 'Europe/Berlin', 'Europe/Moscow', 'Asia/Tokyo', 'Asia/Shanghai', 'Asia/Hong_Kong', 'Asia/Singapore', 'Asia/Dubai', 'Asia/Kolkata', 'Australia/Sydney', 'Pacific/Auckland',], []);
|
||||
|
||||
const handleConvertDatetimeToTimestamp = () => {
|
||||
const timestamp = unit === 'milliseconds' ? formatWithDate(dateValue, selectedZone) : Math.floor(formatWithDate(dateValue, selectedZone) / 1000);
|
||||
setResult(timestamp);
|
||||
};
|
||||
|
||||
return (<div>
|
||||
<h2>日期时间转时间戳</h2>
|
||||
<div>
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="输入日期时间"
|
||||
value={dateValue}
|
||||
onChange={(e) => setDateValue(e.target.value)}/>
|
||||
<select
|
||||
value={selectedZone}
|
||||
onChange={(e) => setSelectedZone(e.target.value)}
|
||||
>
|
||||
<TimezoneOptions zones={timeZoneList}/>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div style={{marginBottom: '20px'}}>
|
||||
<button
|
||||
className={'action-btn'}
|
||||
onClick={handleConvertDatetimeToTimestamp}
|
||||
>
|
||||
转换
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="转换结果"
|
||||
value={result}
|
||||
readOnly
|
||||
/>
|
||||
<select
|
||||
value={unit}
|
||||
aria-label='选择时间戳单位'
|
||||
onChange={(e) => setUnit(e.target.value)}
|
||||
>
|
||||
<option value="milliseconds">毫秒(ms)</option>
|
||||
<option value="seconds">秒(s)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>)
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import {useEffect, useState} from "react";
|
||||
import CopyButton from "./CopyButton";
|
||||
|
||||
export function TimestampExecution() {
|
||||
const [currentTimestamp, setCurrentTimestamp] = useState(Math.floor(Date.now()));
|
||||
const [showMilliseconds, setShowMilliseconds] = useState(true);
|
||||
const [isRunningTimestamp, setIsRunningTimestamp] = useState(true);
|
||||
// 定时更新时间戳
|
||||
useEffect(() => {
|
||||
const timerId = isRunningTimestamp ? setInterval(() => {
|
||||
setCurrentTimestamp(Math.floor(Date.now()));
|
||||
}, showMilliseconds ? 100 : 1000) : null;
|
||||
|
||||
return () => clearInterval(timerId);
|
||||
}, [isRunningTimestamp, showMilliseconds]);
|
||||
|
||||
const toggleUnit = () => setShowMilliseconds(prev => !prev);
|
||||
const toggleTimestamp = () => setIsRunningTimestamp(prev => !prev);
|
||||
|
||||
return (<div>
|
||||
<h2>当前时间戳</h2>
|
||||
<p>
|
||||
<span>{Math.floor(currentTimestamp / (showMilliseconds ? 1 : 1000))}</span>
|
||||
<span>{showMilliseconds ? '毫秒' : '秒'}</span>
|
||||
</p>
|
||||
<div>
|
||||
<button type="button"
|
||||
className="action-btn"
|
||||
onClick={toggleUnit}
|
||||
aria-label={showMilliseconds ? '切换为秒' : '切换为毫秒'}
|
||||
>
|
||||
切换单位
|
||||
</button>
|
||||
<CopyButton text={currentTimestamp.toString()} buttonText='复制'></CopyButton>
|
||||
<button
|
||||
type="button" // 明确指定类型,防止在 Form 中意外触发提交
|
||||
className={`btn ${isRunningTimestamp ? 'stop-btn' : 'action-btn'}`}
|
||||
onClick={toggleTimestamp}
|
||||
aria-label={isRunningTimestamp ? '停止时间戳更新' : '开始时间戳更新'}
|
||||
>
|
||||
{isRunningTimestamp ? '停止' : '开始'}
|
||||
</button>
|
||||
</div>
|
||||
</div>)
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import {useMemo, useState} from "react";
|
||||
import {formatWithZone, TimezoneOptions} from "../utils/timeUtils";
|
||||
|
||||
export function TimestampToDatetime() {
|
||||
const [timestampValue, setTimestampValue] = useState(Date.now());
|
||||
const [timestampResult, setTimestampResult] = useState('');
|
||||
const [unit, setUnit] = useState('milliseconds');
|
||||
const [selectedZone, setSelectedZone] = useState('Asia/Shanghai');
|
||||
const timeZoneList = useMemo(() => ['America/New_York', 'America/Chicago', 'America/Denver', 'America/Los_Angeles', 'America/Anchorage', 'America/Honolulu', 'Europe/London', 'Europe/Paris', 'Europe/Berlin', 'Europe/Moscow', 'Asia/Tokyo', 'Asia/Shanghai', 'Asia/Hong_Kong', 'Asia/Singapore', 'Asia/Dubai', 'Asia/Kolkata', 'Australia/Sydney', 'Pacific/Auckland',], []);
|
||||
|
||||
function handleConvertTimestampToDate() {
|
||||
if (!timestampValue) {
|
||||
setTimestampResult('请输入时间戳');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = formatWithZone(timestampValue, selectedZone, unit);
|
||||
setTimestampResult(result);
|
||||
}
|
||||
|
||||
const handleInputChange = (e) => setTimestampValue(e.target.value);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>时间戳转日期时间</h2>
|
||||
<div>
|
||||
<div style={{display: 'flex', gap: '8px', alignItems: 'center'}}>
|
||||
<input
|
||||
type="number"
|
||||
placeholder="请输入时间戳"
|
||||
value={timestampValue}
|
||||
onChange={handleInputChange}
|
||||
style={{minWidth: '200px'}}
|
||||
/>
|
||||
<select
|
||||
value={unit}
|
||||
aria-label='选择时间戳单位'
|
||||
onChange={(e) => setUnit(e.target.value)}
|
||||
>
|
||||
<option value="milliseconds">毫秒(ms)</option>
|
||||
<option value="seconds">秒(s)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div style={{marginBottom: '20px'}}>
|
||||
<button className={'action-btn'} onClick={handleConvertTimestampToDate}>
|
||||
转换
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div style={{display: 'flex', gap: '8px', alignItems: 'center'}}>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="转换结果"
|
||||
value={timestampResult}
|
||||
readOnly
|
||||
style={{minWidth: '200px'}}
|
||||
/>
|
||||
<select
|
||||
value={selectedZone}
|
||||
onChange={(e) => setSelectedZone(e.target.value)}
|
||||
>
|
||||
<TimezoneOptions zones={timeZoneList}/>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
+9
-157
@@ -1,163 +1,15 @@
|
||||
import {useState, useEffect} from 'react';
|
||||
import CopyButton from "../components/CopyButton";
|
||||
import {formatDate} from "../utils/timeUtils";
|
||||
import {TimestampToDatetime} from "../components/TimestampToDatetime";
|
||||
import {DatetimeToTimestamp} from "../components/DatetimeToTimestamp";
|
||||
import {TimestampExecution} from "../components/TimestampExecution";
|
||||
|
||||
|
||||
const TimestampPage = () => {
|
||||
const [currentTimestamp, setCurrentTimestamp] = useState(Math.floor(Date.now()));
|
||||
const [showMilliseconds, setShowMilliseconds] = useState(true);
|
||||
const [isRunningTimestamp, setIsRunningTimestamp] = useState(true);
|
||||
const [timestampValue, setTimestampValue] = useState(Date.now());
|
||||
const [dateValue, setDateValue] = useState(formatDate(Date.now()));
|
||||
const [timestampResult, setTimestampResult] = useState('');
|
||||
const [dateResult, setDateResult] = useState('');
|
||||
|
||||
// 定时更新时间戳
|
||||
useEffect(() => {
|
||||
let timerId = null;
|
||||
|
||||
if (isRunningTimestamp) {
|
||||
timerId = setInterval(() => {
|
||||
setCurrentTimestamp(Math.floor(Date.now()));
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (timerId) {
|
||||
clearInterval(timerId);
|
||||
timerId = null;
|
||||
}
|
||||
}
|
||||
}, [isRunningTimestamp]);
|
||||
|
||||
function toggleUnit() {
|
||||
setShowMilliseconds(prev => !prev);
|
||||
}
|
||||
|
||||
function toggleTimestamp() {
|
||||
setIsRunningTimestamp((prev => !prev));
|
||||
}
|
||||
|
||||
function handleConvertTimestampToDate() {
|
||||
if (!timestampValue) {
|
||||
setTimestampResult('请输入时间戳');
|
||||
return;
|
||||
}
|
||||
|
||||
setTimestampResult(formatDate(new Date(Number(timestampValue))));
|
||||
}
|
||||
|
||||
function handleConvertDateToTimestamp() {
|
||||
if (!dateValue) {
|
||||
setDateResult('请输入日期222');
|
||||
return;
|
||||
}
|
||||
|
||||
const date = new Date(dateValue);
|
||||
|
||||
if (isNaN(date.getTime())) {
|
||||
setDateResult('请输入日期333');
|
||||
return;
|
||||
}
|
||||
|
||||
setDateResult(date.getTime());
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
<h2>当前时间戳</h2>
|
||||
<p>
|
||||
<span>{Math.floor(currentTimestamp / (showMilliseconds ? 1 : 1000))}</span>
|
||||
<span>{showMilliseconds ? '毫秒' : '秒'}</span>
|
||||
</p>
|
||||
<div>
|
||||
<button type="button"
|
||||
className="action-btn"
|
||||
onClick={toggleUnit}
|
||||
aria-label={showMilliseconds ? '切换为秒' : '切换为毫秒'}
|
||||
>
|
||||
切换单位
|
||||
</button>
|
||||
<CopyButton text={currentTimestamp.toString()} buttonText='复制'></CopyButton>
|
||||
<button
|
||||
type="button" // 明确指定类型,防止在 Form 中意外触发提交
|
||||
className={`btn ${isRunningTimestamp ? 'stop-btn' : 'action-btn'}`}
|
||||
onClick={toggleTimestamp}
|
||||
aria-label={isRunningTimestamp ? '停止时间戳更新' : '开始时间戳更新'}
|
||||
>
|
||||
{isRunningTimestamp ? '停止' : '开始'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2>时间戳转日期时间</h2>
|
||||
<div>
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="请输入时间戳"
|
||||
value={timestampValue}
|
||||
onChange={(e) => setTimestampValue(Number(e.target.value))}/>
|
||||
<label aria-label={'选择时间戳单位'}>
|
||||
<select name='时间戳单位' defaultChecked='milliseconds'>
|
||||
<option value="milliseconds">毫秒(ms)</option>
|
||||
<option value="seconds">秒(s)</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button className={'action-btn'} onClick={handleConvertTimestampToDate}>
|
||||
转换
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="转换结果"
|
||||
value={timestampResult}
|
||||
readOnly
|
||||
/>
|
||||
<select></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2>日期时间转时间戳</h2>
|
||||
<div className="datetime-box">
|
||||
<div className="input-group">
|
||||
<input type="text" id="datetime-input" placeholder="输入日期时间" className="input-text" value={dateValue}
|
||||
onChange={(e) => setDateValue(e.target.value)}/>
|
||||
<select className="select-box" id="timezone-input"></select>
|
||||
</div>
|
||||
|
||||
<div className="input-group">
|
||||
<button id="convert-date-to-timestamp-btn" className="action-btn" onClick={handleConvertDateToTimestamp}>
|
||||
转换
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="input-group">
|
||||
<input
|
||||
type="text"
|
||||
id="date-conversion-result"
|
||||
placeholder="转换结果"
|
||||
className="input-text"
|
||||
value={dateResult}
|
||||
readOnly
|
||||
/>
|
||||
<select id="date-result-unit-select" className="select-box">
|
||||
<option value="milliseconds">毫秒(ms)</option>
|
||||
<option value="seconds">秒(s)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (<div>
|
||||
<TimestampExecution/>
|
||||
<TimestampToDatetime/>
|
||||
<DatetimeToTimestamp/>
|
||||
</div>);
|
||||
};
|
||||
|
||||
export default TimestampPage;
|
||||
|
||||
+59
-1
@@ -1,4 +1,4 @@
|
||||
export function formatDate(value) {
|
||||
export function formatDate(value, timezone = 'Asia/Shanghai') {
|
||||
return (new Intl.DateTimeFormat('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: 'numeric',
|
||||
@@ -6,6 +6,64 @@ export function formatDate(value) {
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
second: 'numeric',
|
||||
timeZone: timezone,
|
||||
})).format(value);
|
||||
}
|
||||
|
||||
export const TimezoneOptions = ({zones}) => {
|
||||
return (zones.map((zone) => (<option key={zone} value={zone}>{zone}</option>)));
|
||||
}
|
||||
|
||||
export const formatWithZone = (timestamp, zone = 'Asia/Shanghai', unit = 'milliseconds') => {
|
||||
try {
|
||||
const ms = unit === 'milliseconds' ? Number(timestamp) : Number(timestamp) * 1000;
|
||||
return new Intl.DateTimeFormat('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: 'numeric',
|
||||
day: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
second: 'numeric',
|
||||
timeZone: zone,
|
||||
}).format(ms);
|
||||
} catch (e) {
|
||||
return '格式错误';
|
||||
}
|
||||
}
|
||||
|
||||
export const getTimeZoneOffset = (timeZone) => {
|
||||
const now = new Date();
|
||||
const utc = new Date(now.toLocaleString('en-US', {timeZone: 'UTC'}));
|
||||
const target = new Date(now.toLocaleString('en-US', {timeZone: timeZone}));
|
||||
return target.getTime() - utc.getTime();
|
||||
}
|
||||
|
||||
export const formatWithDate = (
|
||||
date,
|
||||
timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||
) => {
|
||||
try {
|
||||
// 创建日期对象
|
||||
const dateObj = new Date(date);
|
||||
|
||||
// 检查日期是否有效
|
||||
if (isNaN(dateObj.getTime())) {
|
||||
return '无效的日期';
|
||||
}
|
||||
|
||||
// 如果提供了时区,则需要特殊处理
|
||||
if (timeZone) {
|
||||
// 获取给定时区相对于UTC的时间差(毫秒)
|
||||
const utc = dateObj.getTime() + dateObj.getTimezoneOffset() * 60000;
|
||||
|
||||
// 计算目标时区相对于UTC的偏移量
|
||||
const targetOffset = getTimeZoneOffset(timeZone);
|
||||
|
||||
return utc + targetOffset;
|
||||
} else {
|
||||
return dateObj.getTime();
|
||||
}
|
||||
} catch (error) {
|
||||
return '日期转换错误: ' + error.message;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user