Skip to content

Commit f5f36bb

Browse files
committed
Handle timezones in started times
1 parent eef25d3 commit f5f36bb

File tree

4 files changed

+60
-22
lines changed

4 files changed

+60
-22
lines changed

nextflow/command.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ def _run(
9090
while True:
9191
time.sleep(sleep)
9292
execution, diff = get_execution(
93-
output_path, log_path, nextflow_command, execution, log_start
93+
output_path, log_path, nextflow_command, execution, log_start, timezone
9494
)
9595
log_start += diff
9696
if execution and poll: yield execution
@@ -251,7 +251,7 @@ def wait_for_log_creation(output_path, start):
251251
time.sleep(0.1)
252252

253253

254-
def get_execution(execution_path, log_path, nextflow_command, execution=None, log_start=0):
254+
def get_execution(execution_path, log_path, nextflow_command, execution=None, log_start=0, timezone=None):
255255
"""Creates an execution object from a location. If you are polling, you can
256256
pass in the previous execution to update it with new information.
257257
@@ -274,7 +274,7 @@ def get_execution(execution_path, log_path, nextflow_command, execution=None, lo
274274
for process_execution in process_executions.values():
275275
if not process_execution.finished or not process_execution.started or \
276276
process_execution.identifier in changed:
277-
update_process_execution_from_path(process_execution, execution_path)
277+
update_process_execution_from_path(process_execution, execution_path, timezone)
278278
execution.process_executions = list(process_executions.values())
279279
return execution, len(log)
280280

@@ -380,7 +380,7 @@ def update_process_execution_from_line(process_executions, line):
380380
return identifier
381381

382382

383-
def update_process_execution_from_path(process_execution, execution_path):
383+
def update_process_execution_from_path(process_execution, execution_path, timezone=None):
384384
"""Some attributes of a process execution need to be obtained from files on
385385
disk. This function updates the process execution with these values.
386386
@@ -392,7 +392,7 @@ def update_process_execution_from_path(process_execution, execution_path):
392392
process_execution.stdout = get_file_text(os.path.join(full_path, ".command.out"))
393393
process_execution.stderr = get_file_text(os.path.join(full_path, ".command.err"))
394394
if not process_execution.started and not process_execution.cached:
395-
process_execution.started = get_file_creation_time(os.path.join(full_path, ".command.begin"))
395+
process_execution.started = get_file_creation_time(os.path.join(full_path, ".command.begin"), timezone)
396396
if not process_execution.bash:
397397
process_execution.bash = get_file_text(os.path.join(full_path, ".command.sh"))
398398
if process_execution.execution.finished and not process_execution.return_code:

nextflow/io.py

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os
2+
import pytz
23
import glob
34
from datetime import datetime
45

@@ -14,14 +15,19 @@ def get_file_text(path):
1415
return ""
1516

1617

17-
def get_file_creation_time(path):
18+
def get_file_creation_time(path, timezone=None):
1819
"""Gets the creation time of a file.
1920
2021
:param str path: the location of the file.
2122
:rtype: ``datetime.datetime``"""
2223

2324
try:
24-
return datetime.fromtimestamp(os.path.getctime(path))
25+
dt = datetime.fromtimestamp(os.path.getctime(path))
26+
if timezone:
27+
tz = pytz.timezone(timezone)
28+
dt = dt.astimezone(tz)
29+
dt = dt.replace(tzinfo=None)
30+
return dt
2531
except FileNotFoundError:
2632
return None
2733

@@ -43,4 +49,29 @@ def get_process_ids_to_paths(process_ids, execution_path):
4349
if sub.startswith(process_id):
4450
process_ids_to_paths[process_id] = subdirectory
4551
break
46-
return process_ids_to_paths
52+
return process_ids_to_paths
53+
54+
55+
56+
57+
class CustomIO:
58+
59+
def read(path, mode="r"):
60+
pass
61+
62+
63+
def ctime(path):
64+
pass
65+
66+
67+
def listdir(path):
68+
pass
69+
70+
71+
def abspath(path):
72+
pass
73+
74+
75+
def glob(path):
76+
pass
77+

tests/unit/test_command.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def test_can_run_with_default_values(self, mock_ex, mock_sleep, mock_run, mock_n
2020
universal_newlines=True, shell=True
2121
)
2222
mock_sleep.assert_called_with(1)
23-
mock_ex.assert_called_with(os.path.abspath("."), os.path.abspath("."), mock_nc.return_value, None, 0)
23+
mock_ex.assert_called_with(os.path.abspath("."), os.path.abspath("."), mock_nc.return_value, None, 0, None)
2424
self.assertEqual(executions, [execution])
2525

2626

@@ -46,7 +46,7 @@ def test_can_run_with_custom_values(self, mock_ex, mock_sleep, mock_wait, mock_r
4646
mock_wait.assert_called_with("/log", datetime(2025, 1, 1))
4747
mock_sleep.assert_called_with(4)
4848
self.assertEqual(mock_sleep.call_count, 3)
49-
mock_ex.assert_called_with("/out", "/log", mock_nc.return_value, mock_executions[0], 40)
49+
mock_ex.assert_called_with("/out", "/log", mock_nc.return_value, mock_executions[0], 40, "UTC")
5050
self.assertEqual(mock_ex.call_count, 3)
5151
self.assertEqual(executions, [mock_executions[1]])
5252

@@ -67,7 +67,7 @@ def test_can_run_and_poll(self, mock_ex, mock_sleep, mock_run, mock_nc):
6767
)
6868
mock_sleep.assert_called_with(1)
6969
self.assertEqual(mock_sleep.call_count, 3)
70-
mock_ex.assert_called_with("/out", "/out", mock_nc.return_value, mock_executions[0], 60)
70+
mock_ex.assert_called_with("/out", "/out", mock_nc.return_value, mock_executions[0], 60, None)
7171
self.assertEqual(mock_ex.call_count, 3)
7272
self.assertEqual(executions, mock_executions)
7373

@@ -84,7 +84,7 @@ def test_can_run_with_custom_runner(self, mock_ex, mock_sleep, mock_run, mock_nc
8484
self.assertFalse(mock_run.called)
8585
runner.assert_called_with(mock_nc.return_value)
8686
mock_sleep.assert_called_with(1)
87-
mock_ex.assert_called_with(os.path.abspath("."), os.path.abspath("."), mock_nc.return_value, None, 0)
87+
mock_ex.assert_called_with(os.path.abspath("."), os.path.abspath("."), mock_nc.return_value, None, 0, None)
8888
self.assertEqual(executions, [mock_ex.return_value[0]])
8989

9090

@@ -324,17 +324,17 @@ def test_can_get_first_execution(self, mock_update, mock_paths, mock_init, mock_
324324
"cc/dd": "/ex/cc/dd",
325325
"gg/hh": "/ex/gg/hh",
326326
}
327-
execution, size = get_execution("/ex", "/log", "nf run")
327+
execution, size = get_execution("/ex", "/log", "nf run", timezone="UTC")
328328
self.assertEqual(execution, mock_execution)
329329
self.assertEqual(size, 3)
330330
mock_text.assert_called_with(os.path.join("/log", ".nextflow.log"))
331331
mock_make.assert_called_with("LOG", "/ex", "nf run", None)
332332
mock_init.assert_called_with("LOG", mock_execution)
333333
mock_paths.assert_called_with(["cc/dd","gg/hh"], "/ex")
334334
self.assertEqual([c[0] for c in mock_update.call_args_list], [
335-
(process_executions["aa/bb"], "/ex"),
336-
(process_executions["ee/ff"], "/ex"),
337-
(process_executions["gg/hh"], "/ex"),
335+
(process_executions["aa/bb"], "/ex", "UTC"),
336+
(process_executions["ee/ff"], "/ex", "UTC"),
337+
(process_executions["gg/hh"], "/ex", "UTC"),
338338
])
339339
self.assertEqual(execution.process_executions, [
340340
process_executions["aa/bb"],
@@ -364,17 +364,17 @@ def test_can_get_subsequent_execution(self, mock_update, mock_paths, mock_init,
364364
"cc/dd": "/ex/cc/dd",
365365
"gg/hh": "/ex/gg/hh",
366366
}
367-
execution, size = get_execution("/ex", "/log", "nf run", mock_execution, 4)
367+
execution, size = get_execution("/ex", "/log", "nf run", mock_execution, 4, "UTC")
368368
self.assertEqual(execution, mock_execution)
369369
self.assertEqual(size, 3)
370370
mock_text.assert_called_with(os.path.join("/log", ".nextflow.log"))
371371
mock_make.assert_called_with("LOG", "/ex", "nf run", mock_execution)
372372
mock_init.assert_called_with("LOG", mock_execution)
373373
mock_paths.assert_called_with(["cc/dd","gg/hh"], "/ex")
374374
self.assertEqual([c[0] for c in mock_update.call_args_list], [
375-
(process_executions["aa/bb"], "/ex"),
376-
(process_executions["ee/ff"], "/ex"),
377-
(process_executions["gg/hh"], "/ex"),
375+
(process_executions["aa/bb"], "/ex", "UTC"),
376+
(process_executions["ee/ff"], "/ex", "UTC"),
377+
(process_executions["gg/hh"], "/ex", "UTC"),
378378
])
379379
self.assertEqual(execution.process_executions, [
380380
process_executions["aa/bb"],
@@ -633,7 +633,7 @@ def test_can_update_values(self, mock_text):
633633
def test_can_update_values_with_started(self, mock_time, mock_text):
634634
proc_ex = Mock(stdout=".", stderr=".", bash=".", finished=None, return_code="", path="aa/bb", started=None, execution=Mock(finished=None), cached=False)
635635
mock_text.side_effect = ["ok", "bad"]
636-
update_process_execution_from_path(proc_ex, "/ex")
636+
update_process_execution_from_path(proc_ex, "/ex", timezone="UTC")
637637
self.assertEqual(proc_ex.stdout, "ok")
638638
self.assertEqual(proc_ex.stderr, "bad")
639639
self.assertEqual(proc_ex.bash, ".")
@@ -643,7 +643,7 @@ def test_can_update_values_with_started(self, mock_time, mock_text):
643643
call(os.path.join("/ex", "work", "aa/bb", ".command.out"),),
644644
call(os.path.join("/ex", "work", "aa/bb", ".command.err"),),
645645
])
646-
mock_time.assert_called_with(os.path.join("/ex", "work", "aa/bb", ".command.begin"))
646+
mock_time.assert_called_with(os.path.join("/ex", "work", "aa/bb", ".command.begin"), "UTC")
647647

648648

649649
@patch("nextflow.command.get_file_text")

tests/unit/test_io.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ def test_can_get_creation_time(self, mock_getctime):
2828
mock_getctime.assert_called_with("/ex/file.txt")
2929

3030

31+
@patch("os.path.getctime")
32+
def test_can_get_creation_time_with_timezone(self, mock_getctime):
33+
mock_getctime.return_value = 123456
34+
self.assertEqual(get_file_creation_time("/ex/file.txt", timezone="America/New_York"), datetime.fromtimestamp(123456 - 6 * 60 * 60))
35+
mock_getctime.assert_called_with("/ex/file.txt")
36+
37+
3138
@patch("os.path.getctime")
3239
def test_can_handle_no_file(self, mock_getctime):
3340
mock_getctime.side_effect = FileNotFoundError

0 commit comments

Comments
 (0)