Support Request – Handling Task Durations with Multiple Calendars in Gantt

Hi Team,

I’m using the PRO version of Gantt and facing issues after implementing multiple calendar support in my app.

My requirement is to allow different tasks to have different calendars assigned, and accordingly, their durations should be computed dynamically. I’m using my own backend scheduler to calculate end_date, while the frontend (Gantt) sends start_date and duration. At the database level, I’m storing duration and date values in ISO strings (e.g., "PTnH", where n is the number of hours).

In this setup, the frontend needs to send computed durations based on the calendar assigned to each task and its working time configuration. For example:

tasks: [
  {
    "Task A": {
      "duration": "2 days",
      "calendar": "24/7",
      "expectedPayload": "PT48H"
    }
  },
  {
    "Task B": {
      "duration": "3 days",
      "calendar": "Standard (8h/day, Mon-Fri)",
      "expectedPayload": "PT24H"
    }
  },
  {
    "Task C": {
      "duration": "1 day",
      "calendar": "12h Calendar (8AM–8PM, Mon-Fri)",
      "expectedPayload": "PT12H"
    }
  }
]

This worked fine in a single-calendar setup, where I had hardcoded "1 day = PT8H" in JavaScript and multiplied days by a fixed hoursPerDay. But this approach doesn’t scale for multiple calendars, as each may define different working times.

My questions are:

  1. Feasibility – Is it possible to handle this calculation correctly on the frontend level when using multiple calendars?
  2. Parsing backend response – Can Gantt parse the backend response based on the calendar_id attached to each task? For example, if I send "PT24H" or "PT120H", can Gantt automatically convert these ISO hour values into days according to the assigned calendar’s working hours/days and display them correctly in the UI?

The calendar_id is already available in the task data, and I can see the correct calendar name in the Lightbox when editing tasks.

I hope this explanation makes sense. Could you please advise on the best way to handle duration conversions in multi-calendar scenarios on the frontend?

Thanks!

Hello @Rizwan,

Thank you for the provided details.

Regarding this:

Feasibility – Is it possible to handle this calculation correctly on the frontend level when using multiple calendars?

You can add a conversion layer on the frontend to correctly handle ISO hour values for different calendars.

First, you need to parse the backend’s ISO 8601 durations (e.g., "PT24H") into hours and then convert them into Gantt’s internal duration (in days) depending on the working time of the assigned calendar:

function isoToGanttDuration(task) {
    const hours = parseIsoDuration(task.duration);
    let hoursPerDay = 8; // default calendar = 8h/day
    if (task.calendar_id === 'custom') {
        hoursPerDay = 24; // custom calendar = 24h/day
    }
    return hours / hoursPerDay;
}

When you load the data into Gantt, you can process each task, converting the iso duration into days based on its calendar_id. Gantt won’t parse ISO values automatically, but you can preprocess them before parsing tasks into Gantt:

gantt.parse({
  data: backendData.tasks.map((task) =>
    task.type !== 'project'
      ? { ...task, duration: isoToGanttDuration(task) }
      : task
  ),
});

Parsing backend response – Can Gantt parse the backend response based on the calendar_id attached to each task? For example, if I send “PT24H” or “PT120H”, can Gantt automatically convert these ISO hour values into days according to the assigned calendar’s working hours/days and display them correctly in the UI?

Before sending tasks to the backend, you can reverse the conversion by multiplying back by the calendar’s hours and formatting it as ISO:

function toIsoDuration(hours) {
    return `PT${hours}H`;
}

function ganttDurationToIso(task) {
    let hoursPerDay = 8;
    if (task.calendar_id === 'custom') {
        hoursPerDay = 24;
    }

    return toIsoDuration(task.duration * hoursPerDay);
}

If you use DataProcessor in your code, then you can implement it inside the onBeforeDataSending event. This way, the conversion is handled automatically by the dataProcessor before the data is sent to the server.

Please check the example of how it might be implemented: DHTMLX Snippet Tool.

Best regards,
Valeria Ivashkevich
DHTMLX Support Engineer

1 Like

Thank you for the quick and detailed response!

I tried a similar approach yesterday and it worked. The only difference is that in my case, I’m using gantt.addCalendar to dynamically add the loaded calendars. I’ve also implemented parser functions to convert ISO durations back and forth.

