but was there ever any software that would access the CLOCK$ device file directly, or did DOS do any operations on the file itself internally?
Hard to say for user side software, but DOS did internally query the clock driver whenever date and/or time was needed for file handling or requested by a user program.
I think there could be some internal hooks that would use whatever functions provided by a clock driver installed to the system,
No, there were no internal hooks, DOS only used the public read/write interface via the device driver. The only shortcut was what it didn't open the device via it's name, but did a lookup using a dedicated bit in the driver attribute word to find the active system clock driver. That way it could call the driver without opening it first.
but I'm more interested if there was any use for the device file directly,
So other programs could as well use the driver direct, without going thru DOS first. In addition, the CLOCK driver made a prime example to write a device driver :))
The CLOCK Device
From the MS-DOS 3.3 Programmer's Reference:
2.10 The CLOCK Device
MS-DOS assumes that some sort of clock is available in the system.
This may either be a CMOS real-time clock or an interval timer that
is initialized at boot time by the user. The CLOCK device defines and
performs functions like any other character device, except that it is
identified by a bit in the attribute word. The DOS uses this bit to
identify it; consequently, the CLOCK device may take any name. The IBM
implementation uses the name $CLOCK so as not to conflict with existing
files named clock.
The CLOCK device is unique in that MS-DOS will read or write a 6-byte
sequence that encodes the date and time. A write to this device will set
the date and time; a read will get the date and time.
Figure 2.7 illustrates the binary time format used by the CLOCK device:
byte 0 byte 1 byte 2 byte 3 byte 4 byte 5
+--------+--------+---------+--------+--------+---------+
| | | | | | |
|days since 1-1-80| minutes | hours | sec/100| seconds |
|low byte|hi byte | | | | |
+--------+--------+---------+--------+--------+---------+
Figure 2.7 Format of a Clock Device
(Note the spelling of $CLOCK for PC-DOS)
Usage
The Clock device driver offered an interface to query or set date and time. In all respects CLOCK$ defines and performs functions like any other character device, though most functions will clear the error bit, set the done bit and return. Reading or writing is done with exactly 6 byte transferred:
DOS does not do time keeping, but reads CLOCK$ whenever date/time is needed. This includes user requests for date (INT 21h Function 2Ah) or time ($2C), but as well all (writing) file access. Similar it writes to CLOCK$ when date ($2B) or time ($2D) is set.
The default CLOCK$ driver uses the systemtick (INT 1Ah) for time keeping (*1). Drivers for clock cards do (usually) read their respective Real Time Clock (RTC), converting to above format. There are as well third party drivers doing so for AT-class machines, as the default driver does not honour the build in RTC, except for first time initialisation.
*1 - This, BTW, introduces two very specific errors. Reading the system tick via INT 1Ah Function 00h delivers a 32 bit number of clock ticks (18.2 per second) since midnight in CX:DX and a roll-over flag in AL. A non-zero value signals that a roll-over has happened. Whenever the counter reaches 1573040, it'll be reset to zero and the flag for roll-over set. The flag is reset after the first query reporting it. This introduces two subtile errors:
If any user program queries INT 1Ah/00h after midnight, but before the default clock driver does, then the CLOCK diver will not see it, thus not advance the date.
If more than a day passes between two calls (like on a PC idling over a weekend), the day is still only advanced by a single day, thus loosing one or more days.
In some DOS versions the flag was turned into a counter, recording the days passed, eliminating the second error. This explains why direct RTC reading drivers were quite welcome :))