Runtime Defense
Published
Date locking is one of the easiest options to enable and one of the easiest to misunderstand. Setting a date that makes a build stop working feels like enforcement, and for a certain class of problem it genuinely solves something worth solving. But the mechanism is simpler than most people assume, and once you know exactly what it consults, both its real value and its limit become obvious.
What the guard actually does
When you enable an expiry date, the engine injects a small self-contained function that runs before your code. It creates an ordinary date object, reads the local calendar year, month and day from it, and composes those into a single eight-digit number in year, month, day order. It compares that number against the eight-digit value you configured. If today is earlier than your date, the function returns and your program proceeds. If it is not, the failure action runs.
The start-date lock is the mirror image. It returns and lets your program run once the current date has reached the configured value, and takes the failure action before then. Together they express a window.
There is no network request in any of this. No time server is consulted, no response header is read, nothing is signed. The entire decision is made from what the machine running the code says the date is.
Two consequences of reading the local calendar
The first is a matter of precision rather than security. Because the guard uses the local-calendar accessors rather than their universal-time equivalents, it changes state at midnight in each user's own timezone. Your build does not expire at one instant worldwide; it expires progressively, and across the full range of offsets in use that transition is spread over slightly more than a day. That is almost never a problem, but it does mean a date lock is the wrong tool for anything requiring a sharp cut-off, such as an embargo or a coordinated launch.
The second is the one that matters. The clock belongs to the user. Setting a system date backwards puts a machine back inside the permitted window, and the emitted guard neither detects nor resists that, because it has nothing to compare the clock against. This is not a flaw somebody neglected to fix. It is a structural property of evaluating a condition on hardware the other party controls, using a value that party can set. Everything else on this site that touches licensing lands on the same rule, and this is simply a particularly clean instance of it.
What it is genuinely good at
Naming the limit is not the same as dismissing the feature, and date locks earn their place in a category that gets less attention than it deserves: making sure stale artifacts stop being used.
An evaluation copy that expires on its own. A demonstration build handed out at a conference that does not still work eighteen months later. A time-boxed campaign asset. A beta that should not quietly remain in production after the real release. A pre-release sent to a reviewer or an analyst. In every one of these the objective is that old code stops circulating, and the counterparty is not an adversary at all — they are simply someone who will keep using whatever still works.
For that problem a date lock is a good, cheap, self-contained answer. It needs no infrastructure, survives the artifact being copied around, and works with no connectivity. Judged against that job rather than against a determined attacker, it does well.
When the date matters commercially
If a deadline carries money, the decision belongs on your server. Let the server decline to serve the data, issue the token or complete the operation once the date has passed. The client then discovers the expiry as a consequence of asking, rather than by assessing itself with information it controls.
The arrangement worth recommending is both together. The server stays authoritative. The date lock sits in the client as an independent second condition, so a build that has lost contact with your service — cached, mirrored, running offline against stale data — still becomes inert instead of continuing indefinitely. The two cover different failure modes, and having both is genuinely useful as long as nobody mistakes the client-side half for the enforcement. The broader version of this reasoning is in protecting a JavaScript license check.
Three configuration details worth knowing
The format is eight digits, no separators. The configuration schema pins this with a pattern requiring exactly eight digits, and the command line tooling normalises values into that shape. The runtime comparison itself parses the value leniently, so it is worth entering dates through a path that validates rather than one that does not.
Enabling the option without a value ships nothing. The engine emits a warning saying the option was enabled without a date and that no guard was injected. Build warnings are easy to scroll past, and a lock that never shipped looks identical to a lock that shipped and has not fired yet, so check this deliberately the first time.
The failure action is yours to choose. The date guard draws from the same action set as the other runtime defences: raise an error, blank the page, redirect, reload, invoke your own callback, or degrade. For anything a person will see, the callback is usually right, because an expired evaluation build that explains itself and offers a route forward is a much better outcome than one that simply stops working.
How to test it
Move a real machine's clock past the date and load the protected build, then move it back and confirm normal operation returns. That five-minute exercise catches the two mistakes people actually make: configuring the window in the wrong direction, and shipping a lock that was never injected because the value was missing. Test the failure action in the same pass, since it is the part users will meet and it is easy to leave on a default nobody chose on purpose.
The short version
The guard reads the user's own calendar, in the user's own timezone, with no network involved, and compares eight digits. That makes it a sound way to stop stale builds circulating and a poor way to enforce a commercial deadline. Use the validated eight-digit format, pick a failure action that explains itself, verify by moving a real clock, and keep anything that matters financially on the server where the person it constrains has no reach.
Frequently asked questions
How does a date lock in protected JavaScript actually decide the date?
It asks the browser, using the ordinary date object and the local calendar accessors for year, month and day. Those values are composed into an eight-digit number in year, month, day order and compared against the number you configured. There is no network request involved and no time server consulted. The whole decision is made from what the machine running the code believes the date to be, which is the single most important thing to understand about the feature.
Which direction does each lock work in?
The expiry lock allows the code to run while the current date is earlier than the value you set, and takes its failure action once the date reaches or passes it. The start lock is the mirror image: it allows the code to run once the current date has reached the value, and takes its failure action before then. Between them you can express a window, and the comparison is a plain numeric one on the eight-digit form, so there is no ambiguity about ordering.
Does the comparison use local time or coordinated universal time?
Local time, because the accessors it uses are the local-calendar ones rather than their universal-time counterparts. The practical effect is that the lock changes state at midnight in each user's own timezone, not at a single instant worldwide. Across the full range of offsets in use, the transition is spread over slightly more than a day. That is rarely a problem, but it means a lock is not a precise cut-off and you should not build anything that needs one on top of it.
What happens if a user changes their system clock?
The guard follows the clock, because the clock is the only thing it consults. Setting the date backwards puts a machine back inside the permitted window, and nothing in the emitted code detects or resists that. This is not a defect in the implementation so much as a property of the situation: any check evaluated on hardware the other party controls, using a value that party can set, is advisory. Treat a date lock as a control that keeps honest situations tidy, not one that holds against someone who does not want it to.
So what is a date lock genuinely useful for?
Build hygiene, which is a real and underrated need. An evaluation copy that stops working on its own, a demonstration build handed out at a conference, a time-boxed campaign asset, a beta that should not still be running in production a year later, a pre-release given to a reviewer: in all of these the goal is that a stale artifact stops being used rather than that a determined adversary is stopped. It removes a class of embarrassing situations where old code lingers, and it does that job well.
What should we use instead when the deadline actually matters commercially?
Put the decision on your server and give the client something it cannot compute for itself. If access to a paid feature ends on a date, the server should decline to serve the data, issue the token, or complete the operation after that date. The browser then discovers the expiry as a consequence of asking rather than as a self-assessment. This is the same principle that governs licensing generally, which is to place authority where the person you are protecting against has no reach, and it is the difference between a control and a courtesy.
Can we combine a date lock with a server check to get the best of both?
Yes, and it is the arrangement worth recommending. The server remains authoritative and refuses whatever it should refuse. The date lock sits in the client as a second, independent condition, so a stale build that has lost contact with your service still becomes inert rather than continuing to run indefinitely against cached data. The two failure modes are different enough that having both is genuinely useful, provided nobody mistakes the client-side one for the enforcement.
What format does the date value have to be in?
Eight digits in year, month, day order, with no separators. The configuration schema pins that shape explicitly with a pattern requiring exactly eight digits, and the command line tooling normalises values into it, so those paths will reject or correct a malformed entry before it reaches a build. Since the runtime comparison parses the value leniently, it is worth letting the validated paths do that checking for you rather than typing a date with separators somewhere that does not verify it.
What happens if the lock is enabled but no date is supplied?
Nothing is injected and you are told about it. The engine records a warning explaining that the option was enabled without a value and that no date guard was added to the output. Warnings from a build step are easy to scroll past, so this is worth checking deliberately the first time you configure the option, because a lock that silently did not ship looks exactly like a lock that shipped and has not triggered yet.
What does the code do when the date has passed?
It takes whichever failure action you selected, drawn from the same set the other runtime defences use: raising an error, blanking the page, redirecting, reloading, invoking a callback of your own, or degrading behaviour. The callback is the most useful of these for anything user-facing, because it lets you present an explanation and a route forward rather than a broken page. An expired evaluation build that says so plainly is a far better experience than one that simply stops.
How should we test a date lock before shipping it?
Move a test machine's clock forward past the date and load the protected build, then move it back and confirm normal operation returns. Doing this on a real device rather than reasoning about it catches the two mistakes people actually make, which are configuring the date in the wrong direction and shipping a lock that was never injected because the value was missing. Test the failure action at the same time, since that is what users will encounter and it is easy to leave on a default nobody chose deliberately.
What is the short version?
The date comes from the user's machine, in the user's timezone, with no network involved, so the guard is exactly as trustworthy as the clock it reads. That makes it a good tool for stopping stale builds and a poor one for enforcing a commercial deadline. Use the eight-digit format through a path that validates it, choose a failure action that explains itself, test by moving a real clock, and keep the actual enforcement on your server.
Related reading