coral-spectrum/coral-datetime/src/scripts/DateTime.js
/**
* Copyright 2019 Adobe. All rights reserved.
* This file is licensed to you 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 REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
// todo add tests
// Used to store DateTimeFormat
const dateTimeFormats = {};
// Default supported format
const DEFAULT_FORMAT = 'YYYY-MM-DD';
const transform2digit = (value) => {
const s = value.toString();
return s.length === 1 ? `0${s}` : s;
};
// Default locale
let globalLocale = document.documentElement.lang || window.navigator.language || 'en-US';
// Uses Intl.DateTimeFormat to return a formatted date string
const formatDate = function (date, locale, options) {
let formattedDateString = '';
try {
const key = `${JSON.stringify(locale)}${JSON.stringify(options)}`;
const dateTimeFormat = dateTimeFormats[key];
// Use existing DateTimeFormat or create new one
if (!dateTimeFormat) {
dateTimeFormats[key] = new window.Intl.DateTimeFormat(locale, options);
}
// Format to string
formattedDateString = dateTimeFormats[key].format(date);
} catch (e) {
console.warn(e.message);
}
return formattedDateString;
};
/**
The DateTime API is used as fallback to {@link momentJS}.
@param {DateTime|Date|Array<Number>|String} value
The initial date value. If none provided, the current day is used instead.
*/
class DateTime {
/**
@see https://momentjs.com/docs/#/parsing/now/
*/
constructor(value) {
if (value instanceof this.constructor) {
// Copy properties
this._locale = value._locale;
this._value = value._value;
this._date = value._date;
} else {
this._locale = globalLocale;
this._value = value;
// Support Array
if (Array.isArray(value)) {
this._date = value.length ? new Date(value[0], value[1] || 0, value[2] || 1) : new Date();
} else if (typeof value === 'string') {
const isTime = value.indexOf(':') === 2;
// For time, we only need to set hours and minutes using current date
if (isTime) {
const time = value.split(':');
const hours = parseInt(time[0], 10);
const minutes = parseInt(time[1], 10);
if (hours >= 0 && hours <= 23 && minutes >= 0 && minutes <= 59) {
this._date = new Date();
this._date.setHours(time[0]);
this._date.setMinutes(time[1]);
} else {
this._date = new Date('Invalid Date');
}
} else {
// If string is invalid, the date will be invalid too
// "replace" fixes the one day off issue
this._date = new Date(this._value.replace(/-/g, '/').replace(/T.+/, ''));
}
} else if (this._value === null) {
this._date = new Date('Invalid Date');
} else {
// Create a Date instance from the value or use current day if value is missing
this._date = this._value ? new Date(this._value) : new Date();
}
}
}
/**
@see https://momentjs.com/docs/#/i18n/instance-locale/
*/
locale(value) {
if (value) {
this._locale = value;
}
return this._locale;
}
/**
@see https://momentjs.com/docs/#/displaying/as-javascript-date/
*/
toDate() {
return this._date;
}
/**
@see https://momentjs.com/docs/#/parsing/moment-clone/
*/
clone() {
const clone = new this.constructor(this._value);
clone._date = this._date;
return clone;
}
/**
@see https://momentjs.com/docs/#/displaying/format/
*/
format(format) {
let formattedDateString = '';
if (!format) {
format = DEFAULT_FORMAT;
}
if (format === DEFAULT_FORMAT) {
formattedDateString += this._date.getFullYear();
formattedDateString += '-';
formattedDateString += transform2digit(this._date.getMonth() + 1);
formattedDateString += '-';
formattedDateString += transform2digit(this._date.getDate());
} else if (format === 'MMMM YYYY') {
formattedDateString += formatDate(this._date, this._locale, {month: 'long'});
formattedDateString += ' ';
formattedDateString += this._date.getFullYear();
} else if (format === 'LL') {
formattedDateString += formatDate(this._date, this._locale, {
month: 'long',
year: 'numeric',
day: '2-digit'
});
} else if (format === 'dd') {
formattedDateString += formatDate(this._date, this._locale, {weekday: 'short'});
} else if (format === 'dddd') {
formattedDateString += formatDate(this._date, this._locale, {weekday: 'long'});
} else if (format === 'HH:mm') {
formattedDateString += transform2digit(this._date.getHours());
formattedDateString += ':';
formattedDateString += transform2digit(this._date.getMinutes());
} else if (format === 'HH') {
formattedDateString += transform2digit(this._date.getHours());
} else if (format === 'mm') {
formattedDateString += transform2digit(this._date.getMinutes());
} else if (format === 'YYYY-MM-DD[T]HH:mmZ') {
formattedDateString += this._date.getFullYear();
formattedDateString += '-';
formattedDateString += transform2digit(this._date.getMonth() + 1);
formattedDateString += '-';
formattedDateString += transform2digit(this._date.getDate());
formattedDateString += 'T';
formattedDateString += transform2digit(this._date.getHours());
formattedDateString += ':';
formattedDateString += transform2digit(this._date.getMinutes());
const timezone = -1 * (this._date.getTimezoneOffset() / 60);
let abs = Math.abs(timezone);
abs = abs < 10 ? `0${abs}` : abs.toString();
formattedDateString += timezone < 0 ? `-${abs}:00` : `+${abs}:00`;
} else {
format = typeof format === 'object' ? format : {};
formattedDateString = formatDate(this._date, this._locale, format);
}
return formattedDateString;
}
/**
@see https://momentjs.com/docs/#/get-set/year/
*/
year() {
return this._date.getFullYear();
}
/**
@see https://momentjs.com/docs/#/get-set/month/
*/
month() {
return this._date.getMonth();
}
/**
@see https://momentjs.com/docs/#/get-set/week/
*/
week() {
// Source : https://stackoverflow.com/questions/6117814/get-week-of-year-in-javascript-like-in-php
const date = new Date(Date.UTC(this._date.getFullYear(), this._date.getMonth(), this._date.getDate()));
const dayNum = date.getUTCDay() || 7;
date.setUTCDate(date.getUTCDate() + 4 - dayNum);
const yearStart = new Date(Date.UTC(date.getUTCFullYear(), 0, 1));
return Math.ceil(((date - yearStart) / 86400000 + 1) / 7);
}
/**
@see https://momentjs.com/docs/#/get-set/day/
*/
day(day) {
if (typeof day === 'number') {
this._date.setDate(this._date.getDate() - (this._date.getDay() || 7) + day);
return this;
}
return this._date.getDay();
}
/**
@see https://momentjs.com/docs/#/get-set/hour/
*/
hours(hours) {
if (typeof hours === 'number') {
this._date.setHours(hours);
return this;
}
return this._date.getHours();
}
/**
@see https://momentjs.com/docs/#/get-set/minute/
*/
minutes(minutes) {
if (typeof minutes === 'number') {
this._date.setMinutes(minutes);
return this;
}
return this._date.getMinutes();
}
/**
@see https://momentjs.com/docs/#/get-set/date/
*/
date() {
return this._date.getDate();
}
/**
@see https://momentjs.com/docs/#/manipulating/add/
*/
add(value, type) {
let multiplier = 1;
switch (type) {
case 'year':
case 'years':
multiplier = 12;
case 'month':
case 'months':
const dayOfMonth = this._date.getDate();
this._date.setMonth(this._date.getMonth() + multiplier * value);
if (this._date.getDate() != dayOfMonth) {
this._date.setDate(0);
}
break;
case 'week':
case 'weeks':
multiplier = 7;
case 'day':
case 'days':
this._date.setDate(this._date.getDate() + multiplier * value);
break;
}
return this;
}
/**
@see https://momentjs.com/docs/#/manipulating/subtract/
*/
subtract(value, type) {
let multiplier = 1;
switch (type) {
case 'year':
case 'years':
multiplier = 12;
case 'month':
case 'months':
const dayOfMonth = this._date.getDate();
this._date.setMonth(this._date.getMonth() - multiplier * value);
if (this._date.getDate() != dayOfMonth) {
this._date.setDate(0);
}
break;
case 'week':
case 'weeks':
multiplier = 7;
case 'day':
case 'days':
this._date.setDate(this._date.getDate() - multiplier * value);
break;
}
return this;
}
/**
@see https://momentjs.com/docs/#/displaying/days-in-month/
*/
daysInMonth() {
return new Date(this._date.getFullYear(), this._date.getMonth() + 1, 0).getDate();
}
/**
@see https://momentjs.com/docs/#/displaying/difference/
*/
diff(obj) {
let diff = this._date.getTime() - obj._date.getTime();
let timezoneDiff = this._date.getTimezoneOffset() - obj._date.getTimezoneOffset();
if (timezoneDiff !== 0) {
diff -= timezoneDiff * 60000;
}
return diff / 86400000;
}
/**
@see https://momentjs.com/docs/#/manipulating/start-of/
*/
startOf(value) {
if (value === 'day') {
// Today
this._date = new Date(this._date.getFullYear(), this._date.getMonth(), this._date.getDate());
} else if (value === 'month') {
this._date = new Date(this._date.getFullYear(), this._date.getMonth(), 1);
} else if (value === 'year') {
this._date = new Date(new Date().getFullYear(), 0, 1);
}
return this;
}
/**
@see https://momentjs.com/docs/#/query/is-before/
*/
isBefore(coralDate, unit) {
if (coralDate && coralDate._date) {
return unit ? coralDate[unit]() > this[unit]() : coralDate._date > this._date;
}
return false;
}
/**
@see https://momentjs.com/docs/#/query/is-after/
*/
isAfter(coralDate, unit) {
if (coralDate && coralDate._date) {
return unit ? coralDate[unit]() < this[unit]() : coralDate._date < this._date;
}
return false;
}
/**
@see https://momentjs.com/docs/#/query/is-same/
*/
isSame(obj, type) {
if (type === 'hour') {
return obj && obj.clone()._date.getHours() === this.clone()._date.getHours();
} else if (type === 'minute') {
return obj && obj.clone()._date.getMinutes() === this.clone()._date.getMinutes();
} else if (type === 'day') {
return obj && obj.clone().startOf('day')._date.getTime() === this.clone().startOf('day')._date.getTime();
}
return obj && obj.clone()._date.getTime() === this.clone()._date.getTime();
}
/**
@see https://momentjs.com/docs/#/parsing/is-valid/
*/
isValid() {
return this._date.toString() !== 'Invalid Date';
}
/**
@ignore
Not supported so we return an empty object
*/
static localeData() {
return {};
}
/**
@see https://momentjs.com/docs/#/i18n/changing-locale/
*/
static locale(value) {
if (value) {
globalLocale = value;
}
return globalLocale;
}
/**
@see https://momentjs.com/docs/#/query/is-a-moment/
*/
static isMoment(obj) {
return obj instanceof this;
}
/**
@return {momentJS|DateTime}
*/
static get Moment() {
return window.moment || this;
}
}
export default DateTime;