⏰面试官:不用Date对象,你怎么解析时间戳?

🎯 起因

昨天刷面试题的时候,看到一道很有意思的题:

如果不允许使用 Date 对象和任何时间格式化函数,给你一个时间戳,你能手动计算出对应的年月日时分秒吗?

于是决定挑战一下自己:不查任何现成的库,纯手工实现一个时间戳转换器。

🤔 什么是时间戳?

首先介绍一下什么是时间戳,Unix 时间戳就是从 1970 年 1 月 1 日 00:00:00 UTC 开始,到现在过了多少秒。

javascript 复制代码
// 比如 1609459200 这个数字
// 表示从1970-01-01 00:00:00开始过了1609459200秒
// 对应北京时间就是:2021-01-01 08:00:00

为什么是 1970 年?据说是因为 Unix 系统诞生的年代...反正就这么约定的,我们照着来就行。

🧠 问题分析

要把一个纯数字转成2024-12-02 08:00:00这种格式,需要解决几个问题:

  1. 时区问题 - UTC 时间要加 8 小时变成北京时间
  2. 闰年问题 - 闰年 2 月有 29 天,平年只有 28 天
  3. 年份计算 - 从总天数推算是哪一年
  4. 月份计算 - 每月天数不同,要逐个计算
  5. 时分秒计算 - 从剩余秒数中提取

看起来很复杂,但拆开来其实就是小学数学题。

🚀 开始实现

最终效果如下

🌍 步骤 1:时区调整

这个最简单,UTC+8 就是加 8 小时的秒数:

javascript 复制代码
adjustTimezone(timestamp) {
  return timestamp + 8 * 3600  // 加28800秒
}

📅 步骤 2:算天数

用除法和取模,小学就学过:

javascript 复制代码
calculateDays(adjustedTimestamp) {
  const totalDays = Math.floor(adjustedTimestamp / 86400)  // 一天86400秒
  const secondsInDay = adjustedTimestamp % 86400
  return { totalDays, secondsInDay }
}

totalDays是从 1970 年 1 月 1 日开始的总天数,secondsInDay是当天已经过去的秒数。

🗓️ 步骤 3:闰年和年份

上网查询一番:

  • 能被 400 整除的是闰年
  • 能被 4 整除但不能被 100 整除的也是闰年
  • 其他都不是
javascript 复制代码
isLeapYear(year) {
  return year % 400 === 0 || (year % 4 === 0 && year % 100 !== 0)
}

calculateYear(totalDays) {
  let year = 1970
  let remainingDays = totalDays

  while (remainingDays >= 0) {
    const daysInYear = this.isLeapYear(year) ? 366 : 365
    if (remainingDays >= daysInYear) {
      remainingDays -= daysInYear
      year++
    } else {
      break
    }
  }

  return { year, remainingDays }
}

就是从 1970 年开始一年年往后数,直到剩余天数不够一年为止。

📆 步骤 4:月份和日期

这里有个坑,我一开始算错了好几次。每个月天数不一样,2 月还要看闰年:

javascript 复制代码
calculateMonth(year, remainingDays) {
  let month = 1
  const daysInMonths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]

  while (month <= 12) {
    let daysInThisMonth = daysInMonths[month - 1]
    if (month === 2 && this.isLeapYear(year)) {
      daysInThisMonth = 29  // 闰年2月29天
    }

    if (remainingDays >= daysInThisMonth) {
      remainingDays -= daysInThisMonth
      month++
    } else {
      break
    }
  }

  const day = remainingDays + 1  // 注意要+1,因为日期从1开始
  return { month, day }
}

特别注意最后的+1,我忘记加过,结果每个月 1 号都变成了 0 号。

⏰ 步骤 5-7:时分秒

这个就是纯粹的数学计算了:

javascript 复制代码
calculateHour(secondsInDay) {
  return Math.floor(secondsInDay / 3600)
}

calculateMinute(secondsInDay) {
  return Math.floor((secondsInDay % 3600) / 60)
}

calculateSecond(secondsInDay) {
  return secondsInDay % 60
}

🔧 代码整合

把所有步骤串起来,就是一个完整的转换器:

javascript 复制代码
class TimestampConverter {
  constructor(timezoneOffset = 8) {
    this.TIMEZONE_OFFSET = timezoneOffset * 3600 // 时区偏移秒数
    this.SECONDS_PER_DAY = 86400 // 一天86400秒
    this.SECONDS_PER_HOUR = 3600 // 一小时3600秒
    this.SECONDS_PER_MINUTE = 60 // 一分钟60秒
    // 各月天数:1月31天,2月28天...
    this.DAYS_IN_MONTHS = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
  }

  /**
   * 主函数:把所有步骤串起来
   */
  convert(timestamp) {
    // 第1步: 调整时区
    const adjustedTimestamp = this.adjustTimezone(timestamp)

    // 第2步: 算天数
    const { totalDays, secondsInDay } = this.calculateDays(adjustedTimestamp)

    // 第3步: 算年份
    const { year, remainingDays } = this.calculateYear(totalDays)

    // 第4步: 算月份和日期
    const { month, day } = this.calculateMonth(year, remainingDays)

    // 第5-7步: 算时分秒
    const hour = this.calculateHour(secondsInDay)
    const minute = this.calculateMinute(secondsInDay)
    const second = this.calculateSecond(secondsInDay)

    // 格式化成好看的样子
    return `${year}-${this.pad(month)}-${this.pad(day)} ${this.pad(
      hour
    )}:${this.pad(minute)}:${this.pad(second)}`
  }

  // 数字补零:1 → 01
  pad(num) {
    return num.toString().padStart(2, '0')
  }

  // ... 其他方法见上面的代码
}

用起来超简单:

javascript 复制代码
const converter = new TimestampConverter()
console.log(converter.convert(1609459200)) // 2021-01-01 08:00:00

🧪 测试效果

咱们弄几个经典测试用例试试:

javascript 复制代码
const tests = [
  { ts: 0, expect: '1970-01-01 08:00:00', desc: 'Unix纪元起点' },
  { ts: 946684800, expect: '2000-01-01 08:00:00', desc: '千禧年' },
  { ts: 951782400, expect: '2000-02-29 08:00:00', desc: '闰年2月29日' },
  { ts: 1609459200, expect: '2021-01-01 08:00:00', desc: '2021新年' },
  { ts: 1733097600, expect: '2024-12-02 08:00:00', desc: '随机日期' }
]

tests.forEach((test) => {
  const result = converter.convert(test.ts)
  const isOK = result === test.expect
  console.log(`${test.desc}: ${isOK ? '✅ 通过' : '❌ 失败'} ${result}`)
})

✨ 最后的效果

