feat(timestamp): 实现时间戳与日期时间转换功能

- 新增日期时间转时间戳组件 DatetimeToTimestamp
- 新增时间戳转日期时间组件 TimestampToDatetime
- 新增当前时间戳显示组件 TimestampExecution
- 重构 TimestampPage 页面,拆分功能到独立组件
- 扩展 timeUtils 工具函数,支持时区处理和格式化
- 添加时区选择器和时间单位切换功能
- 优化时间戳转换逻辑,支持毫秒和秒单位切换
- 改进 App.css 样式宽度设置
This commit is contained in:
雨霖铃
2025-12-19 22:25:27 +08:00
parent 4f1a78ebb5
commit d05a8a7065
6 changed files with 243 additions and 159 deletions
+1 -1
View File
@@ -3,7 +3,7 @@
margin: 0 auto; margin: 0 auto;
padding: 20px; padding: 20px;
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
width: 300px; width: 350px;
height: 100vh; height: 100vh;
} }
+60
View File
@@ -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>)
}
+45
View File
@@ -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>)
}
+69
View File
@@ -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
View File
@@ -1,163 +1,15 @@
import {useState, useEffect} from 'react'; import {TimestampToDatetime} from "../components/TimestampToDatetime";
import CopyButton from "../components/CopyButton"; import {DatetimeToTimestamp} from "../components/DatetimeToTimestamp";
import {formatDate} from "../utils/timeUtils"; import {TimestampExecution} from "../components/TimestampExecution";
const TimestampPage = () => { 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('');
// 定时更新时间戳 return (<div>
useEffect(() => { <TimestampExecution/>
let timerId = null; <TimestampToDatetime/>
<DatetimeToTimestamp/>
if (isRunningTimestamp) { </div>);
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>
);
}; };
export default TimestampPage; export default TimestampPage;
+59 -1
View File
@@ -1,4 +1,4 @@
export function formatDate(value) { export function formatDate(value, timezone = 'Asia/Shanghai') {
return (new Intl.DateTimeFormat('zh-CN', { return (new Intl.DateTimeFormat('zh-CN', {
year: 'numeric', year: 'numeric',
month: 'numeric', month: 'numeric',
@@ -6,6 +6,64 @@ export function formatDate(value) {
hour: 'numeric', hour: 'numeric',
minute: 'numeric', minute: 'numeric',
second: 'numeric', second: 'numeric',
timeZone: timezone,
})).format(value); })).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;
}
}