From 404690b08dca855ef57cde9fba0bd32caf4abef1 Mon Sep 17 00:00:00 2001 From: orwell Date: Thu, 22 Jan 2026 20:25:00 +0100 Subject: RailwayTime: Extend to hours precision, wrap clock at 7 days, clip repeating times at day boundary --- advtrains_line_automation/line_editor.lua | 2 +- advtrains_line_automation/line_functions.lua | 76 +++--- advtrains_line_automation/railwaytime.lua | 382 ++++++++++++++++++--------- advtrains_line_automation/station_editor.lua | 4 +- 4 files changed, 292 insertions(+), 172 deletions(-) diff --git a/advtrains_line_automation/line_editor.lua b/advtrains_line_automation/line_editor.lua index 0428035..bd286c7 100644 --- a/advtrains_line_automation/line_editor.lua +++ b/advtrains_line_automation/line_editor.lua @@ -827,7 +827,7 @@ show_last_passages_formspec = function(player, linevar_def, selected_linevar) for j = 1, 10 do local time = passages[j][1] if time ~= nil then - table.insert(formspec, ",("..time..")") + table.insert(formspec, ",("..rwt.t_str(time)..")") if max_time[j] < time then max_time[j] = time end diff --git a/advtrains_line_automation/line_functions.lua b/advtrains_line_automation/line_functions.lua index ecf7616..f53c9d6 100644 --- a/advtrains_line_automation/line_functions.lua +++ b/advtrains_line_automation/line_functions.lua @@ -467,9 +467,9 @@ function al.get_delay_description(line_status, linevar_def, rwtime) return {text = ""} end local stops = linevar_def.stops - local expected_departure = line_status.linevar_dep + assert(stops[line_status.linevar_index]).dep + local expected_departure = rwt.add(line_status.linevar_dep, assert(stops[line_status.linevar_index]).dep) local real_departure = line_status.linevar_last_dep - local delay = real_departure - expected_departure + local delay = rwt.diff(expected_departure, real_departure) if rwtime ~= nil then local expected_departure_next @@ -479,13 +479,13 @@ function al.get_delay_description(line_status, linevar_def, rwtime) else local mode = stops[i].mode if mode == nil or mode ~= MODE_DISABLED then - expected_departure_next = line_status.linevar_dep + stops[i].dep + expected_departure_next = rwt.add(line_status.linevar_dep, stops[i].dep) break end end end if expected_departure_next ~= nil then - local delay2 = rwtime - expected_departure_next + local delay2 = rwt.diff(expected_departure_next, rwtime) if delay2 > delay then delay = delay2 end @@ -563,16 +563,14 @@ function al.get_line_status(train) -- nelinkový vlak local linevar_past = ls.linevar_past if linevar_past ~= nil then - local rwtime = rwt.to_secs(rwt.get_time()) - if train.line ~= linevar_past.line or rwtime - linevar_past.arrival >= 86400 then + if train.line ~= linevar_past.line or rwt.diff(linevar_past.arrival, rwt.now()) >= 86400 then -- smazat linevar_past, pokud se změnilo označení linky nebo uplynulo 24 hodin ls.linevar_past = nil end end return ls, nil end - local rwtime = rwt.to_secs(rwt.get_time()) - if rwtime - ls.linevar_dep >= 86400 then + if rwt.diff(ls.linevar_dep, rwt.now()) >= 86400 then core.log("warning", "Train "..train.id.." put out of linevar '"..ls.linevar.."', because it was riding more then 24 hours.") al.cancel_linevar(train) return ls, nil @@ -655,7 +653,7 @@ function al.on_train_enter(pos, train_id, train, index) end local stn = stdata.stn or "" - local rwtime = assert(rwt.to_secs(rwt.get_time())) + local rwtime = assert(rwt.now()) local ls, linevar_def = al.get_line_status(train) local next_index if stn ~= "" then @@ -663,7 +661,7 @@ function al.on_train_enter(pos, train_id, train, index) next_index = find_next_index(ls, linevar_def, stn) end ls.last_enter = {stn = stn, encpos = pe, rwtime = rwtime} - debug_print("Vlak "..train_id.." zaznamenán: "..stn.." "..core.pos_to_string(pos).." @ "..rwtime) + debug_print("Vlak "..train_id.." zaznamenán: "..stn.." "..core.pos_to_string(pos).." @ "..rwt.t_str(rwtime)) end local should_stop_result = should_stop(pos, stdata, train) if should_stop_result == nil then @@ -700,20 +698,20 @@ function al.on_train_enter(pos, train_id, train, index) local last_dep = stdata.last_dep -- posl. odjezd z této zastávkové koleje if interval ~= nil and last_dep ~= nil then - if last_dep > rwtime then + if rwt.is_after(last_dep, rwtime) then last_dep = rwtime end local ioffset = stdata.ioffset or 0 - local normal_dep = rwtime + wait - local next_dep = last_dep + (interval - (last_dep + (interval - ioffset)) % interval) - if normal_dep < next_dep then + local normal_dep = rwt.add(rwtime, wait) + local next_dep = rwt.next_rpt(last_dep, interval, ioffset) + if rwt.is_before(normal_dep, next_dep) then -- core.log("action", "[INFO] The train "..train_id.." will wait for "..(next_dep - normal_dep).." additional seconds due to interval at "..core.pos_to_string(pos)..".") - wait = wait + (next_dep - normal_dep) + wait = wait + rwt.diff(normal_dep, next_dep) -- else -- will wait normal time end end - local planned_departure = rwtime + wait - debug_print("Vlak "..train_id.." zastavil na "..stn.." a odjede za "..wait.." sekund ("..planned_departure..").") + local planned_departure = rwt.add(rwtime, wait) + debug_print("Vlak "..train_id.." zastavil na "..stn.." a odjede za "..wait.." sekund ("..rwt.t_str(planned_departure)..").") stdata.last_dep = planned_departure -- naplánovaný čas odjezdu stdata.last_wait = wait -- naplánovaná doba čekání ls.standing_at = pe @@ -746,7 +744,7 @@ function al.on_train_enter(pos, train_id, train, index) else -- koncová zastávka can_start_line = stop_def.mode == MODE_FINAL_CONTINUE - core.log("action", "Train "..train_id.." arrived at the final station "..stop_def.stn.." of linevar "..ls.linevar.." after "..(rwtime - ls.linevar_dep).." seconds.") + core.log("action", "Train "..train_id.." arrived at the final station "..stop_def.stn.." of linevar "..ls.linevar.." after "..rwt.diff(ls.linevar_dep, rwtime).." seconds.") debug_print("Vlak "..train_id.." skončil jízdu na lince "..ls.linevar..", může pokračovat na jinou linku: "..(can_start_line and "ihned" or "na příští zastávce")) train.text_inside = al.get_stop_description(linevar_def.stops[next_index]) local current_passage = current_passages[train_id] @@ -819,11 +817,11 @@ function al.on_train_leave(pos, train_id, train, index) end local stn = stdata.stn or "" local ls, linevar_def = al.get_line_status(train) - local rwtime = assert(rwt.to_secs(rwt.get_time())) + local rwtime = rwt.now() if stn ~= "" then ls.last_leave = {stn = stn, encpos = pe, rwtime = rwtime} - debug_print("Vlak "..train_id.." zaznamenán na odjezdu: "..stn.." "..core.pos_to_string(pos).." @ "..rwtime) + debug_print("Vlak "..train_id.." zaznamenán na odjezdu: "..stn.." "..core.pos_to_string(pos).." @ "..rwt.t_str(rwtime)) end if ls.standing_at == pe then @@ -838,8 +836,8 @@ function al.on_train_leave(pos, train_id, train, index) end if stn ~= "" then debug_print("Vlak "..train_id.." odjel ze zastávky "..stn) - if ls.linevar_last_dep ~= nil and ls.linevar_last_dep > rwtime then - debug_print("Vlak "..train_id.." předčasně odjel z dopravny "..stn.." (zbývalo "..(ls.linevar_last_dep - rwtime).." sekund)") + if ls.linevar_last_dep ~= nil and rwt.is_after(ls.linevar_last_dep, rwtime) then + debug_print("Vlak "..train_id.." předčasně odjel z dopravny "..stn.." (zbývalo "..rwt.diff(rwtime, ls.linevar_last_dep).." sekund)") ls.linevar_last_dep = rwtime -- předčasný odjezd if ls.linevar_index == 1 then ls.linevar_dep = rwtime @@ -931,7 +929,7 @@ local function get_last_pos(line_status) last_pos, last_pos_type = last_enter, "enter" if standing_at ~= nil and standing_at == last_enter.encpos then last_pos_type = "standing" - elseif last_leave ~= nil and last_leave.rwtime > last_enter.rwtime then + elseif last_leave ~= nil and rwt.is_after(last_leave.rwtime, last_enter.rwtime) then last_pos, last_pos_type = last_leave, "leave" end elseif last_leave ~= nil then @@ -967,7 +965,7 @@ local function get_train_position(line_status, linevar_def, rwtime) local result = "„"..get_station_name(last_pos.stn).."“" local delay_info = al.get_delay_description(line_status, linevar_def, rwtime) if last_pos_info.type ~= "standing" then - result = result.." ("..(rwtime - last_pos.rwtime).." seconds ago)" + result = result.." ("..rwt.diff(last_pos.rwtime, rwtime).." seconds ago)" end if delay_info.has_delay ~= nil then result = result.." ("..delay_info.text..")" @@ -1018,7 +1016,7 @@ local function predict_train_continue(line, stn, rc, departure, result) local stdata = advtrains.lines.stations[stop.stn] if stop.mode == MODE_FINAL or stop.mode == MODE_FINAL_CONTINUE or stop.mode == MODE_FINAL_HIDDEN then -- koncová zastávka - local arr = departure + stop.dep + local arr = rwt.add(departure + stop.dep) local record = { stn = assert(stop.stn), track = stop.track or "", @@ -1033,7 +1031,7 @@ local function predict_train_continue(line, stn, rc, departure, result) return elseif stop.mode ~= MODE_DISABLED then -- mezilehlá zastávka - local dep = departure + stop.dep + local dep = rwt.add(departure + stop.dep) table.insert(result, { stn = assert(stop.stn), track = stop.track or "", @@ -1076,7 +1074,7 @@ function al.predict_train(line_status, linevar_def, rwtime, allow_continue) local result = {} local index = assert(line_status.linevar_index) if rwtime == nil then - rwtime = rwt.to_secs(rwt.get_time()) + rwtime = rwt.now() end local delay_desc = al.get_delay_description(line_status, linevar_def, rwtime) local delay @@ -1108,7 +1106,7 @@ function al.predict_train(line_status, linevar_def, rwtime, allow_continue) local stdata = advtrains.lines.stations[stop.stn] if stop.mode == MODE_FINAL or stop.mode == MODE_FINAL_CONTINUE or stop.mode == MODE_FINAL_HIDDEN then -- koncová zastávka - local arr = departure + stop.dep + delay + local arr = rwt.add(rwt.add(departure, stop.dep), delay) local record = { stn = assert(stop.stn), track = stop.track or "", @@ -1122,17 +1120,17 @@ function al.predict_train(line_status, linevar_def, rwtime, allow_continue) } table.insert(result, record) if allow_continue and (linevar_def.continue_line or "") ~= "" and stop.mode == MODE_FINAL_CONTINUE then - predict_train_continue(linevar_def.continue_line, stop.stn, linevar_def.continue_rc, arr + (stop.wait or 10), result) + predict_train_continue(linevar_def.continue_line, stop.stn, linevar_def.continue_rc, rwt.add(arr, (stop.wait or 10)), result) end break elseif stop.mode ~= MODE_DISABLED then -- mezilehlá zastávka - local dep = departure + stop.dep + delay + local dep = rwt.add(rwt.add(departure, stop.dep), delay) table.insert(result, { stn = assert(stop.stn), track = stop.track or "", stdata = stdata, - arr = dep - (stop.wait or 10), + arr = rwt.sub(dep, (stop.wait or 10)), arr_linevar_def = linevar_def, arr_index = index, dep = dep, @@ -1166,7 +1164,7 @@ function al.predict_station_departures(linevar_def, linevar_index, rwtime, train trains = al.get_trains_by_linevar()[linevar] or {} end if rwtime == nil then - rwtime = rwt.to_secs(rwt.get_time()) + rwtime = rwt.now() end local result = {} for _, train in ipairs(trains) do @@ -1178,7 +1176,7 @@ function al.predict_station_departures(linevar_def, linevar_index, rwtime, train pr.dep ~= nil and pr.dep_linevar_def ~= nil and pr.dep_index ~= nil and pr.dep_linevar_def.name == linevar and pr.dep_index == linevar_index and - pr.dep > rwtime + rwt.is_after(pr.dep, rwtime) then table.insert(result, pr) break @@ -1186,7 +1184,7 @@ function al.predict_station_departures(linevar_def, linevar_index, rwtime, train end end end - table.sort(result, function(a, b) return a.dep < b.dep end) + table.sort(result, function(a, b) return rwt.is_before(a.dep, b.dep) end) return result end @@ -1276,7 +1274,7 @@ local function vlaky(param, past_trains_too) if param ~= "" then train_line_prefix = param.."/" end - local rwtime = rwt.to_secs(rwt.get_time()) + local rwtime = rwt.now() local players_per_train = {} local results = {} for player_name, train_id in pairs(advtrains.player_to_train_mapping) do @@ -1336,7 +1334,7 @@ core.register_chatcommand("list_trains", def) def = { params = "[line]", description = "List all trains on the given line (or all lines) + trains that have recently completed the line", - privs = {ch_registered_player = true}, + privs = {}, func = function(player_name, param) local result = vlaky(param, true) if #result == 0 then @@ -1361,19 +1359,19 @@ def = { return false, "Vlak "..param.." není linkový!" end -- function al.predict_train(line_status, linevar_def, rwtime, allow_continue) - local rwtime = rwt.to_secs(rwt.get_time()) + local rwtime = rwt.now() local predictions = al.predict_train(line_status, linevar_def, rwtime, true) local result = {"----"} local s for i, record in ipairs(predictions) do local arr, dep if record.arr ~= nil then - arr = "("..(record.arr - rwtime)..", lv="..record.arr_linevar_def.name..", i="..record.arr_index..")" + arr = "("..rwt.diff(rwtime, record.arr)..", lv="..record.arr_linevar_def.name..", i="..record.arr_index..")" else arr = "nil" end if record.dep ~= nil then - dep = "("..(record.dep - rwtime)..", lv="..record.dep_linevar_def.name..", i="..record.dep_index..")" + dep = "("..rwt.diff(rwtime, record.dep)..", lv="..record.dep_linevar_def.name..", i="..record.dep_index..")" else dep = "nil" end diff --git a/advtrains_line_automation/railwaytime.lua b/advtrains_line_automation/railwaytime.lua index 29ef110..ca14ba0 100644 --- a/advtrains_line_automation/railwaytime.lua +++ b/advtrains_line_automation/railwaytime.lua @@ -5,38 +5,66 @@ -- - Server stops/restarts -- However, this means that implementing a "timetable" system using the "real time" is not practical. Therefore, -- we introduce a custom time system, the RWT(Railway Time), which has nothing to do with RLT(Real-Life Time) --- RWT has a time cycle of 1 hour. This should be sufficient for most train lines that will ever be built in Minetest. --- A RWT looks like this: 37;25 --- The ; is to distinguish it from a normal RLT (which has colons e.g. 12:34:56). Left number is minutes, right number is seconds. --- The minimum RWT is 00;00, the maximum is 59;59. --- It is OK to leave one places out at either end, esp. when writing relative times, such as: --- 43;3 22;0 2;30 0;10 ;10 --- Those places are then filled with zeroes. Indeed, ";" would be valid for 00;00 . - +-- RWT has a time cycle of 1 day. This should be sufficient for most train lines that will ever be built in Minetest. +-- A RWT looks like this: 14;37;25 +-- The ; is to distinguish it from a normal RLT (which has colons e.g. 12:34:56). Left number is hours, middle number is minutes, right number is seconds. +-- The minimum RWT is 00;00;00, the maximum is 23;59;59. -- There is an "adapt mode", which was proposed by gpcf, and results in RWT automatically adapting itself to real-world time. --- It works by shifting the minute/second after the realtime minute/second, adjusting the cycle value as needed. +-- It works by shifting the hour/minute/second after the realtime hour/minute/second, adjusting the cycle value as needed. +--[[ +There is an important distinction between: --- Using negative times is discouraged. If you need a negative time, you may insert a minus (-) ONLY in the "c" place +## Time (absolute time) ## +Represents a time instant. +It is identified by the 4 values cycle (day), hour, minute and second. These values are never negative. ---[[ -1;23;45 = { - s=45, - m=23, - c=1, -- Cycle(~hour), not displayed most time -} +In string form, it is written as C;HH;MM;SS. +The following short forms are allowed to display a RWT (but they do not uniquely identify a RWT): +HH;MM;SS +HH;MM +It is discouraged to use the string form to store RWTs, the table form or seconds form is better. + +In table form: +{ s=45, m=23, h=12, c=1 } + +In seconds form: +c*24*60*60 + h*60*60 + m*60 + s + +Absolute time wraps around at a boundary of 7 cycles (valid cycle values are 0 to 6). If the difference between instants A and B +is < 3.5 days, B is considered in the future, otherwise B is considered in the past. +The RWT functions take this wrapping into account. + +## Interval (relative time) ## +Represents a difference between times. + +It is commonly represented in seconds form as a number of seconds. +It can be formatted as a string e.g. + 90 seconds = "+01;30" +-90 seconds = "-01;30" +When converting to a string, a + or - must be prepended. +When parsing a string it is permissible to leave out the + if it is clear from the context that +an interval is parsed. +The meaning of string short forms differ from the time instant case: +[+-]MM;SS (meaning when interval is parsed FROM a string, and the default when converting to string and HH==0 and C==0) +[+-]HH;MM;SS (when HH!=0 and C==0) +[+-]C;HH;MM;SS (when all !=0) +[+-]HH;MM (permissible only when converting TO a string, to be used when it is displayed alongside an instant e.g. 05;30+00;04) -Railway times can exist in 3 forms: -- as table (see above) -- as string (like "12;34") -- as number (of seconds) -Forms are automagically converted as needed by the converter functions to_* -To be sure a rwt is in the required form, explicitly use a converter. +]]-- -]] local rwt = {} +local CYCLE = 24*60*60 +local MODULUS = CYCLE*7 +local MODULUS_H = MODULUS / 2 + +-- adjust so that it is in range [-MODULUS/2, MODULUS/2) +local function i_adjust(sec) + return ((sec + MODULUS_H) % MODULUS) - MODULUS_H +end + --Time Stamp (Seconds since start of world) local e_time = 0 local e_has_loaded = false @@ -62,7 +90,7 @@ local function adapt_real_time() atlog("[lines][rwt] Skipping",secsfwd,"seconds forward to sync rwt (",rwt.to_string(rwttab),") to real time (",os.date("%H:%M:%S"),")") - e_time = e_time + secsfwd + e_time = (e_time + secsfwd) % MODULUS end function rwt.set_time(t) @@ -87,14 +115,14 @@ function rwt.step(dt) if setting_rwt_real=="independent" then -- Regular stepping with dtime - e_time = e_time + dt + e_time = (e_time + dt) % MODULUS else -- advance with real-world time local diff = os.time() - e_last_epoch e_last_epoch = os.time() if diff>0 then - e_time = e_time + diff + e_time = (e_time + diff) % MODULUS end end end @@ -103,17 +131,35 @@ function rwt.now() return rwt.to_table(e_time) end -function rwt.new(c, m, s) +function rwt.new_t(c, h, m, s) + assert(c>=0 and c<7 and c==math.floor(c)) + assert(h>=0 and h<24 and h==math.floor(h)) + assert(m>=0 and m<60 and m==math.floor(m)) + assert(s>=0 and s<60) return { c = c or 0, + h = h or 0, m = m or 0, s = s or 0 } end + +function rwt.new_i(sign, c, h, m, s) + assert(math.abs(sign)==1) + assert(c>=0 and c<7 and c==math.floor(c)) + assert(h>=0 and h<24 and h==math.floor(h)) + assert(m>=0 and m<60 and m==math.floor(m)) + assert(s>=0 and s<60) + + return sign * (c*24*60*60 + h*60*60 + m*60 + s) +end + + function rwt.copy(rwtime) local rwtimet = rwt.to_table(rwtime) return { c = rwtimet.c or 0, + h = rwtimet.h or 0, m = rwtimet.m or 0, s = rwtimet.s or 0 } @@ -123,185 +169,261 @@ function rwt.to_table(rwtime) if type(rwtime) == "table" then return rwtime elseif type(rwtime) == "string" then - return rwt.parse(rwtime) + return rwt.parse_t(rwtime) elseif type(rwtime) == "number" then local res = {} local seconds = atfloor(rwtime) res.s = seconds % 60 local minutes = atfloor(seconds/60) res.m = minutes % 60 - res.c = atfloor(minutes/60) + local hours = atfloor(minutes/60) + res.h = hours % 24 + res.c = atfloor(hours/24) return res end end -function rwt.to_secs(rwtime, c_over) +local function intv_to_table(rwintp) + if type(rwintp) == "number" then + local res = {} + local rwint = math.abs(rwintp) + if rwint~=0 then + res.sign = rwintp/rwint -- 1 or -1 + else + res.sign = 1 + end + local seconds = atfloor(rwint) + res.s = seconds % 60 + local minutes = atfloor(seconds/60) + res.m = minutes % 60 + local hours = atfloor(minutes/60) + res.h = hours % 24 + res.c = atfloor(hours/24) + return res + else + error("intv_to_table needs number") + end +end + +-- time instant to seconds +function rwt.t_sec(rwtime) local res = rwtime if type(rwtime) == "string" then - res = rwt.parse(rwtime) + res = rwt.parse_t(rwtime) elseif type(rwtime) == "number" then return rwtime end if type(res)=="table" then - return (c_over or res.c)*60*60 + res.m*60 + res.s + return res.c*60*60*24 + res.h*60*60 + res.m*60 + res.s + end +end + +-- interval to seconds +function rwt.i_sec(rwint) + if type(rwint) == "string" then + return rwt.parse_i(rwint) + elseif type(rwint) == "number" then + return rwint + elseif type(rwint)=="table" then + return res.sign * (res.c*60*60*24 + res.h*60*60 + res.m*60 + res.s) end end -function rwt.to_string(rwtime_p, no_cycle) +-- time to string +-- fmt: +-- 0, nil or false: full format C;HH;MM;SS +-- 1 or true: format HH;MM;SS +-- 2: format MM;SS +-- 3: format HH;MM +function rwt.t_str(rwtime_p, fmt) local rwtime = rwt.to_table(rwtime_p) - if rwtime.c~=0 and not no_cycle then - return string.format("%d;%02d;%02d", rwtime.c, rwtime.m, rwtime.s) - else + if fmt==0 or not fmt then + return string.format("%d;%02d;%02d;%02d", rwtime.c, rwtime.h, rwtime.m, rwtime.s) + elseif fmt==1 or fmt==true then + return string.format("%02d;%02d;%02d", rwtime.h, rwtime.m, rwtime.s) + elseif fmt==2 then return string.format("%02d;%02d", rwtime.m, rwtime.s) + elseif fmt==3 then + return string.format("%02d;%02d", rwtime.h, rwtime.m) + end +end +-- compatibility alias +rwt.to_string = rwt.t_str + +-- interval to string +function rwt.i_str(rwint_p, omit_plus, omit_second) + local rwint = intv_to_table(rwint_p) + -- sign + local sic = rwint.sign==1 and (omit_plus and "" or "+") or "-" + -- find out formatting + if omit_second then + if rwint.c~=0 then + return string.format("%s%d;%02d;%02d", sic, rwint.c, rwint.h, rwint.m) + else + return string.format("%s%02d;%02d", sic, rwint.h, rwint.m) + end + else + if rwint.c~=0 then + return string.format("%s%d;%02d;%02d;%02d", sic, rwint.c, rwint.h, rwint.m, rwint.s) + elseif rwint.h~=0 then + return string.format("%s%02d;%02d;%02d", sic, rwint.h, rwint.m, rwint.s) + else + return string.format("%s%02d;%02d", sic, rwint.m, rwint.s) + end end end --- -local function v_n(str, cpl) - if not str then return nil end +local function v_n(str) if str == "" then return 0 end - local n = tonumber(str) - if not cpl and (n<0 or n>59) then - return nil - end - return n + return tonumber(str) end +-- parse an interval (the default case - parsing an instant is rare) function rwt.parse(str) --atdebug("parse",str) + --4-value form + local sic, str_c, str_h, str_m, str_s = string.match(str, "^([%-%+]?)(%d?%d?);(%d%d);(%d%d);(%d?%d?)$") + if sic and str_c and str_h and str_m and str_s then + --atdebug("4v", sic, str_c, str_h, str_m, str_s) + local c, h, m, s = v_n(str_c), v_n(str_h), v_n(str_m), v_n(str_s) + if c and h and m and s then + return rwt.new_i(sic=="-" and -1 or 1, c,h,m,s) + end + end --3-value form - local str_c, str_m, str_s = string.match(str, "^(%-?%d?%d?);(%d%d);(%d?%d?)$") - if str_c and str_m and str_s then - --atdebug("3v",str_c, str_m, str_s) - local c, m, s = v_n(str_c, true), v_n(str_m), v_n(str_s) - if c and m and s then - return rwt.new(c,m,s) + local sic, str_h, str_m, str_s = string.match(str, "^([%-%+]?)(%d?%d?);(%d%d);(%d?%d?)$") + if sic and str_h and str_m and str_s then + --atdebug("3v", sic, str_h, str_m, str_s) + local c, h, m, s = 0, v_n(str_h), v_n(str_m), v_n(str_s) + if c and h and m and s then + return rwt.new_i(sic=="-" and -1 or 1, c,h,m,s) end end --2-value form - local str_m, str_s = string.match(str, "^(%d?%d?);(%d?%d?)$") - if str_m and str_s then - --atdebug("2v",str_m, str_s) - local m, s = v_n(str_m), v_n(str_s) - if m and s then - return rwt.new(0,m,s) + local sic, str_m, str_s = string.match(str, "^([%-%+]?)(%d?%d?);(%d?%d?)$") + if sic and str_m and str_s then + --atdebug("2v", sic, str_m, str_s) + local c, h, m, s = 0, 0, v_n(str_m), v_n(str_s) + if c and h and m and s then + return rwt.new_i(sic=="-" and -1 or 1, c,h,m,s) end end end +rwt.parse_i = rwt.parse ---- - -function rwt.add(t1, t2) - local t1s = rwt.to_secs(t1) - local t2s = rwt.to_secs(t2) - return rwt.to_table(t1s + t2s) +-- parse a time instant - only the 4-value form is allowed! +function rwt.parse_t(str) + --atdebug("parse",str) + --4-value form + local str_c, str_h, str_m, str_s = string.match(str, "^(%d?%d?);(%d%d);(%d%d);(%d?%d?)$") + if str_c and str_h and str_m and str_s then + --atdebug("4v", str_c, str_h, str_m, str_s) + local c, h, m, s = v_n(str_c), v_n(str_h), v_n(str_m), v_n(str_s) + if c and h and m and s then + return rwt.new_t(c,h,m,s) + end + end end --- How many seconds FROM t1 TO t2 -function rwt.diff(t1, t2) - local t1s = rwt.to_secs(t1) - local t2s = rwt.to_secs(t2) - return t2s - t1s -end +--- --- Subtract t2 from t1 (inverted argument order compared to diff()) -function rwt.sub(t1, t2) - return rwt.to_table(rwt.diff(t2, t1)) +-- add a time and an interval. The result is a time (table form) +function rwt.add(t1, i) + local t1s = rwt.t_sec(t1) + local t2s = rwt.i_sec(i) + return rwt.to_table( (t1s + t2s) % MODULUS ) end --- Adjusts t2 by thresh and then returns time from t1 to t2 -function rwt.adj_diff(t1, t2, thresh) - local newc = rwt.adjust_cycle(t2, thresh, t1) - local t1s = rwt.to_secs(t1) - local t2s = rwt.to_secs(t2, newc) - return t1s - t2s +-- subtract a time and an interval. The result is a time (table form) +function rwt.sub(t1, i) + local t1s = rwt.t_sec(t1) + local t2s = rwt.i_sec(i) + return rwt.to_table( (t1s - t2s) % MODULUS ) end - --- Threshold values --- "reftime" is the time to which this is made relative and defaults to now. -rwt.CA_FUTURE = 60*60 - 1 -- Selected so that time lies at or in the future of reftime (at nearest point in time) -rwt.CA_FUTURES = 60*60 -- Same, except when times are equal, advances one full cycle -rwt.CA_PAST = 0 -- Selected so that time lies at or in the past of reftime -rwt.CA_PASTS = -1 -- Same, except when times are equal, goes back one full cycle -rwt.CA_CENTER = 30*60 -- If time is within past 30 minutes of reftime, selected as past, else selected as future. - --- Adjusts the "cycle" value of a railway time to be in some relation to reftime. --- Returns new cycle -function rwt.adjust_cycle(rwtime, reftime_p, thresh) - local reftime = reftime_p or rwt.now() - - local reftimes = rwt.to_secs(reftime) - - local rwtimes = rwt.to_secs(rwtime, 0) - local timeres = reftimes + thresh - rwtimes - local cycles = atfloor(timeres / (60*60)) - - return cycles +-- How many seconds FROM t1 TO t2 (two time instants, result is an interval) +function rwt.diff(t1, t2) + local t1s = rwt.t_sec(t1) + local t2s = rwt.t_sec(t2) + return i_adjust(t2s - t1s) end -function rwt.adjust(rwtime, reftime, thresh) - local cp = rwt.copy(rwtime) - cp.c = rwt.adjust_cycle(rwtime, reftime, thresh) - return cp +-- Whether t1 is (strictly) after t2 +function rwt.is_after(t1, t2) + return rwt.diff(t2,t1) > 0 end --- Useful for departure times: returns time (in seconds) --- until the next (adjusted FUTURE) occurence of deptime is reached --- in this case, rwtime is used as reftime and deptime should lie in the future of rwtime --- rwtime defaults to NOW -function rwt.get_time_until(deptime, rwtime_p) - local rwtime = rwtime_p or rwt.now() - return rwt.adj_diff(rwtime, deptime, rwt.CA_FUTURE) +-- Whether t1 is (strictly) before t2 +function rwt.is_before(t1, t2) + return rwt.diff(t1,t2) > 0 end - -- Helper functions for handling "repeating times" (rpt) -- Those are generic declarations for time intervals like "every 5 minutes", with an optional offset --- ( /02;00-00;45 in timetable syntax +-- Note the following: +-- - rwtime is a time instant, rpt_interval and rpt_offset are intervals +-- - repeating times are hard-capped at cycle (24-hour) boundary +-- - if rpt_interval is given as nil, there is exactly 1 match per cycle and it is at X;00;00;00 + offset ( equals a time-of-day) +-- - the offset determines where the "reference cycle" starts e.g. if the off=05;00;00 and int=03;45;00, starting from 0;05;00 the times would be +-- 05;00, 08;45, ..., 20;00, 23;45, 1;03;30, then next cycle starts 1;05;00 +-- - if the cycle is not a multiple of the interval like in the previous example, the very last instance is not considered when it is closer than interval/2 to the cycle boundary +-- in the example above the distance between 1;03;30 and 1;05;00 is only 90s, which is smaller than interval/2 +-- It is preferred to therefore have a longer interval +-- and the actual sequence will be ..., 20;00, 23;45, 1;05;00 -- Get the time (in seconds) until the next time this rpt occurs function rwt.time_to_next_rpt(rwtime, rpt_interval, rpt_offset) - local rpti_s = rwt.to_secs(rpt_interval) + if not rpt_interval then rpt_interval = CYCLE end + + local rwtime_s = rwt.t_sec(rwtime) + local rpti_s = rwt.i_sec(rpt_interval) + local rpto_s = rwt.i_sec(rpt_offset) + local otimcyc = (rwtime_s - rpto_s) % CYCLE -- time into current cycle, offset by the rpt offset + -- otimcyc is the time into a cycle which is assumed to start at rpto_s + local lasttime = otimcyc - (otimcyc % rpti_s) + local nexttime = lasttime + rpti_s -- next occurence is last occurence plus interval + + if nexttime > (CYCLE - rpti_s/2) then -- last time cutoff rule, and overflow of cycle + nexttime = CYCLE + end - return (rpti_s - rwt.time_from_last_rpt(rwtime, rpti_s, rpt_offset)) % rpti_s - -- Modulo is just there to clip a false value of rpti_s to 0 + return nexttime - otimcyc end -- Get the time (in seconds) since the last time this rpt occured function rwt.time_from_last_rpt(rwtime, rpt_interval, rpt_offset) - local rwtime_s = rwt.to_secs(rwtime) - local rpti_s = rwt.to_secs(rpt_interval) - local rpto_s = rwt.to_secs(rpt_offset) + if not rpt_interval then rpt_interval = CYCLE end - return ((rwtime_s - rpto_s) % rpti_s) + local rwtime_s = rwt.t_sec(rwtime) + local rpti_s = rwt.i_sec(rpt_interval) + local rpto_s = rwt.i_sec(rpt_offset) + local otimcyc = (rwtime_s - rpto_s) % CYCLE -- time into current cycle, offset by the rpt offset + -- otimcyc is the time into a cycle which is assumed to start at rpto_s + local lasttime = otimcyc - (otimcyc % rpti_s) + + if lasttime > (CYCLE - rpti_s/2) then -- last time cutoff rule + lasttime = lasttime - rpti_s + end + + return otimcyc - lasttime end -- From rwtime, get the next time that is divisible by rpt_interval offset by rpt_offset function rwt.next_rpt(rwtime, rpt_interval, rpt_offset) - local rwtime_s = rwt.to_secs(rwtime) - local rpti_s = rwt.to_secs(rpt_interval) - local time_from_last = rwt.time_from_last_rpt(rwtime_s, rpti_s, rpt_offset) - - local res_s = rwtime_s - time_from_last + rpti_s - - return rwt.to_table(res_s) + local time_to_next = rwt.time_to_next_rpt(rwtime, rpt_interval, rpt_offset) + return rwt.add(rwtime, time_to_next) end -- from rwtime, get the last time that this rpt matched (which is actually just next_rpt - rpt_offset function rwt.last_rpt(rwtime, rpt_interval, rpt_offset) - local rwtime_s = rwt.to_sec(rwtime) - local rpti_s = rwt.to_sec(rpt_interval) local time_from_last = rwt.time_from_last_rpt(rwtime, rpt_interval, rpt_offset) - - local res_s = rwtime_s - time_from_last - - return rwt.to_table(res_s) + return rwt.sub(rwtime, time_from_last) end function rwt.to_os_time(rwtime) diff --git a/advtrains_line_automation/station_editor.lua b/advtrains_line_automation/station_editor.lua index e9baaf1..4c247c5 100644 --- a/advtrains_line_automation/station_editor.lua +++ b/advtrains_line_automation/station_editor.lua @@ -816,7 +816,7 @@ local function jr_refresh_departure(custom_state) custom_state.message = "" return end - local rwtime = rwt.to_secs(rwt.get_time()) + local rwtime = rwt.now() local prediction = advtrains.lines.predict_station_departures(linevar_def, assert(stop_info.linevar_index), rwtime) if #prediction == 0 then custom_state.message = S("No departures for the selected line found in the near future") @@ -824,7 +824,7 @@ local function jr_refresh_departure(custom_state) end local deps = {} for i, pred in ipairs(prediction) do - deps[i] = tostring(pred.dep - rwtime) + deps[i] = tostring(rwt.diff(rwtime, pred.dep)) end custom_state.message = S("next departures of the selected line in: ")..table.concat(deps, ", ").." s" end -- cgit v1.2.3