Dayjs时区

56 阅读12分钟

本文可能比较水,不再介绍dayjs一些基础的用法,各位巨佬们肯定都用得很6了,本文只是基于本人在工作中的一些关于时区的经验和困惑,特此留念,不喜勿喷。

首先摆明一个概念,时间戳没有时区,全世界在某一个时刻的时间戳都是一样的,这个观点应该没人反对吧。

先上第一张图,显示世界上一些城市的当前时间,参照时间为北京时间:2025-12-12 12:11:38

image.png

以下内容是关于dayjs时区的一些用法、经验、困惑,欢迎各位解惑

  // 虽然默认时区是Asia/Bangkok,但是这里还是会显示Asia/Shanghai的时区时间
  console.log(dayjs().format('YYYY-MM-DD HH:mm:ss')) // 2025-12-09 16:02:37
  // 只有设置了时区,才会根据当前时区显示对应的时间(tz没有传对应的时区,但是设置过默认时区,所以会显示默认时区的时间)
  console.log(dayjs().tz().format('YYYY-MM-DD HH:mm:ss')) // 2025-12-09 15:02:37
  // dayjs.tz(undefined, true) 当传递第二个参数为 true 时,只更新时区 (和偏移量),本地时间将保持不变。所以还是显示本地时间
  console.log(dayjs().tz(undefined, true).format('YYYY-MM-DD HH:mm:ss')) // 2025-12-09 16:03:42
  // dayjs.tz('Asia/Tokyo', true) 当传递第二个参数为 true 时,虽然传了时区,但是只会更新时区 (和偏移量),本地时间将保持不变。所以还是显示本地时间
  console.log(dayjs().tz('Asia/Tokyo', true).format('YYYY-MM-DD HH:mm:ss')) // 2025-12-09 16:03:42
  // dayjs().tz(undefined)第一个参数传undefined代表使用默认时区
  console.log(dayjs().tz(undefined).format('YYYY-MM-DD HH:mm:ss')) // 2025-12-09 15:07:35
  // dayjs.tz('Asia/Tokyo', true) 当传递第二个参数为 true 时,虽然传了时区,但是只会更新时区 (和偏移量),本地时间将保持不变。所以还是显示本地时间
  console.log(dayjs().tz('Asia/Shanghai', true).format('YYYY-MM-DD HH:mm:ss')) // 2025-12-09 16:30:48
  // dayjs.tz('Asia/Tokyo') 当传递第二个参数不传时,会显示当前时区时间
  console.log(dayjs().tz('Asia/Tokyo').format('YYYY-MM-DD HH:mm:ss')) // 2025-12-09 17:30:48
  // 特定时间,没有设置时区,使用本地时间
  console.log(dayjs('2025-08-27').format('YYYY-MM-DD HH:mm:ss')) // 2025-08-27 00:00:00
  // 特定时间,设置时区,使用时区时间
  console.log(dayjs('2025-08-27').tz().format('YYYY-MM-DD HH:mm:ss')) // 2025-08-26 23:00:00
  // 特定时间,设置时区,使用时区时间
  console.log(
    dayjs('2025-08-27').tz('Asia/Tokyo').format('YYYY-MM-DD HH:mm:ss'),
  ) // 2025-08-27 01:00:00

  // 不管什么时区,返回的时间戳都一样,时间戳没有时区的概念
  console.log(dayjs().valueOf()) // 1765269971719
  // 不管什么时区,返回的时间戳都一样,时间戳没有时区的概念
  console.log(dayjs().tz().valueOf()) // 1765269971719
  // 不管什么时区,返回的时间戳都一样,时间戳没有时区的概念
  console.log(dayjs().tz('Asia/Tokyo').valueOf()) // 1765269971719

  // 但是获取开始时间,会根据时区进行计算(这点暂时没想通为什么?求大佬们指点)
  console.log(dayjs().startOf('d').valueOf()) // 1765209600000
  console.log(dayjs().tz().startOf('d').valueOf()) // 1765213200000
  console.log(dayjs().tz('Asia/Tokyo').startOf('d').valueOf()) // 1765206000000

  // 一个特定的时间,不管什么时区时间戳都是一样的,再次证明时间戳没有时区的概念
  console.log(dayjs('2025-08-27 10:00:00').valueOf()) // 1756260000000
  console.log(dayjs('2025-08-27 10:00:00').tz().valueOf()) // 1756260000000
  console.log(dayjs('2025-08-27 10:00:00').tz('Asia/Tokyo').valueOf()) // 1756260000000

  // 但是获取开始时间,同样会根据时区进行计算
  console.log(dayjs('2025-08-27 00:00:00').startOf('d').valueOf()) // 1756224000000
  console.log(dayjs('2025-08-27 00:00:00').tz().startOf('d').valueOf()) // 1756141200000
  console.log(
    dayjs('2025-08-27 00:00:00').tz('Asia/Tokyo').startOf('d').valueOf(),
  ) // 1756220400000

  // dayjs.tz()是dayjs 的静态方法,第一个参数是时间,第二个参数是时区,返回一个dayjs对象,这个对象调用时创建指定时区的时间,需要传入时间和时区作为参数,主要用于直接创建特定时区的时间对象

  // dayjs.tz的第一个参数不传,代表当前时间,全世界当前时间的时间戳都一样
  console.log(dayjs.tz().valueOf()) // 1765276466531
  console.log(dayjs.tz(undefined, 'Asia/Shanghai').valueOf()) // 1765276466531
  console.log(dayjs.tz(undefined, 'Asia/Tokyo').valueOf()) // 1765276466531

  // 同样是第一个参数不传,设置不同的时区,但是加上了获取开始时间,获取到的时间戳就不一样了。(想了一下,不同时区的0点0分0秒时, 时间戳是不一样的,大概是这个原因吧)
  console.log(dayjs.tz().startOf('d').valueOf()) // 1765213200000
  console.log(dayjs.tz(undefined, 'Asia/Shanghai').startOf('d').valueOf()) // 1765209600000
  console.log(dayjs.tz(undefined, 'Asia/Tokyo').startOf('d').valueOf()) // 1765206000000

  // 特定时间格式化,不带时区,显示本地时间
  console.log(dayjs('2025-08-27').format('YYYY-MM-DD HH:mm:ss')) // 2025-08-27 00:00:00
  // 特定时间格式化,当传递第二个参数为 true 时,虽然传了时区,但是只会更新时区 (和偏移量),本地时间将保持不变。所以还是显示本地时间
  console.log(
    dayjs('2025-08-27').tz(undefined, true).format('YYYY-MM-DD HH:mm:ss'),
  ) // 2025-08-27 00:00:00
  // 特定时间格式化, 当传递第二个参数为 true 时,虽然传了时区,但是只会更新时区 (和偏移量),本地时间将保持不变。所以还是显示本地时间
  console.log(
    dayjs('2025-08-27').tz('Asia/Tokyo', true).format('YYYY-MM-DD HH:mm:ss'),
  ) // 2025-08-27 00:00:00
  // 特定时间格式化, 当传递第二个参数不传时,会显示当前时区时间
  console.log(
    dayjs('2025-08-27').tz('Asia/Tokyo').format('YYYY-MM-DD HH:mm:ss'),
  ) // 2025-08-27 10:00:00

  // 注意:一个特定的时间,tz传了时区,

  // 显示本地时间戳
  console.log(dayjs('2025-08-27').valueOf()) // 1756224000000
  // 第二个参数是true, 会更新时区 (和偏移量), 显示时区时间戳, 得出来这个时间戳是指当前时区是2025-08-27 00:00:00时的时间戳,因为默认时区是Asia/Bangkok比本地时区晚一个小时,所以当Asia/Bangkok时区是2025-08-27 00:00:00时,时间戳要比本地时区时间戳多了1小时
  console.log(dayjs('2025-08-27').tz(undefined, true).valueOf()) // 1756227600000
  // 第二个参数是true, 会更新时区 (和偏移量), 显示时区时间戳, 得出来这个时间戳是指当前时区是2025-08-27 00:00:00时的时间戳,因为默认时区是Asia/Bangkok比本地时区晚一个小时,所以当Asia/Bangkok时区是2025-08-27 00:00:00时,时间戳要比本地时区时间戳多了1小时
  console.log(dayjs('2025-08-27').tz('Asia/Tokyo', true).valueOf()) // 1756220400000
  // 第二个参数是false, 不会更新时区 (和偏移量), 显示本地时间戳
  console.log(dayjs('2025-08-27').tz(undefined).valueOf()) // 1756224000000
  // 第二个参数是false, 不会更新时区 (和偏移量), 显示本地时间戳
  console.log(dayjs('2025-08-27').tz('Asia/Tokyo').valueOf()) // 1756224000000
  // 显示本地时间戳
  console.log(dayjs().valueOf()) // 1765508371883
  // 第二个参数是true, 会更新时区 (和偏移量), 显示时区时间戳
  console.log(dayjs().tz(undefined, true).valueOf()) // 1765511971884
  // 第二个参数是false, 不会更新时区 (和偏移量), 显示本地时间戳
  console.log(dayjs().tz().valueOf()) // 1765508371884

  // 获取今天0点时,当前时区应该的时间戳,因为是一个特定时间,所以不同时区到今天的0点,时间戳应该不同
  console.log(dayjs.tz(undefined, 'Asia/Tokyo').startOf('d').valueOf()) // 1765465200000
  console.log(dayjs().tz('Asia/Tokyo').startOf('d').valueOf())  // 1765465200000
  console.log(dayjs().tz('Asia/Tokyo', true).startOf('d').valueOf())  // 1765465200000
  console.log(dayjs.tz(undefined).startOf('d').valueOf()) // 1765472400000
  console.log(dayjs().tz().startOf('d').valueOf()) // 1765472400000
  console.log(dayjs().tz(undefined, true).startOf('d').valueOf()) // 1765472400000
  console.log(dayjs.tz(undefined, 'Asia/Shanghai').startOf('d').valueOf()) // 1765468800000
  console.log(dayjs().tz('Asia/Shanghai').startOf('d').valueOf()) // 1765468800000
  console.log(dayjs().tz('Asia/Shanghai', true).startOf('d').valueOf()) // 1765468800000

  // 获取本地时间和utc时间的时间差
  console.log(dayjs().utcOffset()) // 480
  // 获取当前时区和utc时间的时间差
  console.log(dayjs().tz().utcOffset()) // 420
  // 获取当前时区和utc时间的时间差
  console.log(dayjs().tz('Asia/Tokyo').utcOffset()) // 540
  console.log(dayjs().tz('Asia/Tokyo',  true).utcOffset()) // 540

  // 获取时间戳在当前时区下的时间
  console.log(dayjs(1756369695752).tz().format('YYYY-MM-DD HH:mm:ss')) // 2025-08-28 15:28:15
  // 获取时间戳的本地时间
  console.log(dayjs(1756369695752).format('YYYY-MM-DD HH:mm:ss')) // 2025-08-28 16:28:15

  // 特定时间格式化为本地时间
  console.log(dayjs('2025-08-27').format('YYYY-MM-DD HH:mm:ss')) // 2025-08-27 00:00:00
  // 特定时间格式化为时区时间,因为时间定了,所以在该时区下还是这个时间
  console.log(
    dayjs.tz('2025-08-27', 'Asia/Bangkok').format('YYYY-MM-DD HH:mm:ss'), // 2025-08-27 00:00:00
  )

  // dayjs(xxxx)和dayjs.tz(xxx, xxx)代表的意义是不一样的,dayjs(xxxx)时间已经定好了,不同时区在这个时间下还是这个时间,而dayjs.tz(xxx, xxx)时间没有定,代表某个时间在某个时区下,所以不同时区下时间会不同

  // 特定时间,显示本地时间戳
  console.log(dayjs('2025-08-27').valueOf()) // 1756224000000
  // 显示这个时间在该时区下的时间戳
  console.log(dayjs.tz('2025-08-27', 'Asia/Bangkok').valueOf()) // 1756227600000
  // 特定时间,时间已经定好了,所有时区下都是这个时间,时间戳一样
  console.log(dayjs('2025-08-27').tz('Asia/Bangkok').valueOf()) // 1756224000000
  // 特定时间,时间已经定好了,所有时区下都是这个时间,时间戳一样
  console.log(dayjs('2025-08-27').tz('Asia/Tokyo').valueOf()) // 1756224000000
  // 这个得出的时间是2025-08-26 01:00:00,暂时有点没理解为什么  求大佬们指点
  console.log(dayjs('2025-08-27').tz('Asia/Bangkok').startOf('d').valueOf()) // 1756141200000

  // 该时间戳的本地时间
  console.log(dayjs(1756369695752).format('YYYY-MM-DD HH:mm:ss')) // 2025-08-28 16:28:15
  // 该时间戳的时区时间
  console.log(dayjs.tz(1756369695752).format('YYYY-MM-DD HH:mm:ss')) // 2025-08-28 15:28:15
  // 该时间戳的时区时间
  console.log(dayjs(1756369695752).tz().format('YYYY-MM-DD HH:mm:ss')) // 2025-08-28 15:28:15

  // 获取utc时间,传入 true 将只改变 UTC 模式而不改变本地时间
  console.log(dayjs().utc(true).format('YYYY-MM-DD HH:mm:ss')) // 2025-12-12 12:01:05
  // 获取utc时间,传入 true 将只改变 UTC 模式而不改变本地时间,但是这里转换了时区,所以显示该时区的本地时间
  console.log(dayjs().tz().utc(true).format('YYYY-MM-DD HH:mm:ss')) // 2025-12-12 11:02:02
  // 获取utc时间(标准时间)
  console.log(dayjs().utc().format('YYYY-MM-DD HH:mm:ss')) // 2025-12-12 04:01:23

  // 获取当前时区
  console.log(dayjs.tz.guess()) // Asia/Shanghai

