Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#119: fix clock-triggering and restore local time usage #954

Merged
merged 12 commits into from
May 22, 2014
Merged
16 changes: 10 additions & 6 deletions doc/suiterc.tex
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,10 @@ \subsection{[cylc]}
extra number of year digits and a sign should be used. This extra number needs
to be written down somewhere (here).

For example, if this extra number is set to 2, the 1st of January in the year
10040 will be represented as $+0100400101T0000$ (2 extra year digits used).
06Z on the 4th of May 1985 would be written as $+00019850504T0600$ with this
number set to 3.
For example, if this extra number is set to 2, 00Z on the 1st of January in
the year 10040 will be represented as $+0100400101T0000Z$ (2 extra year digits
used). With this number set to 3, 06Z on the 4th of May 1985 would be written
as $+00019850504T0600Z$.

This number defaults to 0 (no sign or extra digits used).

Expand All @@ -120,14 +120,18 @@ \subsection{[cylc]}

If you set UTC mode to True (\ref{utc-mode}) then this will default to $Z$.
If you use a custom cycle point format (\ref{cycle-point-format}), you should
specify the timezone choice (or null timezone choice) there.
specify the timezone choice (or null timezone choice) here as well.

Otherwise, you may set your own time zone choice here, which will be used
You may set your own time zone choice here, which will be used
for all date/time cycle point dumping. Time zones should be expressed as ISO
8601 time zone offsets from UTC, such as $+13$, $+1300$, $-0500$ or $+0645$,
with $Z$ representing the special $+0000$ case. Cycle points will be converted
to the time zone you give and will be represented with this string at the end.

Cycle points that are input without time zones (e.g. as an initial cycle time
setting) will use this time zone if set. If this isn't set (and UTC mode is
also not set), then they will default to the current local time zone.

Note that the ISO standard also allows writing the hour and minute separated
by a ":" (e.g. $+13:00$) - however, this is not recommended, given that the
time zone is used as part of task output filenames.
Expand Down
2 changes: 2 additions & 0 deletions examples/tutorial/cycling/five/suite.rc
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
title = "Inter-cycle dependence + a cold-start task"
[cylc]
UTC mode = True
[scheduling]
#runahead limit = 120
initial cycle time = 20130808T00
Expand Down
2 changes: 2 additions & 0 deletions examples/tutorial/cycling/four/suite.rc
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
title = "Inter-cycle dependence + a start-up task"
[cylc]
UTC mode = True
[scheduling]
#runahead limit = 120
initial cycle time = 20130808T00
Expand Down
4 changes: 3 additions & 1 deletion examples/tutorial/cycling/one/suite.rc
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
title = "Two cycling tasks, no inter-cycle dependence"
[cylc]
UTC mode = True
[scheduling]
initial cycle time = 20130808T00
final cycle time = 20130812T00
[[dependencies]]
[[[T00,T12]]] # 00 and 12 hours every day
[[[T00,T12]]] # 00 and 12 hours UTC every day
graph = "foo => bar"
[visualization]
initial cycle time = 20130808T00
Expand Down
2 changes: 2 additions & 0 deletions examples/tutorial/cycling/three/suite.rc
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
title = "Inter-cycle dependence + a cold-start task"
[cylc]
cycle point time zone = +13
[scheduling]
initial cycle time = 20130808T00
final cycle time = 20130812T00
Expand Down
2 changes: 2 additions & 0 deletions examples/tutorial/cycling/two/suite.rc
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@