完整代码如下

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>时间戳转换</title>
    <style>
      body {
        font-family: Arial, sans-serif;
        max-width: 800px;
        margin: 20px auto;
        padding: 20px;
        line-height: 1.6;
      }
      .section {
        margin-bottom: 30px;
        padding: 20px;
        border: 1px solid #ddd;
        border-radius: 5px;
      }
      input,
      button {
        padding: 8px 12px;
        margin: 5px;
        border: 1px solid #ccc;
        border-radius: 3px;
      }
      button {
        background: #007bff;
        color: white;
        cursor: pointer;
      }
      button:hover {
        background: #0056b3;
      }
      .result {
        background: #f8f9fa;
        padding: 15px;
        margin: 10px 0;
        border-radius: 3px;
        font-family: monospace;
      }
      .test-item {
        margin: 5px 0;
        padding: 5px;
        background: #f0f0f0;
      }
      .pass {
        color: green;
      }
      .fail {
        color: red;
      }
    </style>
  </head>
  <body>
    <h1>时间戳转换演示</h1>

    <div class="section">
      <h2>基础转换</h2>
      <input
        type="number"
        id="timestamp"
        placeholder="输入时间戳"
        value="1723276800"
      />
      <button onclick="convert()">转换</button>
      <button onclick="useNow()">使用当前时间</button>
      <div class="result" id="result">点击转换查看结果...</div>
    </div>

    <div class="section">
      <h2>测试</h2>
      <button onclick="runTest()">运行测试</button>
      <div id="testResult"></div>
    </div>

    <script>
      class TimestampConverter {
        constructor(timezoneOffset = 8) {
          this.TIMEZONE_OFFSET = timezoneOffset * 3600 // 时区偏移秒数
          this.SECONDS_PER_DAY = 86400 // 每天秒数
          this.SECONDS_PER_HOUR = 3600 // 每小时秒数
          this.SECONDS_PER_MINUTE = 60 // 每分钟秒数
          this.DAYS_IN_MONTHS = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
        }

        /**
         * 判断是否为闰年
         */
        isLeapYear(year) {
          return year % 400 === 0 || (year % 4 === 0 && year % 100 !== 0)
        }

        /**
         * 获取指定年月的天数
         */
        getDaysInMonth(year, month) {
          if (month === 2 && this.isLeapYear(year)) {
            return 29
          }
          return this.DAYS_IN_MONTHS[month - 1]
        }

        /**
         * 第1步: 时区调整
         */
        adjustTimezone(timestamp) {
          return timestamp + this.TIMEZONE_OFFSET
        }

        /**
         * 第2步: 计算总天数和当日秒数
         */
        calculateDays(adjustedTimestamp) {
          const totalDays = Math.floor(adjustedTimestamp / this.SECONDS_PER_DAY)
          const secondsInDay = adjustedTimestamp % this.SECONDS_PER_DAY
          return { totalDays, secondsInDay }
        }

        /**
         * 第3步: 计算年份
         */
        calculateYear(totalDays) {
          let year = 1970
          let remainingDays = totalDays

          while (remainingDays >= 0) {
            const daysInYear = this.isLeapYear(year) ? 366 : 365
            if (remainingDays >= daysInYear) {
              remainingDays -= daysInYear
              year++
            } else {
              break
            }
          }

          return { year, remainingDays }
        }

        /**
         * 第4步: 计算月份和日期
         */
        calculateMonth(year, remainingDays) {
          let month = 1

          while (month <= 12) {
            const daysInMonth = this.getDaysInMonth(year, month)
            if (remainingDays >= daysInMonth) {
              remainingDays -= daysInMonth
              month++
            } else {
              break
            }
          }

          const day = remainingDays + 1
          return { month, day }
        }

        /**
         * 第5步: 计算小时
         */
        calculateHour(secondsInDay) {
          return Math.floor(secondsInDay / this.SECONDS_PER_HOUR)
        }

        /**
         * 第6步: 计算分钟
         */
        calculateMinute(secondsInDay) {
          return Math.floor(
            (secondsInDay % this.SECONDS_PER_HOUR) / this.SECONDS_PER_MINUTE
          )
        }

        /**
         * 第7步: 计算秒
         */
        calculateSecond(secondsInDay) {
          return secondsInDay % this.SECONDS_PER_MINUTE
        }

        /**
         * 格式化数字为两位数
         */
        pad(num) {
          return num.toString().padStart(2, '0')
        }

        /**
         * 主转换函数 - 调用各个步骤
         */
        convert(timestamp) {
          // 第1步: 时区调整
          const adjustedTimestamp = this.adjustTimezone(timestamp)

          // 第2步: 计算天数
          const { totalDays, secondsInDay } =
            this.calculateDays(adjustedTimestamp)

          // 第3步: 计算年份
          const { year, remainingDays } = this.calculateYear(totalDays)

          // 第4步: 计算月份和日期
          const { month, day } = this.calculateMonth(year, remainingDays)

          // 第5-7步: 计算时分秒
          const hour = this.calculateHour(secondsInDay)
          const minute = this.calculateMinute(secondsInDay)
          const second = this.calculateSecond(secondsInDay)

          // 格式化输出
          return `${year}-${this.pad(month)}-${this.pad(day)} ${this.pad(
            hour
          )}:${this.pad(minute)}:${this.pad(second)}`
        }
      }

      // 全局转换器实例
      const converter = new TimestampConverter()

      /**
       * 转换时间戳
       */
      function convert() {
        const timestamp = parseInt(document.getElementById('timestamp').value)
        if (isNaN(timestamp)) {
          alert('请输入有效的时间戳')
          return
        }

        const result = converter.convert(timestamp)
        const nativeResult = new Date(timestamp * 1000)
          .toLocaleString('zh-CN', {
            year: 'numeric',
            month: '2-digit',
            day: '2-digit',
            hour: '2-digit',
            minute: '2-digit',
            second: '2-digit',
            hour12: false
          })
          .replace(/\//g, '-')

        const isMatch =
          result.replace(/\s+/g, ' ') === nativeResult.replace(/\s+/g, ' ')

        document.getElementById('result').innerHTML = `
                <strong>自实现结果:</strong> ${result}<br>
                <strong>原生方法对比:</strong> ${nativeResult}<br>
                <strong>匹配状态:</strong> ${isMatch ? '✅ 正确' : '❌ 不匹配'}
            `
      }

      /**
       * 使用当前时间
       */
      function useNow() {
        document.getElementById('timestamp').value = Math.floor(
          Date.now() / 1000
        )
        convert()
      }

      /**
       * 运行测试用例
       */
      function runTest() {
        const tests = [
          { ts: 0, expect: '1970-01-01 08:00:00', desc: 'Unix纪元' },
          { ts: 946684800, expect: '2000-01-01 08:00:00', desc: '2000年' },
          { ts: 951782400, expect: '2000-02-29 08:00:00', desc: '闰年2月29日' },
          { ts: 1609459200, expect: '2021-01-01 08:00:00', desc: '2021年' },
          { ts: 1733097600, expect: '2024-12-02 08:00:00', desc: '2024年12月' }
        ]

        let html = '<h3>测试结果:</h3>'
        let passed = 0

        tests.forEach((test) => {
          const result = converter.convert(test.ts)
          const isPass = result === test.expect
          if (isPass) passed++

          html += `
                    <div class="test-item">
                        <span class="${isPass ? 'pass' : 'fail'}">
                            ${isPass ? '✅' : '❌'}
                        </span>
                        ${test.desc}: ${
            isPass ? '通过' : `期望 ${test.expect}, 得到 ${result}`
          }
                    </div>
                `
        })

        html += `<p><strong>通过率: ${passed}/${tests.length}</strong></p>`
        document.getElementById('testResult').innerHTML = html
      }

      // 初始化
      convert()
    </script>
  </body>
</html>
相关推荐
熊猫钓鱼>_>2 分钟前
腾讯云EdgeOne Pages深度使用指南
javascript·云计算·腾讯云
LLLLYYYRRRRRTT8 分钟前
MariaDB 数据库管理与web服务器
前端·数据库·mariadb
胡gh9 分钟前
什么是瀑布流?用大白话给你讲明白!
前端·javascript·面试
C4程序员13 分钟前
北京JAVA基础面试30天打卡06
java·开发语言·面试
universe_0115 分钟前
day22|学习前端ts语言
前端·笔记
teeeeeeemo19 分钟前
一些js数组去重的实现算法
开发语言·前端·javascript·笔记·算法
Zz_waiting.20 分钟前
Javaweb - 14.1 - 前端工程化
前端·es6
掘金安东尼22 分钟前
前端周刊第426期(2025年8月4日–8月10日)
前端·javascript·面试
Abadbeginning22 分钟前
FastSoyAdmin导出excel报错‘latin-1‘ codec can‘t encode characters in position 41-54
前端·javascript·后端
ZXT24 分钟前
WebAssembly
前端