UNPKG

sugar

Version:

A Javascript utility library for working with native objects.

173 lines (153 loc) 6.62 kB
'use strict'; var DateUnits = require('../var/DateUnits'), DateUnitIndexes = require('../var/DateUnitIndexes'), trunc = require('../../common/var/trunc'), setDate = require('./setDate'), getDate = require('./getDate'), getMonth = require('./getMonth'), getNewDate = require('./getNewDate'), setWeekday = require('./setWeekday'), mathAliases = require('../../common/var/mathAliases'), callDateGet = require('../../common/internal/callDateGet'), classChecks = require('../../common/var/classChecks'), resetLowerUnits = require('./resetLowerUnits'), getLowerUnitIndex = require('./getLowerUnitIndex'), getHigherUnitIndex = require('./getHigherUnitIndex'), callDateSetWithWeek = require('./callDateSetWithWeek'), iterateOverDateParams = require('./iterateOverDateParams'); var DAY_INDEX = DateUnitIndexes.DAY_INDEX, WEEK_INDEX = DateUnitIndexes.WEEK_INDEX, MONTH_INDEX = DateUnitIndexes.MONTH_INDEX, YEAR_INDEX = DateUnitIndexes.YEAR_INDEX, round = mathAliases.round, isNumber = classChecks.isNumber; function updateDate(d, params, reset, advance, prefer, weekdayDir, contextDate) { var upperUnitIndex; function setUpperUnit(unitName, unitIndex) { if (prefer && !upperUnitIndex) { if (unitName === 'weekday') { upperUnitIndex = WEEK_INDEX; } else { upperUnitIndex = getHigherUnitIndex(unitIndex); } } } function setSpecificity(unitIndex) { // Other functions may preemptively set the specificity before arriving // here so concede to them if they have already set more specific units. if (unitIndex > params.specificity) { return; } params.specificity = unitIndex; } function canDisambiguate() { if (!upperUnitIndex || upperUnitIndex > YEAR_INDEX) { return; } switch(prefer) { case -1: return d >= (contextDate || getNewDate()); case 1: return d <= (contextDate || getNewDate()); } } function disambiguateHigherUnit() { var unit = DateUnits[upperUnitIndex]; advance = prefer; setUnit(unit.name, 1, unit, upperUnitIndex); } function handleFraction(unit, unitIndex, fraction) { if (unitIndex) { var lowerUnit = DateUnits[getLowerUnitIndex(unitIndex)]; var val = round(unit.multiplier / lowerUnit.multiplier * fraction); params[lowerUnit.name] = val; } } function monthHasShifted(d, targetMonth) { if (targetMonth < 0) { targetMonth = targetMonth % 12 + 12; } return targetMonth % 12 !== getMonth(d); } function setUnit(unitName, value, unit, unitIndex) { var method = unit.method, checkMonth, fraction; setUpperUnit(unitName, unitIndex); setSpecificity(unitIndex); fraction = value % 1; if (fraction) { handleFraction(unit, unitIndex, fraction); value = trunc(value); } if (unitName === 'weekday') { if (!advance) { // Weekdays are always considered absolute units so simply set them // here even if it is an "advance" operation. This is to help avoid // ambiguous meanings in "advance" as well as to neatly allow formats // like "Wednesday of next week" without more complex logic. setWeekday(d, value, weekdayDir); } return; } checkMonth = unitIndex === MONTH_INDEX && getDate(d) > 28; // If we are advancing or rewinding, then we need we need to set the // absolute time if the unit is "hours" or less. This is due to the fact // that setting by method is ambiguous during DST shifts. For example, // 1:00am on November 1st 2015 occurs twice in North American timezones // with DST, the second time being after the clocks are rolled back at // 2:00am. When springing forward this is automatically handled as there // is no 2:00am so the date automatically jumps to 3:00am. However, when // rolling back, setHours(2) will always choose the first "2am" even if // the date is currently set to the second, causing unintended jumps. // This ambiguity is unavoidable when setting dates as the notation is // ambiguous. However when advancing, we clearly want the resulting date // to be an acutal hour ahead, which can only be accomplished by setting // the absolute time. Conversely, any unit higher than "hours" MUST use // the internal set methods, as they are ambiguous as absolute units of // time. Years may be 365 or 366 days depending on leap years, months are // all over the place, and even days may be 23-25 hours depending on DST // shifts. Finally, note that the kind of jumping described above will // occur when calling ANY "set" method on the date and will occur even if // the value being set is identical to the one currently set (i.e. // setHours(2) on a date at 2am may not be a noop). This is precarious, // so avoiding this situation in callDateSet by checking up front that // the value is not the same before setting. if (advance && !unit.ambiguous) { d.setTime(d.getTime() + (value * advance * unit.multiplier)); return; } else if (advance) { if (unitIndex === WEEK_INDEX) { value *= 7; method = DateUnits[DAY_INDEX].method; } value = (value * advance) + callDateGet(d, method); } callDateSetWithWeek(d, method, value, advance); if (checkMonth && monthHasShifted(d, value)) { // As we are setting the units in reverse order, there is a chance that // our date may accidentally traverse into a new month, such as setting // { month: 1, date 15 } on January 31st. Check for this here and reset // the date to the last day of the previous month if this has happened. setDate(d, 0); } } if (isNumber(params) && advance) { // If param is a number and advancing, the number is in milliseconds. params = { millisecond: params }; } else if (isNumber(params)) { // Otherwise just set the timestamp and return. d.setTime(params); return d; } iterateOverDateParams(params, setUnit); if (reset && params.specificity) { resetLowerUnits(d, params.specificity); } // If past or future is preferred, then the process of "disambiguation" will // ensure that an ambiguous time/date ("4pm", "thursday", "June", etc.) will // be in the past or future. Weeks are only considered ambiguous if there is // a weekday, i.e. "thursday" is an ambiguous week, but "the 4th" is an // ambiguous month. if (canDisambiguate()) { disambiguateHigherUnit(); } return d; } module.exports = updateDate;