Skip to content

Day.js 课程计划

课程概述

本课程将深入讲解 Day.js 的原理和实现,帮助你掌握轻量级日期处理库的使用和最佳实践。

课程安排

第 1 节:Day.js 简介与环境搭建(20 分钟)

学习目标

  • 了解 Day.js 的历史和设计目标
  • 理解 Day.js 与 Moment.js 的区别
  • 搭建开发环境

课程内容

  1. Day.js 的历史背景
  2. Day.js 的设计目标
  3. Day.js 与 Moment.js 的对比
  4. 环境搭建

实践任务

bash
# 创建项目
mkdir dayjs-core
cd dayjs-core
npm init -y

# 安装依赖
npm install --save-dev jest

# 创建目录结构
mkdir -p src test

测试命令

bash
npm test

预期输出

PASS  test/index.test.js

第 2 节:日期解析实现(30 分钟)

学习目标

  • 理解日期解析的原理
  • 实现日期解析功能
  • 支持多种日期格式

课程内容

  1. 日期解析原理
  2. 支持 Date 对象
  3. 支持时间戳
  4. 支持 ISO 8601 格式
  5. 支持自定义格式

代码实现

javascript
// src/parse.js
export function parseDate(input) {
  if (input === null || input === undefined) {
    return new Date()
  }

  if (input instanceof Date) {
    return input
  }

  if (typeof input === 'number') {
    return new Date(input)
  }

  if (typeof input === 'string') {
    if (/^\d+$/.test(input)) {
      return new Date(parseInt(input, 10))
    }
    return new Date(input)
  }

  return new Date()
}

测试用例

javascript
// test/parse.test.js
import { parseDate } from '../src/parse'

test('parse Date object', () => {
  const date = new Date('2023-01-01')
  const result = parseDate(date)
  expect(result.getTime()).toBe(date.getTime())
})

test('parse timestamp', () => {
  const timestamp = 1672531200000
  const result = parseDate(timestamp)
  expect(result.getTime()).toBe(timestamp)
})

test('parse ISO string', () => {
  const result = parseDate('2023-01-01')
  expect(result.getFullYear()).toBe(2023)
  expect(result.getMonth()).toBe(0)
  expect(result.getDate()).toBe(1)
})

测试命令

bash
npm test

预期输出

PASS  test/parse.test.js
  ✓ parse Date object (2 ms)
  ✓ parse timestamp
  ✓ parse ISO string

第 3 节:日期格式化实现(30 分钟)

学习目标

  • 理解日期格式化的原理
  • 实现日期格式化功能
  • 支持自定义格式字符串

课程内容

  1. 日期格式化原理
  2. 支持常用格式标记
  3. 实现格式化函数
  4. 补零处理

代码实现

javascript
// src/format.js
export function formatDate(date, formatString) {
  const year = date.getFullYear()
  const month = date.getMonth() + 1
  const day = date.getDate()
  const hours = date.getHours()
  const minutes = date.getMinutes()
  const seconds = date.getSeconds()

  const tokens = {
    YYYY: year,
    YY: String(year).slice(-2),
    M: month,
    MM: padZero(month),
    D: day,
    DD: padZero(day),
    H: hours,
    HH: padZero(hours),
    m: minutes,
    mm: padZero(minutes),
    s: seconds,
    ss: padZero(seconds)
  }

  return formatString.replace(
    /YYYY|YY|M|MM|D|DD|H|HH|m|mm|s|ss/g,
    (match) => tokens[match]
  )
}

function padZero(num) {
  return num < 10 ? `0${num}` : num
}

测试用例

javascript
// test/format.test.js
import { formatDate } from '../src/format'

test('format date with YYYY-MM-DD', () => {
  const date = new Date('2023-01-01')
  const result = formatDate(date, 'YYYY-MM-DD')
  expect(result).toBe('2023-01-01')
})

test('format date with HH:mm:ss', () => {
  const date = new Date('2023-01-01T12:30:45')
  const result = formatDate(date, 'HH:mm:ss')
  expect(result).toBe('12:30:45')
})

test('pad zero', () => {
  const date = new Date('2023-01-01T01:02:03')
  const result = formatDate(date, 'YYYY-MM-DD HH:mm:ss')
  expect(result).toBe('2023-01-01 01:02:03')
})

测试命令

bash
npm test

预期输出

PASS  test/format.test.js
  ✓ format date with YYYY-MM-DD
  ✓ format date with HH:mm:ss
  ✓ pad zero

第 4 节:日期操作实现(30 分钟)

学习目标

  • 理解日期操作的原理
  • 实现日期加减功能
  • 支持多种时间单位

