日期工具 Date
介绍
日期工具(date)是RuoYi-Plus-UniApp移动端应用的核心工具库之一,提供了全面的日期处理功能。它封装了日期格式化、解析、计算、范围获取等常用操作,大幅简化了日期相关的开发工作,提高了代码的可读性和可维护性。
移动端应用中经常需要处理各种日期场景:显示文章发布时间、筛选日期范围、计算时间差、显示相对时间(如"3分钟前")等。date工具提供了统一、易用的API接口,自动处理时区、格式转换、边界情况等复杂问题,让开发者专注于业务逻辑。
核心特性:
- 丰富的格式化选项 - 支持多种日期格式,自动兼容 yyyy-MM-dd 和 YYYY-MM-DD 两种语法
- 相对时间显示 - 智能显示"刚刚"、"3分钟前"、"2小时前"等人性化时间
- 日期范围处理 - 快速获取本周、本月、本年等预设范围,支持自定义范围
- 时间戳转换 - 灵活处理秒级和毫秒级时间戳,自动识别和转换
- 日期计算 - 计算日期差值、加减天数、判断是否同一天等
- 类型安全 - 完整的TypeScript类型定义,提供智能提示
- 参数灵活 - 支持Date对象、时间戳、日期字符串多种输入格式
- 边界处理 - 自动处理null/undefined、非法日期等边界情况
- 表格友好 - 专门提供表格时间格式化函数
- 路由集成 - 支持从路由参数初始化日期范围
基本用法
1. 格式化日期
最常用的功能是将日期格式化为字符串,支持多种格式模式。
<template>
<view class="container">
<view class="section">
<text class="title">日期格式化</text>
<view class="demo-item">
<text class="label">标准格式:</text>
<text>{{ standardFormat }}</text>
</view>
<view class="demo-item">
<text class="label">仅日期:</text>
<text>{{ dateOnly }}</text>
</view>
<view class="demo-item">
<text class="label">仅时间:</text>
<text>{{ timeOnly }}</text>
</view>
<view class="demo-item">
<text class="label">中文格式:</text>
<text>{{ chineseFormat }}</text>
</view>
<view class="demo-item">
<text class="label">带星期:</text>
<text>{{ withWeek }}</text>
</view>
<button @click="updateTime">更新时间</button>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { formatDate } from '@/utils/date'
const now = ref(new Date())
const standardFormat = ref('')
const dateOnly = ref('')
const timeOnly = ref('')
const chineseFormat = ref('')
const withWeek = ref('')
// 格式化时间
const formatAllTimes = () => {
const currentTime = now.value
// 标准格式: 2024-01-15 14:30:45
standardFormat.value = formatDate(currentTime, 'yyyy-MM-dd HH:mm:ss')
// 仅日期: 2024-01-15
dateOnly.value = formatDate(currentTime, 'yyyy-MM-dd')
// 仅时间: 14:30:45
timeOnly.value = formatDate(currentTime, 'HH:mm:ss')
// 中文格式: 2024年01月15日 14时30分45秒
chineseFormat.value = formatDate(currentTime, 'yyyy年MM月dd日 HH时mm分ss秒')
// 带星期: 2024-01-15 星期一
withWeek.value = formatDate(currentTime, 'yyyy-MM-dd 星期w')
}
// 更新时间
const updateTime = () => {
now.value = new Date()
formatAllTimes()
uni.showToast({
title: '时间已更新',
icon: 'success',
})
}
// 初始化
formatAllTimes()
</script>
<style lang="scss" scoped>
.container {
padding: 32rpx;
}
.section {
margin-bottom: 40rpx;
}
.title {
font-size: 32rpx;
font-weight: bold;
margin-bottom: 20rpx;
display: block;
}
.demo-item {
display: flex;
justify-content: space-between;
padding: 20rpx 0;
border-bottom: 1rpx solid #e4e7ed;
}
.label {
color: #606266;
font-weight: bold;
}
button {
margin-top: 20rpx;
}
</style>格式说明:
yyyy或YYYY: 四位年份MM: 两位月份 (01-12)M: 月份不补零 (1-12)dd或DD: 两位日期 (01-31)d或D: 日期不补零 (1-31)HH: 两位小时 (00-23)H: 小时不补零 (0-23)mm: 两位分钟 (00-59)m: 分钟不补零 (0-59)ss: 两位秒 (00-59)s: 秒不补零 (0-59)SSS: 三位毫秒 (000-999)w: 星期 (日/一/二/三/四/五/六)
2. 相对时间显示
显示人性化的相对时间,如"刚刚"、"3分钟前"、"2小时前"等。
<template>
<view class="container">
<view class="section">
<text class="title">相对时间显示</text>
<view class="timeline">
<view v-for="item in timelineItems" :key="item.id" class="timeline-item">
<view class="timeline-dot"></view>
<view class="timeline-content">
<text class="timeline-title">{{ item.title }}</text>
<text class="timeline-time">{{ item.relativeTime }}</text>
</view>
</view>
</view>
<button @click="refreshTimeline">刷新时间</button>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { formatRelativeTime } from '@/utils/date'
interface TimelineItem {
id: number
title: string
timestamp: number
relativeTime: string
}
const timelineItems = ref<TimelineItem[]>([])
// 创建时间线数据
const createTimelineData = () => {
const now = Date.now()
return [
{
id: 1,
title: '系统更新完成',
timestamp: now - 10 * 1000, // 10秒前
relativeTime: '',
},
{
id: 2,
title: '收到新消息',
timestamp: now - 5 * 60 * 1000, // 5分钟前
relativeTime: '',
},
{
id: 3,
title: '任务已完成',
timestamp: now - 2 * 3600 * 1000, // 2小时前
relativeTime: '',
},
{
id: 4,
title: '文件上传成功',
timestamp: now - 25 * 3600 * 1000, // 1天多前
relativeTime: '',
},
{
id: 5,
title: '账号登录',
timestamp: now - 3 * 24 * 3600 * 1000, // 3天前
relativeTime: '',
},
]
}
// 更新相对时间
const updateRelativeTimes = () => {
timelineItems.value = createTimelineData().map((item) => ({
...item,
relativeTime: formatRelativeTime(item.timestamp),
}))
}
// 刷新时间线
const refreshTimeline = () => {
updateRelativeTimes()
uni.showToast({
title: '已刷新',
icon: 'success',
})
}
// 定时更新
let timer: number | null = null
onMounted(() => {
updateRelativeTimes()
// 每30秒更新一次
timer = setInterval(updateRelativeTimes, 30000) as unknown as number
})
onUnmounted(() => {
if (timer) {
clearInterval(timer)
}
})
</script>
<style lang="scss" scoped>
.container {
padding: 32rpx;
}
.section {
margin-bottom: 40rpx;
}
.title {
font-size: 32rpx;
font-weight: bold;
margin-bottom: 20rpx;
display: block;
}
.timeline {
position: relative;
padding-left: 40rpx;
}
.timeline-item {
position: relative;
padding-bottom: 40rpx;
&::before {
content: '';
position: absolute;
left: -32rpx;
top: 12rpx;
bottom: -28rpx;
width: 2rpx;
background-color: #e4e7ed;
}
&:last-child::before {
display: none;
}
}
.timeline-dot {
position: absolute;
left: -40rpx;
top: 8rpx;
width: 16rpx;
height: 16rpx;
border-radius: 50%;
background-color: #409eff;
border: 2rpx solid #fff;
box-shadow: 0 0 0 2rpx #e4e7ed;
}
.timeline-content {
display: flex;
flex-direction: column;
}
.timeline-title {
font-size: 28rpx;
color: #303133;
margin-bottom: 8rpx;
}
.timeline-time {
font-size: 24rpx;
color: #909399;
}
button {
margin-top: 20rpx;
}
</style>相对时间规则:
- 30秒内: "刚刚"
- 1小时内: "X分钟前"
- 24小时内: "X小时前"
- 48小时内: "1天前"
- 更早: 显示月日时分或自定义格式
3. 获取当前时间
快速获取当前时间的各种格式。
<template>
<view class="container">
<view class="section">
<text class="title">当前时间</text>
<view class="time-display">
<view class="time-card">
<text class="time-label">完整时间</text>
<text class="time-value">{{ currentDateTime }}</text>
</view>
<view class="time-card">
<text class="time-label">当前日期</text>
<text class="time-value">{{ currentDate }}</text>
</view>
<view class="time-card">
<text class="time-label">当前时刻</text>
<text class="time-value">{{ currentTime }}</text>
</view>
<view class="time-card">
<text class="time-label">时间戳(秒)</text>
<text class="time-value">{{ timestamp }}</text>
</view>
<view class="time-card">
<text class="time-label">时间戳(毫秒)</text>
<text class="time-value">{{ timestampMs }}</text>
</view>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { getCurrentTime, getCurrentDate, getCurrentDateTime, getTimeStamp } from '@/utils/date'
const currentDateTime = ref('')
const currentDate = ref('')
const currentTime = ref('')
const timestamp = ref(0)
const timestampMs = ref(0)
// 更新所有时间
const updateAllTimes = () => {
currentDateTime.value = getCurrentDateTime() // 2024-01-15 14:30:45
currentDate.value = getCurrentDate() // 2024-01-15
currentTime.value = getCurrentTime() // 14:30:45
timestamp.value = getTimeStamp('s') // 1705299045
timestampMs.value = getTimeStamp('ms') // 1705299045000
}
// 定时更新
let timer: number | null = null
onMounted(() => {
updateAllTimes()
// 每秒更新一次
timer = setInterval(updateAllTimes, 1000) as unknown as number
})
onUnmounted(() => {
if (timer) {
clearInterval(timer)
}
})
</script>
<style lang="scss" scoped>
.container {
padding: 32rpx;
}
.section {
margin-bottom: 40rpx;
}
.title {
font-size: 32rpx;
font-weight: bold;
margin-bottom: 20rpx;
display: block;
}
.time-display {
display: grid;
gap: 20rpx;
}
.time-card {
background-color: #f5f7fa;
border-radius: 8rpx;
padding: 20rpx;
display: flex;
flex-direction: column;
align-items: center;
}
.time-label {
font-size: 24rpx;
color: #909399;
margin-bottom: 10rpx;
}
.time-value {
font-size: 28rpx;
color: #303133;
font-weight: bold;
font-family: monospace;
}
</style>使用说明:
- getCurrentDateTime(): 获取完整日期时间 (yyyy-MM-dd HH:mm:ss)
- getCurrentDate(): 获取当前日期 (yyyy-MM-dd)
- getCurrentTime(): 获取当前时刻 (HH:mm:ss)
- getTimeStamp('s'): 获取秒级时间戳
- getTimeStamp('ms'): 获取毫秒级时间戳
4. 日期范围选择
获取预设的日期范围,如今天、本周、本月等。
<template>
<view class="container">
<view class="section">
<text class="title">日期范围选择</text>
<view class="range-selector">
<button
v-for="type in rangeTypes"
:key="type.value"
:class="['range-btn', { active: selectedType === type.value }]"
@click="selectRange(type.value)"
>
{{ type.label }}
</button>
</view>
<view v-if="dateRange" class="range-display">
<text class="range-label">选中范围:</text>
<view class="range-value">
<text>{{ dateRange[0] }}</text>
<text class="separator">至</text>
<text>{{ dateRange[1] }}</text>
</view>
</view>
<view class="range-info">
<text>天数: {{ dayCount }} 天</text>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue'
import { getDateRangeByType, getDaysBetween } from '@/utils/date'
interface RangeType {
label: string
value: string
}
const rangeTypes: RangeType[] = [
{ label: '今天', value: 'today' },
{ label: '昨天', value: 'yesterday' },
{ label: '本周', value: 'week' },
{ label: '本月', value: 'month' },
{ label: '本年', value: 'year' },
]
const selectedType = ref('today')
const dateRange = ref<[string, string] | null>(null)
// 计算天数
const dayCount = computed(() => {
if (!dateRange.value) return 0
const start = new Date(dateRange.value[0])
const end = new Date(dateRange.value[1])
return getDaysBetween(start, end) + 1 // 包含开始和结束当天
})
// 选择范围
const selectRange = (type: string) => {
selectedType.value = type
dateRange.value = getDateRangeByType(type)
if (dateRange.value) {
uni.showToast({
title: `已选择${rangeTypes.find((t) => t.value === type)?.label}`,
icon: 'success',
})
}
}
// 初始化
selectRange('today')
</script>
<style lang="scss" scoped>
.container {
padding: 32rpx;
}
.section {
margin-bottom: 40rpx;
}
.title {
font-size: 32rpx;
font-weight: bold;
margin-bottom: 20rpx;
display: block;
}
.range-selector {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
margin-bottom: 30rpx;
}
.range-btn {
flex: 0 0 calc(33.333% - 14rpx);
height: 70rpx;
line-height: 70rpx;
background-color: #f5f7fa;
color: #606266;
border: 1rpx solid #dcdfe6;
border-radius: 8rpx;
font-size: 28rpx;
&.active {
background-color: #409eff;
color: #fff;
border-color: #409eff;
}
&::after {
border: none;
}
}
.range-display {
background-color: #ecf5ff;
border: 1rpx solid #b3d8ff;
border-radius: 8rpx;
padding: 20rpx;
margin-bottom: 20rpx;
}
.range-label {
display: block;
font-size: 24rpx;
color: #409eff;
margin-bottom: 10rpx;
}
.range-value {
display: flex;
flex-direction: column;
font-size: 28rpx;
color: #303133;
text {
margin-bottom: 5rpx;
}
}
.separator {
color: #909399;
font-size: 24rpx;
}
.range-info {
padding: 15rpx 20rpx;
background-color: #f5f7fa;
border-radius: 8rpx;
text {
font-size: 28rpx;
color: #606266;
}
}
</style>预设范围类型:
today: 今天 (00:00:00 - 23:59:59)yesterday: 昨天week: 本周 (周一到周日)month: 本月 (第一天到最后一天)year: 本年 (1月1日到12月31日)
5. 多种输入格式支持
formatDate函数支持多种输入格式:Date对象、时间戳(秒/毫秒)、日期字符串。
<template>
<view class="container">
<view class="section">
<text class="title">多种输入格式</text>
<view class="demo-list">
<view class="demo-item">
<text class="demo-label">Date对象:</text>
<text class="demo-value">{{ fromDateObject }}</text>
</view>
<view class="demo-item">
<text class="demo-label">毫秒时间戳:</text>
<text class="demo-value">{{ fromTimestampMs }}</text>
</view>
<view class="demo-item">
<text class="demo-label">秒时间戳:</text>
<text class="demo-value">{{ fromTimestampS }}</text>
</view>
<view class="demo-item">
<text class="demo-label">ISO字符串:</text>
<text class="demo-value">{{ fromISOString }}</text>
</view>
<view class="demo-item">
<text class="demo-label">数字字符串:</text>
<text class="demo-value">{{ fromNumberString }}</text>
</view>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref, onMounted } from 'vue'
import { formatDate } from '@/utils/date'
const fromDateObject = ref('')
const fromTimestampMs = ref('')
const fromTimestampS = ref('')
const fromISOString = ref('')
const fromNumberString = ref('')
onMounted(() => {
const now = new Date()
const timestampMs = now.getTime()
const timestampS = Math.floor(timestampMs / 1000)
// 1. Date对象
fromDateObject.value = formatDate(now, 'yyyy-MM-dd HH:mm:ss')
// 2. 毫秒时间戳 (13位)
fromTimestampMs.value = formatDate(timestampMs, 'yyyy-MM-dd HH:mm:ss')
// 3. 秒时间戳 (10位,自动识别并转换)
fromTimestampS.value = formatDate(timestampS, 'yyyy-MM-dd HH:mm:ss')
// 4. ISO 8601 字符串
const isoString = now.toISOString()
fromISOString.value = formatDate(isoString, 'yyyy-MM-dd HH:mm:ss')
// 5. 数字字符串
fromNumberString.value = formatDate(timestampMs.toString(), 'yyyy-MM-dd HH:mm:ss')
})
</script>
<style lang="scss" scoped>
.container {
padding: 32rpx;
}
.section {
margin-bottom: 40rpx;
}
.title {
font-size: 32rpx;
font-weight: bold;
margin-bottom: 20rpx;
display: block;
}
.demo-list {
background-color: #f5f7fa;
border-radius: 8rpx;
overflow: hidden;
}
.demo-item {
padding: 20rpx;
border-bottom: 1rpx solid #e4e7ed;
&:last-child {
border-bottom: none;
}
}
.demo-label {
display: block;
font-size: 24rpx;
color: #909399;
margin-bottom: 8rpx;
}
.demo-value {
display: block;
font-size: 28rpx;
color: #303133;
font-family: monospace;
}
</style>输入格式自动识别:
- Date对象: 直接使用
- 13位数字: 识别为毫秒时间戳
- 10位数字: 识别为秒时间戳,自动转换为毫秒
- ISO字符串: 自动处理T和-符号
- 数字字符串: 转换为数字后处理
6. 日期简化函数
使用formatDay快速获取年月日格式。
<template>
<view class="container">
<view class="section">
<text class="title">日期列表</text>
<view class="date-list">
<view v-for="item in dateItems" :key="item.id" class="date-item">
<view class="date-info">
<text class="date-title">{{ item.title }}</text>
<text class="date-day">{{ item.day }}</text>
</view>
<view class="date-badge">{{ item.status }}</view>
</view>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref, onMounted } from 'vue'
import { formatDay } from '@/utils/date'
interface DateItem {
id: number
title: string
date: Date
day: string
status: string
}
const dateItems = ref<DateItem[]>([])
onMounted(() => {
const today = new Date()
dateItems.value = [
{
id: 1,
title: '今日任务',
date: today,
day: formatDay(today),
status: '进行中',
},
{
id: 2,
title: '明日计划',
date: new Date(today.getTime() + 24 * 3600 * 1000),
day: formatDay(new Date(today.getTime() + 24 * 3600 * 1000)),
status: '未开始',
},
{
id: 3,
title: '昨日回顾',
date: new Date(today.getTime() - 24 * 3600 * 1000),
day: formatDay(new Date(today.getTime() - 24 * 3600 * 1000)),
status: '已完成',
},
]
})
</script>
<style lang="scss" scoped>
.container {
padding: 32rpx;
}
.section {
margin-bottom: 40rpx;
}
.title {
font-size: 32rpx;
font-weight: bold;
margin-bottom: 20rpx;
display: block;
}
.date-list {
display: flex;
flex-direction: column;
gap: 20rpx;
}
.date-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24rpx;
background-color: #fff;
border-radius: 8rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
}
.date-info {
flex: 1;
}
.date-title {
display: block;
font-size: 28rpx;
color: #303133;
font-weight: bold;
margin-bottom: 8rpx;
}
.date-day {
display: block;
font-size: 24rpx;
color: #909399;
font-family: monospace;
}
.date-badge {
padding: 8rpx 16rpx;
background-color: #409eff;
color: #fff;
border-radius: 4rpx;
font-size: 24rpx;
}
</style>使用说明:
- formatDay(date) 等价于 formatDate(date, 'yyyy-MM-dd')
- 适用于只需要年月日,不需要时分秒的场景
7. 表格时间显示
在表格中显示格式化的时间。
<template>
<view class="container">
<view class="section">
<text class="title">订单列表</text>
<view class="table">
<view class="table-header">
<text class="col-order">订单号</text>
<text class="col-time">创建时间</text>
<text class="col-status">状态</text>
</view>
<view v-for="order in orders" :key="order.id" class="table-row">
<text class="col-order">{{ order.orderNo }}</text>
<text class="col-time">{{ order.formattedTime }}</text>
<text class="col-status">{{ order.status }}</text>
</view>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref, onMounted } from 'vue'
import { formatTableDate } from '@/utils/date'
interface Order {
id: number
orderNo: string
createTime: string
formattedTime: string
status: string
}
const orders = ref<Order[]>([])
onMounted(() => {
// 模拟从API获取的数据(ISO格式字符串)
const apiData = [
{
id: 1,
orderNo: 'ORD20240115001',
createTime: '2024-01-15T14:30:45.000Z',
status: '已完成',
},
{
id: 2,
orderNo: 'ORD20240115002',
createTime: '2024-01-15T15:20:30.000Z',
status: '进行中',
},
{
id: 3,
orderNo: 'ORD20240115003',
createTime: '2024-01-15T16:45:12.000Z',
status: '待付款',
},
]
// 格式化时间
orders.value = apiData.map((item) => ({
...item,
formattedTime: formatTableDate(item.createTime, 'yyyy-MM-dd HH:mm'),
}))
})
</script>
<style lang="scss" scoped>
.container {
padding: 32rpx;
}
.section {
margin-bottom: 40rpx;
}
.title {
font-size: 32rpx;
font-weight: bold;
margin-bottom: 20rpx;
display: block;
}
.table {
background-color: #fff;
border-radius: 8rpx;
overflow: hidden;
}
.table-header,
.table-row {
display: flex;
padding: 20rpx 16rpx;
border-bottom: 1rpx solid #e4e7ed;
}
.table-header {
background-color: #f5f7fa;
font-weight: bold;
}
.table-row:last-child {
border-bottom: none;
}
.col-order {
flex: 0 0 180rpx;
font-size: 24rpx;
}
.col-time {
flex: 1;
font-size: 24rpx;
}
.col-status {
flex: 0 0 120rpx;
text-align: right;
font-size: 24rpx;
}
</style>使用场景:
- 表格数据展示
- 列表时间显示
- API返回的ISO格式时间处理
高级用法
1. 日期计算
计算日期之间的天数差,判断是否同一天,日期加减等。
<template>
<view class="container">
<view class="section">
<text class="title">日期计算</text>
<view class="calc-section">
<text class="section-title">计算两个日期之间的天数</text>
<view class="date-inputs">
<picker mode="date" :value="date1" @change="onDate1Change">
<view class="picker-input">{{ date1 || '选择日期1' }}</view>
</picker>
<text class="separator">→</text>
<picker mode="date" :value="date2" @change="onDate2Change">
<view class="picker-input">{{ date2 || '选择日期2' }}</view>
</picker>
</view>
<view v-if="date1 && date2" class="result">
<text>相差 {{ daysBetween }} 天</text>
</view>
</view>
<view class="calc-section">
<text class="section-title">日期加减</text>
<view class="date-add">
<picker mode="date" :value="baseDate" @change="onBaseDateChange">
<view class="picker-input">{{ baseDate || '选择基准日期' }}</view>
</picker>
<view class="add-controls">
<button @click="addDays(-7)">-7天</button>
<button @click="addDays(-1)">-1天</button>
<button @click="addDays(1)">+1天</button>
<button @click="addDays(7)">+7天</button>
</view>
<view class="add-controls">
<button @click="addMonths(-1)">-1月</button>
<button @click="addMonths(1)">+1月</button>
<button @click="addYears(-1)">-1年</button>
<button @click="addYears(1)">+1年</button>
</view>
<view v-if="resultDate" class="result">
<text>结果: {{ resultDate }}</text>
</view>
</view>
</view>
<view class="calc-section">
<text class="section-title">判断是否同一天</text>
<view class="same-day-check">
<picker mode="date" :value="checkDate1" @change="onCheckDate1Change">
<view class="picker-input">{{ checkDate1 || '日期1' }}</view>
</picker>
<picker mode="date" :value="checkDate2" @change="onCheckDate2Change">
<view class="picker-input">{{ checkDate2 || '日期2' }}</view>
</picker>
<view v-if="checkDate1 && checkDate2" class="result">
<text :class="isSame ? 'same' : 'different'">
{{ isSame ? '✓ 是同一天' : '✗ 不是同一天' }}
</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue'
import { getDaysBetween, isSameDay, dateAdd, formatDay } from '@/utils/date'
// 计算天数差
const date1 = ref('')
const date2 = ref('')
const daysBetween = computed(() => {
if (!date1.value || !date2.value) return 0
return getDaysBetween(new Date(date1.value), new Date(date2.value))
})
const onDate1Change = (e: any) => {
date1.value = e.detail.value
}
const onDate2Change = (e: any) => {
date2.value = e.detail.value
}
// 日期加减
const baseDate = ref('')
const resultDate = ref('')
const onBaseDateChange = (e: any) => {
baseDate.value = e.detail.value
resultDate.value = baseDate.value
}
const addDays = (days: number) => {
if (!baseDate.value) {
uni.showToast({
title: '请先选择基准日期',
icon: 'none',
})
return
}
const result = dateAdd(new Date(baseDate.value), 'day', days)
resultDate.value = formatDay(result)
}
const addMonths = (months: number) => {
if (!baseDate.value) {
uni.showToast({
title: '请先选择基准日期',
icon: 'none',
})
return
}
const result = dateAdd(new Date(baseDate.value), 'month', months)
resultDate.value = formatDay(result)
}
const addYears = (years: number) => {
if (!baseDate.value) {
uni.showToast({
title: '请先选择基准日期',
icon: 'none',
})
return
}
const result = dateAdd(new Date(baseDate.value), 'year', years)
resultDate.value = formatDay(result)
}
// 判断是否同一天
const checkDate1 = ref('')
const checkDate2 = ref('')
const isSame = computed(() => {
if (!checkDate1.value || !checkDate2.value) return false
return isSameDay(new Date(checkDate1.value), new Date(checkDate2.value))
})
const onCheckDate1Change = (e: any) => {
checkDate1.value = e.detail.value
}
const onCheckDate2Change = (e: any) => {
checkDate2.value = e.detail.value
}
</script>
<style lang="scss" scoped>
.container {
padding: 32rpx;
}
.section {
margin-bottom: 40rpx;
}
.title {
font-size: 32rpx;
font-weight: bold;
margin-bottom: 20rpx;
display: block;
}
.calc-section {
margin-bottom: 40rpx;
padding: 24rpx;
background-color: #f5f7fa;
border-radius: 8rpx;
}
.section-title {
display: block;
font-size: 28rpx;
color: #606266;
font-weight: bold;
margin-bottom: 20rpx;
}
.date-inputs {
display: flex;
align-items: center;
gap: 20rpx;
margin-bottom: 20rpx;
}
.picker-input {
flex: 1;
height: 70rpx;
line-height: 70rpx;
text-align: center;
background-color: #fff;
border: 1rpx solid #dcdfe6;
border-radius: 8rpx;
font-size: 28rpx;
}
.separator {
color: #909399;
}
.date-add {
display: flex;
flex-direction: column;
gap: 20rpx;
}
.add-controls {
display: flex;
gap: 10rpx;
button {
flex: 1;
height: 60rpx;
line-height: 60rpx;
padding: 0;
font-size: 24rpx;
&::after {
border: none;
}
}
}
.same-day-check {
display: flex;
flex-direction: column;
gap: 20rpx;
}
.result {
padding: 20rpx;
background-color: #ecf5ff;
border: 1rpx solid #b3d8ff;
border-radius: 8rpx;
text-align: center;
text {
font-size: 28rpx;
color: #409eff;
font-weight: bold;
&.same {
color: #67c23a;
}
&.different {
color: #f56c6c;
}
}
}
</style>日期计算函数:
getDaysBetween(start, end): 计算两个日期之间的天数isSameDay(date1, date2): 判断两个日期是否是同一天dateAdd(date, type, value): 日期加减,type可为'day'/'month'/'year'
2. 获取周数和本周范围
获取日期是一年中的第几周,以及获取本周的开始和结束日期。
<template>
<view class="container">
<view class="section">
<text class="title">周历信息</text>
<view class="week-info">
<view class="info-card">
<text class="info-label">今天是本年第几周</text>
<text class="info-value">第 {{ weekOfYear }} 周</text>
</view>
<view class="info-card">
<text class="info-label">本周范围</text>
<view class="week-range">
<text>{{ weekStart }}</text>
<text class="separator">~</text>
<text>{{ weekEnd }}</text>
</view>
</view>
<view class="info-card">
<text class="info-label">本周已过天数</text>
<text class="info-value">{{ daysPassedInWeek }} 天</text>
</view>
<view class="info-card">
<text class="info-label">本周剩余天数</text>
<text class="info-value">{{ daysLeftInWeek }} 天</text>
</view>
</view>
<button @click="refreshWeekInfo">刷新信息</button>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { getWeekOfYear, getCurrentWeekRange, formatDay, getDaysBetween } from '@/utils/date'
const weekOfYear = ref(0)
const weekStart = ref('')
const weekEnd = ref('')
const daysPassedInWeek = ref(0)
const daysLeftInWeek = ref(0)
// 刷新周信息
const refreshWeekInfo = () => {
const today = new Date()
// 获取周数
weekOfYear.value = getWeekOfYear(today)
// 获取本周范围
const range = getCurrentWeekRange()
weekStart.value = formatDay(range[0])
weekEnd.value = formatDay(range[1])
// 计算天数
daysPassedInWeek.value = getDaysBetween(range[0], today)
daysLeftInWeek.value = getDaysBetween(today, range[1])
uni.showToast({
title: '已刷新',
icon: 'success',
})
}
// 初始化
refreshWeekInfo()
</script>
<style lang="scss" scoped>
.container {
padding: 32rpx;
}
.section {
margin-bottom: 40rpx;
}
.title {
font-size: 32rpx;
font-weight: bold;
margin-bottom: 20rpx;
display: block;
}
.week-info {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20rpx;
margin-bottom: 20rpx;
}
.info-card {
padding: 24rpx;
background-color: #fff;
border-radius: 8rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
text-align: center;
}
.info-label {
display: block;
font-size: 24rpx;
color: #909399;
margin-bottom: 10rpx;
}
.info-value {
display: block;
font-size: 36rpx;
color: #409eff;
font-weight: bold;
}
.week-range {
display: flex;
flex-direction: column;
font-size: 24rpx;
color: #303133;
text {
margin-bottom: 5rpx;
}
.separator {
color: #909399;
}
}
button {
width: 100%;
}
</style>周相关函数:
getWeekOfYear(date): 获取日期是一年中的第几周getCurrentWeekRange(): 获取本周范围,返回 [周一日期, 周日日期]
3. 本月范围和日历视图
获取本月的开始和结束日期,可用于实现日历视图。
<template>
<view class="container">
<view class="section">
<text class="title">月历信息</text>
<view class="month-header">
<button class="nav-btn" @click="prevMonth">‹</button>
<text class="month-title">{{ currentYearMonth }}</text>
<button class="nav-btn" @click="nextMonth">›</button>
</view>
<view class="month-info">
<view class="info-item">
<text class="label">本月第一天:</text>
<text class="value">{{ monthStart }}</text>
</view>
<view class="info-item">
<text class="label">本月最后一天:</text>
<text class="value">{{ monthEnd }}</text>
</view>
<view class="info-item">
<text class="label">本月天数:</text>
<text class="value">{{ monthDays }} 天</text>
</view>
<view class="info-item">
<text class="label">已过天数:</text>
<text class="value">{{ daysPassedInMonth }} 天</text>
</view>
</view>
<button @click="goToday">回到今天</button>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue'
import { getCurrentMonthRange, formatDay, formatDate, getDaysBetween, dateAdd } from '@/utils/date'
const viewDate = ref(new Date())
const monthStart = ref('')
const monthEnd = ref('')
const currentYearMonth = computed(() => {
return formatDate(viewDate.value, 'yyyy年MM月')
})
const monthDays = computed(() => {
if (!monthStart.value || !monthEnd.value) return 0
return getDaysBetween(new Date(monthStart.value), new Date(monthEnd.value)) + 1
})
const daysPassedInMonth = computed(() => {
const today = new Date()
const startDate = new Date(monthStart.value)
// 只在当前月份时计算
if (
today.getFullYear() === viewDate.value.getFullYear() &&
today.getMonth() === viewDate.value.getMonth()
) {
return getDaysBetween(startDate, today)
}
return 0
})
// 更新月份信息
const updateMonthInfo = () => {
// 临时设置Date为查看的月份
const temp = new Date()
temp.setFullYear(viewDate.value.getFullYear())
temp.setMonth(viewDate.value.getMonth())
const range = getCurrentMonthRange()
monthStart.value = formatDay(range[0])
monthEnd.value = formatDay(range[1])
}
// 上一月
const prevMonth = () => {
viewDate.value = dateAdd(viewDate.value, 'month', -1)
updateMonthInfo()
}
// 下一月
const nextMonth = () => {
viewDate.value = dateAdd(viewDate.value, 'month', 1)
updateMonthInfo()
}
// 回到今天
const goToday = () => {
viewDate.value = new Date()
updateMonthInfo()
uni.showToast({
title: '已回到今天',
icon: 'success',
})
}
// 初始化
updateMonthInfo()
</script>
<style lang="scss" scoped>
.container {
padding: 32rpx;
}
.section {
margin-bottom: 40rpx;
}
.title {
font-size: 32rpx;
font-weight: bold;
margin-bottom: 20rpx;
display: block;
}
.month-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30rpx;
}
.nav-btn {
width: 80rpx;
height: 60rpx;
line-height: 60rpx;
padding: 0;
font-size: 36rpx;
background-color: #f5f7fa;
&::after {
border: none;
}
}
.month-title {
font-size: 32rpx;
font-weight: bold;
color: #303133;
}
.month-info {
background-color: #f5f7fa;
border-radius: 8rpx;
padding: 20rpx;
margin-bottom: 20rpx;
}
.info-item {
display: flex;
justify-content: space-between;
padding: 15rpx 0;
border-bottom: 1rpx solid #e4e7ed;
&:last-child {
border-bottom: none;
}
}
.label {
color: #606266;
font-size: 28rpx;
}
.value {
color: #303133;
font-size: 28rpx;
font-weight: bold;
}
button {
width: 100%;
}
</style>月份相关函数:
getCurrentMonthRange(): 获取本月范围,返回 [第一天, 最后一天]
4. API查询参数日期范围
在API请求中添加日期范围参数。
<template>
<view class="container">
<view class="section">
<text class="title">数据筛选</text>
<view class="filter-form">
<view class="form-item">
<text class="label">日期范围:</text>
<view class="date-range-picker">
<picker mode="date" :value="dateRange[0]" @change="onStartDateChange">
<view class="picker-input">{{ dateRange[0] || '开始日期' }}</view>
</picker>
<text class="separator">~</text>
<picker mode="date" :value="dateRange[1]" @change="onEndDateChange">
<view class="picker-input">{{ dateRange[1] || '结束日期' }}</view>
</picker>
</view>
</view>
<button @click="fetchData">查询数据</button>
</view>
<view v-if="queryParams" class="query-result">
<text class="result-title">请求参数:</text>
<text class="result-content">{{ JSON.stringify(queryParams, null, 2) }}</text>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { addDateRange } from '@/utils/date'
const dateRange = ref<[string, string]>(['', ''])
const queryParams = ref<any>(null)
const onStartDateChange = (e: any) => {
dateRange.value[0] = e.detail.value
}
const onEndDateChange = (e: any) => {
dateRange.value[1] = e.detail.value
}
// 查询数据
const fetchData = () => {
// 构建基础参数
let params: any = {
params: {
keyword: '测试',
status: 1,
},
}
// 添加日期范围参数
// 会自动添加 beginTime 和 endTime 字段
params = addDateRange(params, dateRange.value)
queryParams.value = params
// 实际项目中这里会调用API
console.log('查询参数:', params)
uni.showToast({
title: '参数已生成',
icon: 'success',
})
}
</script>
<style lang="scss" scoped>
.container {
padding: 32rpx;
}
.section {
margin-bottom: 40rpx;
}
.title {
font-size: 32rpx;
font-weight: bold;
margin-bottom: 20rpx;
display: block;
}
.filter-form {
background-color: #f5f7fa;
border-radius: 8rpx;
padding: 24rpx;
margin-bottom: 20rpx;
}
.form-item {
margin-bottom: 20rpx;
}
.label {
display: block;
font-size: 28rpx;
color: #606266;
margin-bottom: 10rpx;
}
.date-range-picker {
display: flex;
align-items: center;
gap: 10rpx;
}
.picker-input {
flex: 1;
height: 70rpx;
line-height: 70rpx;
text-align: center;
background-color: #fff;
border: 1rpx solid #dcdfe6;
border-radius: 8rpx;
font-size: 28rpx;
}
.separator {
color: #909399;
}
.query-result {
padding: 20rpx;
background-color: #f5f7fa;
border-radius: 8rpx;
}
.result-title {
display: block;
font-size: 28rpx;
color: #606266;
font-weight: bold;
margin-bottom: 10rpx;
}
.result-content {
display: block;
font-size: 24rpx;
color: #303133;
font-family: monospace;
white-space: pre-wrap;
word-break: break-all;
}
button {
width: 100%;
}
</style>addDateRange函数说明:
- 自动添加 beginTime 和 endTime 参数
- 支持自定义字段名前缀
- 自动处理空值情况
API
formatDate
格式化日期为指定格式字符串。
function formatDate(
time: Date | string | number,
pattern?: string
): string参数:
time: 日期,可以是Date对象、时间戳(秒/毫秒)或日期字符串pattern: 格式模式,默认'yyyy-MM-dd HH:mm:ss'
返回值: 格式化后的日期字符串
格式模式:
yyyy或YYYY: 四位年份 (2024)yy或YY: 两位年份 (24)MM: 两位月份,补零 (01-12)M: 月份,不补零 (1-12)dd或DD: 两位日期,补零 (01-31)d或D: 日期,不补零 (1-31)HH: 两位小时,24小时制,补零 (00-23)H: 小时,24小时制,不补零 (0-23)hh: 两位小时,12小时制,补零 (01-12)h: 小时,12小时制,不补零 (1-12)mm: 两位分钟,补零 (00-59)m: 分钟,不补零 (0-59)ss: 两位秒,补零 (00-59)s: 秒,不补零 (0-59)SSS: 三位毫秒 (000-999)w: 星期 (日/一/二/三/四/五/六)
示例:
formatDate(new Date(), 'yyyy-MM-dd') // '2024-01-15'
formatDate(1705299045000, 'HH:mm:ss') // '14:30:45'
formatDate('2024-01-15', 'yyyy年MM月dd日') // '2024年01月15日'formatTableDate
格式化表格中的日期字段,专门用于处理后端返回的日期字符串。
function formatTableDate(
cellValue: string,
pattern?: string
): string参数:
cellValue: 表格单元格的日期值(通常是ISO格式字符串)pattern: 格式模式,默认'yyyy-MM-dd HH:mm:ss'
返回值: 格式化后的日期字符串,如果输入为空则返回空字符串
示例:
formatTableDate('2024-01-15T14:30:45.000Z') // '2024-01-15 14:30:45'
formatTableDate('2024-01-15T14:30:45.000Z', 'yyyy-MM-dd') // '2024-01-15'
formatTableDate('', 'yyyy-MM-dd') // ''formatDay
快速格式化日期为年月日格式(yyyy-MM-dd)。
function formatDay(
time: Date | string | number
): string参数:
time: 日期,可以是Date对象、时间戳或日期字符串
返回值: 年月日格式的日期字符串
示例:
formatDay(new Date()) // '2024-01-15'
formatDay(1705299045000) // '2024-01-15'formatRelativeTime
格式化相对时间,显示人性化的时间描述。
function formatRelativeTime(
time: string | number,
option?: string
): string参数:
time: 时间戳(秒或毫秒)或ISO字符串option: 可选格式,当时间超过48小时时使用此格式
返回值: 相对时间字符串
规则:
- 30秒内: "刚刚"
- 1小时内: "X分钟前"
- 24小时内: "X小时前"
- 48小时内: "1天前"
- 更早: 显示为 "MM-dd HH:mm" 或自定义格式
示例:
formatRelativeTime(Date.now() - 10000) // '刚刚'
formatRelativeTime(Date.now() - 5 * 60 * 1000) // '5分钟前'
formatRelativeTime(Date.now() - 2 * 3600 * 1000) // '2小时前'
formatRelativeTime(Date.now() - 25 * 3600 * 1000) // '1天前'
formatRelativeTime(Date.now() - 3 * 24 * 3600 * 1000) // '01-12 14:30'formatDateRange
格式化日期范围为字符串。
function formatDateRange(
dateRange: [Date, Date],
separator?: string,
format?: string
): string参数:
dateRange: 日期范围数组 [开始日期, 结束日期]separator: 分隔符,默认' ~ 'format: 日期格式,默认'yyyy-MM-dd'
返回值: 格式化后的日期范围字符串
示例:
const range: [Date, Date] = [new Date('2024-01-01'), new Date('2024-01-15')]
formatDateRange(range) // '2024-01-01 ~ 2024-01-15'
formatDateRange(range, ' 至 ', 'yyyy年MM月dd日') // '2024年01月01日 至 2024年01月15日'getCurrentTime
获取当前时间的格式化字符串。
function getCurrentTime(
pattern?: string
): string参数:
pattern: 格式模式,默认'HH:mm:ss'
返回值: 格式化后的当前时间字符串
示例:
getCurrentTime() // '14:30:45'
getCurrentTime('HH:mm') // '14:30'getCurrentDate
获取当前日期(yyyy-MM-dd格式)。
function getCurrentDate(): string返回值: 当前日期字符串
示例:
getCurrentDate() // '2024-01-15'getCurrentDateTime
获取当前完整日期时间(yyyy-MM-dd HH:mm:ss格式)。
function getCurrentDateTime(): string返回值: 当前完整日期时间字符串
示例:
getCurrentDateTime() // '2024-01-15 14:30:45'getTimeStamp
获取当前时间戳。
function getTimeStamp(
type: 'ms' | 's'
): number参数:
type: 时间戳类型,'ms'表示毫秒,'s'表示秒
返回值: 当前时间戳
示例:
getTimeStamp('ms') // 1705299045000 (13位)
getTimeStamp('s') // 1705299045 (10位)parseDate
解析日期字符串为Date对象。
function parseDate(
dateStr: string
): Date | null参数:
dateStr: 日期字符串,支持多种格式
返回值: Date对象,解析失败返回null
支持的格式:
- ISO 8601:
'2024-01-15T14:30:45.000Z' - 标准格式:
'2024-01-15','2024/01/15' - 带时间:
'2024-01-15 14:30:45'
示例:
parseDate('2024-01-15') // Date对象
parseDate('2024-01-15T14:30:45.000Z') // Date对象
parseDate('invalid') // nullgetDateRange
获取指定天数的日期范围。
function getDateRange(
days: number
): [Date, Date]参数:
days: 天数,正数表示未来,负数表示过去
返回值: 日期范围 [开始日期, 结束日期]
示例:
getDateRange(7) // 今天到7天后
getDateRange(-7) // 7天前到今天getCurrentWeekRange
获取本周的日期范围(周一到周日)。
function getCurrentWeekRange(): [Date, Date]返回值: 本周范围 [周一, 周日]
示例:
getCurrentWeekRange() // [2024-01-15 00:00:00, 2024-01-21 23:59:59]getCurrentMonthRange
获取本月的日期范围(第一天到最后一天)。
function getCurrentMonthRange(): [Date, Date]返回值: 本月范围 [第一天, 最后一天]
示例:
getCurrentMonthRange() // [2024-01-01 00:00:00, 2024-01-31 23:59:59]getDateRangeByType
根据类型获取预设的日期范围字符串。
function getDateRangeByType(
dateType: string
): [string, string] | null参数:
dateType: 范围类型'today': 今天'yesterday': 昨天'week': 本周'month': 本月'year': 本年
返回值: 日期范围字符串 [开始, 结束],无效类型返回null
示例:
getDateRangeByType('today') // ['2024-01-15 00:00:00', '2024-01-15 23:59:59']
getDateRangeByType('week') // ['2024-01-15 00:00:00', '2024-01-21 23:59:59']
getDateRangeByType('month') // ['2024-01-01 00:00:00', '2024-01-31 23:59:59']initDateRangeFromQuery
从URL查询参数初始化日期范围。
function initDateRangeFromQuery(
query: Record<string, any>,
dateParamName?: string
): [string, string] | ['', '']参数:
query: 路由查询对象dateParamName: 日期范围参数名,默认'dateRange'
返回值: 日期范围数组,未找到返回空数组
示例:
const query = { dateRange: 'today', keyword: 'test' }
initDateRangeFromQuery(query) // ['2024-01-15 00:00:00', '2024-01-15 23:59:59']
initDateRangeFromQuery({}, 'dateRange') // ['', '']addDateRange
为API请求参数添加日期范围字段。
function addDateRange(
params: any,
dateRange: any[],
propName?: string
): any参数:
params: 请求参数对象dateRange: 日期范围数组 [开始, 结束]propName: 字段名前缀,默认为空
返回值: 添加了日期范围的参数对象
添加的字段:
- 无前缀:
beginTime,endTime - 有前缀:
{propName}BeginTime,{propName}EndTime
示例:
const params = { keyword: 'test' }
addDateRange(params, ['2024-01-01', '2024-01-15'])
// { keyword: 'test', params: { beginTime: '2024-01-01', endTime: '2024-01-15' } }
addDateRange(params, ['2024-01-01', '2024-01-15'], 'create')
// { keyword: 'test', params: { createBeginTime: '2024-01-01', createEndTime: '2024-01-15' } }getDaysBetween
计算两个日期之间的天数差。
function getDaysBetween(
start: Date,
end: Date
): number参数:
start: 开始日期end: 结束日期
返回值: 天数差(整数)
示例:
const start = new Date('2024-01-01')
const end = new Date('2024-01-15')
getDaysBetween(start, end) // 14isSameDay
判断两个日期是否是同一天。
function isSameDay(
date1: Date,
date2: Date
): boolean参数:
date1: 第一个日期date2: 第二个日期
返回值: 是否是同一天
示例:
const date1 = new Date('2024-01-15 10:00:00')
const date2 = new Date('2024-01-15 18:00:00')
isSameDay(date1, date2) // true
const date3 = new Date('2024-01-16')
isSameDay(date1, date3) // falsegetWeekOfYear
获取日期是一年中的第几周。
function getWeekOfYear(
date: Date
): number参数:
date: 日期对象
返回值: 周数 (1-53)
示例:
getWeekOfYear(new Date('2024-01-15')) // 3
getWeekOfYear(new Date('2024-12-31')) // 53dateAdd
日期加减运算。
function dateAdd(
date: Date,
type: 'day' | 'month' | 'year',
value: number
): Date参数:
date: 基准日期type: 加减类型'day': 天'month': 月'year': 年
value: 加减的数值,正数为加,负数为减
返回值: 计算后的新Date对象
示例:
const date = new Date('2024-01-15')
dateAdd(date, 'day', 7) // 2024-01-22
dateAdd(date, 'day', -7) // 2024-01-08
dateAdd(date, 'month', 1) // 2024-02-15
dateAdd(date, 'year', 1) // 2025-01-15padZero
数字补零工具函数。
function padZero(
num: number,
targetLength?: number
): string参数:
num: 要补零的数字targetLength: 目标长度,默认 2
返回值: 补零后的字符串
示例:
padZero(5) // '05'
padZero(15) // '15'
padZero(5, 3) // '005'类型定义
/**
* 日期范围元组类型
*/
type DateRange = [Date, Date]
/**
* 日期范围字符串元组类型
*/
type DateRangeString = [string, string]
/**
* 时间戳类型
*/
type TimeStampType = 'ms' | 's'
/**
* 日期输入类型
*/
type DateInput = Date | string | number
/**
* 日期加减类型
*/
type DateAddType = 'day' | 'month' | 'year'
/**
* 预设日期范围类型
*/
type DateRangePreset =
| 'today'
| 'yesterday'
| 'week'
| 'month'
| 'year'
/**
* 格式化模式类型
*/
type FormatPattern = string
/**
* API参数扩展接口
*/
interface ApiParams {
params?: {
[key: string]: any
beginTime?: string
endTime?: string
}
}最佳实践
1. 统一使用date工具,避免直接操作Date
✅ 推荐:
import { formatDate, getCurrentDate } from '@/utils/date'
const today = getCurrentDate()
const formatted = formatDate(new Date(), 'yyyy-MM-dd')❌ 不推荐:
const today = new Date().toISOString().split('T')[0]
const year = new Date().getFullYear()
const month = String(new Date().getMonth() + 1).padStart(2, '0')
const day = String(new Date().getDate()).padStart(2, '0')
const formatted = `${year}-${month}-${day}`原因: date工具已处理各种边界情况和格式兼容问题,直接使用更安全可靠。
2. 相对时间显示需要定时更新
✅ 推荐:
<script lang="ts" setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { formatRelativeTime } from '@/utils/date'
const relativeTime = ref('')
let timer: number | null = null
const updateTime = () => {
relativeTime.value = formatRelativeTime(publishTime)
}
onMounted(() => {
updateTime()
// 每30秒更新一次
timer = setInterval(updateTime, 30000) as unknown as number
})
onUnmounted(() => {
if (timer) clearInterval(timer)
})
</script>❌ 不推荐:
<script lang="ts" setup>
import { formatRelativeTime } from '@/utils/date'
// 只计算一次,不会更新
const relativeTime = formatRelativeTime(publishTime)
</script>原因: 相对时间是动态的,"3分钟前"会变成"4分钟前",需要定时刷新。
3. 时间戳统一使用毫秒,formatDate会自动转换
✅ 推荐:
import { formatDate } from '@/utils/date'
// 后端返回秒级时间戳
const timestamp = 1705299045 // 10位
// formatDate会自动识别并转换
const formatted = formatDate(timestamp, 'yyyy-MM-dd HH:mm:ss')❌ 不推荐:
// 手动转换
const timestamp = 1705299045
const timestampMs = timestamp * 1000
const formatted = formatDate(timestampMs, 'yyyy-MM-dd HH:mm:ss')原因: formatDate内部已实现自动识别10位和13位时间戳,无需手动转换。
4. 表格日期使用formatTableDate
✅ 推荐:
import { formatTableDate } from '@/utils/date'
interface TableRow {
createTime: string // 后端返回的ISO字符串
}
const formatRow = (row: TableRow) => ({
...row,
createTimeFormatted: formatTableDate(row.createTime, 'yyyy-MM-dd HH:mm')
})❌ 不推荐:
import { formatDate } from '@/utils/date'
const formatRow = (row: TableRow) => ({
...row,
createTimeFormatted: row.createTime
? formatDate(row.createTime, 'yyyy-MM-dd HH:mm')
: ''
})原因: formatTableDate专门处理表格场景,自动处理空值情况。
5. 日期范围查询使用addDateRange
✅ 推荐:
import { addDateRange } from '@/utils/date'
const fetchList = (params: any, dateRange: [string, string]) => {
const queryParams = addDateRange(params, dateRange)
return api.get('/list', queryParams)
}❌ 不推荐:
const fetchList = (params: any, dateRange: [string, string]) => {
const queryParams = {
...params,
beginTime: dateRange[0],
endTime: dateRange[1]
}
return api.get('/list', { params: queryParams })
}原因: addDateRange统一处理参数结构,支持自定义前缀,更灵活。
6. 计算日期差使用getDaysBetween而非手动计算
✅ 推荐:
import { getDaysBetween } from '@/utils/date'
const start = new Date('2024-01-01')
const end = new Date('2024-01-15')
const days = getDaysBetween(start, end) // 14❌ 不推荐:
const start = new Date('2024-01-01')
const end = new Date('2024-01-15')
const days = Math.floor((end.getTime() - start.getTime()) / (1000 * 3600 * 24))原因: getDaysBetween考虑了时区和夏令时等问题,结果更准确。
7. 日期加减使用dateAdd而非手动计算
✅ 推荐:
import { dateAdd } from '@/utils/date'
const tomorrow = dateAdd(new Date(), 'day', 1)
const nextMonth = dateAdd(new Date(), 'month', 1)
const lastYear = dateAdd(new Date(), 'year', -1)❌ 不推荐:
const tomorrow = new Date(Date.now() + 24 * 3600 * 1000)
const now = new Date()
const nextMonth = new Date(now.getFullYear(), now.getMonth() + 1, now.getDate())原因: dateAdd处理了月末、闰年等边界情况,避免出错。
8. 组合使用多个工具函数实现复杂逻辑
✅ 推荐:
import { getDateRangeByType, addDateRange, formatDate } from '@/utils/date'
// 获取本周数据
const fetchWeekData = () => {
const weekRange = getDateRangeByType('week')
if (weekRange) {
const params = addDateRange({}, weekRange)
return api.get('/data', params)
}
}
// 格式化并计算
const displayDateInfo = (date: Date) => {
const formatted = formatDate(date, 'yyyy-MM-dd 星期w')
const today = new Date()
const days = getDaysBetween(date, today)
return {
formatted,
daysFromNow: days
}
}原因: 工具函数设计为可组合,通过组合实现复杂业务逻辑更清晰。
常见问题
1. 为什么formatDate支持yyyy和YYYY两种写法?
问题原因:
- 不同日期库使用不同的格式标记
- moment.js 使用 YYYY-MM-DD
- date-fns 等使用 yyyy-MM-dd
- 为了兼容开发者习惯,date工具同时支持两种写法
解决方案:
// 两种写法都支持,结果相同
formatDate(new Date(), 'yyyy-MM-dd') // '2024-01-15'
formatDate(new Date(), 'YYYY-MM-DD') // '2024-01-15'
// 推荐使用小写(与TypeScript、ISO 8601一致)
formatDate(new Date(), 'yyyy-MM-dd HH:mm:ss')2. formatRelativeTime什么时候显示"刚刚",什么时候显示具体时间?
规则说明:
- 30秒内: "刚刚"
- 1分钟-59分钟: "X分钟前"
- 1小时-23小时: "X小时前"
- 24小时-48小时: "1天前"
- 超过48小时: 显示 "MM-dd HH:mm" 或自定义格式
示例:
const now = Date.now()
formatRelativeTime(now) // '刚刚'
formatRelativeTime(now - 10000) // '刚刚' (10秒前)
formatRelativeTime(now - 5 * 60 * 1000) // '5分钟前'
formatRelativeTime(now - 2 * 3600 * 1000) // '2小时前'
formatRelativeTime(now - 25 * 3600 * 1000) // '1天前'
formatRelativeTime(now - 3 * 24 * 3600 * 1000) // '01-12 14:30'
// 自定义超过48小时后的格式
formatRelativeTime(now - 3 * 24 * 3600 * 1000, 'yyyy-MM-dd') // '2024-01-12'3. 后端返回的时间戳是10位还是13位?如何处理?
问题原因:
- JavaScript时间戳是13位(毫秒)
- 部分后端(如Java、PHP)返回10位(秒)
- 需要统一处理避免错误
解决方案:
// formatDate会自动识别并转换
const timestamp10 = 1705299045 // 10位秒级
const timestamp13 = 1705299045000 // 13位毫秒级
formatDate(timestamp10, 'yyyy-MM-dd') // 自动转换为毫秒
formatDate(timestamp13, 'yyyy-MM-dd') // 直接使用
// 判断逻辑:
// 如果数字小于10000000000(10位数最大值),则认为是秒级,乘以1000转换4. getDateRangeByType返回的时间包含时分秒吗?
是的,包含完整的时分秒:
// 今天: 00:00:00 到 23:59:59
getDateRangeByType('today')
// ['2024-01-15 00:00:00', '2024-01-15 23:59:59']
// 本周: 周一00:00:00 到 周日23:59:59
getDateRangeByType('week')
// ['2024-01-15 00:00:00', '2024-01-21 23:59:59']
// 本月: 第一天00:00:00 到 最后一天23:59:59
getDateRangeByType('month')
// ['2024-01-01 00:00:00', '2024-01-31 23:59:59']原因: 用于查询时需要覆盖整天,所以开始时间是00:00:00,结束时间是23:59:59。
5. getCurrentWeekRange的周一是如何计算的?
计算规则:
- 使用ISO 8601标准: 周一作为一周的第一天
- 周日作为一周的最后一天
- 自动处理跨年情况
示例:
// 假设今天是2024-01-15(周一)
getCurrentWeekRange()
// [2024-01-15 00:00:00, 2024-01-21 23:59:59]
// 假设今天是2024-01-17(周三)
getCurrentWeekRange()
// [2024-01-15 00:00:00, 2024-01-21 23:59:59]
// 假设今天是2024-01-21(周日)
getCurrentWeekRange()
// [2024-01-15 00:00:00, 2024-01-21 23:59:59]6. dateAdd加减月份时,如何处理月末日期?
处理规则:
- 使用JavaScript原生Date逻辑
- 如果目标月份没有该日期,自动调整到该月最后一天
示例:
// 1月31日 + 1个月
const jan31 = new Date('2024-01-31')
dateAdd(jan31, 'month', 1)
// 2024-02-29 (2024年是闰年,2月有29天)
// 1月31日 + 2个月
dateAdd(jan31, 'month', 2)
// 2024-03-31
// 8月31日 + 1个月
const aug31 = new Date('2024-08-31')
dateAdd(aug31, 'month', 1)
// 2024-09-30 (9月只有30天)7. 如何从路由参数自动初始化日期范围?
使用initDateRangeFromQuery:
<script lang="ts" setup>
import { ref, onMounted } from 'vue'
import { initDateRangeFromQuery } from '@/utils/date'
import { onLoad } from '@dcloudio/uni-app'
const dateRange = ref<[string, string]>(['', ''])
// uni-app页面加载
onLoad((options) => {
// URL: /pages/list?dateRange=week
dateRange.value = initDateRangeFromQuery(options)
// 自动设置为本周范围
})
</script>路由参数示例:
// 路由: /pages/list?dateRange=today
initDateRangeFromQuery({ dateRange: 'today' })
// ['2024-01-15 00:00:00', '2024-01-15 23:59:59']
// 路由: /pages/list?dateRange=week
initDateRangeFromQuery({ dateRange: 'week' })
// ['2024-01-15 00:00:00', '2024-01-21 23:59:59']
// 无参数
initDateRangeFromQuery({})
// ['', '']8. formatTableDate和formatDate有什么区别?
主要区别:
- 空值处理:
// formatTableDate自动处理空值
formatTableDate('') // ''
formatTableDate(null as any) // ''
formatTableDate(undefined as any) // ''
// formatDate会报错或返回Invalid Date
formatDate('') // 可能出错- 使用场景:
// formatTableDate: 表格数据展示
const tableData = apiData.map(row => ({
...row,
createTime: formatTableDate(row.createTime)
}))
// formatDate: 通用日期格式化
const displayDate = formatDate(new Date(), 'yyyy-MM-dd')- 默认格式:
// 两者默认格式相同
formatTableDate(time) // 默认 'yyyy-MM-dd HH:mm:ss'
formatDate(time) // 默认 'yyyy-MM-dd HH:mm:ss'推荐: 表格场景使用formatTableDate,其他场景使用formatDate。
