Skip to content

Commit

Permalink
refactor(deps): replace moment with dayjs
Browse files Browse the repository at this point in the history
Replace Mermaid's dependency on `moment` with `dayjs`.

[Moment is now in maintenance mode][1], and they don't recommend
using it.

[Dayjs][2] has almost exactly the same API as moment, and is still
curently being maintained. Unlike moment, dayjs objects are immutable,
which makes our life much easier, but we need to do
`a = a.add(1, "day")` instead of just `a.add(1, "day")`.

**Issues**:
  - `date.add(dayjs.duration(1, "day"))` always adds 24 hours instead
    of 1 day (different from Moment.js).
    This causes issues with daylight savings, as those days have 23/25
    hours.
    Maybe we should replace this with a different date library?

[1]: https://momentjs.com/docs/#/-project-status/
[2]: https://day.js.org/
  • Loading branch information
aloisklink committed Feb 18, 2023
1 parent fec193e commit 3964cc5
Show file tree
Hide file tree
Showing 9 changed files with 156 additions and 122 deletions.
4 changes: 2 additions & 2 deletions docs/syntax/gantt.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ The following formatting options are supported:
| `YY` | 14 | 2 digit year |
| `Q` | 1..4 | Quarter of year. Sets month to first month in quarter. |
| `M MM` | 1..12 | Month number |
| `MMM MMMM` | January..Dec | Month name in locale set by `moment.locale()` |
| `MMM MMMM` | January..Dec | Month name in locale set by `dayjs.locale()` |
| `D DD` | 1..31 | Day of month |
| `Do` | 1st..31st | Day of month with ordinal |
| `DDD DDDD` | 1..365 | Day of year |
Expand All @@ -200,7 +200,7 @@ The following formatting options are supported:
| `SSS` | 0..999 | Thousandths of a second |
| `Z ZZ` | +12:00 | Offset from UTC as +-HH:mm, +-HHmm, or Z |

More info in: <https://momentjs.com/docs/#/parsing/string-format/>
More info in: <https://day.js.org/docs/en/parse/string-format/>

### Output date format on the axis

Expand Down
2 changes: 1 addition & 1 deletion packages/mermaid/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,11 @@
"cytoscape-fcose": "^2.1.0",
"d3": "^7.0.0",
"dagre-d3-es": "7.0.8",
"dayjs": "^1.11.7",
"dompurify": "2.4.3",
"elkjs": "^0.8.2",
"khroma": "^2.0.0",
"lodash-es": "^4.17.21",
"moment": "^2.29.4",
"non-layered-tidy-tree-layout": "^2.0.2",
"stylis": "^4.1.2",
"ts-dedent": "^2.2.0",
Expand Down
84 changes: 63 additions & 21 deletions packages/mermaid/src/diagrams/gantt/ganttDb.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import moment from 'moment';
import dayjs from 'dayjs';
import dayjsDuration from 'dayjs/plugin/duration';
import dayjsIsoWeek from 'dayjs/plugin/isoWeek';
import dayjsCustomParseFormat from 'dayjs/plugin/customParseFormat';
import dayjsAdvancedFormat from 'dayjs/plugin/advancedFormat';
import { sanitizeUrl } from '@braintree/sanitize-url';
import { log } from '../../logger';
import * as configApi from '../../config';
Expand All @@ -15,6 +19,11 @@ import {
getDiagramTitle,
} from '../../commonDb';

dayjs.extend(dayjsDuration);
dayjs.extend(dayjsIsoWeek);
dayjs.extend(dayjsCustomParseFormat);
dayjs.extend(dayjsAdvancedFormat);

let dateFormat = '';
let axisFormat = '';
let tickInterval = undefined;
Expand Down Expand Up @@ -162,15 +171,46 @@ export const isInvalidDate = function (date, dateFormat, excludes, includes) {
return excludes.includes(date.format(dateFormat.trim()));
};