title = "Two cycling tasks with inter-cycle dependence"
[cylc]
cycle point time zone = Z
[scheduling]
#runahead limit = 120
initial cycle time = 20130808T00
Expand Down
37 changes: 23 additions & 14 deletions lib/cylc/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -1153,6 +1153,8 @@ def process_graph_line( self, line, section, ttype, seq,
else:
r = rt

pruned_lnames = list(lnames) # Create copy of LHS tasks.

asyncid_pattern = None
if ttype != 'cycling':
for n in lnames + [r]:
Expand All @@ -1171,24 +1173,28 @@ def process_graph_line( self, line, section, ttype, seq,
asyncid_pattern = m.groups()[0]

if ttype == 'cycling':
for n in list(lnames):
for n in lnames:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

n should be lname.

try:
name = graphnode(
n, base_interval=seq.get_interval()).name
node = graphnode(
n, base_interval=seq.get_interval())
except GraphNodeError, x:
print >> sys.stderr, orig_line
raise SuiteConfigError, str(x)
raise SuiteConfigError, str(x)
name = node.name
output = node.output
if name in tasks_to_prune or return_all_dependencies:
special_dependencies.append((name, r))
special_dependencies.append((name, output, r))
if name in tasks_to_prune:
lnames.remove(n)
pruned_lnames.remove(n)

if not self.validation and not graphing_disabled:
# edges not needed for validation
self.generate_edges( lexpression, lnames, r, ttype, seq, suicide )
self.generate_taskdefs( orig_line, lnames, r, ttype, section,
asyncid_pattern, seq.get_interval() )
self.generate_triggers( lexpression, lnames, r, seq,
self.generate_edges( lexpression, pruned_lnames, r, ttype,
seq, suicide )
self.generate_taskdefs( orig_line, pruned_lnames, r, ttype,
section, asyncid_pattern,
seq.get_interval() )
self.generate_triggers( lexpression, pruned_lnames, r, seq,
asyncid_pattern, suicide )
return special_dependencies

Expand Down Expand Up @@ -1524,7 +1530,7 @@ def close_families( self, nlid, nrid ):
if lname in members[fam] and rname in members[fam]:
# l and r are both members of fam
#nl, nr = None, None # this makes 'the graph disappear if grouping 'root'
nl,nr = TaskID.get(fam,tag), TaskID.get(fam,rtag)
nl,nr = TaskID.get(fam,ltag), TaskID.get(fam,rtag)
break
elif lname in members[fam]:
# l is a member of fam
Expand Down Expand Up @@ -1552,7 +1558,7 @@ def load_graph( self ):
section, async_graph,
return_all_dependencies=True
)
for left, right in async_dependencies:
for left, left_output, right in async_dependencies:
if left:
initial_tasks.append(left)
if right:
Expand Down Expand Up @@ -1603,8 +1609,11 @@ def load_graph( self ):
get_point(self.cfg['scheduling']['initial cycle time'])
)
graph_text = ""
for left, right in special_dependencies:
graph_text += left + "[] => " + right + "\n"
for left, left_output, right in special_dependencies:
graph_text += left + "[]"
if left_output:
graph_text += ":" + left_output
graph_text += " => " + right + "\n"
if (left in start_up_tasks and
left not in start_up_tasks_graphed):
# Start-up tasks need their own explicit section.
Expand Down
14 changes: 14 additions & 0 deletions lib/cylc/cycling/integer.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,20 @@ def get_prev_point( self, p ):
p_prev = p - self.i_step
return self._get_point_in_bounds( p_prev )

def get_nearest_prev_point(self, p):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p not a good name.

"""Return the largest point < some arbitrary point p."""
if self.is_on_sequence(p):
return self.get_prev_point(p)
point = self._get_point_in_bounds( self.p_start )
prev_point = None
while point is not None:
if point > p:
# Technically, >=, but we already test for this above.
break
prev_point = point
point = self.get_next_point(point)
return prev_point

def get_next_point( self, p ):
"""Return the next point > p, or None if out of bounds."""
if not self.i_step:
Expand Down
44 changes: 39 additions & 5 deletions lib/cylc/cycling/iso8601.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@

import re
from isodatetime.data import TimeInterval
from isodatetime.dumpers import TimePointDumper
from isodatetime.parsers import TimePointParser, TimeIntervalParser
from isodatetime.timezone import (
get_local_time_zone, get_local_time_zone_format)
from cylc.time_parser import CylcTimeParser
from cylc.cycling import PointBase, IntervalBase
from parsec.validate import IllegalValueError
Expand Down Expand Up @@ -51,6 +54,7 @@
point_parser = None
NUM_EXPANDED_YEAR_DIGITS = None
DUMP_FORMAT = None
ASSUMED_TIME_ZONE = None


def memoize(function):
Expand Down Expand Up @@ -276,7 +280,8 @@ def __init__(self, dep_section, context_start_point=None,
self.context_start_point, self.context_end_point,
num_expanded_year_digits=NUM_EXPANDED_YEAR_DIGITS,
dump_format=DUMP_FORMAT,
custom_point_parse_function=self.custom_point_parse_function
custom_point_parse_function=self.custom_point_parse_function,
assumed_time_zone=ASSUMED_TIME_ZONE
)
self.recurrence = self.time_parser.parse_recurrence(i)
self.step = ISO8601Interval(str(self.recurrence.interval))
Expand All @@ -298,7 +303,8 @@ def set_offset(self, i):
str(end_point),
num_expanded_year_digits=NUM_EXPANDED_YEAR_DIGITS,
dump_format=DUMP_FORMAT,
custom_point_parse_function=self.custom_point_parse_function
custom_point_parse_function=self.custom_point_parse_function,
assumed_time_zone=ASSUMED_TIME_ZONE
)
self.recurrence = self.time_parser.parse_recurrence(self.spec)
self.value = str(self.recurrence)
Expand All @@ -320,6 +326,21 @@ def get_prev_point(self, p):
res = ISO8601Point(str(prv))
return res