课程内容

  1. 日期操作原理
  2. 实现加法功能
  3. 实现减法功能
  4. 支持多种时间单位

代码实现

javascript
// src/manipulate.js
export function addDate(date, amount, unit) {
  const result = new Date(date)

  switch (unit) {
    case 'year':
    case 'years':
      result.setFullYear(result.getFullYear() + amount)
      break
    case 'month':
    case 'months':
      result.setMonth(result.getMonth() + amount)
      break
    case 'day':
    case 'days':
      result.setDate(result.getDate() + amount)
      break
    case 'hour':
    case 'hours':
      result.setHours(result.getHours() + amount)
      break
    case 'minute':
    case 'minutes':
      result.setMinutes(result.getMinutes() + amount)
      break
    case 'second':
    case 'seconds':
      result.setSeconds(result.getSeconds() + amount)
      break
  }

  return result
}

export function subtractDate(date, amount, unit) {
  return addDate(date, -amount, unit)
}

测试用例

javascript
// test/manipulate.test.js
import { addDate, subtractDate } from '../src/manipulate'

test('add 1 day', () => {
  const date = new Date('2023-01-01')
  const result = addDate(date, 1, 'day')
  expect(result.getDate()).toBe(2)
})

test('add 1 month', () => {
  const date = new Date('2023-01-01')
  const result = addDate(date, 1, 'month')
  expect(result.getMonth()).toBe(1)
})

test('subtract 1 day', () => {
  const date = new Date('2023-01-02')
  const result = subtractDate(date, 1, 'day')
  expect(result.getDate()).toBe(1)
})

test('add 1 year', () => {
  const date = new Date('2023-01-01')
  const result = addDate(date, 1, 'year')
  expect(result.getFullYear()).toBe(2024)
})

测试命令

bash
npm test

预期输出

PASS  test/manipulate.test.js
  ✓ add 1 day
  ✓ add 1 month
  ✓ subtract 1 day
  ✓ add 1 year

第 5 节:日期比较实现(20 分钟)

学习目标

  • 理解日期比较的原理
  • 实现日期比较功能
  • 支持多种比较方式

课程内容

  1. 日期比较原理
  2. 实现比较函数
  3. 支持不同粒度的比较

代码实现

javascript
// src/compare.js
export function isBefore(date1, date2, unit) {
  const time1 = unit ? getUnitTime(date1, unit) : date1.getTime()
  const time2 = unit ? getUnitTime(date2, unit) : date2.getTime()
  return time1 < time2
}

export function isAfter(date1, date2, unit) {
  const time1 = unit ? getUnitTime(date1, unit) : date1.getTime()
  const time2 = unit ? getUnitTime(date2, unit) : date2.getTime()
  return time1 > time2
}

export function isSame(date1, date2, unit) {
  const time1 = unit ? getUnitTime(date1, unit) : date1.getTime()
  const time2 = unit ? getUnitTime(date2, unit) : date2.getTime()
  return time1 === time2
}

function getUnitTime(date, unit) {
  switch (unit) {
    case 'year':
      return date.getFullYear()
    case 'month':
      return date.getFullYear() * 100 + date.getMonth()
    case 'day':
      return date.getFullYear() * 10000 + (date.getMonth() + 1) * 100 + date.getDate()
    default:
      return date.getTime()
  }
}

测试用例

javascript
// test/compare.test.js
import { isBefore, isAfter, isSame } from '../src/compare'

test('isBefore', () => {
  const date1 = new Date('2023-01-01')
  const date2 = new Date('2023-01-02')
  expect(isBefore(date1, date2)).toBe(true)
  expect(isBefore(date2, date1)).toBe(false)
})

test('isAfter', () => {
  const date1 = new Date('2023-01-01')
  const date2 = new Date('2023-01-02')
  expect(isAfter(date2, date1)).toBe(true)
  expect(isAfter(date1, date2)).toBe(false)
})

test('isSame', () => {
  const date1 = new Date('2023-01-01')
  const date2 = new Date('2023-01-01')
  expect(isSame(date1, date2)).toBe(true)
})

test('isSame with unit', () => {
  const date1 = new Date('2023-01-01T12:00:00')
  const date2 = new Date('2023-01-01T18:00:00')
  expect(isSame(date1, date2, 'day')).toBe(true)
  expect(isSame(date1, date2, 'hour')).toBe(false)
})

测试命令

bash
npm test

预期输出

PASS  test/compare.test.js
  ✓ isBefore
  ✓ isAfter
  ✓ isSame
  ✓ isSame with unit

第 6 节:获取和设置实现(20 分钟)

学习目标

  • 理解获取和设置的原理
  • 实现日期属性获取和设置
  • 支持链式调用

