Formatting negative time on a countdown timer

Hi again,
I have the following code running a countdown timer. It works just fine, until the clock hits zero when it starts counting back up as a negative number. This in itself is fine (and actually quite useful for our needs), but I have two minor issues. One is that it displays as "-1:-1, -1:12 etc", so I need to somehow add leading zeros (I guess) to it when it's in negative. The other is a slightly bigger problem which is that it seems to jump straight from "0" to "-1:-1", so it basically skips a minute (the first minus minute should be "-00:01").

This function node is setting the remaining time (there are other buttons involved to start, stop, add and remove time on the fly):

var currentTime = msg.payload;
var lastTickTime = flow.get("lastTickTime") || currentTime;
var timeRemaining = flow.get("timeRemaining") || 0;
var gameState = flow.get("gameState");

if(gameState == "playing") {

    timeRemaining -= (currentTime - lastTickTime);
    
    flow.set("timeRemaining", timeRemaining);
    
}

msg.timeRemaining = timeRemaining;
flow.set("lastTickTime", currentTime);

node.status({fill:"green", shape:"dot", text:timeRemaining});


return msg;

And then this function formats the time to a simple mm:ss display:

var t = msg.timeRemaining / 1000;
var m = Math.floor(t / 60);
var s = Math.floor(t%60 % 60);

msg.formattedTimeRemaining = ("0" + m).slice(-2) + ":" + ("0" + s).slice(-2);

node.status({fill:"green", shape:"dot", msg:flow.formattedTimeRemaining});


return msg;

The node status on the first function counts down as 2000, 1000, 0, -1000, -2000 etc, so I guess I just need to figure out what I can add to the second function to format the negative time properly.

Any ideas?

I can't see anywhere that you are actually generating a timestamp?

You should probably be using a millisecond based timestamp (e.g. JavaScript Date) and then you could use JavaScript date/time functions to work everything out.

I appreciate you are trying to do it in a function node - and I don't necessarily have the bigger picture, but rather than re-inventing the wheel, are you open to using a separate node as the countdown timer. Something like stoptimer-varidelay (https://flows.nodered.org/node/node-red-contrib-stoptimer-varidelay)?

The timestamp comes into the first function as the msg.payload.

I'm not trying to reinvent anything here, just using what others have supplied (I'd have never of got this far on my own! :sweat_smile:), and trying to tweak it a little. The timer works just fine as it is, and I've managed to add some buttons for adding and removing time successfully. It just seems to be something in the formatting that isn't quite right, or non existing, for when it goes into minus time. It's for an escape room timer/gamemaster software, so there's other buttons and arduinos going to be linked into it all, but I here (I think) are all the parts that relate to the timer section:

[
    {
        "id": "906339c5.ce4fa",
        "type": "inject",
        "z": "29662af6.96a146",
        "name": "Setup",
        "props": [
            {
                "p": "payload"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": true,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 190,
        "y": 100,
        "wires": [
            [
                "becf850a.cbcee"
            ]
        ]
    },
    {
        "id": "becf850a.cbcee",
        "type": "change",
        "z": "29662af6.96a146",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "gameState",
                "pt": "flow",
                "to": "stopped",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "timeRemaining",
                "pt": "flow",
                "to": "3600000",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "currentTime",
                "pt": "flow",
                "to": "payload",
                "tot": "msg"
            },
            {
                "t": "set",
                "p": "logging",
                "pt": "flow",
                "to": "true",
                "tot": "bool"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 360,
        "y": 100,
        "wires": [
            [
                "cc5aea0b.1742e"
            ]
        ]
    },
    {
        "id": "ca343107.aec8b",
        "type": "inject",
        "z": "29662af6.96a146",
        "name": "Start",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 190,
        "y": 240,
        "wires": [
            [
                "49b44f5a.132568"
            ]
        ]
    },
    {
        "id": "9266125d.112b38",
        "type": "inject",
        "z": "29662af6.96a146",
        "name": "Stop",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 610,
        "y": 240,
        "wires": [
            [
                "8157f92c.9324e"
            ]
        ]
    },
    {
        "id": "49b44f5a.132568",
        "type": "change",
        "z": "29662af6.96a146",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "gameState",
                "pt": "flow",
                "to": "playing",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 380,
        "y": 240,
        "wires": [
            []
        ]
    },
    {
        "id": "8157f92c.9324e",
        "type": "change",
        "z": "29662af6.96a146",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "gameState",
                "pt": "flow",
                "to": "stopped",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 810,
        "y": 240,
        "wires": [
            []
        ]
    },
    {
        "id": "91ccf9a6.2af778",
        "type": "inject",
        "z": "29662af6.96a146",
        "name": "Reset",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 1030,
        "y": 240,
        "wires": [
            [
                "b8b19971.5380a8"
            ]
        ]
    },
    {
        "id": "b8b19971.5380a8",
        "type": "change",
        "z": "29662af6.96a146",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "timeRemaining",
                "pt": "flow",
                "to": "3600000",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 1240,
        "y": 240,
        "wires": [
            []
        ]
    },
    {
        "id": "bf54624f.1f05b8",
        "type": "ui_button",
        "z": "29662af6.96a146",
        "name": "",
        "group": "e28e9683.8bc2e8",
        "order": 4,
        "width": 2,
        "height": 2,
        "passthru": true,
        "label": "Start",
        "tooltip": "",
        "color": "",
        "bgcolor": "green",
        "icon": "",
        "payload": "start",
        "payloadType": "str",
        "topic": "TIMER",
        "x": 170,
        "y": 180,
        "wires": [
            [
                "49b44f5a.132568",
                "e070cc33.ebeea"
            ]
        ]
    },
    {
        "id": "cf24c6ee.702378",
        "type": "ui_button",
        "z": "29662af6.96a146",
        "name": "",
        "group": "e28e9683.8bc2e8",
        "order": 5,
        "width": 2,
        "height": 2,
        "passthru": true,
        "label": "Stop",
        "tooltip": "",
        "color": "",
        "bgcolor": "red",
        "icon": "",
        "payload": "stop",
        "payloadType": "str",
        "topic": "TIMER",
        "x": 610,
        "y": 180,
        "wires": [
            [
                "8157f92c.9324e",
                "b7ffebb9.b20958"
            ]
        ]
    },
    {
        "id": "47ca99f6.2d9bf",
        "type": "ui_button",
        "z": "29662af6.96a146",
        "name": "",
        "group": "e28e9683.8bc2e8",
        "order": 6,
        "width": "2",
        "height": 2,
        "passthru": false,
        "label": "<b>!!! Reset !!!</b>",
        "tooltip": "",
        "color": "",
        "bgcolor": "red",
        "icon": "",
        "payload": "reset",
        "payloadType": "str",
        "topic": "TIMER",
        "x": 980,
        "y": 180,
        "wires": [
            [
                "b8b19971.5380a8",
                "f03e9c77.072b48"
            ]
        ]
    },
    {
        "id": "f03e9c77.072b48",
        "type": "link out",
        "z": "29662af6.96a146",
        "name": "reset",
        "links": [
            "7105f2a1.21812c",
            "ad7b2357.d2e098",
            "c1443e15.6728c8",
            "ab7c57db.9ca698",
            "a6cd94c7.4d6bd",
            "b91d3509.17212",
            "66ce8743.b007a8",
            "f6e1656c.d7af6",
            "a7abf569.ae721",
            "2d230649.2da632"
        ],
        "x": 1155,
        "y": 180,
        "wires": []
    },
    {
        "id": "ad7b2357.d2e098",
        "type": "link in",
        "z": "29662af6.96a146",
        "name": "reset-stop",
        "links": [
            "f03e9c77.072b48",
            "6a781a22.d020c4"
        ],
        "x": 515,
        "y": 180,
        "wires": [
            [
                "cf24c6ee.702378"
            ]
        ]
    },
    {
        "id": "9b7fbc0.3a3a0c8",
        "type": "change",
        "z": "29662af6.96a146",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "timeRemaining",
                "pt": "flow",
                "to": "timeRemaining",
                "tot": "msg"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 620,
        "y": 300,
        "wires": [
            []
        ]
    },
    {
        "id": "a3531003.a731d",
        "type": "ui_button",
        "z": "29662af6.96a146",
        "name": "",
        "group": "e28e9683.8bc2e8",
        "order": 7,
        "width": 2,
        "height": "2",
        "passthru": false,
        "label": "+1 min",
        "tooltip": "",
        "color": "",
        "bgcolor": "",
        "icon": "",
        "payload": "+1 min",
        "payloadType": "str",
        "topic": "TIMER",
        "x": 170,
        "y": 300,
        "wires": [
            [
                "77f6da55.553bcc",
                "ec0c7f2f.eb6ab8"
            ]
        ]
    },
    {
        "id": "77f6da55.553bcc",
        "type": "function",
        "z": "29662af6.96a146",
        "name": "",
        "func": "var timeRemaining = flow.get(\"timeRemaining\") || 0;\nvar addOne = 60000\n\ntimeRemaining += addOne;\n    \nflow.set(\"timeRemaining\", timeRemaining);\n\nmsg.timeRemaining = timeRemaining;\n\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "x": 420,
        "y": 300,
        "wires": [
            [
                "9b7fbc0.3a3a0c8"
            ]
        ]
    },
    {
        "id": "d74ba333.870fc8",
        "type": "change",
        "z": "29662af6.96a146",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "timeRemaining",
                "pt": "flow",
                "to": "timeRemaining",
                "tot": "msg"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 1240,
        "y": 300,
        "wires": [
            []
        ]
    },
    {
        "id": "6873a943.66294",
        "type": "ui_button",
        "z": "29662af6.96a146",
        "name": "",
        "group": "e28e9683.8bc2e8",
        "order": 8,
        "width": 2,
        "height": "2",
        "passthru": false,
        "label": "+5 min",
        "tooltip": "",
        "color": "",
        "bgcolor": "",
        "icon": "",
        "payload": "+5 min",
        "payloadType": "str",
        "topic": "TIMER",
        "x": 810,
        "y": 300,
        "wires": [
            [
                "e69b3482.115b38",
                "16cc4509.c631bb"
            ]
        ]
    },
    {
        "id": "e69b3482.115b38",
        "type": "function",
        "z": "29662af6.96a146",
        "name": "",
        "func": "var timeRemaining = flow.get(\"timeRemaining\") || 0;\nvar addOne = 60000*5\n\ntimeRemaining += addOne;\n    \nflow.set(\"timeRemaining\", timeRemaining);\n\nmsg.timeRemaining = timeRemaining;\n\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "x": 1040,
        "y": 300,
        "wires": [
            [
                "d74ba333.870fc8"
            ]
        ]
    },
    {
        "id": "e070cc33.ebeea",
        "type": "link out",
        "z": "29662af6.96a146",
        "name": "start",
        "links": [
            "c1443e15.6728c8"
        ],
        "x": 295,
        "y": 180,
        "wires": []
    },
    {
        "id": "b7ffebb9.b20958",
        "type": "link out",
        "z": "29662af6.96a146",
        "name": "stop",
        "links": [
            "c1443e15.6728c8"
        ],
        "x": 735,
        "y": 180,
        "wires": []
    },
    {
        "id": "ec0c7f2f.eb6ab8",
        "type": "link out",
        "z": "29662af6.96a146",
        "name": "+1min",
        "links": [
            "c1443e15.6728c8"
        ],
        "x": 275,
        "y": 300,
        "wires": []
    },
    {
        "id": "16cc4509.c631bb",
        "type": "link out",
        "z": "29662af6.96a146",
        "name": "+5min",
        "links": [
            "c1443e15.6728c8"
        ],
        "x": 915,
        "y": 300,
        "wires": []
    },
    {
        "id": "e17ffd4e.433bf",
        "type": "change",
        "z": "29662af6.96a146",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "timeRemaining",
                "pt": "flow",
                "to": "timeRemaining",
                "tot": "msg"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 620,
        "y": 360,
        "wires": [
            []
        ]
    },
    {
        "id": "486e099f.3951b",
        "type": "ui_button",
        "z": "29662af6.96a146",
        "name": "",
        "group": "e28e9683.8bc2e8",
        "order": 10,
        "width": 2,
        "height": "2",
        "passthru": false,
        "label": "-1 min",
        "tooltip": "",
        "color": "",
        "bgcolor": "",
        "icon": "",
        "payload": "-1 min",
        "payloadType": "str",
        "topic": "TIMER",
        "x": 170,
        "y": 360,
        "wires": [
            [
                "af55c2c6.06b228",
                "ffd60e8e.f7d84"
            ]
        ]
    },
    {
        "id": "af55c2c6.06b228",
        "type": "function",
        "z": "29662af6.96a146",
        "name": "",
        "func": "var timeRemaining = flow.get(\"timeRemaining\") || 0;\nvar addOne = 60000\n\ntimeRemaining -= addOne;\n    \nflow.set(\"timeRemaining\", timeRemaining);\n\nmsg.timeRemaining = timeRemaining;\n\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "x": 420,
        "y": 360,
        "wires": [
            [
                "e17ffd4e.433bf"
            ]
        ]
    },
    {
        "id": "374eb5e7.9cc7a2",
        "type": "change",
        "z": "29662af6.96a146",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "timeRemaining",
                "pt": "flow",
                "to": "timeRemaining",
                "tot": "msg"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 1240,
        "y": 360,
        "wires": [
            []
        ]
    },
    {
        "id": "24347276.8a2706",
        "type": "ui_button",
        "z": "29662af6.96a146",
        "name": "",
        "group": "e28e9683.8bc2e8",
        "order": 11,
        "width": 2,
        "height": "2",
        "passthru": false,
        "label": "-5 min",
        "tooltip": "",
        "color": "",
        "bgcolor": "",
        "icon": "",
        "payload": "-5 min",
        "payloadType": "str",
        "topic": "TIMER",
        "x": 810,
        "y": 360,
        "wires": [
            [
                "dee13c37.ab0b",
                "60a4437.54128bc"
            ]
        ]
    },
    {
        "id": "dee13c37.ab0b",
        "type": "function",
        "z": "29662af6.96a146",
        "name": "",
        "func": "var timeRemaining = flow.get(\"timeRemaining\") || 0;\nvar addOne = 60000*5\n\ntimeRemaining -= addOne;\n    \nflow.set(\"timeRemaining\", timeRemaining);\n\nmsg.timeRemaining = timeRemaining;\n\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "x": 1040,
        "y": 360,
        "wires": [
            [
                "374eb5e7.9cc7a2"
            ]
        ]
    },
    {
        "id": "ffd60e8e.f7d84",
        "type": "link out",
        "z": "29662af6.96a146",
        "name": "-1min",
        "links": [
            "c1443e15.6728c8"
        ],
        "x": 275,
        "y": 360,
        "wires": []
    },
    {
        "id": "60a4437.54128bc",
        "type": "link out",
        "z": "29662af6.96a146",
        "name": "-5min",
        "links": [
            "c1443e15.6728c8"
        ],
        "x": 915,
        "y": 360,
        "wires": []
    },
    {
        "id": "cc5aea0b.1742e",
        "type": "link out",
        "z": "29662af6.96a146",
        "name": "",
        "links": [],
        "x": 495,
        "y": 100,
        "wires": []
    },
    {
        "id": "a1304d26.6c6b98",
        "type": "inject",
        "z": "29662af6.96a146",
        "name": "Loop",
        "props": [
            {
                "p": "payload"
            }
        ],
        "repeat": "1",
        "crontab": "",
        "once": true,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 170,
        "y": 480,
        "wires": [
            [
                "f8f9c3af.4996c8"
            ]
        ]
    },
    {
        "id": "f8f9c3af.4996c8",
        "type": "function",
        "z": "29662af6.96a146",
        "name": "",
        "func": "var currentTime = msg.payload;\nvar lastTickTime = flow.get(\"lastTickTime\") || currentTime;\nvar timeRemaining = flow.get(\"timeRemaining\") || 0;\nvar gameState = flow.get(\"gameState\");\n\nif(gameState == \"playing\") {\n\n    timeRemaining -= (currentTime - lastTickTime);\n    \n    flow.set(\"timeRemaining\", timeRemaining);\n    \n}\n\nmsg.timeRemaining = timeRemaining;\nflow.set(\"lastTickTime\", currentTime);\n\nnode.status({fill:\"green\", shape:\"dot\", text:timeRemaining});\n\n\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "x": 340,
        "y": 480,
        "wires": [
            [
                "c7146f2b.9b30e"
            ]
        ]
    },
    {
        "id": "c7146f2b.9b30e",
        "type": "function",
        "z": "29662af6.96a146",
        "name": "Format time",
        "func": "var t = msg.timeRemaining / 1000;\nvar m = Math.floor(t / 60);\nvar s = Math.floor(t%60 % 60);\n\nmsg.formattedTimeRemaining = (\"0\" + m).slice(-2) + \":\" + (\"0\" + s).slice(-2);\n\nnode.status({fill:\"green\", shape:\"dot\", msg:flow.formattedTimeRemaining});\n\n\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "x": 530,
        "y": 480,
        "wires": [
            [
                "5970c5ac.84e9fc"
            ]
        ]
    },
    {
        "id": "5970c5ac.84e9fc",
        "type": "join",
        "z": "29662af6.96a146",
        "name": "",
        "mode": "custom",
        "build": "merged",
        "property": "",
        "propertyType": "full",
        "key": "topic",
        "joiner": "\\n",
        "joinerType": "str",
        "accumulate": true,
        "timeout": "",
        "count": "1",
        "reduceRight": false,
        "reduceExp": "",
        "reduceInit": "",
        "reduceInitType": "num",
        "reduceFixup": "",
        "x": 1090,
        "y": 480,
        "wires": [
            [
                "3e91b62d.cfd2e2"
            ]
        ]
    },
    {
        "id": "3e91b62d.cfd2e2",
        "type": "uibuilder",
        "z": "29662af6.96a146",
        "name": "Game Timer and Clue Display",
        "topic": "",
        "url": "atlantis",
        "fwdInMessages": false,
        "allowScripts": false,
        "allowStyles": false,
        "copyIndex": true,
        "showfolder": false,
        "x": 1300,
        "y": 480,
        "wires": [
            [],
            []
        ]
    },
    {
        "id": "e28e9683.8bc2e8",
        "type": "ui_group",
        "z": "",
        "name": "Timer",
        "tab": "759f7fcb.6ad23",
        "order": 1,
        "disp": false,
        "width": "8",
        "collapse": false
    },
    {
        "id": "759f7fcb.6ad23",
        "type": "ui_tab",
        "z": "",
        "name": "Atlantis",
        "icon": "dashboard",
        "disabled": false,
        "hidden": false
    }
]

This topic was automatically closed 60 days after the last reply. New replies are no longer allowed.