下面是两个基于antd DatePicker关于时间选择的自定义组件

MyDatePicker

import { useControllableValue } from 'ahooks'
import { DatePicker, DatePickerProps } from 'antd'
import dayjs from 'dayjs'
import { useMemo } from 'react'

const defaultShortcuts = [
  {
    label: '今天',
    value: dayjs(),
  },
  {
    label: '昨天',
    value: dayjs().subtract(1, 'day'),
  },
  {
    label: '三天前',
    value: dayjs().subtract(3, 'days'),
  },
  {
    label: '一周前',
    value: dayjs().subtract(1, 'week'),
  },
  {
    label: '15天前',
    value: dayjs().subtract(15, 'days'),
  },
  {
    label: '一个月前',
    value: dayjs().subtract(1, 'month'),
  },
]

// 时间选择器组件的属性
export interface MyDatePickerProps extends DatePickerProps {
  shortcuts?: number[] // 快捷选项
  shortcutsMap?: Record<number, string> // 快捷选项的映射
  shortcutsRender?: (shortcuts?: number[]) => DatePickerProps['presets']
  showPresets?: boolean // 是否显示快捷选项
}

// 非受控/受控时间选择器组件
export default function MyDatePicker(props: MyDatePickerProps) {
  const {
    shortcuts,
    shortcutsMap,
    showPresets = true,
    shortcutsRender,
    ...rests
  } = props

  const [values, setValues] =
    useControllableValue<DatePickerProps['value']>(props)

  const presets = useMemo(() => {
    if (!showPresets) return undefined
    if (shortcutsRender && shortcuts?.length) {
      return shortcutsRender(shortcuts)
    }
    if (shortcuts?.length) {
      return shortcuts.map((shortcut) => {
        return {
          label: shortcutsMap?.[shortcut] || `近${shortcut}天`,
          value: dayjs().subtract(shortcut, 'days'),
        }
      })
    }
    return defaultShortcuts
  }, [shortcuts, shortcutsMap, showPresets, shortcutsRender])

  return (
    <DatePicker
      presets={presets}
      {...rests}
      value={values}
      onChange={setValues}
    />
  )
}

