Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[bugfix] Fix startOf/endOf DST issues while boosting performance (#4338)
* Fix startOf/endOf DST issues * Performance improvements * Rewrite for legibility * Fix for years in range 0-99 * Remove placeholder comments * Revert comment * Remove useless special-case * Code review change: use break consistenly * Rename per code review * Fix typo * Inline variable to end naming debate * Address review comments
- Loading branch information
Showing
3 changed files
with
146 additions
and
39 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,21 +1,35 @@ | ||
export function createDate (y, m, d, h, M, s, ms) { | ||
// can't just apply() to create a date: | ||
// https://stackoverflow.com/q/181348 | ||
var date = new Date(y, m, d, h, M, s, ms); | ||
|
||
var date; | ||
// the date constructor remaps years 0-99 to 1900-1999 | ||
if (y < 100 && y >= 0 && isFinite(date.getFullYear())) { | ||
date.setFullYear(y); | ||
if (y < 100 && y >= 0) { | ||
// preserve leap years using a full 400 year cycle, then reset | ||
date = new Date(y + 400, m, d, h, M, s, ms); | ||
if (isFinite(date.getFullYear())) { | ||
date.setFullYear(y); | ||
} | ||
} else { | ||
date = new Date(y, m, d, h, M, s, ms); | ||
} | ||
|
||
return date; | ||
} | ||
|
||
export function createUTCDate (y) { | ||
var date = new Date(Date.UTC.apply(null, arguments)); | ||
|
||
var date; | ||
// the Date.UTC function remaps years 0-99 to 1900-1999 | ||
if (y < 100 && y >= 0 && isFinite(date.getUTCFullYear())) { | ||
date.setUTCFullYear(y); | ||
if (y < 100 && y >= 0) { | ||
var args = Array.prototype.slice.call(arguments); | ||
// preserve leap years using a full 400 year cycle, then reset | ||
args[0] = y + 400; | ||
date = new Date(Date.UTC.apply(null, args)); | ||
if (isFinite(date.getUTCFullYear())) { | ||
date.setUTCFullYear(y); | ||
} | ||
} else { | ||
date = new Date(Date.UTC.apply(null, arguments)); | ||
} | ||
|
||
return date; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,59 +1,128 @@ | ||
import { normalizeUnits } from '../units/aliases'; | ||
import { hooks } from '../utils/hooks'; | ||
|
||
var MS_PER_SECOND = 1000; | ||
var MS_PER_MINUTE = 60 * MS_PER_SECOND; | ||
var MS_PER_HOUR = 60 * MS_PER_MINUTE; | ||
var MS_PER_400_YEARS = (365 * 400 + 97) * 24 * MS_PER_HOUR; | ||
|
||
// actual modulo - handles negative numbers (for dates before 1970): | ||
function mod(dividend, divisor) { | ||
return (dividend % divisor + divisor) % divisor; | ||
} | ||
|
||
function localStartOfDate(y, m, d) { | ||
// the date constructor remaps years 0-99 to 1900-1999 | ||
if (y < 100 && y >= 0) { | ||
// preserve leap years using a full 400 year cycle, then reset | ||
return new Date(y + 400, m, d) - MS_PER_400_YEARS; | ||
} else { | ||
return new Date(y, m, d).valueOf(); | ||
} | ||
} | ||
|
||
function utcStartOfDate(y, m, d) { | ||
// Date.UTC remaps years 0-99 to 1900-1999 | ||
if (y < 100 && y >= 0) { | ||
// preserve leap years using a full 400 year cycle, then reset | ||
return Date.UTC(y + 400, m, d) - MS_PER_400_YEARS; | ||
} else { | ||
return Date.UTC(y, m, d); | ||
} | ||
} | ||
|
||
export function startOf (units) { | ||
var time; | ||
units = normalizeUnits(units); | ||
// the following switch intentionally omits break keywords | ||
// to utilize falling through the cases. | ||
if (units === undefined || units === 'millisecond' || !this.isValid()) { | ||
return this; | ||
} | ||
|
||
var startOfDate = this._isUTC ? utcStartOfDate : localStartOfDate; | ||
|
||
switch (units) { | ||
case 'year': | ||
this.month(0); | ||
/* falls through */ | ||
time = startOfDate(this.year(), 0, 1); | ||
break; | ||
case 'quarter': | ||
time = startOfDate(this.year(), this.month() - this.month() % 3, 1); | ||
break; | ||
case 'month': | ||
this.date(1); | ||
/* falls through */ | ||
time = startOfDate(this.year(), this.month(), 1); | ||
break; | ||
case 'week': | ||
time = startOfDate(this.year(), this.month(), this.date() - this.weekday()); | ||
break; | ||
case 'isoWeek': | ||
time = startOfDate(this.year(), this.month(), this.date() - (this.isoWeekday() - 1)); | ||
break; | ||
case 'day': | ||
case 'date': | ||
this.hours(0); | ||
/* falls through */ | ||
time = startOfDate(this.year(), this.month(), this.date()); | ||
break; | ||
case 'hour': | ||
this.minutes(0); | ||
/* falls through */ | ||
time = this._d.valueOf(); | ||
time -= mod(time + (this._isUTC ? 0 : this.utcOffset() * MS_PER_MINUTE), MS_PER_HOUR); | ||
break; | ||
case 'minute': | ||
this.seconds(0); | ||
/* falls through */ | ||
time = this._d.valueOf(); | ||
time -= mod(time, MS_PER_MINUTE); | ||
break; | ||
case 'second': | ||
this.milliseconds(0); | ||
} | ||
|
||
// weeks are a special case | ||
if (units === 'week') { | ||
this.weekday(0); | ||
} | ||
if (units === 'isoWeek') { | ||
this.isoWeekday(1); | ||
} | ||
|
||
// quarters are also special | ||
if (units === 'quarter') { | ||
this.month(Math.floor(this.month() / 3) * 3); | ||
time = this._d.valueOf(); | ||
time -= mod(time, MS_PER_SECOND); | ||
break; | ||
} | ||
|
||
this._d.setTime(time); | ||
hooks.updateOffset(this, true); | ||
return this; | ||
} | ||
|
||
export function endOf (units) { | ||
var time; | ||
units = normalizeUnits(units); | ||
if (units === undefined || units === 'millisecond') { | ||
if (units === undefined || units === 'millisecond' || !this.isValid()) { | ||
return this; | ||
} | ||
|
||
// 'date' is an alias for 'day', so it should be considered as such. | ||
if (units === 'date') { | ||
units = 'day'; | ||
var startOfDate = this._isUTC ? utcStartOfDate : localStartOfDate; | ||
|
||
switch (units) { | ||
case 'year': | ||
time = startOfDate(this.year() + 1, 0, 1) - 1; | ||
break; | ||
case 'quarter': | ||
time = startOfDate(this.year(), this.month() - this.month() % 3 + 3, 1) - 1; | ||
break; | ||
case 'month': | ||
time = startOfDate(this.year(), this.month() + 1, 1) - 1; | ||
break; | ||
case 'week': | ||
time = startOfDate(this.year(), this.month(), this.date() - this.weekday() + 7) - 1; | ||
break; | ||
case 'isoWeek': | ||
time = startOfDate(this.year(), this.month(), this.date() - (this.isoWeekday() - 1) + 7) - 1; | ||
break; | ||
case 'day': | ||
case 'date': | ||
time = startOfDate(this.year(), this.month(), this.date() + 1) - 1; | ||
break; | ||
case 'hour': | ||
time = this._d.valueOf(); | ||
time += MS_PER_HOUR - mod(time + (this._isUTC ? 0 : this.utcOffset() * MS_PER_MINUTE), MS_PER_HOUR) - 1; | ||
break; | ||
case 'minute': | ||
time = this._d.valueOf(); | ||
time += MS_PER_MINUTE - mod(time, MS_PER_MINUTE) - 1; | ||
break; | ||
case 'second': | ||
time = this._d.valueOf(); | ||
time += MS_PER_SECOND - mod(time, MS_PER_SECOND) - 1; | ||
break; | ||
} | ||
|
||
return this.startOf(units).add(1, (units === 'isoWeek' ? 'week' : units)).subtract(1, 'ms'); | ||
this._d.setTime(time); | ||
hooks.updateOffset(this, true); | ||
return this; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters