'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); var _createClass2 = require('babel-runtime/helpers/createClass'); var _createClass3 = _interopRequireDefault(_createClass2); exports.normalizeSecondsForDuration = normalizeSecondsForDuration; exports.normalizeNanosecondsForDuration = normalizeNanosecondsForDuration; exports.localTimeToNanoOfDay = localTimeToNanoOfDay; exports.nanoOfDayToLocalTime = nanoOfDayToLocalTime; exports.localDateTimeToEpochSecond = localDateTimeToEpochSecond; exports.epochSecondAndNanoToLocalDateTime = epochSecondAndNanoToLocalDateTime; exports.dateToEpochDay = dateToEpochDay; exports.epochDayToDate = epochDayToDate; exports.durationToIsoString = durationToIsoString; exports.timeToIsoString = timeToIsoString; exports.timeZoneOffsetToIsoString = timeZoneOffsetToIsoString; exports.dateToIsoString = dateToIsoString; exports.totalNanoseconds = totalNanoseconds; exports.timeZoneOffsetInSeconds = timeZoneOffsetInSeconds; exports.assertValidYear = assertValidYear; exports.assertValidMonth = assertValidMonth; exports.assertValidDay = assertValidDay; exports.assertValidHour = assertValidHour; exports.assertValidMinute = assertValidMinute; exports.assertValidSecond = assertValidSecond; exports.assertValidNanosecond = assertValidNanosecond; var _integer = require('../integer'); var _temporalTypes = require('../temporal-types'); var _util = require('./util'); var _error = require('../error'); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /* Code in this util should be compatible with code in the database that uses JSR-310 java.time APIs. It is based on a library called ThreeTen (https://github.com/ThreeTen/threetenbp) which was derived from JSR-310 reference implementation previously hosted on GitHub. Code uses `Integer` type everywhere to correctly handle large integer values that are greater than Number.MAX_SAFE_INTEGER. Please consult either ThreeTen or js-joda (https://github.com/js-joda/js-joda) when working with the conversion functions. */ /** * Copyright (c) 2002-2018 "Neo4j," * Neo4j Sweden AB [http://neo4j.com] * * This file is part of Neo4j. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ var ValueRange = function () { function ValueRange(min, max) { (0, _classCallCheck3.default)(this, ValueRange); this._minNumber = min; this._maxNumber = max; this._minInteger = (0, _integer.int)(min); this._maxInteger = (0, _integer.int)(max); } (0, _createClass3.default)(ValueRange, [{ key: 'contains', value: function contains(value) { if ((0, _integer.isInt)(value)) { return value.greaterThanOrEqual(this._minInteger) && value.lessThanOrEqual(this._maxInteger); } else { return value >= this._minNumber && value <= this._maxNumber; } } }, { key: 'toString', value: function toString() { return '[' + this._minNumber + ', ' + this._maxNumber + ']'; } }]); return ValueRange; }(); var YEAR_RANGE = new ValueRange(-999999999, 999999999); var MONTH_OF_YEAR_RANGE = new ValueRange(1, 12); var DAY_OF_MONTH_RANGE = new ValueRange(1, 31); var HOUR_OF_DAY_RANGE = new ValueRange(0, 23); var MINUTE_OF_HOUR_RANGE = new ValueRange(0, 59); var SECOND_OF_MINUTE_RANGE = new ValueRange(0, 59); var NANOSECOND_OF_SECOND_RANGE = new ValueRange(0, 999999999); var MINUTES_PER_HOUR = 60; var SECONDS_PER_MINUTE = 60; var SECONDS_PER_HOUR = SECONDS_PER_MINUTE * MINUTES_PER_HOUR; var NANOS_PER_SECOND = 1000000000; var NANOS_PER_MILLISECOND = 1000000; var NANOS_PER_MINUTE = NANOS_PER_SECOND * SECONDS_PER_MINUTE; var NANOS_PER_HOUR = NANOS_PER_MINUTE * MINUTES_PER_HOUR; var DAYS_0000_TO_1970 = 719528; var DAYS_PER_400_YEAR_CYCLE = 146097; var SECONDS_PER_DAY = 86400; function normalizeSecondsForDuration(seconds, nanoseconds) { return (0, _integer.int)(seconds).add(floorDiv(nanoseconds, NANOS_PER_SECOND)); } function normalizeNanosecondsForDuration(nanoseconds) { return floorMod(nanoseconds, NANOS_PER_SECOND); } /** * Converts given local time into a single integer representing this same time in nanoseconds of the day. * @param {Integer|number|string} hour the hour of the local time to convert. * @param {Integer|number|string} minute the minute of the local time to convert. * @param {Integer|number|string} second the second of the local time to convert. * @param {Integer|number|string} nanosecond the nanosecond of the local time to convert. * @return {Integer} nanoseconds representing the given local time. */ function localTimeToNanoOfDay(hour, minute, second, nanosecond) { hour = (0, _integer.int)(hour); minute = (0, _integer.int)(minute); second = (0, _integer.int)(second); nanosecond = (0, _integer.int)(nanosecond); var totalNanos = hour.multiply(NANOS_PER_HOUR); totalNanos = totalNanos.add(minute.multiply(NANOS_PER_MINUTE)); totalNanos = totalNanos.add(second.multiply(NANOS_PER_SECOND)); return totalNanos.add(nanosecond); } /** * Converts nanoseconds of the day into local time. * @param {Integer|number|string} nanoOfDay the nanoseconds of the day to convert. * @return {LocalTime} the local time representing given nanoseconds of the day. */ function nanoOfDayToLocalTime(nanoOfDay) { nanoOfDay = (0, _integer.int)(nanoOfDay); var hour = nanoOfDay.div(NANOS_PER_HOUR); nanoOfDay = nanoOfDay.subtract(hour.multiply(NANOS_PER_HOUR)); var minute = nanoOfDay.div(NANOS_PER_MINUTE); nanoOfDay = nanoOfDay.subtract(minute.multiply(NANOS_PER_MINUTE)); var second = nanoOfDay.div(NANOS_PER_SECOND); var nanosecond = nanoOfDay.subtract(second.multiply(NANOS_PER_SECOND)); return new _temporalTypes.LocalTime(hour, minute, second, nanosecond); } /** * Converts given local date time into a single integer representing this same time in epoch seconds UTC. * @param {Integer|number|string} year the year of the local date-time to convert. * @param {Integer|number|string} month the month of the local date-time to convert. * @param {Integer|number|string} day the day of the local date-time to convert. * @param {Integer|number|string} hour the hour of the local date-time to convert. * @param {Integer|number|string} minute the minute of the local date-time to convert. * @param {Integer|number|string} second the second of the local date-time to convert. * @param {Integer|number|string} nanosecond the nanosecond of the local date-time to convert. * @return {Integer} epoch second in UTC representing the given local date time. */ function localDateTimeToEpochSecond(year, month, day, hour, minute, second, nanosecond) { var epochDay = dateToEpochDay(year, month, day); var localTimeSeconds = localTimeToSecondOfDay(hour, minute, second); return epochDay.multiply(SECONDS_PER_DAY).add(localTimeSeconds); } /** * Converts given epoch second and nanosecond adjustment into a local date time object. * @param {Integer|number|string} epochSecond the epoch second to use. * @param {Integer|number|string} nano the nanosecond to use. * @return {LocalDateTime} the local date time representing given epoch second and nano. */ function epochSecondAndNanoToLocalDateTime(epochSecond, nano) { var epochDay = floorDiv(epochSecond, SECONDS_PER_DAY); var secondsOfDay = floorMod(epochSecond, SECONDS_PER_DAY); var nanoOfDay = secondsOfDay.multiply(NANOS_PER_SECOND).add(nano); var localDate = epochDayToDate(epochDay); var localTime = nanoOfDayToLocalTime(nanoOfDay); return new _temporalTypes.LocalDateTime(localDate.year, localDate.month, localDate.day, localTime.hour, localTime.minute, localTime.second, localTime.nanosecond); } /** * Converts given local date into a single integer representing it's epoch day. * @param {Integer|number|string} year the year of the local date to convert. * @param {Integer|number|string} month the month of the local date to convert. * @param {Integer|number|string} day the day of the local date to convert. * @return {Integer} epoch day representing the given date. */ function dateToEpochDay(year, month, day) { year = (0, _integer.int)(year); month = (0, _integer.int)(month); day = (0, _integer.int)(day); var epochDay = year.multiply(365); if (year.greaterThanOrEqual(0)) { epochDay = epochDay.add(year.add(3).div(4).subtract(year.add(99).div(100)).add(year.add(399).div(400))); } else { epochDay = epochDay.subtract(year.div(-4).subtract(year.div(-100)).add(year.div(-400))); } epochDay = epochDay.add(month.multiply(367).subtract(362).div(12)); epochDay = epochDay.add(day.subtract(1)); if (month.greaterThan(2)) { epochDay = epochDay.subtract(1); if (!isLeapYear(year)) { epochDay = epochDay.subtract(1); } } return epochDay.subtract(DAYS_0000_TO_1970); } /** * Converts given epoch day to a local date. * @param {Integer|number|string} epochDay the epoch day to convert. * @return {Date} the date representing the epoch day in years, months and days. */ function epochDayToDate(epochDay) { epochDay = (0, _integer.int)(epochDay); var zeroDay = epochDay.add(DAYS_0000_TO_1970).subtract(60); var adjust = (0, _integer.int)(0); if (zeroDay.lessThan(0)) { var adjustCycles = zeroDay.add(1).div(DAYS_PER_400_YEAR_CYCLE).subtract(1); adjust = adjustCycles.multiply(400); zeroDay = zeroDay.add(adjustCycles.multiply(-DAYS_PER_400_YEAR_CYCLE)); } var year = zeroDay.multiply(400).add(591).div(DAYS_PER_400_YEAR_CYCLE); var dayOfYearEst = zeroDay.subtract(year.multiply(365).add(year.div(4)).subtract(year.div(100)).add(year.div(400))); if (dayOfYearEst.lessThan(0)) { year = year.subtract(1); dayOfYearEst = zeroDay.subtract(year.multiply(365).add(year.div(4)).subtract(year.div(100)).add(year.div(400))); } year = year.add(adjust); var marchDayOfYear = dayOfYearEst; var marchMonth = marchDayOfYear.multiply(5).add(2).div(153); var month = marchMonth.add(2).modulo(12).add(1); var day = marchDayOfYear.subtract(marchMonth.multiply(306).add(5).div(10)).add(1); year = year.add(marchMonth.div(10)); return new _temporalTypes.Date(year, month, day); } /** * Format given duration to an ISO 8601 string. * @param {Integer|number|string} months the number of months. * @param {Integer|number|string} days the number of days. * @param {Integer|number|string} seconds the number of seconds. * @param {Integer|number|string} nanoseconds the number of nanoseconds. * @return {string} ISO string that represents given duration. */ function durationToIsoString(months, days, seconds, nanoseconds) { var monthsString = formatNumber(months); var daysString = formatNumber(days); var secondsAndNanosecondsString = formatSecondsAndNanosecondsForDuration(seconds, nanoseconds); return 'P' + monthsString + 'M' + daysString + 'DT' + secondsAndNanosecondsString + 'S'; } /** * Formats given time to an ISO 8601 string. * @param {Integer|number|string} hour the hour value. * @param {Integer|number|string} minute the minute value. * @param {Integer|number|string} second the second value. * @param {Integer|number|string} nanosecond the nanosecond value. * @return {string} ISO string that represents given time. */ function timeToIsoString(hour, minute, second, nanosecond) { var hourString = formatNumber(hour, 2); var minuteString = formatNumber(minute, 2); var secondString = formatNumber(second, 2); var nanosecondString = formatNanosecond(nanosecond); return hourString + ':' + minuteString + ':' + secondString + nanosecondString; } /** * Formats given time zone offset in seconds to string representation like '±HH:MM', '±HH:MM:SS' or 'Z' for UTC. * @param {Integer|number|string} offsetSeconds the offset in seconds. * @return {string} ISO string that represents given offset. */ function timeZoneOffsetToIsoString(offsetSeconds) { offsetSeconds = (0, _integer.int)(offsetSeconds); if (offsetSeconds.equals(0)) { return 'Z'; } var isNegative = offsetSeconds.isNegative(); if (isNegative) { offsetSeconds = offsetSeconds.multiply(-1); } var signPrefix = isNegative ? '-' : '+'; var hours = formatNumber(offsetSeconds.div(SECONDS_PER_HOUR), 2); var minutes = formatNumber(offsetSeconds.div(SECONDS_PER_MINUTE).modulo(MINUTES_PER_HOUR), 2); var secondsValue = offsetSeconds.modulo(SECONDS_PER_MINUTE); var seconds = secondsValue.equals(0) ? null : formatNumber(secondsValue, 2); return seconds ? '' + signPrefix + hours + ':' + minutes + ':' + seconds : '' + signPrefix + hours + ':' + minutes; } /** * Formats given date to an ISO 8601 string. * @param {Integer|number|string} year the date year. * @param {Integer|number|string} month the date month. * @param {Integer|number|string} day the date day. * @return {string} ISO string that represents given date. */ function dateToIsoString(year, month, day) { year = (0, _integer.int)(year); var isNegative = year.isNegative(); if (isNegative) { year = year.multiply(-1); } var yearString = formatNumber(year, 4); if (isNegative) { yearString = '-' + yearString; } var monthString = formatNumber(month, 2); var dayString = formatNumber(day, 2); return yearString + '-' + monthString + '-' + dayString; } /** * Get the total number of nanoseconds from the milliseconds of the given standard JavaScript date and optional nanosecond part. * @param {global.Date} standardDate the standard JavaScript date. * @param {Integer|number|undefined} nanoseconds the optional number of nanoseconds. * @return {Integer|number} the total amount of nanoseconds. */ function totalNanoseconds(standardDate, nanoseconds) { nanoseconds = nanoseconds || 0; var nanosFromMillis = standardDate.getMilliseconds() * NANOS_PER_MILLISECOND; return (0, _integer.isInt)(nanoseconds) ? nanoseconds.add(nanosFromMillis) : nanoseconds + nanosFromMillis; } /** * Get the time zone offset in seconds from the given standard JavaScript date. * @param {global.Date} standardDate the standard JavaScript date. * @return {number} the time zone offset in seconds. */ function timeZoneOffsetInSeconds(standardDate) { return standardDate.getTimezoneOffset() * SECONDS_PER_MINUTE; } /** * Assert that the year value is valid. * @param {Integer|number} year the value to check. * @return {Integer|number} the value of the year if it is valid. Exception is thrown otherwise. */ function assertValidYear(year) { return assertValidTemporalValue(year, YEAR_RANGE, 'Year'); } /** * Assert that the month value is valid. * @param {Integer|number} month the value to check. * @return {Integer|number} the value of the month if it is valid. Exception is thrown otherwise. */ function assertValidMonth(month) { return assertValidTemporalValue(month, MONTH_OF_YEAR_RANGE, 'Month'); } /** * Assert that the day value is valid. * @param {Integer|number} day the value to check. * @return {Integer|number} the value of the day if it is valid. Exception is thrown otherwise. */ function assertValidDay(day) { return assertValidTemporalValue(day, DAY_OF_MONTH_RANGE, 'Day'); } /** * Assert that the hour value is valid. * @param {Integer|number} hour the value to check. * @return {Integer|number} the value of the hour if it is valid. Exception is thrown otherwise. */ function assertValidHour(hour) { return assertValidTemporalValue(hour, HOUR_OF_DAY_RANGE, 'Hour'); } /** * Assert that the minute value is valid. * @param {Integer|number} minute the value to check. * @return {Integer|number} the value of the minute if it is valid. Exception is thrown otherwise. */ function assertValidMinute(minute) { return assertValidTemporalValue(minute, MINUTE_OF_HOUR_RANGE, 'Minute'); } /** * Assert that the second value is valid. * @param {Integer|number} second the value to check. * @return {Integer|number} the value of the second if it is valid. Exception is thrown otherwise. */ function assertValidSecond(second) { return assertValidTemporalValue(second, SECOND_OF_MINUTE_RANGE, 'Second'); } /** * Assert that the nanosecond value is valid. * @param {Integer|number} nanosecond the value to check. * @return {Integer|number} the value of the nanosecond if it is valid. Exception is thrown otherwise. */ function assertValidNanosecond(nanosecond) { return assertValidTemporalValue(nanosecond, NANOSECOND_OF_SECOND_RANGE, 'Nanosecond'); } /** * Check if the given value is of expected type and is in the expected range. * @param {Integer|number} value the value to check. * @param {ValueRange} range the range. * @param {string} name the name of the value. * @return {Integer|number} the value if valid. Exception is thrown otherwise. */ function assertValidTemporalValue(value, range, name) { (0, _util.assertNumberOrInteger)(value, name); if (!range.contains(value)) { throw (0, _error.newError)(name + ' is expected to be in range ' + range + ' but was: ' + value); } return value; } /** * Converts given local time into a single integer representing this same time in seconds of the day. Nanoseconds are skipped. * @param {Integer|number|string} hour the hour of the local time. * @param {Integer|number|string} minute the minute of the local time. * @param {Integer|number|string} second the second of the local time. * @return {Integer} seconds representing the given local time. */ function localTimeToSecondOfDay(hour, minute, second) { hour = (0, _integer.int)(hour); minute = (0, _integer.int)(minute); second = (0, _integer.int)(second); var totalSeconds = hour.multiply(SECONDS_PER_HOUR); totalSeconds = totalSeconds.add(minute.multiply(SECONDS_PER_MINUTE)); return totalSeconds.add(second); } /** * Check if given year is a leap year. Uses algorithm described here {@link https://en.wikipedia.org/wiki/Leap_year#Algorithm}. * @param {Integer|number|string} year the year to check. Will be converted to {@link Integer} for all calculations. * @return {boolean} true if given year is a leap year, false otherwise. */ function isLeapYear(year) { year = (0, _integer.int)(year); if (!year.modulo(4).equals(0)) { return false; } else if (!year.modulo(100).equals(0)) { return true; } else if (!year.modulo(400).equals(0)) { return false; } else { return true; } } /** * @param {Integer|number|string} x the divident. * @param {Integer|number|string} y the divisor. * @return {Integer} the result. */ function floorDiv(x, y) { x = (0, _integer.int)(x); y = (0, _integer.int)(y); var result = x.div(y); if (x.isPositive() !== y.isPositive() && result.multiply(y).notEquals(x)) { result = result.subtract(1); } return result; } /** * @param {Integer|number|string} x the divident. * @param {Integer|number|string} y the divisor. * @return {Integer} the result. */ function floorMod(x, y) { x = (0, _integer.int)(x); y = (0, _integer.int)(y); return x.subtract(floorDiv(x, y).multiply(y)); } /** * @param {Integer|number|string} seconds the number of seconds to format. * @param {Integer|number|string} nanoseconds the number of nanoseconds to format. * @return {string} formatted value. */ function formatSecondsAndNanosecondsForDuration(seconds, nanoseconds) { seconds = (0, _integer.int)(seconds); nanoseconds = (0, _integer.int)(nanoseconds); var secondsString = void 0; var nanosecondsString = void 0; var secondsNegative = seconds.isNegative(); var nanosecondsGreaterThanZero = nanoseconds.greaterThan(0); if (secondsNegative && nanosecondsGreaterThanZero) { if (seconds.equals(-1)) { secondsString = '-0'; } else { secondsString = seconds.add(1).toString(); } } else { secondsString = seconds.toString(); } if (nanosecondsGreaterThanZero) { if (secondsNegative) { nanosecondsString = formatNanosecond(nanoseconds.negate().add(2 * NANOS_PER_SECOND).modulo(NANOS_PER_SECOND)); } else { nanosecondsString = formatNanosecond(nanoseconds.add(NANOS_PER_SECOND).modulo(NANOS_PER_SECOND)); } } return nanosecondsString ? secondsString + nanosecondsString : secondsString; } /** * @param {Integer|number|string} value the number of nanoseconds to format. * @return {string} formatted and possibly left-padded nanoseconds part as string. */ function formatNanosecond(value) { value = (0, _integer.int)(value); return value.equals(0) ? '' : '.' + formatNumber(value, 9); } /** * @param {Integer|number|string} num the number to format. * @param {number} [stringLength=undefined] the string length to left-pad to. * @return {string} formatted and possibly left-padded number as string. */ function formatNumber(num) { var stringLength = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : undefined; num = (0, _integer.int)(num); var isNegative = num.isNegative(); if (isNegative) { num = num.negate(); } var numString = num.toString(); if (stringLength) { // left pad the string with zeroes while (numString.length < stringLength) { numString = '0' + numString; } } return isNegative ? '-' + numString : numString; }