MyTzDatePicker

import { useControllableValue } from 'ahooks'
import { DatePickerProps } from 'antd'
import dayjs from 'dayjs'
import { useCallback, useEffect, useMemo } from 'react'
import MyDatePicker, { MyDatePickerProps } from '../MyDatePicker/Index'

// 带时区时间选择器组件的属性
export interface MyTzDatePickerProps extends MyDatePickerProps {
  tzDiff: number // 时区差秒数
}

// 非受控/受控时间带时区选择器组件
export default function MyTzDatePicker(props: MyTzDatePickerProps) {
  const { tzDiff = 0, defaultValue, ...rests } = props

  const [values, setValues] =
    useControllableValue<DatePickerProps['value']>(props)

  const valuesWithTz = useMemo(() => {
    if (!values) return values
    return dayjs(values).subtract(tzDiff, 'seconds')
  }, [values, tzDiff])

  const setValuesWithTz = useCallback(() => {
    return (value: DatePickerProps['value']) => {
      if (!value) return setValues(value)
      setValues(dayjs(value).add(tzDiff, 'seconds'))
    }
  }, [setValues, tzDiff])

  useEffect(() => {
    if (defaultValue) {
      setValues(dayjs(defaultValue).add(tzDiff, 'seconds'))
    }
  }, [defaultValue, setValues, setValuesWithTz, tzDiff])

  return (
    <MyDatePicker {...rests} value={valuesWithTz} onChange={setValuesWithTz} />
  )
}