Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
175 commits
Select commit Hold shift + click to select a range
edbda0b
add info on sensor configuration
j-atkins May 20, 2026
cd5b523
add list of available sensors to documentation
j-atkins May 20, 2026
4668e54
remove redundant TODO
j-atkins May 20, 2026
ae2d528
improve phrasing
j-atkins May 20, 2026
17d5b40
add details on sensor configurations to quickstart guide
j-atkins May 20, 2026
baa982a
update sail_the_ship with sensor configuration instructions
j-atkins May 20, 2026
a8a4978
enahnce phrasing
j-atkins May 20, 2026
6b3628e
add testing that docs match the code for sensor options, ensure instr…
j-atkins May 20, 2026
e23087b
add check that all instruments in code are addressed in sensor table
j-atkins May 20, 2026
a5ff3a4
update environments to pull parcels v4 alpha
j-atkins May 20, 2026
db44324
changed parcels logging api
j-atkins May 20, 2026
6f732d3
first wave of changes to the instrument logic with v4 logic, and part…
j-atkins May 20, 2026
71881cc
remove add dummy UV func, shouldn't be needed in v4 (?)
j-atkins May 20, 2026
62b2b1b
pull v4 from parcels/main
j-atkins May 20, 2026
38afc56
use AdvectionRK2
j-atkins May 20, 2026
573da9e
migrate fieldset ingestion protocol
j-atkins May 20, 2026
d13a036
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 20, 2026
5c7fd29
standardise variable naming for bathy data ingestion
j-atkins May 21, 2026
ee17230
env fixes
j-atkins May 21, 2026
72161df
Merge branch 'main' into migrate-v4
j-atkins May 21, 2026
92e74bd
add ipdb for devs
j-atkins May 21, 2026
0a83437
further changes to suit v4
j-atkins May 21, 2026
ca9e306
first steps considering handling depth positive up or down
j-atkins May 22, 2026
d4093cf
Merge branch 'migrate-v4' of github.com:OceanParcels/virtualship into…
j-atkins May 22, 2026
63122b9
refactor to _compute_max_depths; more changes in line with Parcels v4…
j-atkins May 26, 2026
a6ad631
depth -> z in ParticleSet's
j-atkins May 26, 2026
8e0a33f
times to datetime64 and update fieldset eval output
j-atkins May 28, 2026
8b76854
tmp ds.load() step for v4.0
j-atkins May 29, 2026
27c36e2
Merge branch 'main' into migrate-v4
j-atkins Jun 5, 2026
81f857f
error messaging in argo floats
j-atkins Jun 5, 2026
cd2f9ff
refactor _generate_fieldset()
j-atkins Jun 5, 2026
39e63d1
small tidy up
j-atkins Jun 5, 2026
0dc5975
from zarr -> parquet output
j-atkins Jun 5, 2026
3e52b10
endtime/time_origin API update
j-atkins Jun 5, 2026
07209bf
no temp sampling in phase 3
j-atkins Jun 5, 2026
9cb321c
endtimes access scalars directly
j-atkins Jun 5, 2026
137978d
fix wrong var name in kernel
j-atkins Jun 5, 2026
c29efe6
combined vector fields for instruments
j-atkins Jun 5, 2026
a8242c1
small tweaks/fixes
j-atkins Jun 5, 2026
95d84b7
cmo to pixi.toml and tweaked particleset API
j-atkins Jun 5, 2026
7f68c83
depth -> z for argo sampling, update interp method in base, fix ctd v…
j-atkins Jun 10, 2026
003f79f
move to positive down API throughout virtualship
j-atkins Jun 10, 2026
da86613
Revert "move to positive down API throughout virtualship"
j-atkins Jun 11, 2026
a468ade
negate *and* reindex depth in ds before fieldset creation
j-atkins Jun 11, 2026
fd2232a
fill land/nans with 0s
j-atkins Jun 11, 2026
0be180d
fixes to argo kernels + some refactoring
j-atkins Jun 12, 2026
c5b8378
Merge branch 'main' into migrate-v4
j-atkins Jun 16, 2026
1faf09d
Merge branch 'main' into migrate-v4
j-atkins Jul 13, 2026
c05d2f7
switch to using .to_windowed_arrays() for performance
j-atkins Jul 13, 2026
cff0240
neater way of negating depth
j-atkins Jul 13, 2026
d9df5b6
use interpolator object
j-atkins Jul 13, 2026
f89ce73
lat/lon/time -> y/x/t
j-atkins Jul 14, 2026
d8bd074
performance step: windowed arrays
j-atkins Jul 14, 2026
ca3ba1f
time -> t
j-atkins Jul 14, 2026
c740630
fix argo bug, min_depth spec to avoid immediately error out of bounds
j-atkins Jul 16, 2026
abb5380
remove ds.load/to_windowed_arrays experiments
j-atkins Jul 17, 2026
583fc7b
lon -> x
j-atkins Jul 20, 2026
f2becc0
use vertical_axis arg for depth convention
j-atkins Jul 20, 2026
ae98059
Refactor dataset retrieval specs (#359)
j-atkins Jul 22, 2026
391b930
add tmp file write/read step (#358)
j-atkins Jul 23, 2026
1ca6860
`to_windowed_arrays` only for non-underway instruments (#361)
j-atkins Jul 27, 2026
4dfa96f
Update underway (#360)
j-atkins Jul 31, 2026
ebd2c0c
Add initial Field sampling for non-underway instruments (#364)
j-atkins Aug 3, 2026
ac43f15
remove .fillna() now that it's in Parcels internals
j-atkins Aug 3, 2026
1e08b96
via_tmp_ds is only needed when streaming data
j-atkins Aug 4, 2026
c202418
FetchSpec takes the VirtualShip-native negative depth values
j-atkins Aug 4, 2026
9057312
bring --from-data methods up to date with new v4 workflows, also incl…
j-atkins Aug 4, 2026
d68aa09
update test_utils for new v4 workflows
j-atkins Aug 4, 2026
707328d
update test_expedition for v4 fieldset ingestion
j-atkins Aug 4, 2026
56bf4df
use context managers for _via_tmp_ds, fix using abs() for copernicusm…
j-atkins Aug 4, 2026
8954456
avoid annoying warnings concerning potential pixi build issues and co…
j-atkins Aug 4, 2026
acca7e3
revert need to specify h5netcdf engine
j-atkins Aug 4, 2026
fefcc20
update test_base.py for new v4 workflows
j-atkins Aug 4, 2026
de8c627
update tests for v4 workflows and enhance out of bounds error checking
j-atkins Aug 5, 2026
f17b43e
update tests for v4 workflows
j-atkins Aug 5, 2026
52ff1fd
update drifter tests for v4 workflows
j-atkins Aug 5, 2026
90bef40
fix bugs in xbt behavious, also update particleset inspection for v4
j-atkins Aug 5, 2026
12d87f6
update xbt tests for v4 workflows
j-atkins Aug 5, 2026
4e075a2
attempt to fix pixi env and CI
j-atkins Aug 5, 2026
dc783c0
update pixi and CI to python 3.14
j-atkins Aug 6, 2026
1d3955a
update contribution guide for pixi instructions
j-atkins Aug 6, 2026
616f5d8
fix pre-commit issue
j-atkins Aug 6, 2026
9a53435
sort out encoding to avoid UserWarnings
j-atkins Aug 6, 2026
f37ce1d
use fieldset.time_interval.left notation
j-atkins Aug 6, 2026
b3fe2de
add _handle_grounding test
j-atkins Aug 6, 2026
9601783
Merge branch 'main' into migrate-v4
j-atkins Aug 6, 2026
84db2eb
remove docstring for consistency
j-atkins Aug 7, 2026
fb789fc
filter out parcels v4 user warning for (temporary measure)
j-atkins Aug 7, 2026
d95cc5e
Merge branch 'migrate-v4' of github.com:OceanParcels/virtualship into…
j-atkins Aug 7, 2026
495c143
remove initial sampling from argo floats
j-atkins Aug 10, 2026
26d6681
argo kernel: solve issue of not updating cycle_age during ascent
j-atkins Aug 10, 2026
bc7a6b6
add state_date argument to init command
j-atkins Aug 11, 2026
9c14261
handle timedeltas from mfp export, .xlsx only now that MFP export is …
j-atkins Aug 11, 2026
2c303d1
update click.echo(); no time instructions required
j-atkins Aug 11, 2026
ff2f8f8
add waypoint numbers (comments) to expedition.yaml
j-atkins Aug 11, 2026
bf3a442
add unit test: waypoint field in yaml always starts with "- instrument"
j-atkins Aug 11, 2026
a1d0b5e
new Port class
j-atkins Aug 12, 2026
e40defd
Update docs for v4 migration (#371)
j-atkins Aug 12, 2026
f6bca0f
ingest and validate mfp exports with/without (or mixture of) ports
j-atkins Aug 12, 2026
e91686b
update static expedition.yaml with ports API
j-atkins Aug 12, 2026
03da307
fix timings in static expedition.yaml
j-atkins Aug 12, 2026
8cf91de
move `virtualship init` logic to new initialise.py module
j-atkins Aug 12, 2026
46e7828
update init docstring
j-atkins Aug 12, 2026
02a1dd7
refactor _initialise.py
j-atkins Aug 12, 2026
2f5b5e1
rename method and small update
j-atkins Aug 14, 2026
ed07b42
add Port logic to problems simulator
j-atkins Aug 14, 2026
d756cbe
`add_field` removed from Parcels API
j-atkins Aug 27, 2026
81f7459
update tests for new parcels API
j-atkins Aug 27, 2026
a77c59e
add state_date argument to init command
j-atkins Aug 11, 2026
d8b5611
handle timedeltas from mfp export, .xlsx only now that MFP export is …
j-atkins Aug 11, 2026
b863760
update click.echo(); no time instructions required
j-atkins Aug 11, 2026
4faa24a
add waypoint numbers (comments) to expedition.yaml
j-atkins Aug 11, 2026
1119153
add unit test: waypoint field in yaml always starts with "- instrument"
j-atkins Aug 11, 2026
6a460a4
new Port class
j-atkins Aug 12, 2026
585d54c
ingest and validate mfp exports with/without (or mixture of) ports
j-atkins Aug 12, 2026
03989fb
update static expedition.yaml with ports API
j-atkins Aug 12, 2026
804f909
fix timings in static expedition.yaml
j-atkins Aug 12, 2026
b2c43dc
move `virtualship init` logic to new initialise.py module
j-atkins Aug 12, 2026
a8d647d
update init docstring
j-atkins Aug 12, 2026
33d11a9
refactor _initialise.py
j-atkins Aug 12, 2026
373f4c5
rename method and small update
j-atkins Aug 14, 2026
2042b4d
add Port logic to problems simulator
j-atkins Aug 14, 2026
fe0c5a3
Merge branch 'cruise-data' of github.com:OceanParcels/virtualship int…
j-atkins Aug 27, 2026
f7ac2aa
remove tmp dev step
j-atkins Aug 27, 2026
3ac7383
Merge branch 'migrate-v4' into cruise-data
j-atkins Aug 28, 2026
60a58b4
move _get_example_expedition back to utils
j-atkins Aug 31, 2026
7946f7b
fix datetime handling
j-atkins Aug 31, 2026
92c5335
move mfp_to_yaml test to test_initialise
j-atkins Aug 31, 2026
a7b55e2
fix bug: UV vector field remains even after adding bathymetry
j-atkins Sep 1, 2026
8bfe86b
fix: high RAM spikes with argo fieldsets
j-atkins Sep 2, 2026
0df9d99
move to using context manager protocol for instrument simulations, en…
j-atkins Sep 2, 2026
7f31c9c
additional optimisation: ignore waypoints irrelevant to instrument type
j-atkins Sep 2, 2026
0266907
further improve performance by writing to tmp zarr store
j-atkins Sep 2, 2026
4fe2a8d
further optimisation
j-atkins Sep 2, 2026
11e930b
improved spinner handling: use spinner before progress bar appears
j-atkins Sep 2, 2026
a36bd84
refactor spinner handling: move to utils, restore progress bar format…
j-atkins Sep 2, 2026
c43e86a
add context management to dummyinstrument
j-atkins Sep 2, 2026
397a286
reduce argo spatial halo further
j-atkins Sep 3, 2026
a0d4e9d
refactor and add test for auto-build UV vector field
j-atkins Sep 3, 2026
834dd4e
add test that no initial sampling for argo float
j-atkins Sep 3, 2026
a7dbc97
add todo to track potential speed vs. memory trade-off
j-atkins Sep 3, 2026
538e201
remove duplicate cycle age update
j-atkins Sep 4, 2026
ea14cd6
clean up: unnecessary or old/stale code
j-atkins Sep 4, 2026
0a20306
Merge branch 'migrate-v4' into cruise-data
j-atkins Sep 7, 2026
7947ecd
using waypoint index for in-port problems; use public facing wp numbe…
j-atkins Sep 7, 2026
9f63dc3
first steps update plan tool to take port waypoints
j-atkins Sep 7, 2026
4c1b9e6
add/move some structural schedule verfication checks to pydantic fiel…
j-atkins Sep 8, 2026
3aa1206
add fieldsets rather than building list
j-atkins Sep 8, 2026
3ee8d04
fieldset[particles.x, particles.y, ...] notation -> fieldset[particles]
j-atkins Sep 8, 2026
0e8cd88
np.where() -> boolean indexing for all kernels
j-atkins Sep 8, 2026
dc8874f
remove unnecessary np.float
j-atkins Sep 8, 2026
c930123
bump pixi up from old pinnings
j-atkins Sep 8, 2026
0160d44
prevent ctd from going out of bounds by exceeding max/min depths; upd…
j-atkins Sep 8, 2026
a7ce809
use raw datetime objects in psets
j-atkins Sep 8, 2026
dfddaa0
Merge branch 'main' into migrate-v4
j-atkins Sep 8, 2026
adabe91
fix bug when using local data: handle both depth_min and depth_max be…
j-atkins Sep 9, 2026
501410d
remove broken/unnecessary warning filter
j-atkins Sep 9, 2026
3776a6e
remove tmp dev spinner skipping
j-atkins Sep 9, 2026
de539cc
Merge branch 'migrate-v4' into cruise-data
j-atkins Sep 14, 2026
c8cbd47
Merge branch 'main' into cruise-data
j-atkins Sep 14, 2026
278e125
plan tool fixes: ignore wp instruments when Port
j-atkins Sep 14, 2026
8dcf8f9
re-implement ADCP help button
j-atkins Sep 14, 2026
78108b5
Merge branch 'main' into cruise-data
j-atkins Sep 16, 2026
3536107
adapt expedition/schedule model to use ports properly
j-atkins Sep 16, 2026
30999d7
more informative error messaging
j-atkins Sep 16, 2026
059086a
remove duplicate/redundant schedule verification checks
j-atkins Sep 16, 2026
13a315d
filter out placeholder Port wps if necessary; add _verified internal …
j-atkins Sep 16, 2026
fe4f23e
filter out placeholder wps from Instrument object if necessary
j-atkins Sep 16, 2026
de66fc0
tidy up base.py with SpatialBounds object for fieldset bounding box
j-atkins Sep 16, 2026
646f732
add tests for spatial bounds and refactor to use waypoints and expedi…
j-atkins Sep 16, 2026
8b139c0
sort out test_expedition to handle new Port logic
j-atkins Sep 16, 2026
7b158cd
tidy up unnecessary lines
j-atkins Sep 16, 2026
53bc885
more robust validation of ports
j-atkins Sep 16, 2026
602a2c0
test suite expedition.yaml updated for new port logic
j-atkins Sep 16, 2026
72ce6d0
new test for filtering out placeholder ports
j-atkins Sep 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
260 changes: 260 additions & 0 deletions src/virtualship/cli/_initialise.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
import os
import re
import warnings
from datetime import timedelta
from pathlib import Path

import click
import pandas as pd
import yaml

from virtualship.models import (
Expedition,
InstrumentsConfig,
Location,
Port,
Schedule,
Waypoint,
)
from virtualship.utils import EXPEDITION, _get_example_expedition

ERR_SUPPLEMENT = "If the MFP export format has changed, please submit an issue at: https://github.com/Parcels-code/virtualship/issues."


def _initialise(
path: str | Path, from_mfp: str | None = None, start_date: str | None = None
):
path = Path(path)
path.mkdir(exist_ok=True)

expedition = path / EXPEDITION

if expedition.exists():
raise FileExistsError(
f"File '{expedition}' already exists. Please remove it or choose another directory."
)

if from_mfp:
mfp_file = Path(from_mfp)
click.echo(f"Generating schedule from {mfp_file}...")

# catch warnings raised to propagate them via click.echo
with warnings.catch_warnings(record=True) as captured_warnings:
warnings.simplefilter("always")
_mfp_to_yaml(mfp_file, start_date, expedition)

indent = " " * 4
click.echo(
"\n⚠️ The generated schedule does not contain INSTRUMENT selections. ⚠️"
"\n\nNow please either use the `\033[4mvirtualship plan\033[0m` app to complete the configuration, "
"\nOR edit 'expedition.yaml' and manually add the instrument selections under the 'schedule' heading."
"\n\nIf editing 'expedition.yaml' manually:"
"\n\n🌡️ Expected instrument(s) format: one line per instrument e.g."
f"\n\n{indent * 4}waypoints:\n{indent * 4}- instrument:\n{indent * 5}- CTD\n{indent * 5}- ARGO_FLOAT\n"
)

# output captured warnings to the terminal
if captured_warnings:
click.echo("\n❗️ WARNINGS:")
for w in captured_warnings:
click.echo(f"{indent}• {w.message}")
click.echo(
f"\n{indent}If you believe any of these warnings are incorrect (e.g. you have selected departure/arrival ports), and {ERR_SUPPLEMENT.replace('If ', '')}\n"
)
else:
expedition.write_text(_get_example_expedition())

click.echo(f"Created '{expedition.name}' at {path}.")


def _mfp_to_yaml(file_path: Path, start_date: str, output_path: Path):
"""Generates an expedition.yaml file from MFP Excel export."""
mfp_data = _validate_mfp_data(file_path)

# convert start_date string to datetime object if needed, ensuring it's standard Python datetime
if isinstance(start_date, str):
current_time = pd.to_datetime(start_date).to_pydatetime()
elif isinstance(start_date, pd.Timestamp):
current_time = start_date.to_pydatetime()
else:
current_time = start_date

waypoints = []
previous_timedelta = None

for i, row in mfp_data.iterrows():
if i > 0:
current_time += previous_timedelta

is_port = "Port" in str(row["Station"]) or "Port" in str(row["Type"])
lat = None if pd.isna(row["Latitude"]) else float(row["Latitude"])
lon = None if pd.isna(row["Longitude"]) else float(row["Longitude"])
loc = Location(latitude=lat, longitude=lon)

# Ensure timestamp passed is a native python datetime (or string) to prevent PyYAML pandas pickle tags
time_val = (
current_time.to_pydatetime()
if isinstance(current_time, pd.Timestamp)
else current_time
)

if is_port:
has_latlon = lat is not None and lon is not None
waypoints.append(Port(location=loc, time=time_val if has_latlon else None))
else:
waypoints.append(Waypoint(instrument=None, location=loc, time=time_val))

previous_timedelta = (
row["Total Time"] if pd.notna(row["Total Time"]) else timedelta(0)
)

# build and dump expedition YAML
static_yaml = yaml.safe_load(_get_example_expedition())
expedition = Expedition(
schedule=Schedule(waypoints=waypoints),
instruments_config=InstrumentsConfig.model_validate(
static_yaml.get("instruments_config")
),
ship_config=static_yaml.get("ship_config"),
)
expedition.to_yaml(output_path)


def _validate_mfp_data(file_path: Path) -> pd.DataFrame:
"""Load and validate MFP CruiseData export."""
mfp_data = _load_mfp_export(file_path)

# clean up column names
mfp_data.columns = mfp_data.columns.astype(str).str.strip()
junk_col_pattern = r"^(Unnamed:.*||\.\d+)$"
mfp_data = mfp_data.loc[:, ~mfp_data.columns.str.match(junk_col_pattern)]

expected_columns = [
"Station",
"Type",
"Latitude",
"Longitude",
"Sea Depth",
"Time at Station",
"Travel Time to Next",
"Distance to Next (NM)",
"Ship Speed (kn)",
"EEZ",
]
expected_set = set(expected_columns)
actual_set = set(mfp_data.columns)

missing_columns = expected_set - actual_set
if missing_columns:
raise ValueError(
f"Error: Found columns {list(actual_set)}, but expected columns {list(expected_columns)}. "
f"Are you sure that you're using the correct export from MFP?\n\n{ERR_SUPPLEMENT}"
)

extra_columns = actual_set - expected_set
if extra_columns:
warnings.warn(
f"Found additional unexpected columns {list(extra_columns)}. Manually added columns have no effect.",
stacklevel=2,
)

# safe float conversion for lat/lon
for coord in ["Latitude", "Longitude"]:
if mfp_data[coord].dtype in ["object", "string"]:
mfp_data[coord] = pd.to_numeric(
mfp_data[coord].astype(str).str.replace(",", "."), errors="coerce"
)

# check for missing departure/arrival ports and add placeholders if necessary
# check against both 'Station' and 'Type' columns; variations can occur when importing to MFP before re-exporting
has_departure = (
"Departure Port" in mfp_data["Station"].values
or "Departure Port" in mfp_data["Type"].values
)
has_arrival = (
"Arrival Port" in mfp_data["Station"].values
or "Arrival Port" in mfp_data["Type"].values
)

if not has_departure or not has_arrival:
warnings.warn(
"The MFP export is missing either a 'Departure Port' or 'Arrival Port', or both. "
"Any missing port will be replaced with an empty placeholder in `expedition.yaml` but will be ignored in the simulation. "
"If missing the 'Departure Port', the prescribed start date will be used for Waypoint #1 instead. ",
stacklevel=2,
)

if not has_departure:
dept_row = _create_port_row(expected_columns, "Departure Port")
mfp_data = pd.concat([dept_row, mfp_data], ignore_index=True) # first row

if not has_arrival:
arr_row = _create_port_row(expected_columns, "Arrival Port")
mfp_data = pd.concat([mfp_data, arr_row], ignore_index=True) # last row

# Drop unexpected columns
mfp_data = mfp_data[list(expected_columns)]

# convert 'Travel Time to Next' and 'Time at Station' to timedelta
mfp_data["Travel Time to Next"] = mfp_data["Travel Time to Next"].apply(
_mfp_string_to_timedelta
)
mfp_data["Time at Station"] = mfp_data["Time at Station"].apply(
_mfp_string_to_timedelta
)

# combine 'Travel Time to Next' and 'Time at Station' into a single 'Total Time' column
# add 0 when Time at Station is NaN, to avoid NaT in Total Time, but not to Travel Time to keep NaT at the arrival port
mfp_data["Total Time"] = mfp_data["Travel Time to Next"] + mfp_data[
"Time at Station"
].fillna(pd.Timedelta(0))

return mfp_data


def _load_mfp_export(file_path: Path) -> pd.DataFrame:
if not os.path.isfile(file_path):
raise FileNotFoundError(f"File not found: {file_path}")

try:
return pd.read_excel(file_path).dropna(how="all", axis=1) # drop empty columns
except Exception as e:
raise RuntimeError(
"Could not read coordinates data from the provided file. "
"Ensure it is an exported .xlsx file from MFP."
) from e


def _create_port_row(columns, port_type: str) -> pd.DataFrame:
"""Generate a single placeholder row for missing departure/arrival ports."""
row = {col: None for col in columns}
row["Station"] = port_type
row["Type"] = port_type
return pd.DataFrame([row])


def _mfp_string_to_timedelta(value: str | None) -> timedelta | None:
"""Parse MFP duration string (e.g., '0d 13h 13m') to timedelta."""
if pd.isna(value):
return None

match = re.search(r"(\d+)d\s*(\d+)h\s*(\d+)m", str(value))
if match:
days, hours, minutes = map(int, match.groups())
return timedelta(days=days, hours=hours, minutes=minutes)

else:
raise ValueError(
f"Invalid MFP duration format: '{value}'. Expected format: 'Xd Yh Zm' (e.g., '0d 13h 13m'). {ERR_SUPPLEMENT}"
)


def _validate_start_date(ctx, param, value):
"""Enforce --start-date when --from-mfp is used."""
if ctx.params.get("from_mfp"):
if not value:
raise click.BadParameter(
"The '--start-date' option is required when using '--from-mfp'."
"\n\nExpected format: 'YYYY-MM-DD HH:MM:SS' (with quotes, e.g., '2023-10-20 01:00:00'). If only the date is provided, the time will default to 00:00:00."
)
return value
Loading
Loading