Skip to content

Commit

Permalink
refactor: ♻️ migrate moment.js to dayjs (sequelize#14400)
Browse files Browse the repository at this point in the history
  • Loading branch information
DraftProducts authored and vanthome committed Jun 11, 2022
1 parent 21dfb99 commit dfd7d97
Show file tree
Hide file tree
Showing 30 changed files with 153 additions and 155 deletions.
3 changes: 1 addition & 2 deletions package.json
Expand Up @@ -40,12 +40,11 @@
"@types/debug": "^4.1.7",
"@types/inflection": "^1.13.0",
"@types/validator": "^13.7.1",
"dayjs": "^1.11.1",
"debug": "^4.3.3",
"dottie": "^2.0.2",
"inflection": "^1.13.2",
"lodash": "^4.17.21",
"moment": "^2.29.1",
"moment-timezone": "^0.5.34",
"pg-connection-string": "^2.5.0",
"retry-as-promised": "^5.0.0",
"semver": "^7.3.5",
Expand Down
22 changes: 9 additions & 13 deletions src/data-types.js
Expand Up @@ -5,13 +5,13 @@ const _ = require('lodash');
const wkx = require('wkx');
const sequelizeErrors = require('./errors');
const Validator = require('./utils/validator-extras').validator;
const momentTz = require('moment-timezone');
const moment = require('moment');
const dayjs = require('dayjs');
const { logger } = require('./utils/logger');

const warnings = {};
const { classToInvokable } = require('./utils/class-to-invokable');
const { joinSQLFragments } = require('./utils/join-sql-fragments');
const { isValidTimeZone } = require('./utils/dayjs');

class ABSTRACT {
toString(options) {
Expand Down Expand Up @@ -518,23 +518,19 @@ class DATE extends ABSTRACT {

_applyTimezone(date, options) {
if (options.timezone) {
if (momentTz.tz.zone(options.timezone)) {
return momentTz(date).tz(options.timezone);
if (isValidTimeZone(options.timezone)) {
return dayjs(date).tz(options.timezone);
}

return moment(date).utcOffset(options.timezone);
return dayjs(date).utcOffset(options.timezone);
}

return momentTz(date);
return dayjs(date);
}

_stringify(date, options) {
if (!moment.isMoment(date)) {
date = this._applyTimezone(date, options);
}

// Z here means current timezone, _not_ UTC
return date.format('YYYY-MM-DD HH:mm:ss.SSS Z');
return this._applyTimezone(date, options).format('YYYY-MM-DD HH:mm:ss.SSS Z');
}
}

Expand All @@ -547,12 +543,12 @@ class DATEONLY extends ABSTRACT {
}

_stringify(date) {
return moment(date).format('YYYY-MM-DD');
return dayjs(date).format('YYYY-MM-DD');
}

_sanitize(value, options) {
if ((!options || options && !options.raw) && Boolean(value)) {
return moment(value).format('YYYY-MM-DD');
return dayjs(value).format('YYYY-MM-DD');
}

return value;
Expand Down
11 changes: 4 additions & 7 deletions src/dialects/db2/data-types.js
@@ -1,7 +1,6 @@
'use strict';

const momentTz = require('moment-timezone');
const moment = require('moment');
const dayjs = require('dayjs');

module.exports = BaseTypes => {
const warn = BaseTypes.ABSTRACT.warn.bind(undefined,
Expand Down Expand Up @@ -228,9 +227,7 @@ module.exports = BaseTypes => {
}

_stringify(date, options) {
if (!moment.isMoment(date)) {
date = this._applyTimezone(date, options);
}
date = this._applyTimezone(date, options);

if (this._length > 0) {
let msec = '.';
Expand All @@ -253,15 +250,15 @@ module.exports = BaseTypes => {
return value;
}

value = new Date(momentTz.utc(value));
value = new Date(dayjs.utc(value));

return value;
}
}

class DATEONLY extends BaseTypes.DATEONLY {
static parse(value) {
return momentTz(value).format('YYYY-MM-DD');
return dayjs(value).format('YYYY-MM-DD');
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/dialects/db2/query.js
Expand Up @@ -8,7 +8,7 @@ const sequelizeErrors = require('../../errors');
const parserStore = require('../parserStore')('db2');
const _ = require('lodash');
const { logger } = require('../../utils/logger');
const moment = require('moment');
const dayjs = require('dayjs');

const debug = logger.debugContext('sql:db2');

Expand Down Expand Up @@ -160,7 +160,7 @@ export class Db2Query extends AbstractQuery {
if (parse) {
data[i][column] = parse(value);
} else if (coltypes[column] === 'TIMESTAMP') {
data[i][column] = new Date(moment.utc(value));
data[i][column] = new Date(dayjs.utc(value));
} else if (coltypes[column] === 'BLOB') {
data[i][column] = new Buffer.from(value);
} else if (coltypes[column].indexOf('FOR BIT DATA') > 0) {
Expand Down
10 changes: 3 additions & 7 deletions src/dialects/ibmi/data-types.js
@@ -1,6 +1,6 @@
'use strict';

const moment = require('moment');
const dayjs = require('dayjs');

module.exports = BaseTypes => {
const warn = BaseTypes.ABSTRACT.warn.bind(undefined, 'https://www.ibm.com/support/knowledgecenter/en/ssw_ibm_i_73/db2/rbafzch2data.htm');
Expand Down Expand Up @@ -61,14 +61,10 @@ module.exports = BaseTypes => {
static parse(date) {
if (!date.includes('+')) {
// For backwards compat. Dates inserted by sequelize < 2.0dev12 will not have a timestamp set
const mome = moment.utc(date);

return mome.toDate();
return dayjs.utc(date).toDate();
}

const mome = moment.utc(date);

return mome.toDate();
return dayjs.utc(date).toDate();
}

_stringify(date, options) {
Expand Down
4 changes: 2 additions & 2 deletions src/dialects/mariadb/connection-manager.js
Expand Up @@ -5,7 +5,7 @@ const { ConnectionManager } = require('../abstract/connection-manager');
const SequelizeErrors = require('../../errors');
const { logger } = require('../../utils/logger');
const DataTypes = require('../../data-types').mariadb;
const momentTz = require('moment-timezone');
const dayjs = require('dayjs');

const debug = logger.debugContext('connection:mariadb');
const parserStore = require('../parserStore')('mariadb');
Expand Down Expand Up @@ -55,7 +55,7 @@ export class MariaDbConnectionManager extends ConnectionManager {
async connect(config) {
// Named timezone is not supported in mariadb, convert to offset
let tzOffset = this.sequelize.options.timezone;
tzOffset = /\//.test(tzOffset) ? momentTz.tz(tzOffset).format('Z')
tzOffset = /\//.test(tzOffset) ? dayjs.tz(undefined, tzOffset).format('Z')
: tzOffset;

const connectionConfig = {
Expand Down
14 changes: 5 additions & 9 deletions src/dialects/mariadb/data-types.js
Expand Up @@ -2,8 +2,8 @@

const wkx = require('wkx');
const _ = require('lodash');
const momentTz = require('moment-timezone');
const moment = require('moment');
const dayjs = require('dayjs');
const { isValidTimeZone } = require('../../utils/dayjs');

module.exports = BaseTypes => {
BaseTypes.ABSTRACT.prototype.dialectTypes = 'https://mariadb.com/kb/en/library/resultset/#field-types';
Expand Down Expand Up @@ -58,11 +58,7 @@ module.exports = BaseTypes => {
}

_stringify(date, options) {
if (!moment.isMoment(date)) {
date = this._applyTimezone(date, options);
}

return date.format('YYYY-MM-DD HH:mm:ss.SSS');
return this._applyTimezone(date, options).format('YYYY-MM-DD HH:mm:ss.SSS');
}

static parse(value, options) {
Expand All @@ -71,8 +67,8 @@ module.exports = BaseTypes => {
return value;
}

if (momentTz.tz.zone(options.timezone)) {
value = momentTz.tz(value, options.timezone).toDate();
if (isValidTimeZone(options.timezone)) {
value = dayjs.tz(value, options.timezone).toDate();
} else {
value = new Date(`${value} ${options.timezone}`);
}
Expand Down
4 changes: 2 additions & 2 deletions src/dialects/mssql/data-types.js
@@ -1,6 +1,6 @@
'use strict';

const moment = require('moment');
const dayjs = require('dayjs');

module.exports = BaseTypes => {
const warn = BaseTypes.ABSTRACT.warn.bind(undefined, 'https://msdn.microsoft.com/en-us/library/ms187752%28v=sql.110%29.aspx');
Expand Down Expand Up @@ -138,7 +138,7 @@ module.exports = BaseTypes => {

class DATEONLY extends BaseTypes.DATEONLY {
static parse(value) {
return moment(value).format('YYYY-MM-DD');
return dayjs(value).format('YYYY-MM-DD');
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/dialects/mysql/connection-manager.js
Expand Up @@ -4,7 +4,7 @@ const { ConnectionManager } = require('../abstract/connection-manager');
const SequelizeErrors = require('../../errors');
const { logger } = require('../../utils/logger');
const DataTypes = require('../../data-types').mysql;
const momentTz = require('moment-timezone');
const dayjs = require('dayjs');

const debug = logger.debugContext('connection:mysql');
const parserStore = require('../parserStore')('mysql');
Expand Down Expand Up @@ -107,7 +107,7 @@ export class MySqlConnectionManager extends ConnectionManager {
// set timezone for this connection
// but named timezone are not directly supported in mysql, so get its offset first
let tzOffset = this.sequelize.options.timezone;
tzOffset = /\//.test(tzOffset) ? momentTz.tz(tzOffset).format('Z') : tzOffset;
tzOffset = /\//.test(tzOffset) ? dayjs.tz(undefined, tzOffset).format('Z') : tzOffset;
await promisify(cb => connection.query(`SET time_zone = '${tzOffset}'`, cb))();
}

Expand Down
12 changes: 5 additions & 7 deletions src/dialects/mysql/data-types.js
Expand Up @@ -2,8 +2,8 @@

const wkx = require('wkx');
const _ = require('lodash');
const momentTz = require('moment-timezone');
const moment = require('moment');
const dayjs = require('dayjs');
const { isValidTimeZone } = require('../../utils/dayjs');

module.exports = BaseTypes => {
BaseTypes.ABSTRACT.prototype.dialectTypes = 'https://dev.mysql.com/doc/refman/5.7/en/data-types.html';
Expand Down Expand Up @@ -58,9 +58,7 @@ module.exports = BaseTypes => {
}

_stringify(date, options) {
if (!moment.isMoment(date)) {
date = this._applyTimezone(date, options);
}
date = this._applyTimezone(date, options);

// Fractional DATETIMEs only supported on MySQL 5.6.4+
if (this._length) {
Expand All @@ -76,8 +74,8 @@ module.exports = BaseTypes => {
return value;
}

if (momentTz.tz.zone(options.timezone)) {
value = momentTz.tz(value, options.timezone).toDate();
if (isValidTimeZone(options.timezone)) {
value = dayjs.tz(value, options.timezone).toDate();
} else {
value = new Date(`${value} ${options.timezone}`);
}
Expand Down
6 changes: 3 additions & 3 deletions src/dialects/postgres/connection-manager.js
Expand Up @@ -3,12 +3,13 @@
const _ = require('lodash');
const { ConnectionManager } = require('../abstract/connection-manager');
const { logger } = require('../../utils/logger');
const { isValidTimeZone } = require('../../utils/dayjs');

const debug = logger.debugContext('connection:pg');
const sequelizeErrors = require('../../errors');
const semver = require('semver');
const dataTypes = require('../../data-types');
const momentTz = require('moment-timezone');
const dayjs = require('dayjs');
const { promisify } = require('util');

export class PostgresConnectionManager extends ConnectionManager {
Expand Down Expand Up @@ -243,8 +244,7 @@ export class PostgresConnectionManager extends ConnectionManager {
}

if (!this.sequelize.config.keepDefaultTimezone) {
const isZone = Boolean(momentTz.tz.zone(this.sequelize.options.timezone));
if (isZone) {
if (isValidTimeZone(this.sequelize.options.timezone)) {
query += `SET TIME ZONE '${this.sequelize.options.timezone}';`;
} else {
query += `SET TIME ZONE INTERVAL '${this.sequelize.options.timezone}' HOUR TO MINUTE;`;
Expand Down
12 changes: 5 additions & 7 deletions src/dialects/snowflake/data-types.js
@@ -1,7 +1,7 @@
'use strict';

const momentTz = require('moment-timezone');
const moment = require('moment');
const dayjs = require('dayjs');
const { isValidTimeZone } = require('../../utils/dayjs');

module.exports = BaseTypes => {
BaseTypes.ABSTRACT.prototype.dialectTypes = 'https://dev.snowflake.com/doc/refman/5.7/en/data-types.html';
Expand Down Expand Up @@ -43,9 +43,7 @@ module.exports = BaseTypes => {
}

_stringify(date, options) {
if (!moment.isMoment(date)) {
date = this._applyTimezone(date, options);
}
date = this._applyTimezone(date, options);

if (this._length) {
return date.format('YYYY-MM-DD HH:mm:ss.SSS');
Expand All @@ -60,8 +58,8 @@ module.exports = BaseTypes => {
return value;
}

if (momentTz.tz.zone(options.timezone)) {
value = momentTz.tz(value, options.timezone).toDate();
if (isValidTimeZone(options.timezone)) {
value = dayjs.tz(value, options.timezone).toDate();
} else {
value = new Date(`${value} ${options.timezone}`);
}
Expand Down
2 changes: 1 addition & 1 deletion src/sequelize.d.ts
Expand Up @@ -278,7 +278,7 @@ export interface Options extends Logging {
* used to SET TIMEZONE when connecting to the server, to ensure that the result of NOW, CURRENT_TIMESTAMP
* and other time related functions have in the right timezone. For best cross platform performance use the
* format
* +/-HH:MM. Will also accept string versions of timezones used by moment.js (e.g. 'America/Los_Angeles');
* +/-HH:MM. Will also accept string versions of timezones supported by Intl.Locale (e.g. 'America/Los_Angeles');
* this is useful to capture daylight savings time changes.
*
* @default '+00:00'
Expand Down
3 changes: 2 additions & 1 deletion src/sequelize.js
Expand Up @@ -31,6 +31,7 @@ const { BelongsTo } = require('./associations/belongs-to');
const { HasOne } = require('./associations/has-one');
const { BelongsToMany } = require('./associations/belongs-to-many');
const { HasMany } = require('./associations/has-many');
require('./utils/dayjs');

/**
* This is the main class, the entry point to sequelize.
Expand Down Expand Up @@ -152,7 +153,7 @@ export class Sequelize {
* @param {string} [options.schema=null] A schema to use
* @param {object} [options.set={}] Default options for sequelize.set
* @param {object} [options.sync={}] Default options for sequelize.sync
* @param {string} [options.timezone='+00:00'] The timezone used when converting a date from the database into a JavaScript date. The timezone is also used to SET TIMEZONE when connecting to the server, to ensure that the result of NOW, CURRENT_TIMESTAMP and other time related functions have in the right timezone. For best cross platform performance use the format +/-HH:MM. Will also accept string versions of timezones used by moment.js (e.g. 'America/Los_Angeles'); this is useful to capture daylight savings time changes.
* @param {string} [options.timezone='+00:00'] The timezone used when converting a date from the database into a JavaScript date. The timezone is also used to SET TIMEZONE when connecting to the server, to ensure that the result of NOW, CURRENT_TIMESTAMP and other time related functions have in the right timezone. For best cross platform performance use the format +/-HH:MM. Will also accept string versions of timezones supported by Intl.Locale (e.g. 'America/Los_Angeles'); this is useful to capture daylight savings time changes.
* @param {string|boolean} [options.clientMinMessages='warning'] (Deprecated) The PostgreSQL `client_min_messages` session parameter. Set to `false` to not override the database's default.
* @param {boolean} [options.standardConformingStrings=true] The PostgreSQL `standard_conforming_strings` session parameter. Set to `false` to not set the option. WARNING: Setting this to false may expose vulnerabilities and is not recommended!
* @param {Function} [options.logging=console.log] A function that gets executed every time Sequelize would log something. Function may receive multiple parameters but only first one is printed by `console.log`. To print all values use `(...msg) => console.log(msg)`
Expand Down
27 changes: 27 additions & 0 deletions src/utils/dayjs.ts
@@ -0,0 +1,27 @@
import dayjs from 'dayjs';
import timezone from 'dayjs/plugin/timezone';
import utc from 'dayjs/plugin/utc';

dayjs.extend(utc);
dayjs.extend(timezone);

const history = new Map<string, boolean>();

export function isValidTimeZone(tz: string) {
if (history.has(tz)) {
return history.get(tz);
}

let status: boolean;
try {
Intl.DateTimeFormat(undefined, { timeZone: tz });

status = true;
} catch {
status = false;
}

history.set(tz, status);

return status;
}
1 change: 1 addition & 0 deletions src/utils/index.ts
Expand Up @@ -10,6 +10,7 @@ export * from './join-sql-fragments';
export * from './object';
export * from './sequelize-method';
export * from './string';
export * from './dayjs';

/**
* getComplexSize
Expand Down

0 comments on commit dfd7d97

Please sign in to comment.