tltv / gantt

Gantt Chart Add-on for Vaadin 8
Apache License 2.0
20 stars 23 forks source link

Steps spanning daylight saving time changes #86

Open FSchliephacke opened 5 years ago

FSchliephacke commented 5 years ago

OS: OpenSuse Leap 15.0 (but also tested under Windows 10) Java version: 1.8.0u202 64 bit Vaadin version: 8.7.0 Gantt addon version: 1.0.4 Firefox version: 60.4.0

I have encountered an issue when creating Steps with SubSteps spanning daylight saving time changes. Everything works fine while the horizontal scrollbar is visible, but as soon as it vanishes, the SubSteps move to the right (in Firefox) or to the left (in Edge).

I am not even sure that this is a bug in this addon, but maybe someone a bit more knowledgeable in CSS could look into this. Setting the overflow-x property to scroll was the first idea I thought could be a workaround, but to no avail.

To see what is happening, here are two screenshots showing the exact same data.

No Scrollbar visible, the second step spans two daylight saving changes:

gantt-bug-no-scrollbar

Scrollbar visible, everything works as expected:

gantt-bug-scrollbar

Setting the end date to a value between the two I selected still shows no scrollbar on my screen and the displayed substeps are still shifted.

Feel free to test out this example, as I found the behaviour quite confusing and it took me some time to figure out on which conditions the view is wrong. This is the view I used to create the screenshots:

import com.vaadin.ui.DateField;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.NativeSelect;
import com.vaadin.ui.VerticalLayout;
import java.time.Instant;
import java.time.LocalDate;
import java.time.Month;
import java.time.Period;
import java.time.ZoneId;
import java.util.Arrays;
import java.util.List;
import java.util.TimeZone;
import java.util.stream.Collectors;
import org.tltv.gantt.Gantt;
import org.tltv.gantt.client.shared.Resolution;
import org.tltv.gantt.client.shared.Step;
import org.tltv.gantt.client.shared.SubStep;

public class GanttView extends VerticalLayout
{
    private static final Resolution DEFAULT_RESOLUTION = Resolution.Hour;

    private final Gantt gantt = new Gantt();

    public GanttView()
    {
        setSizeFull();
        setMargin(false);

        gantt.setSizeFull();
        gantt.setMovableSteps(false);
        gantt.setResizableSteps(false);
        gantt.setShowCurrentTime(true);

        final NativeSelect<Resolution> resolution = new NativeSelect<>("Resolution", Arrays.asList(Resolution.Hour, Resolution.Day, Resolution.Week));
        resolution.setEmptySelectionAllowed(false);
        resolution.addValueChangeListener(e -> gantt.setResolution(e.getValue()));
        resolution.setValue(DEFAULT_RESOLUTION);

        final DateField start = new DateField("From");
        final DateField end = new DateField("To");

        final NativeSelect<String> timezoneSelect = new NativeSelect<>("Timezone", Gantt.getSupportedTimeZoneIDs().stream().sorted().collect(Collectors.toList()));
        timezoneSelect.setValue(TimeZone.getDefault().getID());
        timezoneSelect.addValueChangeListener(tz -> gantt.setTimeZone(TimeZone.getTimeZone(tz.getValue())));

        final HorizontalLayout buttonBar = new HorizontalLayout(resolution, start, end, timezoneSelect);
        buttonBar.setMargin(false);
        final HorizontalLayout content = new HorizontalLayout();
        content.setSizeFull();
        content.setMargin(false);

        addComponent(buttonBar);
        addComponent(content);
        setExpandRatio(content, 1);

        content.addComponent(gantt);

        start.addValueChangeListener(e ->
        {
            gantt.setStartDate(e.getValue());
            if (e.isUserOriginated() && e.getValue().isAfter(end.getValue()))
            {
                end.setValue(e.getValue().plus(Period.ofDays(1)));
            }
        });
        end.addValueChangeListener(e ->
        {
            gantt.setEndDate(e.getValue());
            if (e.isUserOriginated() && e.getValue().isBefore(start.getValue()))
            {
                start.setValue(e.getValue().minus(Period.ofDays(1)));
            }
        });

        addSteps();

        start.setValue(LocalDate.of(2018, Month.OCTOBER, 29));
        end.setValue(LocalDate.of(2018, Month.OCTOBER, 30));
    }

    private void addSteps()
    {
        addStep(Arrays.asList(substep(Instant.parse("2018-10-29T00:00:00.0Z"), Instant.parse("2018-10-29T12:00:00.0Z"))));
        addStep(Arrays.asList(substep(Instant.parse("2018-03-25T00:00:00.0Z"), Instant.parse("2018-03-25T01:00:00.0Z")),
                              substep(Instant.parse("2018-10-29T00:00:00.0Z"), Instant.parse("2018-10-29T12:00:00.0Z"))));
    }

    private SubStep substep(Instant from, Instant to)
    {
        final SubStep substep = new SubStep(from.atZone(ZoneId.systemDefault()) + " - " + to.atZone(ZoneId.systemDefault()));
        substep.setStartDate(from.toEpochMilli());
        substep.setEndDate(to.toEpochMilli());
        substep.setDescription(from.atZone(ZoneId.systemDefault()) + " - " + to.atZone(ZoneId.systemDefault()));
        return substep;
    }

    private void addStep(List<SubStep> substeps)
    {
        final Instant from = substeps.stream()
                .map(SubStep::getStartDate)
                .min(Long::compare)
                .map(Instant::ofEpochMilli)
                .orElse(Instant.ofEpochMilli(0));
        final Instant to = substeps.stream()
                .map(SubStep::getEndDate)
                .max(Long::compare)
                .map(Instant::ofEpochMilli)
                .orElse(Instant.ofEpochMilli(1));
        final Step step = new Step();
        step.setDescription(from.atZone(ZoneId.systemDefault()) + " - " + to.atZone(ZoneId.systemDefault()));
        step.addSubSteps(substeps);
        step.setStartDate(from.toEpochMilli());
        step.setEndDate(to.toEpochMilli());
        gantt.addStep(step);
    }
}