def get_nearest_prev_point(self, p):
"""Return the largest point < some arbitrary point p."""
if self.is_on_sequence(p):
return self.get_prev_point(p)
p_iso_point = point_parse(p.value)
prev_iso_point = None
for recurrence_iso_point in self.recurrence:
if recurrence_iso_point > p_iso_point:
# Technically, >=, but we already test for this above.
break
prev_iso_point = recurrence_iso_point
if prev_iso_point is None:
return None
return ISO8601Point(str(prev_iso_point))

def get_next_point(self, p):
"""Return the next point > p, or None if out of bounds."""
p_iso_point = point_parse(p.value)
Expand Down Expand Up @@ -411,6 +432,7 @@ def init_from_cfg(cfg):
custom_dump_format = cfg['cylc']['cycle point format']
initial_cycle_time = cfg['scheduling']['initial cycle time']
final_cycle_time = cfg['scheduling']['final cycle time']
assume_utc = cfg['cylc']['UTC mode']
test_cycle_time = initial_cycle_time
if initial_cycle_time is None:
test_cycle_time = final_cycle_time
Expand All @@ -433,17 +455,28 @@ def init_from_cfg(cfg):
init(
num_expanded_year_digits=num_expanded_year_digits,
custom_dump_format=custom_dump_format,
time_zone=time_zone
time_zone=time_zone,
assume_utc=assume_utc
)


def init(num_expanded_year_digits=0, custom_dump_format=None, time_zone=None):
def init(num_expanded_year_digits=0, custom_dump_format=None, time_zone=None,
assume_utc=False):
"""Initialise global variables (yuk)."""
global point_parser
global DUMP_FORMAT
global NUM_EXPANDED_YEAR_DIGITS
global ASSUMED_TIME_ZONE
if time_zone is None:
time_zone = "Z"
if assume_utc:
time_zone = "Z"
time_zone_hours_minutes = (0, 0)
else:
time_zone = get_local_time_zone_format(reduced_mode=True)
time_zone_hours_minutes = get_local_time_zone()
else:
time_zone_hours_minutes = TimePointDumper().get_time_zone(time_zone)
ASSUMED_TIME_ZONE = time_zone_hours_minutes
NUM_EXPANDED_YEAR_DIGITS = num_expanded_year_digits
if custom_dump_format is None:
if num_expanded_year_digits > 0:
Expand All @@ -464,6 +497,7 @@ def init(num_expanded_year_digits=0, custom_dump_format=None, time_zone=None):
allow_truncated=True,
num_expanded_year_digits=NUM_EXPANDED_YEAR_DIGITS,
dump_format=DUMP_FORMAT,
assumed_time_zone=time_zone_hours_minutes
)


Expand Down
2 changes: 1 addition & 1 deletion lib/cylc/task_pool.py
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,7 @@ def set_stop_tag( self, stop_tag ):
for itask in self.get_tasks():
# check cycle stop or hold conditions
if (self.stop_tag and itask.c_time > self.stop_tag and
itask.state.is_currently('waiting')):
itask.state.is_currently('waiting', 'queued')):
itask.log( 'WARNING',
"not running (beyond suite stop cycle) " +
str(self.stop_tag) )
Expand Down
10 changes: 9 additions & 1 deletion lib/cylc/task_types/clocktriggered.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@

import sys
import cylc.cycling.iso8601
from isodatetime.timezone import get_local_time_zone
from task import task
import time
from cylc.wallclock import now
from cylc.flags import utc

