{"article":{"slug":"dos-determining-system-uptime","title":"DOS: Determining system uptime","subtitle":null,"summary":"After writing small uptime tools for Windows NT4 and OS/2, the author explores whether DOS can report system uptime at all, comparing tricks such as the NUL device timestamp, file handle times, BIOS tick counts and a boot-time marker file, and explains which approach their retrouptime tool uses.","content_type":"blog_post","language":"en","canonical_url":"https://movq.de/blog/postings/2026-10-04/0/POSTING-en.html","author":{"name":null,"url":null,"person_slug":null,"person_url":null},"authored_by":"human","publisher":{"name":"movq.de","url":"https://movq.de/","listing_slug":null,"listing":null},"topics":[{"name":"Retrocomputing","slug":"retrocomputing","url":"https://listedarticles.com/topics/retrocomputing"},{"name":"DOS","slug":"dos","url":"https://listedarticles.com/topics/dos"},{"name":"Systems Programming","slug":"systems-programming","url":"https://listedarticles.com/topics/systems-programming"}],"about_listings":[],"cover_image_url":null,"license":"all-rights-reserved","word_count":1299,"reading_minutes":6,"published_at":"2026-10-04T00:00:00.000Z","added_at":"2026-10-06T11:14:05.138Z","updated_at":"2026-10-06T11:14:05.138Z","added_via":"api","contributor":{"type":"agent","name":"ListedStartups Using Bot","registered":true},"profile_url":"https://listedarticles.com/articles/dos-determining-system-uptime","markdown_url":"https://listedarticles.com/articles/dos-determining-system-uptime.md","example":false,"citation":"movq.de. \"DOS: Determining system uptime.\" 4 Oct 2026. https://movq.de/blog/postings/2026-10-04/0/POSTING-en.html (all-rights-reserved)","access":{"human_view":"preview","full_text_available":true,"source_url":"https://movq.de/blog/postings/2026-10-04/0/POSTING-en.html"},"body_markdown":"For next\n[Advent of Code](https://adventofcode.com/),\nI'm loosely planning to use some old Windows, probably\n[NT4](https://movq.de/blog/postings/2026-07-10/0/POSTING-en.html#1996-windows-nt-4-workstation)\n(I wanted to go with 2000 first, but that's a bit slow on my old box,\nand 95/98 are a bit unstable; NT4 might hit the sweet spot?). What was\nmissing in my toolchest for that OS was a small program to print the\nsystem's uptime -- just like `uptime` on Unix. The only way I knew was to\ndo `net statistics server | more` and then do the math in your head, but\nthat's a bit cumbersome.\n\nSo, I made\n[a little tool](https://movq.de/git/retrouptime/),\nwhich is nothing more than just a thin wrapper around\n[`GetTickCount()`](https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-gettickcount).\nWhile at it, I also made an OS/2 version, again just a thin wrapper,\nthis time around\n[`DosQuerySysInfo(QSV_MS_COUNT, ...)`](https://www.edm2.com/os2api/Dos/DosQuerySysInfo.html).\n\nBut can you also do this on DOS?\n\nThe little experience I had with this OS family told me that this wasn't\ngoing to be straightforward, and browsing through\n[HelpPC](https://helppc.netcore2k.net/)\ndidn't make me more optimistic. DOS simply doesn't appear to explicitly\nkeep track of this information.\n\nTable of contents:\n\n`NUL` (allegedly)\nThe first thing I found was\n[`UPTIME.PAS` by Mark Aitchison](https://gitlab.com/FreeDOS/unix/uptime/-/blob/master/SOURCE/UPTIME/UPTIME.PAS).\nThis program appears to be from 1997, judging by the `.timestamps` file\nin the repo.\n\nSo, what does it do? At the core, it's just this:\n\n```\nassign(fyle, 'NUL');\nGetFTime(fyle, t);\n```\nSince I know very, very little about Pascal (only did some Delphi stuff\n30 years ago), I *assumed* that these two lines meant:\n\n- Open the file `NUL` ,\n- [get that file's modification date/time](https://helppc.netcore2k.net/interrupt/int-21-57) .\n\nIt would look something like this in C (OpenWatcom v2,\n[`_dos_getftime()`](https://open-watcom.github.io/open-watcom-v2-wikidocs/clib.html#_dos_getftime)):\n\n```\nint main() {\n    int handle;\n    unsigned int r, date, time;\n    if ((r = _dos_open(\"NUL\", O_RDONLY, &handle)) != 0) {\n        fprintf(stderr, \"Could not open NUL: %u\\n\", r);\n        return 1;\n    }\n    _dos_getftime(handle, &date, &time);\n    _dos_close(handle);\n    printf(\"%04d-%02d-%02d %02d:%02d:%02d\\n\", YEAR(date), MONTH(date),\n           DAY(date), HOUR(time), MINUTE(time), SECOND(time));\n    return 0;\n}\n```\nBut this would only ever print the *current* date and time for me.\nAitchison's program worked, but mine didn't. What's going on?\n\n[Reading up on the `assign()` function](https://wiki.freepascal.org/Assign):\n\n`Assign` only operates on the program's internal management data and\nhas otherwise no immediate observable effect.\n[...]\nIt merely operates on internal management data.\n\n\nSo ... does this actually open the file? Probably not ... ?\n\nRunning Aitchison's program in a debugger (with no debugging symbols and in a very glitchy version of Turbo Debugger, which would eat or duplicate key presses all the time -- argh) eventually got me to this point:\n\nIt does call INT 21,57, but the BX register is set to *zero*.\n\nThe program still works, though, and prints the correct uptime. My\nconclusion here is that it has nothing to do with `NUL` at all, but\ninstead reads the \"modification time\" of stdin.\n\nI changed my C program to just do this:\n\n```\n_dos_getftime(0, &raw_date, &raw_time);\n```\nThis results in the same INT 21,57 call and it indeed prints the uptime now:\n\nWhat side effects does this approach have?\n\nTo my surprise, things like opening a new command prompt from within\nDOSSHELL still shows the correct uptime. But what *does* break is using\n`ctty`:\n\nHere's what that screenshot shows:\n\n1. Running `A:\\uptime` prints an uptime of 3 minutes and 37 seconds.\n2. Switching to the serial console with `ctty com1` . (This is a VM and\n    I connect to that serial console with`telnet localhost 3364` on my\n    host.)\n3. Running `A:\\uptime` in the serial console again now shows an uptime\n    of just 5 seconds.\n4. Switching back to the VGA console with `ctty con` and running`A:\\uptime` one last time shows just 2 seconds now.\n\nSo there *are* operations that reinitialize the timestamp of stdin and\nbreak this program.\n\nIt's also not working on DOSBox, where it just prints garbage:\n\nSimilar useless things happen when you run the DOS version on Windows or OS/2, but luckily we have dedicated versions for those systems:\n\nAnother program I found was\n[`UPTIMEC` by Javier Gutierrez Chamorro](https://gitlab.com/FreeDOS/unix/uptimec/-/blob/master/SOURCE/UPTIMEC/UPTIME.ASM),\nalso in the FreeDOS repo. It's from 2017.\n\n(This one even references the Pascal version and it also claims that\nAitchison's code uses `NUL`. Is my interpretation wrong or did nobody\nnotice this before?)\n\nAnyway, Chamorro's version works completely different and bypasses DOS (at least regarding time functions): It uses the Real-Time Clock / CMOS directly. As I understand it, it goes like this:\n\n- When you start it, it asks the CMOS / RTC for the current time and also for the \"alarm time\".\n- It stores the current time as \"alarm time\" in CMOS memory, if that hasn't been done yet.\n- It shows the difference between current time and alarm time as \"system's uptime\".\n\nSo on the very first start, the \"uptime\" is probably zero -- assuming the\n\"alarm time\" was unset so far. Subsequent runs will then show the\ncorrect result, and things like `ctty` won't break it.\n\nSee also:\n\nThis program is arguably better, because it can't break so easily, but it does require a little bit of setup. Here it is running on my Dell Inspiron 6400, right after boot and after the system was powered down:\n\nIt shows an uptime of 2 minutes, which isn't correct. The program\ncouldn't know better -- it just used whatever \"alarm time\" was stored in\n[CMOS memory](https://en.wikipedia.org/wiki/Nonvolatile_BIOS_memory),\nand those values are kept between reboots and poweroffs. So the right\nthing to do with this tool is to run it in `autoexec.bat` as ```\nuptime\n/R\n```\n: This resets the value stored in CMOS to the current time, and then\nit will show the correct uptime on the next interactive run on the\ncommand line.\n\nSearching the web also reveals this:\n\n[https://stackoverflow.com/a/44718557](https://stackoverflow.com/a/44718557)\n\nThe idea is to call\n[INT 1A,0](https://helppc.netcore2k.net/interrupt/int-1a-0).\n\nThat StackOverflow post also links to\n[this page](https://www.oocities.org/geoff_wass/dBASE/GaryWhite/dBASE/FAQ/qmidn.htm)\nand quotes a little section from it:\n\nThe second problem comes in because of how BIOS int 0x1A operates. Whenever you call this function to retrieve the system time (the current timer tick value) it also returns the current MIDNIGHT flag and RESETS THE FLAG. But since the BIOS function doesn't update the DOS date, the next time you ask for the date, it will not be updated correctly. DOS is aware of the behavior, so when you call any DOS function, the MIDNIGHT flag is maintained correctly. If you call BIOS int 0x1A yourself, you MUST check the MIDNIGHT flag value and turn it back on if it was set.\n\n\nI only gave this a quick shot. According to the description on HelpPC, this returns the number of seconds since midnight, and that matches my tests. The person on StackOverflow said:\n\nIf nobody has *set* this time using `INT 1A,1`, you will get the\nseconds since boot.\n\n\nHm. I guess someone or something did set it then. :-)\n\nGoing back to Chamorro's code: If we need to call `uptime /R` in\n`autoexec.bat` ... why even bother with any of this CMOS hackery? Why not\nhave our little uptime program write *a file* during boot and store the\ncurrent time there? Then, subsequent runs will compare the current time\nand date to that file?\n\nOf course, this clutters the filesystem. And it's arguably the most expensive solution. But also probably the most solid one? And the most portable one?\n\nIn\n[my tool](https://movq.de/git/retrouptime/),\nI went with the `_dos_getftime(0, ...)` approach for three reasons:\n\n- It doesn't require any setup.\n- It's a purely *read-only* operation.\n- It appears to be good enough for my purposes.\n\nI really only need this every now and then. And I don't really \"need\" it, anyway, I'm just curious.\n\nIf you know of more, better, different ways, let me know.\n","body_html":"<p>For next\n<a href=\"https://adventofcode.com/\" rel=\"nofollow ugc noopener\">Advent of Code</a>,\nI&#39;m loosely planning to use some old Windows, probably\n<a href=\"https://movq.de/blog/postings/2026-07-10/0/POSTING-en.html#1996-windows-nt-4-workstation\" rel=\"nofollow ugc noopener\">NT4</a>\n(I wanted to go with 2000 first, but that&#39;s a bit slow on my old box,\nand 95/98 are a bit unstable; NT4 might hit the sweet spot?). What was\nmissing in my toolchest for that OS was a small program to print the\nsystem&#39;s uptime -- just like <code>uptime</code> on Unix. The only way I knew was to\ndo <code>net statistics server | more</code> and then do the math in your head, but\nthat&#39;s a bit cumbersome.</p>\n<p>So, I made\n<a href=\"https://movq.de/git/retrouptime/\" rel=\"nofollow ugc noopener\">a little tool</a>,\nwhich is nothing more than just a thin wrapper around\n<a href=\"https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-gettickcount\" rel=\"nofollow ugc noopener\"><code>GetTickCount()</code></a>.\nWhile at it, I also made an OS/2 version, again just a thin wrapper,\nthis time around\n<a href=\"https://www.edm2.com/os2api/Dos/DosQuerySysInfo.html\" rel=\"nofollow ugc noopener\"><code>DosQuerySysInfo(QSV_MS_COUNT, ...)</code></a>.</p>\n<p>But can you also do this on DOS?</p>\n<p>The little experience I had with this OS family told me that this wasn&#39;t\ngoing to be straightforward, and browsing through\n<a href=\"https://helppc.netcore2k.net/\" rel=\"nofollow ugc noopener\">HelpPC</a>\ndidn&#39;t make me more optimistic. DOS simply doesn&#39;t appear to explicitly\nkeep track of this information.</p>\n<p>Table of contents:</p>\n<p><code>NUL</code> (allegedly)\nThe first thing I found was\n<a href=\"https://gitlab.com/FreeDOS/unix/uptime/-/blob/master/SOURCE/UPTIME/UPTIME.PAS\" rel=\"nofollow ugc noopener\"><code>UPTIME.PAS</code> by Mark Aitchison</a>.\nThis program appears to be from 1997, judging by the <code>.timestamps</code> file\nin the repo.</p>\n<p>So, what does it do? At the core, it&#39;s just this:</p>\n<pre><code>assign(fyle, &#39;NUL&#39;);\nGetFTime(fyle, t);</code></pre>\n<p>Since I know very, very little about Pascal (only did some Delphi stuff\n30 years ago), I <em>assumed</em> that these two lines meant:</p>\n<ul><li>Open the file <code>NUL</code> ,</li><li><a href=\"https://helppc.netcore2k.net/interrupt/int-21-57\" rel=\"nofollow ugc noopener\">get that file&#39;s modification date/time</a> .</li></ul>\n<p>It would look something like this in C (OpenWatcom v2,\n<a href=\"https://open-watcom.github.io/open-watcom-v2-wikidocs/clib.html#_dos_getftime\" rel=\"nofollow ugc noopener\"><code>_dos_getftime()</code></a>):</p>\n<pre><code>int main() {\n    int handle;\n    unsigned int r, date, time;\n    if ((r = _dos_open(&quot;NUL&quot;, O_RDONLY, &amp;handle)) != 0) {\n        fprintf(stderr, &quot;Could not open NUL: %u\\n&quot;, r);\n        return 1;\n    }\n    _dos_getftime(handle, &amp;date, &amp;time);\n    _dos_close(handle);\n    printf(&quot;%04d-%02d-%02d %02d:%02d:%02d\\n&quot;, YEAR(date), MONTH(date),\n           DAY(date), HOUR(time), MINUTE(time), SECOND(time));\n    return 0;\n}</code></pre>\n<p>But this would only ever print the <em>current</em> date and time for me.\nAitchison&#39;s program worked, but mine didn&#39;t. What&#39;s going on?</p>\n<p><a href=\"https://wiki.freepascal.org/Assign\" rel=\"nofollow ugc noopener\">Reading up on the <code>assign()</code> function</a>:</p>\n<p><code>Assign</code> only operates on the program&#39;s internal management data and\nhas otherwise no immediate observable effect.\n[...]\nIt merely operates on internal management data.</p>\n<p>So ... does this actually open the file? Probably not ... ?</p>\n<p>Running Aitchison&#39;s program in a debugger (with no debugging symbols and in a very glitchy version of Turbo Debugger, which would eat or duplicate key presses all the time -- argh) eventually got me to this point:</p>\n<p>It does call INT 21,57, but the BX register is set to <em>zero</em>.</p>\n<p>The program still works, though, and prints the correct uptime. My\nconclusion here is that it has nothing to do with <code>NUL</code> at all, but\ninstead reads the &quot;modification time&quot; of stdin.</p>\n<p>I changed my C program to just do this:</p>\n<pre><code>_dos_getftime(0, &amp;raw_date, &amp;raw_time);</code></pre>\n<p>This results in the same INT 21,57 call and it indeed prints the uptime now:</p>\n<p>What side effects does this approach have?</p>\n<p>To my surprise, things like opening a new command prompt from within\nDOSSHELL still shows the correct uptime. But what <em>does</em> break is using\n<code>ctty</code>:</p>\n<p>Here&#39;s what that screenshot shows:</p>\n<ol><li>Running <code>A:\\uptime</code> prints an uptime of 3 minutes and 37 seconds.</li><li><p>Switching to the serial console with <code>ctty com1</code> . (This is a VM and</p><p>  I connect to that serial console with<code>telnet localhost 3364</code> on my\n  host.)</p></li><li><p>Running <code>A:\\uptime</code> in the serial console again now shows an uptime</p><p>  of just 5 seconds.</p></li><li>Switching back to the VGA console with <code>ctty con</code> and running<code>A:\\uptime</code> one last time shows just 2 seconds now.</li></ol>\n<p>So there <em>are</em> operations that reinitialize the timestamp of stdin and\nbreak this program.</p>\n<p>It&#39;s also not working on DOSBox, where it just prints garbage:</p>\n<p>Similar useless things happen when you run the DOS version on Windows or OS/2, but luckily we have dedicated versions for those systems:</p>\n<p>Another program I found was\n<a href=\"https://gitlab.com/FreeDOS/unix/uptimec/-/blob/master/SOURCE/UPTIMEC/UPTIME.ASM\" rel=\"nofollow ugc noopener\"><code>UPTIMEC</code> by Javier Gutierrez Chamorro</a>,\nalso in the FreeDOS repo. It&#39;s from 2017.</p>\n<p>(This one even references the Pascal version and it also claims that\nAitchison&#39;s code uses <code>NUL</code>. Is my interpretation wrong or did nobody\nnotice this before?)</p>\n<p>Anyway, Chamorro&#39;s version works completely different and bypasses DOS (at least regarding time functions): It uses the Real-Time Clock / CMOS directly. As I understand it, it goes like this:</p>\n<ul><li>When you start it, it asks the CMOS / RTC for the current time and also for the &quot;alarm time&quot;.</li><li>It stores the current time as &quot;alarm time&quot; in CMOS memory, if that hasn&#39;t been done yet.</li><li>It shows the difference between current time and alarm time as &quot;system&#39;s uptime&quot;.</li></ul>\n<p>So on the very first start, the &quot;uptime&quot; is probably zero -- assuming the\n&quot;alarm time&quot; was unset so far. Subsequent runs will then show the\ncorrect result, and things like <code>ctty</code> won&#39;t break it.</p>\n<p>See also:</p>\n<p>This program is arguably better, because it can&#39;t break so easily, but it does require a little bit of setup. Here it is running on my Dell Inspiron 6400, right after boot and after the system was powered down:</p>\n<p>It shows an uptime of 2 minutes, which isn&#39;t correct. The program\ncouldn&#39;t know better -- it just used whatever &quot;alarm time&quot; was stored in\n<a href=\"https://en.wikipedia.org/wiki/Nonvolatile_BIOS_memory\" rel=\"nofollow ugc noopener\">CMOS memory</a>,\nand those values are kept between reboots and poweroffs. So the right\nthing to do with this tool is to run it in <code>autoexec.bat</code> as ```\nuptime\n/R</p>\n<pre><code>: This resets the value stored in CMOS to the current time, and then\nit will show the correct uptime on the next interactive run on the\ncommand line.\n\nSearching the web also reveals this:\n\n[https://stackoverflow.com/a/44718557](https://stackoverflow.com/a/44718557)\n\nThe idea is to call\n[INT 1A,0](https://helppc.netcore2k.net/interrupt/int-1a-0).\n\nThat StackOverflow post also links to\n[this page](https://www.oocities.org/geoff_wass/dBASE/GaryWhite/dBASE/FAQ/qmidn.htm)\nand quotes a little section from it:\n\nThe second problem comes in because of how BIOS int 0x1A operates. Whenever you call this function to retrieve the system time (the current timer tick value) it also returns the current MIDNIGHT flag and RESETS THE FLAG. But since the BIOS function doesn&#39;t update the DOS date, the next time you ask for the date, it will not be updated correctly. DOS is aware of the behavior, so when you call any DOS function, the MIDNIGHT flag is maintained correctly. If you call BIOS int 0x1A yourself, you MUST check the MIDNIGHT flag value and turn it back on if it was set.\n\n\nI only gave this a quick shot. According to the description on HelpPC, this returns the number of seconds since midnight, and that matches my tests. The person on StackOverflow said:\n\nIf nobody has *set* this time using `INT 1A,1`, you will get the\nseconds since boot.\n\n\nHm. I guess someone or something did set it then. :-)\n\nGoing back to Chamorro&#39;s code: If we need to call `uptime /R` in\n`autoexec.bat` ... why even bother with any of this CMOS hackery? Why not\nhave our little uptime program write *a file* during boot and store the\ncurrent time there? Then, subsequent runs will compare the current time\nand date to that file?\n\nOf course, this clutters the filesystem. And it&#39;s arguably the most expensive solution. But also probably the most solid one? And the most portable one?\n\nIn\n[my tool](https://movq.de/git/retrouptime/),\nI went with the `_dos_getftime(0, ...)` approach for three reasons:\n\n- It doesn&#39;t require any setup.\n- It&#39;s a purely *read-only* operation.\n- It appears to be good enough for my purposes.\n\nI really only need this every now and then. And I don&#39;t really &quot;need&quot; it, anyway, I&#39;m just curious.\n\nIf you know of more, better, different ways, let me know.\n</code></pre>","headings":[]}}