However, I have a concern regarding fractions and decimal values for duration. Although I haven’t encountered this yet, I plan to enhance the Duration column with inline-editing and leverage gantt.durationFormatter. My goal is to allow users to enter duration in flexible formats such as "1.5d", "4 hours", "1 week", etc., and have Gantt parse them correctly using the same logic we’re applying now.

In this scenario, would Gantt be able to:

  • Accept fractional values (e.g., 15.5h) entered by the user,
  • Convert them into ISO format like "PT15H30M" when sending to the backend,
  • And then display them back in days with fractions (e.g., raw "PT15H" on a 12h/day calendar displaying as 1.25 days in the column)?

I hope I conveyed the question clearly. Thanks again for your support, Velaria!

Hello @Rizwan,

Yes, this can be achieved with a combination of gantt.durationFormatter, and a custom inline editor. You’ll need to customize the editor for the duration column because we use multiple worktime calendars in the project.

Here’s how it might be implemented:

  1. We define two formatters: the default one (8h/day, 40h/week) and a custom calendar (8h/day, 56h/week). Both work with hours under the hood, but can parse flexible inputs like 1.5d.
const defaultCalFormatter = gantt.ext.formatters.durationFormatter({
    enter: 'day',
    store: 'hour',
    format: 'day',
    short: true,
    hoursPerDay: 8,
    hoursPerWeek: 40,
});

const customCalFormatter = gantt.ext.formatters.durationFormatter({
    enter: 'day',
    store: 'hour',
    format: 'day',
    short: true,
    hoursPerDay: 8,
    hoursPerWeek: 56,
});

Note that to display duration with fractions, you need to store task durations in a unit smaller than the units of the values displayed in decimal format.
https://docs.dhtmlx.com/gantt/desktop__working_time.html#taskdurationindecimalformat:~:text=Implementing%20decimal%20format

  1. To make the duration column inline-editable, we create a custom duration_editor which allows us to edit durations taking into consideration the working hours of the edited task. The editor shows the formatted value and parses user input back to hours:
gantt.config.editor_types.duration_editor = {
    type: 'custom',
    show: (id, column, config, placeholder) => {
        const task = gantt.getTask(id);
        const formatter = task.calendar_id === 'custom' ? customCalFormatter : defaultCalFormatter;
        const html = `<div><input type='text' value='${formatter.format(task.duration)}'></div>`;
        placeholder.innerHTML = html;
    },
    render: function (task, column) {
        const formatter = task.calendar_id === 'custom' ? customCalFormatter : defaultCalFormatter;
        const input = document.createElement('input');
        input.type = 'text';
        input.value = formatter.format(task.duration);
        return input;
    },
    set_value: function (value, id, column, node) {
        const task = gantt.getTask(id);
        const formatter = task.calendar_id === 'custom' ? customCalFormatter : defaultCalFormatter;
        node.value = formatter.format(value);
    },
    get_value: function (id, column, node) {
        const task = gantt.getTask(id);
        const formatter = task.calendar_id === 'custom' ? customCalFormatter : defaultCalFormatter;
        const input = node.querySelector('input');
        return formatter.parse(input.value);
    },
    is_changed: (value, id, column, node) => {
        return true;
    },
    is_valid: (value, id, column, node) => {
        // validate, changes will be discarded if the method returns false
        return true;
    },
    focus: (node) => {
        const input = node.querySelector('input');
        input.focus();
    },
};
  1. In the duration column template, we choose the right formatter depending on the task’s calendar. So the same duration = 15.5 hours might be shown as 1.9d with an 8h/day calendar, or ~2d with a different calendar:
const durationEditor = { type: 'duration_editor', map_to: 'duration' };

gantt.config.columns = [
    {
        name: 'duration',
        label: 'Duration',
        width: 100,
        align: 'center',
        template: (task) =>
            task.calendar_id === 'custom'
                ? customCalFormatter.format(task.duration)
                : defaultCalFormatter.format(task.duration),
        editor: durationEditor,
        resize: true,
    },
   // ...
];
  1. When sending the data to the backend, convert hours into ISO durations. Here’s a simple helper:
function toIsoDuration(hours) {
    return `PT${hours}H`;
}

Please check a full example: DHTMLX Snippet Tool.

Best regards,
Valeria Ivashkevich
DHTMLX Support Engineer