Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"main": "dayjs.min.js",
"types": "index.d.ts",
"scripts": {
"test": "TZ=Pacific/Auckland npm run test-tz && TZ=Europe/London npm run test-tz && TZ=America/Whitehorse npm run test-tz && npm run test-tz && jest --coverage --coverageThreshold='{ \"global\": { \"lines\": 100} }'",
"test": "TZ=Pacific/Auckland npm run test-tz && TZ=Europe/London npm run test-tz && TZ=America/Whitehorse npm run test-tz && TZ=Asia/Singapore npm run test-issue3003 && npm run test-tz && jest --coverage --coverageThreshold='{ \"global\": { \"lines\": 100} }'",
"test-issue3003": "jest test/issues/issue3003.test.js --runInBand --coverage=false",
"test-tz": "date && jest test/timezone.test --coverage=false",
"lint": "./node_modules/.bin/eslint src/* test/* build/*",
"prettier": "prettier --write \"docs/**/*.md\"",
Expand Down
72 changes: 54 additions & 18 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,42 +146,78 @@ class Dayjs {
startOf(units, startOf) { // startOf -> endOf
const isStartOf = !Utils.u(startOf) ? startOf : true
const unit = Utils.p(units)
const instanceFactory = (d, m) => {
const ins = Utils.w(this.$u ?
Date.UTC(this.$y, m, d) : new Date(this.$y, m, d), this)
return isStartOf ? ins : ins.endOf(C.D)
const hasCustomOffset = !this.$u && !Utils.u(this.$offset)
const instanceFactory = (y, m, d) => Utils.w(this.$u ?
Date.UTC(y, m, d) : new Date(y, m, d), this)
const instanceFactorySet = (method, args) => {
const date = this.toDate()
date[method].apply(date, args) // eslint-disable-line prefer-spread
return Utils.w(date, this)
}
const instanceFactorySet = (method, slice) => {
const instanceFactorySetOffset = (method, slice) => {
const argumentStart = [0, 0, 0, 0]
const argumentEnd = [23, 59, 59, 999]
return Utils.w(this.toDate()[method].apply( // eslint-disable-line prefer-spread
this.toDate('s'),
(isStartOf ? argumentStart : argumentEnd).slice(slice)
), this)
const date = this.toDate('s')
// eslint-disable-next-line prefer-spread
date[method].apply(date, (isStartOf ? argumentStart : argumentEnd).slice(slice))
return Utils.w(date, this)
}
const instanceFactoryEnd = instance => Utils.w(instance.valueOf() - 1, this)
const { $W, $M, $D } = this
const utcPad = `set${this.$u ? 'UTC' : ''}`
if (hasCustomOffset) {
switch (unit) {
case C.Y:
return isStartOf ? instanceFactory(this.$y, 0, 1) :
instanceFactory(this.$y, 11, 31).endOf(C.D)
case C.M:
return isStartOf ? instanceFactory(this.$y, $M, 1) :
instanceFactory(this.$y, $M + 1, 0).endOf(C.D)
case C.W: {
const weekStart = this.$locale().weekStart || 0
const gap = ($W < weekStart ? $W + 7 : $W) - weekStart
return isStartOf ? instanceFactory(this.$y, $M, $D - gap) :
instanceFactory(this.$y, $M, $D + (6 - gap)).endOf(C.D)
}
case C.D:
case C.DATE:
return instanceFactorySetOffset(`${utcPad}Hours`, 0)
case C.H:
return instanceFactorySetOffset(`${utcPad}Minutes`, 1)
case C.MIN:
return instanceFactorySetOffset(`${utcPad}Seconds`, 2)
case C.S:
return instanceFactorySetOffset(`${utcPad}Milliseconds`, 3)
default:
return this.clone()
}
}
switch (unit) {
case C.Y:
return isStartOf ? instanceFactory(1, 0) :
instanceFactory(31, 11)
return isStartOf ? instanceFactory(this.$y, 0, 1) :
instanceFactoryEnd(instanceFactory(this.$y + 1, 0, 1))
case C.M:
return isStartOf ? instanceFactory(1, $M) :
instanceFactory(0, $M + 1)
return isStartOf ? instanceFactory(this.$y, $M, 1) :
instanceFactoryEnd(instanceFactory(this.$y, $M + 1, 1))
case C.W: {
const weekStart = this.$locale().weekStart || 0
const gap = ($W < weekStart ? $W + 7 : $W) - weekStart
return instanceFactory(isStartOf ? $D - gap : $D + (6 - gap), $M)
return isStartOf ? instanceFactory(this.$y, $M, $D - gap) :
instanceFactoryEnd(instanceFactory(this.$y, $M, $D + (7 - gap)))
}
case C.D:
case C.DATE:
return instanceFactorySet(`${utcPad}Hours`, 0)
return isStartOf ? instanceFactory(this.$y, $M, $D) :
instanceFactoryEnd(instanceFactory(this.$y, $M, $D + 1))
case C.H:
return instanceFactorySet(`${utcPad}Minutes`, 1)
return isStartOf ? instanceFactorySet(`${utcPad}Minutes`, [0, 0, 0]) :
instanceFactoryEnd(instanceFactorySet(`${utcPad}Minutes`, [60, 0, 0]))
case C.MIN:
return instanceFactorySet(`${utcPad}Seconds`, 2)
return isStartOf ? instanceFactorySet(`${utcPad}Seconds`, [0, 0]) :
instanceFactoryEnd(instanceFactorySet(`${utcPad}Seconds`, [60, 0]))
case C.S:
return instanceFactorySet(`${utcPad}Milliseconds`, 3)
return isStartOf ? instanceFactorySet(`${utcPad}Milliseconds`, [0]) :
instanceFactoryEnd(instanceFactorySet(`${utcPad}Milliseconds`, [1000]))
default:
return this.clone()
}
Expand Down
22 changes: 22 additions & 0 deletions test/issues/issue3003.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import dayjs from '../../src'

// Run this file with TZ=Asia/Singapore or TZ=Asia/Kuala_Lumpur.
describe('issue 3003', () => {
it('returns the correct number of days in month across midnight offset shifts', () => {
expect(dayjs('1981-12-01').daysInMonth()).toBe(31)
})

it('keeps endOf("month") on the last local day', () => {
const endOfMonth = dayjs('1981-12-01').endOf('month')

expect(endOfMonth.month()).toBe(11)
expect(endOfMonth.date()).toBe(31)
})

it('keeps endOf("day") on the same local day when midnight shifts', () => {
const endOfDay = dayjs('1981-12-31').endOf('day')

expect(endOfDay.month()).toBe(11)
expect(endOfDay.date()).toBe(31)
})
})
42 changes: 42 additions & 0 deletions test/plugin/utc-utcOffset.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,48 @@ test('utc startOf', () => {
.toBe(1500465600000)
})

test('offset mode startOf/endOf matches moment for larger and smaller units', () => {
const d = '2019-09-11T12:34:56.789Z'
const units = ['year', 'hour', 'minute', 'second']

units.forEach((unit) => {
expect(dayjs(d).utcOffset(480).startOf(unit).valueOf())
.toBe(moment(d).utcOffset(480).startOf(unit).valueOf())
expect(dayjs(d).utcOffset(480).endOf(unit).valueOf())
.toBe(moment(d).utcOffset(480).endOf(unit).valueOf())
})

const original = dayjs(d).utcOffset(480)
expect(original.startOf('otherString').valueOf()).toBe(original.valueOf())
expect(original.endOf('otherString').valueOf()).toBe(original.valueOf())
})

test('offset mode startOf/endOf week respects locale weekStart', () => {
moment.updateLocale('offset-week-start', { week: { dow: 3 } })

const sunday = '2019-09-01T12:00:00Z'
const friday = '2019-09-06T12:00:00Z'
const locale = { name: 'offset-week-start', weekStart: 3 }

expect(dayjs(sunday).utcOffset(480).locale(locale).startOf('week')
.valueOf())
.toBe(moment(sunday).utcOffset(480).locale('offset-week-start').startOf('week')
.valueOf())
expect(dayjs(sunday).utcOffset(480).locale(locale).endOf('week')
.valueOf())
.toBe(moment(sunday).utcOffset(480).locale('offset-week-start').endOf('week')
.valueOf())

expect(dayjs(friday).utcOffset(480).locale(locale).startOf('week')
.valueOf())
.toBe(moment(friday).utcOffset(480).locale('offset-week-start').startOf('week')
.valueOf())
expect(dayjs(friday).utcOffset(480).locale(locale).endOf('week')
.valueOf())
.toBe(moment(friday).utcOffset(480).locale('offset-week-start').endOf('week')
.valueOf())
})

test('cloning dates modified with utcOffset', () => {
const djs = dayjs('2023-10-29T00:00:00+03:00')

Expand Down
Loading