def concatenate(
    series: Sequence[TimeSeries],
    axis: Union[str, int] = 0,
    ignore_time_axis: bool = False,
    ignore_static_covariates: bool = False,
    drop_hierarchy: bool = True,
    drop_metadata: bool = False,
):
    """Concatenates multiple ``TimeSeries`` along a given axis.

    ``axis`` can be an integer in (0, 1, 2) to denote (time, component, sample) or, alternatively,
    a string denoting the corresponding dimension of the underlying ``DataArray``.

    Parameters
    ----------
    series : Sequence[TimeSeries]
        Sequence of ``TimeSeries`` to concatenate
    axis : Union[str, int]
        Axis along which the series will be concatenated.
    ignore_time_axis : bool
        Allow concatenation even when some series do not have matching time axes.
        When done along component or sample dimensions, concatenation will work as long as the series
        have the same lengths (in this case the resulting series will have the time axis of the first
        provided series). When done along time dimension, concatenation will work even if the time axes
        are not contiguous (in this case, the resulting series will have a start time matching the start time
        of the first provided series). Default: False.
    ignore_static_covariates : bool
        Whether to ignore all requirements for static covariate concatenation and only transfer the static covariates
        of the first TimeSeries element in `series` to the concatenated TimeSeries. Only effective when `axis=1`.
    drop_hierarchy : bool
        When `axis=1`, whether to drop hierarchy information. True by default. When False, the hierarchies will be
        "concatenated" as well (by merging the hierarchy dictionaries), which may cause issues if the component
        names of the resulting series and that of the merged hierarchy do not match.
        When `axis=0` or `axis=2`, the hierarchy of the first series is always kept.
    drop_metadata : bool
        Whether to drop the metadata information of the concatenated series. False by default.
        When False, the concatenated series will inherit the metadata from the first TimeSeries element in `series`.

    Returns
    -------
    TimeSeries
        concatenated series
    """

    time_dims = [ts.time_dim for ts in series]
    if isinstance(axis, str):
        if axis == DIMS[1]:
            axis = 1
        elif axis == DIMS[2]:
            axis = 2
        else:
            raise_if_not(
                len(set(time_dims)) == 1 and axis == time_dims[0],
                "Unrecognised `axis` name. If `axis` denotes the time axis, all provided "
                "series must have the same time axis name (if that is not the case, try providing "
                "`axis=0` to concatenate along time dimension).",
            )
            axis = 0

    # At this point all series are supposed to have same time dim name
    time_dim_name = time_dims[0]

    da_sequence = [ts.data_array(copy=False) for ts in series]

    component_axis_equal = len({ts.width for ts in series}) == 1
    sample_axis_equal = len({ts.n_samples for ts in series}) == 1

    metadata = None if drop_metadata else series[0].metadata

    if axis == 0:
        # time
        raise_if(
            (not (component_axis_equal and sample_axis_equal)),
            "when concatenating along time dimension, the component and sample dimensions of all "
            "provided series must match.",
        )

        da_concat = xr.concat(da_sequence, dim=time_dim_name)

        # check, if timeseries are consecutive
        consecutive_time_axes = True
        for i in range(1, len(series)):
            if series[i - 1].end_time() + series[0].freq != series[i].start_time():
                consecutive_time_axes = False
                break

        if not consecutive_time_axes:
            raise_if_not(
                ignore_time_axis,
                "When concatenating over time axis, all series need to be contiguous "
                "in the time dimension. Use `ignore_time_axis=True` to override "
                "this behavior and concatenate the series by extending the time axis "
                "of the first series.",
            )

            tindex = generate_index(
                start=series[0].start_time(),
                freq=series[0].freq_str,
                length=da_concat.shape[0],
            )

            da_concat = da_concat.assign_coords({time_dim_name: tindex})
            da_concat = _xarray_with_attrs(
                da_concat, series[0].static_covariates, series[0].hierarchy, metadata
            )

    else:
        time_axes_equal = all(
            list(
                map(
                    lambda t: t[0].has_same_time_as(t[1]), zip(series[0:-1], series[1:])
                )
            )
        )
        time_axes_ok = (
            time_axes_equal
            if not ignore_time_axis
            else len({len(ts) for ts in series}) == 1
        )

        raise_if_not(
            (
                time_axes_ok
                and (
                    (axis == 1 and sample_axis_equal)
                    or (axis == 2 and component_axis_equal)
                )
            ),
            "When concatenating along component or sample dimensions, all the series must have the same time "
            "axes (unless `ignore_time_axis` is True), or time axes of same lengths (if `ignore_time_axis` is "
            "True), and all series must have the same number of samples (if concatenating along component "
            "dimension), or the same number of components (if concatenating along sample dimension).",
        )

        # we concatenate raw values using Numpy because not all series might have the same time axes
        # and joining using xarray.concatenate() won't work in some cases
        concat_vals = np.concatenate([da.values for da in da_sequence], axis=axis)

        if axis == 1:
            # When concatenating along component dimension, we have to re-create a component index
            # we rely on the factory method of TimeSeries to disambiguate names later on if needed.
            component_index = pd.Index([
                c for cl in [ts.components for ts in series] for c in cl
            ])
            static_covariates = (
                _concat_static_covs(series)
                if not ignore_static_covariates
                else series[0].static_covariates
            )
            hierarchy = None if drop_hierarchy else _concat_hierarchy(series)
        else:
            component_index = da_sequence[0].get_index(DIMS[1])
            static_covariates = series[0].static_covariates
            hierarchy = series[0].hierarchy

        da_concat = xr.DataArray(
            concat_vals,
            dims=(time_dim_name,) + DIMS[-2:],
            coords={time_dim_name: series[0].time_index, DIMS[1]: component_index},
            attrs={
                STATIC_COV_TAG: static_covariates,
                HIERARCHY_TAG: hierarchy,
                METADATA_TAG: metadata,
            },
        )

    return TimeSeries.from_xarray(da_concat, fill_missing_dates=False)
Concatenates multiple ``TimeSeries`` along a given axis.

    ``axis`` can be an integer in (0, 1, 2) to denote (time, component, sample) or, alternatively,
    a string denoting the corresponding dimension of the underlying ``DataArray``.

    Parameters
    ----------
    series : Sequence[TimeSeries]
        Sequence of ``TimeSeries`` to concatenate
    axis : Union[str, int]
        Axis along which the series will be concatenated.
    ignore_time_axis : bool
        Allow concatenation even when some series do not have matching time axes.
        When done along component or sample dimensions, concatenation will work as long as the series
        have the same lengths (in this case the resulting series will have the time axis of the first
        provided series). When done along time dimension, concatenation will work even if the time axes
        are not contiguous (in this case, the resulting series will have a start time matching the start time
        of the first provided series). Default: False.
    ignore_static_covariates : bool
        Whether to ignore all requirements for static covariate concatenation and only transfer the static covariates
        of the first TimeSeries element in `series` to the concatenated TimeSeries. Only effective when `axis=1`.
    drop_hierarchy : bool
        When `axis=1`, whether to drop hierarchy information. True by default. When False, the hierarchies will be
        "concatenated" as well (by merging the hierarchy dictionaries), which may cause issues if the component
        names of the resulting series and that of the merged hierarchy do not match.
        When `axis=0` or `axis=2`, the hierarchy of the first series is always kept.
    drop_metadata : bool
        Whether to drop the metadata information of the concatenated series. False by default.
        When False, the concatenated series will inherit the metadata from the first TimeSeries element in `series`.

    Returns
    -------
    TimeSeries
        concatenated series
    
darts/timeseries.py