# TODO - the task base class now has clock-triggering functionality too, to
# handle retry delays, so this class could probably disappear now to leave
Expand All @@ -42,6 +43,13 @@ def start_time_reached( self ):
iso_timepoint = cylc.cycling.iso8601.point_parse(str(self.c_time))
self.c_time_as_seconds = int(iso_timepoint.get(
"seconds_since_unix_epoch"))
if iso_timepoint.time_zone.unknown:
utc_offset_hours, utc_offset_minutes = (
get_local_time_zone()
)
utc_offset_in_seconds = (
3600 * utc_offset_hours + 60 * utc_offset_minutes)
self.c_time_as_seconds += utc_offset_in_seconds
delayed_start = self.c_time_as_seconds + self.real_time_delay * 3600
if time.time() > delayed_start:
reached = True
Expand Down
2 changes: 1 addition & 1 deletion lib/cylc/taskdef.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ def tclass_add_prerequisites( sself, tag ):
p_prev = None
adjusted = []
for seq in self.sequences:
prv = seq.get_prev_point(sself.c_time)
prv = seq.get_nearest_prev_point(sself.c_time)
if prv:
# may be None if out of sequence bounds
adjusted.append( prv )
Expand Down
20 changes: 12 additions & 8 deletions lib/cylc/time_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@ class CylcTimeParser(object):
def __init__(self, context_start_point,
context_end_point, num_expanded_year_digits=0,
dump_format=None,
custom_point_parse_function=None):
custom_point_parse_function=None,
assumed_time_zone=None):
if context_start_point is not None:
context_start_point = str(context_start_point)
if context_end_point is not None:
Expand All @@ -101,7 +102,8 @@ def __init__(self, context_start_point,
allow_only_basic=False, # TODO - Ben: why was this set True
allow_truncated=True,
num_expanded_year_digits=num_expanded_year_digits,
dump_format=dump_format
dump_format=dump_format,
assumed_time_zone=assumed_time_zone
)
self._recur_format_recs = []
for regex, format_num in self.RECURRENCE_FORMAT_REGEXES:
Expand Down Expand Up @@ -297,10 +299,12 @@ def setUp(self):
# or offsets are applied.
self._end_point = "20010506T1200+0200"
self._parsers = {0: CylcTimeParser(self._start_point,
self._end_point),
self._end_point,
assumed_time_zone=(0, 0)),
2: CylcTimeParser(self._start_point,
self._end_point,
num_expanded_year_digits=2)}
num_expanded_year_digits=2,
assumed_time_zone=(0, 0))}

def test_first_recurrence_format(self):
"""Test the first ISO 8601 recurrence format."""
Expand Down Expand Up @@ -400,15 +404,15 @@ def test_fourth_recurrence_format(self):
tests = [("PT6H/20000101T0500Z", "R/PT6H/20000101T0500Z"),
("P12D/+P2W", "R/P12D/20010520T1000Z"),
("P1W/-P1M1D", "R/P1W/20010405T1000Z"),
("P6D/T12", "R/P6D/20010506T1000Z"),
("P6DT12H/01T", "R/P6DT12H/20010531T2200Z"),
("P6D/T12+02", "R/P6D/20010506T1000Z"),
("P6DT12H/01T00+02", "R/P6DT12H/20010531T2200Z"),
("R/P1D/20010506T1200+0200", "R/P1D/20010506T1000Z"),
("R/PT5M/+PT2M", "R/PT5M/20010506T1002Z"),
("R/P20Y/-P20Y", "R/P20Y/19810506T1000Z"),
("R/P3YT2H/T18-02", "R/P3YT2H/20010506T2000Z"),
("R/PT3H/31T", "R/PT3H/20010530T2200Z"),
("R/PT3H/31T", "R/PT3H/20010531T0000Z"),
("R5/P1Y/", "R5/P1Y/20010506T1000Z"),
("R3/P2Y/02T", "R3/P2Y/20010601T2200Z"),
("R3/P2Y/02T", "R3/P2Y/20010602T0000Z"),
("R/P2Y", "R/P2Y/20010506T1000Z"),
("R48/PT2H", "R48/PT2H/20010506T1000Z"),
("R/P21Y/", "R/P21Y/20010506T1000Z")]
Expand Down
2 changes: 2 additions & 0 deletions lib/isodatetime/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,5 @@
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#-----------------------------------------------------------------------------

__version__ = "2014-03+"
Loading