计算日期间隔计算器代码

https://tool.52ipc.top/DateTimeCalculator.html
F12 自取
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>日期时间计算器 - 精确计算日期间隔、推算目标日期、量化时间差异</title>
<meta name="description" content="免费在线日期时间计算器,支持日期间隔计算、日期推算、时间差精确计算。可计算两个日期之间相差多少天、周、月、年,支持闰年判断、ISO 周数查询等功能。">
<meta name="keywords" content="日期计算器,时间计算器,日期间隔计算,日期推算,时间差计算,天数计算,闰年查询,ISO 周数,工作日计算,日期工具">
<meta name="author" content="DateTime Calculator">
<meta name="robots" content="index, follow">
<!-- <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" rel="stylesheet">-->
<link href="https://cdn.bootcdn.net/ajax/libs/font-awesome/7.0.1/css/all.min.css" rel="stylesheet">
<style>
:root {
--bg: #0c0f14;
--bg2: #141821;
--card: #1a1f2e;
--card-hover: #1f2538;
--border: #2a3042;
--fg: #e8ecf4;
--fg-dim: #8892a8;
--accent: #e8943a;
--accent-glow: rgba(232, 148, 58, 0.25);
--accent2: #3ae8b0;
--accent2-glow: rgba(58, 232, 176, 0.15);
--danger: #e85a5a;
--radius: 14px;
--radius-sm: 8px;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Noto Sans SC', 'Space Grotesk', sans-serif;
background: var(--bg); color: var(--fg);
min-height: 100vh; overflow-x: hidden; position: relative;
}
/* 动态背景 */
.bg-layer { position: fixed; inset: 0; z-index: 0; pointer-events: none; overflow: hidden; }
.bg-blob {
position: absolute; border-radius: 50%; filter: blur(100px); opacity: 0.3;
animation: blobFloat 22s ease-in-out infinite alternate;
}
.bg-blob:nth-child(1) { width: 500px; height: 500px; background: var(--accent); top: -15%; left: -10%; }
.bg-blob:nth-child(2) { width: 400px; height: 400px; background: var(--accent2); bottom: -10%; right: -5%; animation-delay: -8s; opacity: 0.18; }
.bg-blob:nth-child(3) { width: 300px; height: 300px; background: #6366f1; top: 50%; left: 60%; animation-delay: -15s; opacity: 0.12; }
@keyframes blobFloat {
0% { transform: translate(0,0) scale(1); }
33% { transform: translate(40px,-30px) scale(1.05); }
66% { transform: translate(-20px,20px) scale(0.95); }
100% { transform: translate(30px,-10px) scale(1.02); }
}
.bg-grid {
position: fixed; inset: 0; z-index: 0; pointer-events: none;
background-image: linear-gradient(rgba(255,255,255,0.015) 1px, transparent 1px),
linear-gradient(90deg, rgba(255,255,255,0.015) 1px, transparent 1px);
background-size: 60px 60px;
}
.container { position: relative; z-index: 1; max-width: 960px; margin: 0 auto; padding: 30px 20px 60px; }
/* 顶部 */
header { text-align: center; margin-bottom: 36px; }
.logo-row { display: flex; align-items: center; justify-content: center; gap: 14px; margin-bottom: 8px; }
.logo-icon {
width: 48px; height: 48px; border-radius: 12px;
background: linear-gradient(135deg, var(--accent), #d47a20);
display: flex; align-items: center; justify-content: center;
font-size: 22px; color: #fff; box-shadow: 0 4px 20px var(--accent-glow);
}
header h1 {
font-family: 'Space Grotesk', sans-serif; font-size: 32px; font-weight: 700;
letter-spacing: -0.5px;
background: linear-gradient(135deg, var(--fg), var(--accent));
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
}
header p { color: var(--fg-dim); font-size: 14px; margin-top: 4px; }
/* 实时时钟 */
.clock-bar {
background: var(--card); border: 1px solid var(--border); border-radius: var(--radius);
padding: 16px 24px; display: flex; align-items: center; justify-content: center;
gap: 20px; margin-bottom: 28px; flex-wrap: wrap;
}
.clock-time {
font-family: 'Space Grotesk', sans-serif; font-size: 36px; font-weight: 700;
letter-spacing: 2px; color: var(--accent); text-shadow: 0 0 30px var(--accent-glow);
}
.clock-date { font-size: 15px; color: var(--fg-dim); }
.clock-seconds { font-size: 18px; color: var(--accent2); font-weight: 500; min-width: 28px; text-align: center; }
.clock-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--accent); animation: pulse 1s ease-in-out infinite; }
@keyframes pulse { 0%,100%{ opacity:1; transform:scale(1); } 50%{ opacity:0.3; transform:scale(0.7); } }
/* 标签导航 */
.tabs {
display: flex; gap: 6px; margin-bottom: 24px;
background: var(--bg2); border-radius: 12px; padding: 5px; border: 1px solid var(--border);
}
.tab-btn {
flex: 1; padding: 12px 16px; border: none; background: transparent;
color: var(--fg-dim); font-size: 14px; font-weight: 500; border-radius: 9px;
cursor: pointer; transition: all 0.3s; font-family: inherit;
display: flex; align-items: center; justify-content: center; gap: 8px;
}
.tab-btn:hover { color: var(--fg); background: rgba(255,255,255,0.04); }
.tab-btn.active { background: var(--card); color: var(--accent); box-shadow: 0 2px 12px rgba(0,0,0,0.3); }
.tab-btn i { font-size: 15px; }
/* 面板 */
.panel { display: none; animation: fadeUp 0.35s ease; }
.panel.active { display: block; }
@keyframes fadeUp { from { opacity:0; transform:translateY(12px); } to { opacity:1; transform:translateY(0); } }
/* 卡片 */
.card {
background: var(--card); border: 1px solid var(--border); border-radius: var(--radius);
padding: 28px; margin-bottom: 20px; transition: border-color 0.3s, box-shadow 0.3s;
}
.card:hover { border-color: rgba(232,148,58,0.2); box-shadow: 0 4px 30px rgba(0,0,0,0.2); }
.card-title { font-size: 16px; font-weight: 600; margin-bottom: 20px; display: flex; align-items: center; gap: 10px; }
.card-title i { color: var(--accent); font-size: 18px; }
/* 表单 */
.form-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 14px; }
.form-group { display: flex; flex-direction: column; gap: 6px; }
.form-group.full { grid-column: 1 / -1; }
.form-label { font-size: 12px; color: var(--fg-dim); font-weight: 500; text-transform: uppercase; letter-spacing: 0.5px; }
.form-input, .form-select {
background: var(--bg2); border: 1px solid var(--border); border-radius: var(--radius-sm);
padding: 11px 14px; color: var(--fg); font-size: 14px; font-family: inherit;
transition: border-color 0.25s, box-shadow 0.25s; outline: none; width: 100%;
}
.form-input:focus, .form-select:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-glow); }
.form-select {
cursor: pointer; appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%238892a8' viewBox='0 0 16 16'%3E%3Cpath d='M8 11L3 6h10z'/%3E%3C/svg%3E");
background-repeat: no-repeat; background-position: right 12px center; padding-right: 32px;
}
.form-select option { background: var(--card); color: var(--fg); }
/* 隐藏原生日期/时间输入 */
.native-hidden { position: absolute; width: 0; height: 0; opacity: 0; pointer-events: none; }
/* ============ 自定义日期选择器 ============ */
.dp-trigger {
display: flex; align-items: center; gap: 10px;
background: var(--bg2); border: 1px solid var(--border); border-radius: var(--radius-sm);
padding: 10px 14px; cursor: pointer; transition: border-color 0.25s, box-shadow 0.25s;
user-select: none; position: relative;
}
.dp-trigger:hover { border-color: rgba(232,148,58,0.4); }
.dp-trigger.active { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-glow); }
.dp-trigger i.fa-calendar { color: var(--accent); font-size: 16px; }
.dp-trigger-text { flex: 1; font-size: 14px; color: var(--fg); }
.dp-trigger-text.placeholder { color: var(--fg-dim); }
.dp-trigger .fa-angle-down { color: var(--fg-dim); font-size: 12px; transition: transform 0.25s; }
.dp-trigger.active .fa-angle-down { transform: rotate(180deg); }
/* 时间触发器 */
.tp-trigger {
display: flex; align-items: center; gap: 10px;
background: var(--bg2); border: 1px solid var(--border); border-radius: var(--radius-sm);
padding: 10px 14px; cursor: pointer; transition: border-color 0.25s, box-shadow 0.25s;
user-select: none;
}
.tp-trigger:hover { border-color: rgba(232,148,58,0.4); }
.tp-trigger.active { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-glow); }
.tp-trigger i.fa-clock { color: var(--accent2); font-size: 16px; }
.tp-trigger-text { flex: 1; font-size: 14px; color: var(--fg); font-family: 'Space Grotesk', sans-serif; }
.tp-trigger-text.placeholder { color: var(--fg-dim); font-family: inherit; }
/* 弹出层 */
.picker-overlay {
position: fixed; inset: 0; z-index: 100; display: none;
}
.picker-overlay.open { display: block; }
/* 日历面板 */
.calendar-panel {
position: absolute; z-index: 110;
background: var(--card); border: 1px solid var(--border);
border-radius: var(--radius); padding: 16px;
box-shadow: 0 12px 48px rgba(0,0,0,0.5), 0 0 0 1px rgba(255,255,255,0.03);
min-width: 300px; display: none;
animation: calFadeIn 0.2s ease;
}
.calendar-panel.open { display: block; }
@keyframes calFadeIn { from { opacity:0; transform:translateY(-6px); } to { opacity:1; transform:translateY(0); } }
.cal-header {
display: flex; align-items: center; justify-content: space-between; margin-bottom: 14px;
}
.cal-header-title {
font-family: 'Space Grotesk', sans-serif; font-size: 15px; font-weight: 600;
color: var(--fg); cursor: pointer; padding: 4px 8px; border-radius: 6px;
transition: background 0.2s;
}
.cal-header-title:hover { background: rgba(255,255,255,0.06); }
.cal-nav {
width: 32px; height: 32px; border: none; background: transparent;
color: var(--fg-dim); border-radius: 8px; cursor: pointer;
display: flex; align-items: center; justify-content: center;
transition: all 0.2s; font-size: 14px;
}
.cal-nav:hover { background: rgba(255,255,255,0.08); color: var(--fg); }
.cal-weekdays {
display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; margin-bottom: 6px;
}
.cal-weekday {
text-align: center; font-size: 11px; color: var(--fg-dim); padding: 4px 0;
font-weight: 600; text-transform: uppercase;
}
.cal-weekday.weekend { color: rgba(232,148,58,0.5); }
.cal-days { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.cal-day {
aspect-ratio: 1; display: flex; align-items: center; justify-content: center;
font-size: 13px; border-radius: 8px; cursor: pointer; transition: all 0.15s;
color: var(--fg); border: none; background: transparent; font-family: 'Space Grotesk', sans-serif;
position: relative;
}
.cal-day:hover { background: rgba(255,255,255,0.08); }
.cal-day.other-month { color: var(--fg-dim); opacity: 0.35; }
.cal-day.other-month:hover { opacity: 0.6; }
.cal-day.today { color: var(--accent); font-weight: 700; }
.cal-day.today::after {
content: ''; position: absolute; bottom: 3px; left: 50%; transform: translateX(-50%);
width: 4px; height: 4px; border-radius: 50%; background: var(--accent);
}
.cal-day.selected {
background: var(--accent) !important; color: #fff !important; font-weight: 700;
box-shadow: 0 2px 10px var(--accent-glow);
}
.cal-day.selected::after { display: none; }
.cal-day.in-range { background: rgba(232,148,58,0.1); }
.cal-day.range-start { border-radius: 8px 0 0 8px; }
.cal-day.range-end { border-radius: 0 8px 8px 0; }
.cal-day.range-start.range-end { border-radius: 8px; }
.cal-day.weekend-day { color: rgba(232,148,58,0.7); }
.cal-day.weekend-day.selected { color: #fff; }
.cal-footer {
display: flex; justify-content: space-between; align-items: center;
margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border);
}
.cal-today-btn {
padding: 6px 14px; border: 1px solid var(--border); background: transparent;
color: var(--fg-dim); border-radius: 6px; font-size: 12px; cursor: pointer;
transition: all 0.2s; font-family: inherit;
}
.cal-today-btn:hover { border-color: var(--accent); color: var(--accent); }
.cal-clear-btn {
padding: 6px 14px; border: 1px solid var(--border); background: transparent;
color: var(--fg-dim); border-radius: 6px; font-size: 12px; cursor: pointer;
transition: all 0.2s; font-family: inherit;
}
.cal-clear-btn:hover { border-color: var(--danger); color: var(--danger); }
/* 年月选择器 */
.ym-panel {
position: absolute; z-index: 115;
background: var(--card); border: 1px solid var(--border);
border-radius: var(--radius); padding: 16px;
box-shadow: 0 12px 48px rgba(0,0,0,0.5);
min-width: 300px; display: none;
animation: calFadeIn 0.2s ease;
}
.ym-panel.open { display: block; }
.ym-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 14px; }
.ym-header-title { font-family: 'Space Grotesk', sans-serif; font-size: 15px; font-weight: 600; }
.ym-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; }
.ym-cell {
padding: 10px 8px; text-align: center; border-radius: 8px;
cursor: pointer; font-size: 13px; color: var(--fg);
border: 1px solid transparent; transition: all 0.15s; background: transparent;
font-family: inherit;
}
.ym-cell:hover { background: rgba(255,255,255,0.06); border-color: var(--border); }
.ym-cell.current { border-color: var(--accent); color: var(--accent); font-weight: 600; }
.ym-cell.selected { background: var(--accent); color: #fff; border-color: var(--accent); font-weight: 600; }
/* ============ 时间选择器 ============ */
.time-panel {
position: absolute; z-index: 110;
background: var(--card); border: 1px solid var(--border);
border-radius: var(--radius); padding: 20px 16px;
box-shadow: 0 12px 48px rgba(0,0,0,0.5);
min-width: 240px; display: none;
animation: calFadeIn 0.2s ease;
}
.time-panel.open { display: block; }
.time-col-wrap { display: flex; gap: 6px; justify-content: center; align-items: center; }
.time-col { display: flex; flex-direction: column; align-items: center; gap: 4px; }
.time-col-label { font-size: 10px; color: var(--fg-dim); text-transform: uppercase; letter-spacing: 1px; margin-bottom: 2px; }
.time-arrow {
width: 36px; height: 28px; border: none; background: transparent;
color: var(--fg-dim); border-radius: 6px; cursor: pointer;
display: flex; align-items: center; justify-content: center;
transition: all 0.15s; font-size: 14px;
}
.time-arrow:hover { background: rgba(255,255,255,0.08); color: var(--fg); }
.time-arrow:active { background: rgba(232,148,58,0.15); color: var(--accent); }
.time-value {
font-family: 'Space Grotesk', sans-serif; font-size: 32px; font-weight: 700;
color: var(--fg); min-width: 48px; text-align: center; line-height: 1;
padding: 4px 0;
}
.time-sep { font-family: 'Space Grotesk', sans-serif; font-size: 28px; font-weight: 700; color: var(--fg-dim); padding-top: 18px; }
.time-footer { display: flex; justify-content: center; margin-top: 14px; gap: 8px; }
.time-ok-btn {
padding: 8px 24px; border: none; border-radius: 6px;
background: var(--accent); color: #fff; font-size: 13px;
font-weight: 600; cursor: pointer; font-family: inherit;
transition: all 0.2s; box-shadow: 0 2px 10px var(--accent-glow);
}
.time-ok-btn:hover { transform: translateY(-1px); box-shadow: 0 4px 16px var(--accent-glow); }
.time-now-btn {
padding: 8px 16px; border: 1px solid var(--border); background: transparent;
color: var(--fg-dim); border-radius: 6px; font-size: 13px;
cursor: pointer; font-family: inherit; transition: all 0.2s;
}
.time-now-btn:hover { border-color: var(--accent2); color: var(--accent2); }
/* 交换按钮 */
.swap-btn {
display: flex; align-items: center; justify-content: center;
width: 40px; height: 40px; border-radius: 50%;
background: var(--bg2); border: 1px solid var(--border);
color: var(--fg-dim); cursor: pointer; transition: all 0.3s;
margin: 6px auto; font-size: 14px;
}
.swap-btn:hover { background: var(--accent); color: #fff; border-color: var(--accent); transform: rotate(180deg); }
/* 计算按钮 */
.calc-btn {
display: flex; align-items: center; justify-content: center; gap: 8px;
width: 100%; padding: 14px; border: none; border-radius: var(--radius-sm);
background: linear-gradient(135deg, var(--accent), #d47a20);
color: #fff; font-size: 15px; font-weight: 600; font-family: inherit;
cursor: pointer; transition: all 0.3s;
box-shadow: 0 4px 16px var(--accent-glow); margin-top: 20px;
}
.calc-btn:hover { transform: translateY(-1px); box-shadow: 0 6px 24px var(--accent-glow); }
.calc-btn:active { transform: translateY(0); }
/* 结果 */
.result-area { display: none; animation: fadeUp 0.4s ease; }
.result-area.show { display: block; }
.result-box {
background: linear-gradient(135deg, rgba(232,148,58,0.08), rgba(58,232,176,0.05));
border: 1px solid rgba(232,148,58,0.2); border-radius: var(--radius); padding: 24px; margin-top: 20px;
}
.result-main {
font-family: 'Space Grotesk', sans-serif; font-size: 26px; font-weight: 700;
color: var(--accent); margin-bottom: 16px; line-height: 1.3;
text-shadow: 0 0 20px var(--accent-glow);
}
.result-details { display: grid; grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); gap: 12px; }
.result-item {
background: rgba(0,0,0,0.2); border-radius: var(--radius-sm); padding: 14px;
text-align: center; border: 1px solid rgba(255,255,255,0.04); transition: transform 0.2s, border-color 0.2s;
}
.result-item:hover { transform: translateY(-2px); border-color: rgba(232,148,58,0.3); }
.result-value { font-family: 'Space Grotesk', sans-serif; font-size: 24px; font-weight: 700; color: var(--fg); display: block; }
.result-label { font-size: 11px; color: var(--fg-dim); margin-top: 4px; text-transform: uppercase; letter-spacing: 0.5px; }
.result-date-display {
font-family: 'Space Grotesk', sans-serif; font-size: 32px; font-weight: 700;
background: linear-gradient(135deg, var(--accent), var(--accent2));
-webkit-background-clip: text; -webkit-text-fill-color: transparent; margin-bottom: 8px;
}
.result-weekday { font-size: 16px; color: var(--accent2); margin-bottom: 12px; }
.time-result-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; }
.time-result-item {
text-align: center; padding: 16px 8px; background: rgba(0,0,0,0.25);
border-radius: var(--radius-sm); border: 1px solid rgba(255,255,255,0.04);
}
.time-result-val { font-family: 'Space Grotesk', sans-serif; font-size: 32px; font-weight: 700; color: var(--fg); }
.time-result-unit { font-size: 12px; color: var(--fg-dim); margin-top: 4px; }
.time-total-row {
margin-top: 8px; padding: 10px 16px; background: rgba(0,0,0,0.15);
border-radius: var(--radius-sm); display: flex; justify-content: space-between;
align-items: center; font-size: 13px; color: var(--fg-dim);
}
.time-total-row span:last-child {
font-family: 'Space Grotesk', sans-serif; color: var(--accent2); font-weight: 600; font-size: 15px;
}
/* 快捷按钮 */
.quick-btns { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.quick-btn {
padding: 7px 14px; border: 1px solid var(--border); background: var(--bg2);
color: var(--fg-dim); border-radius: 20px; font-size: 12px; cursor: pointer;
transition: all 0.25s; font-family: inherit;
}
.quick-btn:hover { border-color: var(--accent); color: var(--accent); background: rgba(232,148,58,0.08); }
/* Toast */
.toast {
position: fixed; top: 20px; right: 20px; z-index: 999;
background: var(--danger); color: #fff; padding: 12px 20px;
border-radius: var(--radius-sm); font-size: 13px;
box-shadow: 0 4px 20px rgba(232,90,90,0.3);
transform: translateX(120%); transition: transform 0.35s ease;
}
.toast.show { transform: translateX(0); }
/* 响应式 */
@media (max-width: 600px) {
header h1 { font-size: 24px; }
.clock-time { font-size: 26px; }
.tabs { flex-direction: column; }
.tab-btn { justify-content: flex-start; padding: 10px 16px; }
.result-main { font-size: 20px; }
.result-date-display { font-size: 24px; }
.time-result-grid { grid-template-columns: repeat(2, 1fr); }
.form-grid { grid-template-columns: 1fr; }
.card { padding: 20px; }
.calendar-panel { min-width: 280px; }
.cal-day { font-size: 12px; }
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}
</style>
</head>
<body>
<div class="bg-layer" aria-hidden="true">
<div class="bg-blob"></div><div class="bg-blob"></div><div class="bg-blob"></div>
</div>
<div class="bg-grid" aria-hidden="true"></div>
<div class="toast" id="toast" role="alert"></div>
<div class="container">
<header>
<div class="logo-row">
<div class="logo-icon"><i class="fa-solid fa-calendar-days"></i></div>
<h1>DateTime Calculator</h1>
</div>
<p>精确计算日期间隔、推算目标日期、量化时间差异</p>
</header>
<!-- 实时时钟 -->
<div class="clock-bar" aria-label="实时时钟">
<div>
<div class="clock-date" id="clockDate"></div>
<div style="display:flex;align-items:baseline;gap:4px;margin-top:2px;">
<span class="clock-time" id="clockTime"></span>
<span class="clock-seconds" id="clockSec"></span>
</div>
</div>
<div class="clock-dot" aria-hidden="true"></div>
<div style="text-align:right;">
<div style="font-size:12px;color:var(--fg-dim);text-transform:uppercase;letter-spacing:1px;">星期</div>
<div style="font-size:20px;font-weight:600;color:var(--accent2);margin-top:2px;" id="clockWeekday"></div>
</div>
</div>
<!-- 标签导航 -->
<nav class="tabs" role="tablist" aria-label="计算模式">
<button class="tab-btn active" role="tab" data-tab="interval" aria-selected="true"><i class="fa-solid fa-arrows-left-right"></i>日期间隔</button>
<button class="tab-btn" role="tab" data-tab="offset" aria-selected="false"><i class="fa-solid fa-forward"></i>日期推算</button>
<button class="tab-btn" role="tab" data-tab="timediff" aria-selected="false"><i class="fa-solid fa-clock"></i>时间差计算</button>
</nav>
<!-- 面板1:日期间隔 -->
<section class="panel active" id="panel-interval" role="tabpanel">
<div class="card">
<div class="card-title"><i class="fa-solid fa-ruler-horizontal"></i>计算两个日期之间的间隔</div>
<div class="form-grid">
<div class="form-group full">
<label class="form-label">起始日期</label>
<div class="dp-trigger" id="intStartTrigger" tabindex="0" role="combobox" aria-label="选择起始日期">
<i class="fa-solid fa-calendar"></i>
<span class="dp-trigger-text placeholder" id="intStartText">请选择日期</span>
<i class="fa-solid fa-angle-down"></i>
</div>
</div>
</div>
<div style="display:flex;justify-content:center;">
<button class="swap-btn" id="swapDates" aria-label="交换日期" title="交换日期"><i class="fa-solid fa-arrow-down-up-across-line"></i></button>
</div>
<div class="form-grid" style="margin-top:6px;">
<div class="form-group full">
<label class="form-label">结束日期</label>
<div class="dp-trigger" id="intEndTrigger" tabindex="0" role="combobox" aria-label="选择结束日期">
<i class="fa-solid fa-calendar"></i>
<span class="dp-trigger-text placeholder" id="intEndText">请选择日期</span>
<i class="fa-solid fa-angle-down"></i>
</div>
</div>
</div>
<div class="quick-btns">
<button class="quick-btn" data-quick="today">今天</button>
<button class="quick-btn" data-quick="yesterday">昨天</button>
<button class="quick-btn" data-quick="tomorrow">明天</button>
<button class="quick-btn" data-quick="thisMonthStart">本月第一天</button>
<button class="quick-btn" data-quick="thisMonthEnd">本月最后一天</button>
<button class="quick-btn" data-quick="thisYearStart">今年第一天</button>
<button class="quick-btn" data-quick="thisYearEnd">今年最后一天</button>
</div>
<button class="calc-btn" id="calcInterval"><i class="fa-solid fa-calculator"></i>计算间隔</button>
</div>
<div class="result-area" id="resultInterval">
<div class="result-box">
<div class="result-main" id="intMain"></div>
<div class="result-details" id="intDetails"></div>
<div style="margin-top:16px;">
<div class="time-total-row"><span>总小时数</span><span id="intHours"></span></div>
<div class="time-total-row"><span>总分钟数</span><span id="intMinutes"></span></div>
<div class="time-total-row"><span>总秒数</span><span id="intSeconds"></span></div>
</div>
</div>
</div>
</section>
<!-- 面板2:日期推算 -->
<section class="panel" id="panel-offset" role="tabpanel">
<div class="card">
<div class="card-title"><i class="fa-solid fa-calendar-plus"></i>从指定日期推算目标日期</div>
<div class="form-grid">
<div class="form-group full">
<label class="form-label">基准日期</label>
<div class="dp-trigger" id="offsetBaseTrigger" tabindex="0" role="combobox" aria-label="选择基准日期">
<i class="fa-solid fa-calendar"></i>
<span class="dp-trigger-text placeholder" id="offsetBaseText">请选择日期</span>
<i class="fa-solid fa-angle-down"></i>
</div>
</div>
<div class="form-group">
<label class="form-label">方向</label>
<select class="form-select" id="offsetDir" aria-label="方向">
<option value="1">之后 (+)</option>
<option value="-1">之前 (-)</option>
</select>
</div>
<div class="form-group"><label class="form-label">年</label><input type="number" class="form-input" id="offsetY" value="0" min="0" aria-label="年数"></div>
<div class="form-group"><label class="form-label">月</label><input type="number" class="form-input" id="offsetM" value="0" min="0" aria-label="月数"></div>
<div class="form-group"><label class="form-label">周</label><input type="number" class="form-input" id="offsetW" value="0" min="0" aria-label="周数"></div>
<div class="form-group"><label class="form-label">天</label><input type="number" class="form-input" id="offsetD" value="0" min="0" aria-label="天数"></div>
</div>
<div class="quick-btns">
<button class="quick-btn" data-off="100d">100天后</button>
<button class="quick-btn" data-off="-100d">100天前</button>
<button class="quick-btn" data-off="1y">1年后</button>
<button class="quick-btn" data-off="-1y">1年前</button>
<button class="quick-btn" data-off="6m">6个月后</button>
<button class="quick-btn" data-off="1w">1周后</button>
<button class="quick-btn" data-off="1000d">1000天后</button>
</div>
<button class="calc-btn" id="calcOffset"><i class="fa-solid fa-calculator"></i>推算日期</button>
</div>
<div class="result-area" id="resultOffset">
<div class="result-box">
<div class="result-date-display" id="offDate"></div>
<div class="result-weekday" id="offWeekday"></div>
<div class="result-details" id="offDetails"></div>
</div>
</div>
</section>
<!-- 面板3:时间差计算 -->
<section class="panel" id="panel-timediff" role="tabpanel">
<div class="card">
<div class="card-title"><i class="fa-solid fa-hourglass-half"></i>精确计算两个时间点的差异</div>
<div class="form-grid">
<div class="form-group">
<label class="form-label">起始日期</label>
<div class="dp-trigger" id="tdStartDTrigger" tabindex="0" role="combobox" aria-label="选择起始日期">
<i class="fa-solid fa-calendar"></i>
<span class="dp-trigger-text placeholder" id="tdStartDText">选择日期</span>
<i class="fa-solid fa-angle-down"></i>
</div>
</div>
<div class="form-group">
<label class="form-label">起始时间</label>
<div class="tp-trigger" id="tdStartTTrigger" tabindex="0" role="combobox" aria-label="选择起始时间">
<i class="fa-solid fa-clock"></i>
<span class="tp-trigger-text placeholder" id="tdStartTText">选择时间</span>
</div>
</div>
<div class="form-group">
<label class="form-label">结束日期</label>
<div class="dp-trigger" id="tdEndDTrigger" tabindex="0" role="combobox" aria-label="选择结束日期">
<i class="fa-solid fa-calendar"></i>
<span class="dp-trigger-text placeholder" id="tdEndDText">选择日期</span>
<i class="fa-solid fa-angle-down"></i>
</div>
</div>
<div class="form-group">
<label class="form-label">结束时间</label>
<div class="tp-trigger" id="tdEndTTrigger" tabindex="0" role="combobox" aria-label="选择结束时间">
<i class="fa-solid fa-clock"></i>
<span class="tp-trigger-text placeholder" id="tdEndTText">选择时间</span>
</div>
</div>
</div>
<div class="quick-btns">
<button class="quick-btn" data-td="nowToEnd">现在到今天结束</button>
<button class="quick-btn" data-td="startToNow">今天开始到现在</button>
<button class="quick-btn" data-td="now24h">过去24小时</button>
</div>
<button class="calc-btn" id="calcTimediff"><i class="fa-solid fa-calculator"></i>计算时间差</button>
</div>
<div class="result-area" id="resultTimediff">
<div class="result-box">
<div class="result-main" id="tdMain"></div>
<div class="time-result-grid" id="tdGrid"></div>
<div style="margin-top:14px;">
<div class="time-total-row"><span>总秒数</span><span id="tdTotalSec"></span></div>
<div class="time-total-row"><span>总分钟数</span><span id="tdTotalMin"></span></div>
<div class="time-total-row"><span>总小时数</span><span id="tdTotalHour"></span></div>
<div class="time-total-row"><span>总天数</span><span id="tdTotalDay"></span></div>
</div>
</div>
</div>
</section>
</div>
<!-- 全局弹窗遮罩 -->
<div class="picker-overlay" id="pickerOverlay"></div>
<!-- 日历面板(共享) -->
<div class="calendar-panel" id="calendarPanel">
<div class="cal-header">
<button class="cal-nav" id="calPrev" aria-label="上一月"><i class="fa-solid fa-chevron-left"></i></button>
<span class="cal-header-title" id="calTitle" role="button" tabindex="0">2024年1月</span>
<button class="cal-nav" id="calNext" aria-label="下一月"><i class="fa-solid fa-chevron-right"></i></button>
</div>
<div class="cal-weekdays" id="calWeekdays"></div>
<div class="cal-days" id="calDays"></div>
<div class="cal-footer">
<button class="cal-today-btn" id="calTodayBtn">今天</button>
<button class="cal-clear-btn" id="calClearBtn">清除</button>
</div>
</div>
<!-- 年月选择面板 -->
<div class="ym-panel" id="ymPanel">
<div class="ym-header">
<button class="cal-nav" id="ymPrev" aria-label="上一年"><i class="fa-solid fa-chevron-left"></i></button>
<span class="ym-header-title" id="ymTitle">2024年</span>
<button class="cal-nav" id="ymNext" aria-label="下一年"><i class="fa-solid fa-chevron-right"></i></button>
</div>
<div class="ym-grid" id="ymGrid"></div>
</div>
<!-- 时间选择面板(共享) -->
<div class="time-panel" id="timePanel">
<div class="time-col-wrap">
<div class="time-col">
<div class="time-col-label">时</div>
<button class="time-arrow" data-dir="up" data-field="hour"><i class="fa-solid fa-chevron-up"></i></button>
<div class="time-value" id="tpHour">00</div>
<button class="time-arrow" data-dir="down" data-field="hour"><i class="fa-solid fa-chevron-down"></i></button>
</div>
<div class="time-sep">:</div>
<div class="time-col">
<div class="time-col-label">分</div>
<button class="time-arrow" data-dir="up" data-field="min"><i class="fa-solid fa-chevron-up"></i></button>
<div class="time-value" id="tpMin">00</div>
<button class="time-arrow" data-dir="down" data-field="min"><i class="fa-solid fa-chevron-down"></i></button>
</div>
<div class="time-sep">:</div>
<div class="time-col">
<div class="time-col-label">秒</div>
<button class="time-arrow" data-dir="up" data-field="sec"><i class="fa-solid fa-chevron-up"></i></button>
<div class="time-value" id="tpSec">00</div>
<button class="time-arrow" data-dir="down" data-field="sec"><i class="fa-solid fa-chevron-down"></i></button>
</div>
</div>
<div class="time-footer">
<button class="time-now-btn" id="tpNowBtn">当前时间</button>
<button class="time-ok-btn" id="tpOkBtn">确定</button>
</div>
</div>
<script>
// ========== 工具 ==========
const $ = s => document.querySelector(s);
const $$ = s => document.querySelectorAll(s);
const pad = n => String(n).padStart(2, '0');
const WEEKDAYS = ['星期日','星期一','星期二','星期三','星期四','星期五','星期六'];
const WEEKDAY_SHORT = ['日','一','二','三','四','五','六'];
function showToast(msg) {
const t = $('#toast'); t.textContent = msg; t.classList.add('show');
clearTimeout(t._timer); t._timer = setTimeout(() => t.classList.remove('show'), 2800);
}
function formatDate(d) { return `${d.getFullYear()}年${pad(d.getMonth()+1)}月${pad(d.getDate())}日`; }
function numFmt(n) { return n.toLocaleString('zh-CN'); }
function dateStr(d) { return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}`; }
function timeStr(d) { return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`; }
function parseDateStr(s) { const p = s.split('-'); return new Date(+p[0], +p[1]-1, +p[2]); }
function parseTimeStr(s) { const p = s.split(':'); return { h: +p[0]||0, m: +p[1]||0, s: +p[2]||0 }; }
// ========== 实时时钟 ==========
function updateClock() {
const now = new Date();
$('#clockDate').textContent = formatDate(now);
$('#clockTime').textContent = `${pad(now.getHours())}:${pad(now.getMinutes())}`;
$('#clockSec').textContent = pad(now.getSeconds());
$('#clockWeekday').textContent = WEEKDAYS[now.getDay()];
}
updateClock(); setInterval(updateClock, 1000);
// ========== 标签切换 ==========
$$('.tab-btn').forEach(btn => {
btn.addEventListener('click', () => {
$$('.tab-btn').forEach(b => { b.classList.remove('active'); b.setAttribute('aria-selected','false'); });
btn.classList.add('active'); btn.setAttribute('aria-selected','true');
$$('.panel').forEach(p => p.classList.remove('active'));
$(`#panel-${btn.dataset.tab}`).classList.add('active');
closeAllPickers();
});
});
// ========== 日期选择器类 ==========
class DatePicker {
constructor(triggerId, textId) {
this.trigger = $(triggerId);
this.textEl = $(textId);
this.value = null; // "YYYY-MM-DD" 或 null
this.onSelect = null;
this._bindEvents();
}
_bindEvents() {
this.trigger.addEventListener('click', (e) => { e.stopPropagation(); datePickerManager.open(this); });
this.trigger.addEventListener('keydown', (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); datePickerManager.open(this); } });
}
setValue(dateString) {
this.value = dateString;
if (dateString) {
const d = parseDateStr(dateString);
this.textEl.textContent = formatDate(d);
this.textEl.classList.remove('placeholder');
} else {
this.textEl.textContent = this.trigger.closest('.panel')?.id === 'panel-timediff' ? '选择日期' : '请选择日期';
this.textEl.classList.add('placeholder');
}
}
getDate() { return this.value ? parseDateStr(this.value) : null; }
}
// ========== 时间选择器类 ==========
class TimePicker {
constructor(triggerId, textId) {
this.trigger = $(triggerId);
this.textEl = $(textId);
this.hour = 0; this.min = 0; this.sec = 0;
this.value = null; // "HH:MM:SS"
this.onSelect = null;
this._bindEvents();
}
_bindEvents() {
this.trigger.addEventListener('click', (e) => { e.stopPropagation(); timePickerManager.open(this); });
this.trigger.addEventListener('keydown', (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); timePickerManager.open(this); } });
}
setValue(timeString) {
this.value = timeString;
if (timeString) {
const t = parseTimeStr(timeString);
this.hour = t.h; this.min = t.m; this.sec = t.s;
this.textEl.textContent = timeString;
this.textEl.classList.remove('placeholder');
} else {
this.textEl.textContent = '选择时间';
this.textEl.classList.add('placeholder');
}
}
setFromComponents() {
this.value = `${pad(this.hour)}:${pad(this.min)}:${pad(this.sec)}`;
this.textEl.textContent = this.value;
this.textEl.classList.remove('placeholder');
}
}
// ========== 日期选择器管理器 ==========
const datePickerManager = {
panel: $('#calendarPanel'),
ymPanel: $('#ymPanel'),
overlay: $('#pickerOverlay'),
currentPicker: null,
viewYear: 0,
viewMonth: 0, // 0-based
ymViewYear: 0,
open(picker) {
closeAllPickers();
this.currentPicker = picker;
picker.trigger.classList.add('active');
if (picker.value) {
const d = parseDateStr(picker.value);
this.viewYear = d.getFullYear();
this.viewMonth = d.getMonth();
} else {
const now = new Date();
this.viewYear = now.getFullYear();
this.viewMonth = now.getMonth();
}
this.overlay.classList.add('open');
this._renderWeekdays();
this._renderDays();
this._positionPanel();
},
close() {
if (this.currentPicker) this.currentPicker.trigger.classList.remove('active');
this.currentPicker = null;
this.panel.classList.remove('open');
this.ymPanel.classList.remove('open');
this.overlay.classList.remove('open');
},
_renderWeekdays() {
const el = $('#calWeekdays');
el.innerHTML = WEEKDAY_SHORT.map((w, i) =>
`<div class="cal-weekday${(i===0||i===6)?' weekend':''}">${w}</div>`
).join('');
},
_renderDays() {
$('#calTitle').textContent = `${this.viewYear}年${this.viewMonth + 1}月`;
const el = $('#calDays');
el.innerHTML = '';
const firstDay = new Date(this.viewYear, this.viewMonth, 1);
const lastDay = new Date(this.viewYear, this.viewMonth + 1, 0);
const startDow = firstDay.getDay(); // 0=周日
const daysInMonth = lastDay.getDate();
// 上月填充
const prevMonthLast = new Date(this.viewYear, this.viewMonth, 0).getDate();
for (let i = startDow - 1; i >= 0; i--) {
const day = prevMonthLast - i;
const btn = document.createElement('button');
btn.className = 'cal-day other-month';
btn.textContent = day;
btn.addEventListener('click', () => { this.viewMonth--; if (this.viewMonth < 0) { this.viewMonth = 11; this.viewYear--; } this._renderDays(); });
el.appendChild(btn);
}
const todayStr = dateStr(new Date());
const selectedStr = this.currentPicker?.value || '';
for (let d = 1; d <= daysInMonth; d++) {
const ds = `${this.viewYear}-${pad(this.viewMonth+1)}-${pad(d)}`;
const btn = document.createElement('button');
let cls = 'cal-day';
if (ds === todayStr) cls += ' today';
if (ds === selectedStr) cls += ' selected';
const dow = new Date(this.viewYear, this.viewMonth, d).getDay();
if (dow === 0 || dow === 6) cls += ' weekend-day';
btn.className = cls;
btn.textContent = d;
btn.addEventListener('click', () => this._selectDay(ds));
el.appendChild(btn);
}
// 下月填充
const totalCells = startDow + daysInMonth;
const remaining = (7 - totalCells % 7) % 7;
for (let d = 1; d <= remaining; d++) {
const btn = document.createElement('button');
btn.className = 'cal-day other-month';
btn.textContent = d;
btn.addEventListener('click', () => { this.viewMonth++; if (this.viewMonth > 11) { this.viewMonth = 0; this.viewYear++; } this._renderDays(); });
el.appendChild(btn);
}
},
_selectDay(ds) {
this.currentPicker.setValue(ds);
if (this.currentPicker.onSelect) this.currentPicker.onSelect(ds);
this.close();
},
_positionPanel() {
const rect = this.currentPicker.trigger.getBoundingClientRect();
const panel = this.panel;
panel.classList.add('open');
// 先显示以获取尺寸
const pw = panel.offsetWidth;
const ph = panel.offsetHeight;
let left = rect.left;
let top = rect.bottom + 6;
// 防止超出右边
if (left + pw > window.innerWidth - 10) left = window.innerWidth - pw - 10;
if (left < 10) left = 10;
// 防止超出底部
if (top + ph > window.innerHeight - 10) {
top = rect.top - ph - 6;
if (top < 10) top = 10;
}
panel.style.position = 'fixed';
panel.style.left = left + 'px';
panel.style.top = top + 'px';
},
openYearMonth() {
this.ymViewYear = this.viewYear;
this._renderYM();
const panelRect = this.panel.getBoundingClientRect();
this.ymPanel.classList.add('open');
this.ymPanel.style.position = 'fixed';
this.ymPanel.style.left = panelRect.left + 'px';
this.ymPanel.style.top = panelRect.top + 'px';
this.ymPanel.style.width = panelRect.width + 'px';
},
closeYearMonth() { this.ymPanel.classList.remove('open'); },
_renderYM() {
$('#ymTitle').textContent = `${this.ymViewYear}年`;
const el = $('#ymGrid');
el.innerHTML = '';
for (let m = 0; m < 12; m++) {
const btn = document.createElement('button');
btn.className = 'ym-cell';
if (m === this.viewMonth && this.ymViewYear === this.viewYear) btn.className += ' current';
btn.textContent = `${m + 1}月`;
btn.addEventListener('click', () => {
this.viewYear = this.ymViewYear;
this.viewMonth = m;
this.closeYearMonth();
this._renderDays();
this._positionPanel();
});
el.appendChild(btn);
}
},
prevMonth() {
this.viewMonth--;
if (this.viewMonth < 0) { this.viewMonth = 11; this.viewYear--; }
this._renderDays();
},
nextMonth() {
this.viewMonth++;
if (this.viewMonth > 11) { this.viewMonth = 0; this.viewYear++; }
this._renderDays();
},
selectToday() {
this._selectDay(dateStr(new Date()));
},
clear() {
this.currentPicker.setValue(null);
this.close();
}
};
// 日历导航事件
$('#calPrev').addEventListener('click', (e) => { e.stopPropagation(); datePickerManager.prevMonth(); });
$('#calNext').addEventListener('click', (e) => { e.stopPropagation(); datePickerManager.nextMonth(); });
$('#calTitle').addEventListener('click', (e) => { e.stopPropagation(); datePickerManager.openYearMonth(); });
$('#calTitle').addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); datePickerManager.openYearMonth(); } });
$('#calTodayBtn').addEventListener('click', (e) => { e.stopPropagation(); datePickerManager.selectToday(); });
$('#calClearBtn').addEventListener('click', (e) => { e.stopPropagation(); datePickerManager.clear(); });
$('#ymPrev').addEventListener('click', (e) => { e.stopPropagation(); datePickerManager.ymViewYear--; datePickerManager._renderYM(); });
$('#ymNext').addEventListener('click', (e) => { e.stopPropagation(); datePickerManager.ymViewYear++; datePickerManager._renderYM(); });
$('#pickerOverlay').addEventListener('click', closeAllPickers);
// ========== 时间选择器管理器 ==========
const timePickerManager = {
panel: $('#timePanel'),
currentPicker: null,
open(picker) {
closeAllPickers();
this.currentPicker = picker;
picker.trigger.classList.add('active');
if (picker.value) {
const t = parseTimeStr(picker.value);
this._setValues(t.h, t.m, t.s);
} else {
this._setValues(0, 0, 0);
}
$('#pickerOverlay').classList.add('open');
this._positionPanel();
},
close() {
if (this.currentPicker) this.currentPicker.trigger.classList.remove('active');
this.currentPicker = null;
this.panel.classList.remove('open');
$('#pickerOverlay').classList.remove('open');
},
_setValues(h, m, s) {
this.currentPicker.hour = h;
this.currentPicker.min = m;
this.currentPicker.sec = s;
$('#tpHour').textContent = pad(h);
$('#tpMin').textContent = pad(m);
$('#tpSec').textContent = pad(s);
},
confirm() {
this.currentPicker.setFromComponents();
if (this.currentPicker.onSelect) this.currentPicker.onSelect(this.currentPicker.value);
this.close();
},
setNow() {
const now = new Date();
this._setValues(now.getHours(), now.getMinutes(), now.getSeconds());
},
adjust(field, dir) {
const p = this.currentPicker;
if (field === 'hour') {
p.hour = (p.hour + (dir === 'up' ? 1 : -1) + 24) % 24;
} else if (field === 'min') {
p.min = (p.min + (dir === 'up' ? 1 : -1) + 60) % 60;
} else {
p.sec = (p.sec + (dir === 'up' ? 1 : -1) + 60) % 60;
}
$('#tpHour').textContent = pad(p.hour);
$('#tpMin').textContent = pad(p.min);
$('#tpSec').textContent = pad(p.sec);
},
_positionPanel() {
const rect = this.currentPicker.trigger.getBoundingClientRect();
const panel = this.panel;
panel.classList.add('open');
const pw = panel.offsetWidth;
const ph = panel.offsetHeight;
let left = rect.left;
let top = rect.bottom + 6;
if (left + pw > window.innerWidth - 10) left = window.innerWidth - pw - 10;
if (left < 10) left = 10;
if (top + ph > window.innerHeight - 10) {
top = rect.top - ph - 6;
if (top < 10) top = 10;
}
panel.style.position = 'fixed';
panel.style.left = left + 'px';
panel.style.top = top + 'px';
}
};
// 时间选择器事件
$$('.time-arrow').forEach(btn => {
btn.addEventListener('click', (e) => {
e.stopPropagation();
timePickerManager.adjust(btn.dataset.field, btn.dataset.dir);
});
});
$('#tpOkBtn').addEventListener('click', (e) => { e.stopPropagation(); timePickerManager.confirm(); });
$('#tpNowBtn').addEventListener('click', (e) => { e.stopPropagation(); timePickerManager.setNow(); });
// 阻止面板内点击冒泡关闭
$('#calendarPanel').addEventListener('click', e => e.stopPropagation());
$('#ymPanel').addEventListener('click', e => e.stopPropagation());
$('#timePanel').addEventListener('click', e => e.stopPropagation());
// ESC 关闭
document.addEventListener('keydown', e => { if (e.key === 'Escape') closeAllPickers(); });
function closeAllPickers() {
datePickerManager.close();
datePickerManager.closeYearMonth();
timePickerManager.close();
}
// ========== 创建选择器实例 ==========
const intStartPicker = new DatePicker('#intStartTrigger', '#intStartText');
const intEndPicker = new DatePicker('#intEndTrigger', '#intEndText');
const offsetBasePicker = new DatePicker('#offsetBaseTrigger', '#offsetBaseText');
const tdStartDPicker = new DatePicker('#tdStartDTrigger', '#tdStartDText');
const tdEndDPicker = new DatePicker('#tdEndDTrigger', '#tdEndDText');
const tdStartTPicker = new TimePicker('#tdStartTTrigger', '#tdStartTText');
const tdEndTPicker = new TimePicker('#tdEndTTrigger', '#tdEndTText');
// 设置默认值
const todayS = dateStr(new Date());
intStartPicker.setValue(todayS);
intEndPicker.setValue(todayS);
offsetBasePicker.setValue(todayS);
tdStartDPicker.setValue(todayS);
tdEndDPicker.setValue(todayS);
tdStartTPicker.setValue('00:00:00');
tdEndTPicker.setValue(timeStr(new Date()));
// ========== 日期间隔计算 ==========
$('#calcInterval').addEventListener('click', () => {
if (!intStartPicker.value || !intEndPicker.value) { showToast('请选择起始和结束日期'); return; }
let s = intStartPicker.getDate(), e = intEndPicker.getDate();
let reversed = false;
if (s > e) { [s, e] = [e, s]; reversed = true; }
let years = e.getFullYear() - s.getFullYear();
let months = e.getMonth() - s.getMonth();
let days = e.getDate() - s.getDate();
if (days < 0) { months--; days += new Date(e.getFullYear(), e.getMonth(), 0).getDate(); }
if (months < 0) { years--; months += 12; }
const totalMs = e - s;
const totalDays = Math.round(totalMs / 86400000);
const weeks = Math.floor(totalDays / 7);
const remainDays = totalDays % 7;
let parts = [];
if (years > 0) parts.push(`${years} 年`);
if (months > 0) parts.push(`${months} 个月`);
if (days > 0) parts.push(`${days} 天`);
if (!parts.length) parts.push('0 天');
$('#intMain').textContent = `${parts.join(' ')}${reversed ? '(已自动交换顺序)' : ''}`;
$('#intDetails').innerHTML = `
<div class="result-item"><span class="result-value">${years}</span><span class="result-label">年</span></div>
<div class="result-item"><span class="result-value">${months}</span><span class="result-label">月</span></div>
<div class="result-item"><span class="result-value">${days}</span><span class="result-label">天</span></div>
<div class="result-item"><span class="result-value">${weeks}</span><span class="result-label">周零${remainDays}天</span></div>
<div class="result-item"><span class="result-value">${numFmt(totalDays)}</span><span class="result-label">总天数</span></div>`;
$('#intHours').textContent = numFmt(Math.round(totalMs / 3600000));
$('#intMinutes').textContent = numFmt(Math.round(totalMs / 60000));
$('#intSeconds').textContent = numFmt(Math.round(totalMs / 1000));
$('#resultInterval').classList.add('show');
});
// 交换
$('#swapDates').addEventListener('click', () => {
const tmp = intStartPicker.value;
intStartPicker.setValue(intEndPicker.value);
intEndPicker.setValue(tmp);
});
// 快捷日期
$$('[data-quick]').forEach(btn => {
btn.addEventListener('click', () => {
const now = new Date();
let d;
switch(btn.dataset.quick) {
case 'today': d = now; break;
case 'yesterday': d = new Date(now - 86400000); break;
case 'tomorrow': d = new Date(now.getTime() + 86400000); break;
case 'thisMonthStart': d = new Date(now.getFullYear(), now.getMonth(), 1); break;
case 'thisMonthEnd': d = new Date(now.getFullYear(), now.getMonth() + 1, 0); break;
case 'thisYearStart': d = new Date(now.getFullYear(), 0, 1); break;
case 'thisYearEnd': d = new Date(now.getFullYear(), 11, 31); break;
}
intEndPicker.setValue(dateStr(d));
});
});
// ========== 日期推算 ==========
$('#calcOffset').addEventListener('click', () => {
if (!offsetBasePicker.value) { showToast('请选择基准日期'); return; }
const base = offsetBasePicker.getDate();
const dir = parseInt($('#offsetDir').value);
const y = parseInt($('#offsetY').value) || 0;
const m = parseInt($('#offsetM').value) || 0;
const w = parseInt($('#offsetW').value) || 0;
const d = parseInt($('#offsetD').value) || 0;
if (!y && !m && !w && !d) { showToast('请输入要推算的时间量'); return; }
const result = new Date(base);
result.setFullYear(result.getFullYear() + y * dir);
result.setMonth(result.getMonth() + m * dir);
result.setDate(result.getDate() + (w * 7 + d) * dir);
const today = new Date(); today.setHours(0,0,0,0);
const diffDays = Math.round((result - today) / 86400000);
const diffText = diffDays > 0 ? `${diffDays} 天后` : diffDays < 0 ? `${Math.abs(diffDays)} 天前` : '就是今天';
$('#offDate').textContent = formatDate(result);
$('#offWeekday').textContent = WEEKDAYS[result.getDay()] + ` · 距今 ${diffText}`;
const dayOfYear = Math.ceil((result - new Date(result.getFullYear(), 0, 1)) / 86400000) + 1;
const isLeap = (result.getFullYear() % 4 === 0 && result.getFullYear() % 100 !== 0) || result.getFullYear() % 400 === 0;
const jan1 = new Date(result.getFullYear(), 0, 1);
const isoWeek = Math.ceil((Math.floor((result - jan1) / 86400000) + 1 + jan1.getDay()) / 7);
$('#offDetails').innerHTML = `
<div class="result-item"><span class="result-value">${dayOfYear}</span><span class="result-label">年中第几天</span></div>
<div class="result-item"><span class="result-value">${(isLeap?366:365) - dayOfYear}</span><span class="result-label">距年末天数</span></div>
<div class="result-item"><span class="result-value">${isLeap?'是':'否'}</span><span class="result-label">闰年</span></div>
<div class="result-item"><span class="result-value">Q${Math.ceil((result.getMonth()+1)/3)}</span><span class="result-label">季度</span></div>
<div class="result-item"><span class="result-value">W${isoWeek}</span><span class="result-label">ISO周数</span></div>`;
$('#resultOffset').classList.add('show');
});
$$('[data-off]').forEach(btn => {
btn.addEventListener('click', () => {
const match = btn.dataset.off.match(/^(-?\d+)([ymwd])$/);
if (!match) return;
const num = parseInt(match[1]), unit = match[2];
$('#offsetY').value = 0; $('#offsetM').value = 0; $('#offsetW').value = 0; $('#offsetD').value = 0;
$('#offsetDir').value = num >= 0 ? '1' : '-1';
const abs = Math.abs(num);
if (unit==='y') $('#offsetY').value = abs;
else if (unit==='m') $('#offsetM').value = abs;
else if (unit==='w') $('#offsetW').value = abs;
else $('#offsetD').value = abs;
$('#calcOffset').click();
});
});
// ========== 时间差计算 ==========
$('#calcTimediff').addEventListener('click', () => {
if (!tdStartDPicker.value || !tdStartTPicker.value || !tdEndDPicker.value || !tdEndTPicker.value) {
showToast('请选择完整的日期和时间'); return;
}
let s = new Date(`${tdStartDPicker.value}T${tdStartTPicker.value}`);
let e = new Date(`${tdEndDPicker.value}T${tdEndTPicker.value}`);
let reversed = false;
if (s > e) { [s, e] = [e, s]; reversed = true; }
const diffMs = e - s;
const totalSec = Math.floor(diffMs / 1000);
const days = Math.floor(totalSec / 86400);
const hours = Math.floor((totalSec % 86400) / 3600);
const minutes = Math.floor((totalSec % 3600) / 60);
const seconds = totalSec % 60;
let parts = [];
if (days > 0) parts.push(`${days} 天`);
if (hours > 0) parts.push(`${hours} 小时`);
if (minutes > 0) parts.push(`${minutes} 分钟`);
if (seconds > 0 || !parts.length) parts.push(`${seconds} 秒`);
$('#tdMain').textContent = `${parts.join(' ')}${reversed ? '(已自动交换顺序)' : ''}`;
$('#tdGrid').innerHTML = `
<div class="time-result-item"><div class="time-result-val">${pad(days)}</div><div class="time-result-unit">天</div></div>
<div class="time-result-item"><div class="time-result-val">${pad(hours)}</div><div class="time-result-unit">时</div></div>
<div class="time-result-item"><div class="time-result-val">${pad(minutes)}</div><div class="time-result-unit">分</div></div>
<div class="time-result-item"><div class="time-result-val">${pad(seconds)}</div><div class="time-result-unit">秒</div></div>`;
$('#tdTotalSec').textContent = numFmt(totalSec) + ' 秒';
$('#tdTotalMin').textContent = numFmt(Math.floor(diffMs / 60000)) + ' 分钟';
$('#tdTotalHour').textContent = numFmt(Math.floor(diffMs / 3600000)) + ' 小时';
$('#tdTotalDay').textContent = numFmt(Math.floor(diffMs / 86400000)) + ' 天';
$('#resultTimediff').classList.add('show');
});
$$('[data-td]').forEach(btn => {
btn.addEventListener('click', () => {
const now = new Date();
const ts = dateStr(now);
switch(btn.dataset.td) {
case 'nowToEnd':
tdStartDPicker.setValue(ts); tdStartTPicker.setValue(timeStr(now));
tdEndDPicker.setValue(ts); tdEndTPicker.setValue('23:59:59');
break;
case 'startToNow':
tdStartDPicker.setValue(ts); tdStartTPicker.setValue('00:00:00');
tdEndDPicker.setValue(ts); tdEndTPicker.setValue(timeStr(now));
break;
case 'now24h':
const ago = new Date(now - 86400000);
tdStartDPicker.setValue(dateStr(ago)); tdStartTPicker.setValue(timeStr(ago));
tdEndDPicker.setValue(ts); tdEndTPicker.setValue(timeStr(now));
break;
}
$('#calcTimediff').click();
});
});
// 回车触发计算
document.addEventListener('keydown', e => {
if (e.key !== 'Enter') return;
const panel = document.activeElement?.closest('.panel');
if (!panel) return;
if (panel.id === 'panel-interval') $('#calcInterval').click();
else if (panel.id === 'panel-offset') $('#calcOffset').click();
else if (panel.id === 'panel-timediff') $('#calcTimediff').click();
});
</script>
</body>
</html>
本文来自博客园,作者:Micky233,转载请注明原文链接:https://www.cnblogs.com/geek233/p/19807060

浙公网安备 33010602011771号