课程内容

  1. 获取日期属性
  2. 设置日期属性
  3. 链式调用实现
  4. 不可变性保证

代码实现

javascript
// src/get-set.js
export function getDateProperty(date, property) {
  switch (property) {
    case 'year':
      return date.getFullYear()
    case 'month':
      return date.getMonth()
    case 'date':
      return date.getDate()
    case 'day':
      return date.getDay()
    case 'hour':
      return date.getHours()
    case 'minute':
      return date.getMinutes()
    case 'second':
      return date.getSeconds()
    case 'millisecond':
      return date.getMilliseconds()
    default:
      throw new Error(`Invalid property: ${property}`)
  }
}

export function setDateProperty(date, property, value) {
  const result = new Date(date)

  switch (property) {
    case 'year':
      result.setFullYear(value)
      break
    case 'month':
      result.setMonth(value)
      break
    case 'date':
      result.setDate(value)
      break
    case 'hour':
      result.setHours(value)
      break
    case 'minute':
      result.setMinutes(value)
      break
    case 'second':
      result.setSeconds(value)
      break
    case 'millisecond':
      result.setMilliseconds(value)
      break
    default:
      throw new Error(`Invalid property: ${property}`)
  }

  return result
}

测试用例

javascript
// test/get-set.test.js
import { getDateProperty, setDateProperty } from '../src/get-set'

test('get year', () => {
  const date = new Date('2023-01-01')
  expect(getDateProperty(date, 'year')).toBe(2023)
})

test('get month', () => {
  const date = new Date('2023-01-01')
  expect(getDateProperty(date, 'month')).toBe(0)
})

test('set year', () => {
  const date = new Date('2023-01-01')
  const result = setDateProperty(date, 'year', 2024)
  expect(result.getFullYear()).toBe(2024)
  expect(date.getFullYear()).toBe(2023) // 原对象不变
})

test('set month', () => {
  const date = new Date('2023-01-01')
  const result = setDateProperty(date, 'month', 5)
  expect(result.getMonth()).toBe(5)
  expect(date.getMonth()).toBe(0) // 原对象不变
})

测试命令

bash
npm test

预期输出

PASS  test/get-set.test.js
  ✓ get year
  ✓ get month
  ✓ set year
  ✓ set month

第 7 节:Day.js 类实现(30 分钟)

学习目标

  • 理解 Day.js 类的设计
  • 实现 Day.js 类
  • 集成所有功能

课程内容

  1. Day.js 类设计
  2. 构造函数实现
  3. 方法集成
  4. 链式调用支持

代码实现

javascript
// src/dayjs.js
import { parseDate } from './parse'
import { formatDate } from './format'
import { addDate, subtractDate } from './manipulate'
import { isBefore, isAfter, isSame } from './compare'
import { getDateProperty, setDateProperty } from './get-set'

export class Dayjs {
  constructor(input) {
    this.$d = parseDate(input)
  }

  format(formatString) {
    return formatDate(this.$d, formatString)
  }

  add(amount, unit) {
    const newDate = addDate(this.$d, amount, unit)
    return new Dayjs(newDate)
  }

  subtract(amount, unit) {
    const newDate = subtractDate(this.$d, amount, unit)
    return new Dayjs(newDate)
  }

  isBefore(date, unit) {
    const otherDate = date instanceof Dayjs ? date.$d : parseDate(date)
    return isBefore(this.$d, otherDate, unit)
  }

  isAfter(date, unit) {
    const otherDate = date instanceof Dayjs ? date.$d : parseDate(date)
    return isAfter(this.$d, otherDate, unit)
  }

  isSame(date, unit) {
    const otherDate = date instanceof Dayjs ? date.$d : parseDate(date)
    return isSame(this.$d, otherDate, unit)
  }

  get(property) {
    return getDateProperty(this.$d, property)
  }

  set(property, value) {
    const newDate = setDateProperty(this.$d, property, value)
    return new Dayjs(newDate)
  }

  year() {
    return this.get('year')
  }

  month() {
    return this.get('month')
  }

  date() {
    return this.get('date')
  }

  hour() {
    return this.get('hour')
  }

  minute() {
    return this.get('minute')
  }

  second() {
    return this.get('second')
  }

  millisecond() {
    return this.get('millisecond')
  }
}

export default function dayjs(input) {
  return new Dayjs(input)
}

测试用例

javascript
// test/dayjs.test.js
import dayjs, { Dayjs } from '../src/dayjs'

test('create dayjs instance', () => {
  const d = dayjs('2023-01-01')
  expect(d).toBeInstanceOf(Dayjs)
})