/**
* TODO: fully document what this function does and what types it accepts
*
* @param {object} task
* @param {string | Date} task.startTime - Might be a `Date` or a `string`.
* TODO: is this always a Date?
* @param {string | Date} task.endTime - Might be a `Date` or a `string`.
* TODO: is this always a Date?
* @param {*} dateFormat
* @param {*} excludes
* @param {*} includes
* @returns
*/
const checkTaskDates = function (task, dateFormat, excludes, includes) {
if (!excludes.length || task.manualEndTime) {
return;
}
let startTime = moment(task.startTime, dateFormat, true);
startTime.add(1, 'd');
let endTime = moment(task.endTime, dateFormat, true);
let renderEndTime = fixTaskDates(startTime, endTime, dateFormat, excludes, includes);
task.endTime = endTime.toDate();
let startTime;
if (task.startTime instanceof Date) {
startTime = dayjs(task.startTime);
} else {
startTime = dayjs(task.startTime, dateFormat, true);
}
startTime = startTime.add(1, 'd');

let originalEndTime;
if (task.endTime instanceof Date) {
originalEndTime = dayjs(task.endTime);
} else {
originalEndTime = dayjs(task.endTime, dateFormat, true);
}
const [fixedEndTime, renderEndTime] = fixTaskDates(
startTime,
originalEndTime,
dateFormat,
excludes,
includes
);
// todo, both endTime and renderEndTime might be the same value?
task.endTime = fixedEndTime.toDate();
task.renderEndTime = renderEndTime;
};

Expand All @@ -183,11 +223,11 @@ const fixTaskDates = function (startTime, endTime, dateFormat, excludes, include
}
invalid = isInvalidDate(startTime, dateFormat, excludes, includes);
if (invalid) {
endTime.add(1, 'd');
endTime = endTime.add(1, 'd');
}
startTime.add(1, 'd');
startTime = startTime.add(1, 'd');
}
return renderEndTime;
return [endTime, renderEndTime];
};

const getStartDate = function (prevTime, dateFormat, str) {
Expand Down Expand Up @@ -223,7 +263,7 @@ const getStartDate = function (prevTime, dateFormat, str) {
}

// Check for actual date set
let mDate = moment(str, dateFormat.trim(), true);
let mDate = dayjs(str, dateFormat.trim(), true);
if (mDate.isValid()) {
return mDate.toDate();
} else {
Expand All @@ -238,7 +278,7 @@ const getStartDate = function (prevTime, dateFormat, str) {
};

/**
* Parse a string as a moment duration.
* Parse a string as a dayjs duration.
*
* The string have to be compound by a value and a shorthand duration unit. For example `5d`
* represents 5 days.
Expand All @@ -254,33 +294,35 @@ const getStartDate = function (prevTime, dateFormat, str) {
* - `ms` for milliseconds
*
* @param {string} str - A string representing the duration.
* @returns {moment.Duration} A moment duration, including an invalid moment for invalid input
* @returns {ReturnType<dayjs["duration"]>} A dayjs duration, including an invalid dayjs for invalid input
* string.
*/
const parseDuration = function (str) {
const statement = /^(\d+(?:\.\d+)?)([Mdhmswy]|ms)$/.exec(str.trim());
if (statement !== null) {
return moment.duration(Number.parseFloat(statement[1]), statement[2]);
return dayjs.duration(Number.parseFloat(statement[1]), statement[2]);
}
return moment.duration.invalid();
// NaN means an invalid duration
return dayjs.duration(NaN);
};

const getEndDate = function (prevTime, dateFormat, str, inclusive = false) {
str = str.trim();

// Check for actual date
let mDate = moment(str, dateFormat.trim(), true);
let mDate = dayjs(str, dateFormat.trim(), true);
if (mDate.isValid()) {
if (inclusive) {
mDate.add(1, 'd');
mDate = mDate.add(1, 'd');
}
return mDate.toDate();
}

const endTime = moment(prevTime);
let endTime = dayjs(prevTime);
const duration = parseDuration(str);
if (duration.isValid()) {
endTime.add(duration);
// dayjs doesn't yet support duration.isValid(), so instead we check for NaN
if (!Number.isNaN(duration.asMilliseconds())) {
endTime = endTime.add(duration);
}
return endTime.toDate();
};
Expand Down Expand Up @@ -346,7 +388,7 @@ const compileData = function (prevTask, dataStr) {

if (endTimeData) {
task.endTime = getEndDate(task.startTime, dateFormat, endTimeData, inclusiveEndDates);
task.manualEndTime = moment(endTimeData, 'YYYY-MM-DD', true).isValid();
task.manualEndTime = dayjs(endTimeData, 'YYYY-MM-DD', true).isValid();
checkTaskDates(task, dateFormat, excludes, includes);
}

Expand Down Expand Up @@ -496,7 +538,7 @@ const compileTasks = function () {
);
if (rawTasks[pos].endTime) {
rawTasks[pos].processed = true;
rawTasks[pos].manualEndTime = moment(
rawTasks[pos].manualEndTime = dayjs(
rawTasks[pos].raw.endTime.data,
'YYYY-MM-DD',
true
Expand Down
Loading

0 comments on commit 3964cc5

Please sign in to comment.