test('format date', () => {
  const d = dayjs('2023-01-01')
  expect(d.format('YYYY-MM-DD')).toBe('2023-01-01')
})

test('add 1 day', () => {
  const d = dayjs('2023-01-01')
  const result = d.add(1, 'day')
  expect(result.format('YYYY-MM-DD')).toBe('2023-01-02')
  expect(d.format('YYYY-MM-DD')).toBe('2023-01-01') // 原对象不变
})

test('chain calls', () => {
  const result = dayjs('2023-01-01')
    .add(1, 'day')
    .subtract(1, 'month')
    .format('YYYY-MM-DD')
  expect(result).toBe('2022-12-02')
})

test('isBefore', () => {
  const d1 = dayjs('2023-01-01')
  const d2 = dayjs('2023-01-02')
  expect(d1.isBefore(d2)).toBe(true)
})

test('get and set', () => {
  const d = dayjs('2023-01-01')
  expect(d.year()).toBe(2023)
  const d2 = d.set('year', 2024)
  expect(d2.year()).toBe(2024)
  expect(d.year()).toBe(2023)
})

测试命令

bash
npm test

预期输出

PASS  test/dayjs.test.js
  ✓ create dayjs instance
  ✓ format date
  ✓ add 1 day
  ✓ chain calls
  ✓ isBefore
  ✓ get and set

第 8 节:综合实践与总结(30 分钟)

学习目标

  • 综合运用所有功能
  • 实现实际应用场景
  • 总结学习要点

课程内容

  1. 综合实践
  2. 实际应用场景
  3. 最佳实践
  4. 学习总结

综合实践

javascript
// 实践 1:计算两个日期之间的天数
function daysBetween(date1, date2) {
  const d1 = dayjs(date1)
  const d2 = dayjs(date2)
  const diff = d2.subtract(d1, 'millisecond')
  return Math.floor(diff.get('millisecond') / (1000 * 60 * 60 * 24))
}

// 实践 2:判断是否是工作日
function isWeekday(date) {
  const d = dayjs(date)
  const day = d.day()
  return day !== 0 && day !== 6
}

// 实践 3:获取本月的最后一天
function getLastDayOfMonth(date) {
  const d = dayjs(date)
  return d.set('date', 1).add(1, 'month').subtract(1, 'day').date()
}

// 实践 4:格式化相对时间
function formatRelativeTime(date) {
  const now = dayjs()
  const d = dayjs(date)
  const diff = now.subtract(d, 'millisecond').get('millisecond')

  const minutes = Math.floor(diff / (1000 * 60))
  const hours = Math.floor(diff / (1000 * 60 * 60))
  const days = Math.floor(diff / (1000 * 60 * 60 * 24))

  if (minutes < 1) return '刚刚'
  if (minutes < 60) return `${minutes} 分钟前`
  if (hours < 24) return `${hours} 小时前`
  if (days < 7) return `${days} 天前`
  return d.format('YYYY-MM-DD')
}

测试用例

javascript
// test/integration.test.js
import dayjs from '../src/dayjs'

test('days between', () => {
  const days = daysBetween('2023-01-01', '2023-01-10')
  expect(days).toBe(9)
})

test('is weekday', () => {
  expect(isWeekday('2023-01-02')).toBe(true) // 周一
  expect(isWeekday('2023-01-01')).toBe(false) // 周日
})

test('get last day of month', () => {
  expect(getLastDayOfMonth('2023-01-15')).toBe(31)
  expect(getLastDayOfMonth('2023-02-15')).toBe(28)
})

test('format relative time', () => {
  const now = dayjs()
  const fiveMinutesAgo = now.subtract(5, 'minute').format()
  const twoHoursAgo = now.subtract(2, 'hour').format()
  const threeDaysAgo = now.subtract(3, 'day').format()

  expect(formatRelativeTime(fiveMinutesAgo)).toBe('5 分钟前')
  expect(formatRelativeTime(twoHoursAgo)).toBe('2 小时前')
  expect(formatRelativeTime(threeDaysAgo)).toBe('3 天前')
})

测试命令

bash
npm test

预期输出

PASS  test/integration.test.js
  ✓ days between
  ✓ is weekday
  ✓ get last day of month
  ✓ format relative time

总结

通过本课程的学习,你已经掌握了:

  1. Day.js 的设计理念和架构
  2. 日期解析、格式化、操作的实现
  3. 日期比较和获取设置
  4. Day.js 类的实现和链式调用
  5. 实际应用场景的综合实践

下一步学习

完成本课程后,建议继续学习:

  1. Moment.js 课程 - 学习 Moment.js 的实现
  2. 插件系统 - 学习 Day.js 插件开发
  3. 其他工具库 - 学习更多前端工具库