Compatibility

Does Obfuscation Break Date and Time Handling?

Date is the part of JavaScript people trust least, so when a protected build produces a wrong timestamp the obfuscator is the first suspect. We built a sample that pins every date behaviour worth pinning to a fixed instant, ran it through five protection configurations, and diffed every line. Nothing moved. What follows is the measurement, and then the far more useful part: which date bugs are real, were already in your code, and survive protection perfectly intact.

What was measured, and how

Every value in the sample is derived from one fixed instant, Date.UTC(2026, 7, 15, 13, 45, 30, 250), so the output is deterministic and a diff means something. Nothing reads the wall clock, because a sample that calls Date.now() cannot be compared against itself.

The sample covers construction from UTC parts and from an epoch number, the ISO round trip, every UTC field accessor, parsing of both a full ISO string and a bare date-only string, arithmetic in milliseconds, month and day-of-month normalisation, leap years, invalid dates, comparison against equality, the coercion protocol, fixed-locale formatting, JSON serialisation, duration formatting, and sorting through a comparator.

All five configurations produced output identical to the original, line for line: the ES5 target, the modern target, the two identifier-renaming presets our emit-validation gate uses, and the string-table preset that moves and encodes every literal. Date is a built-in object reached through built-in method names, and none of the transforms has a reason to touch it.

The results worth quoting

The ISO round trip is exact. toISOString() returned 2026-08-15T13:45:30.250Z before and after protection, and getTime() returned 1786801530250 in every configuration. Milliseconds are preserved; nothing is truncated to whole seconds anywhere in the pipeline.

Parsing is unchanged, including the inconsistency people trip over. Date.parse("2026-08-15T13:45:30.250Z") and Date.parse("2026-08-15") return different instants because a date-only string is treated as UTC while some other formats are treated as local. That asymmetry is in the specification, it produced the same two numbers after protection, and it is not something a build step can fix or worsen.

Normalisation still normalises. Setting the month of 31 January to February produced 3 March, because the day-of-month overflowed. Adding three days to 30 January rolled into February correctly. Leap-year day counts came back 29 28 29 28 for 2024, 2025, 2000 and 1900, which is the correct answer including the century rule that catches out hand-written formulas.

Invalid dates stay invalid in the same way. new Date("not a date") gave a NaN time and stringified to Invalid Date identically before and after. Comparison operators still coerce through valueOf while === still compares object identity, so two Date objects for the same instant compared as neither less nor greater, subtracted to zero, and were still not === each other.

The bugs that survive protection perfectly

This is the part that matters more than the compatibility answer. Most reported date bugs in protected builds are date bugs full stop, and the protected build is simply the first place anyone looked closely.

The commonest is the date-only round trip. Taking toISOString().slice(0, 10) gives you a UTC calendar date, and if the user is west of Greenwich in the evening that is tomorrow from their point of view. Our sample does exactly this and produces the same string before and after protection, because the transform is faithful to code that was already ambiguous.

The second is comparison by identity. Code that checks dateA === dateB silently never matches, because every Date is a distinct object. Our sample records this explicitly: identical instants, === is false, getTime() equality is true. Protection preserves the bug precisely.

The third is host-dependent formatting. If you call toLocaleDateString() without pinning a locale and a time zone, the output depends on the machine. Our sample pins both (en-GB, UTC) so the result is stable and comparable. If your protected bundle formats dates differently on a customer's machine than on yours, pin the locale and the time zone and check again before suspecting the build.

Where renaming can reach, and where it cannot

Identifier renaming does not touch Date or its methods; they are built-in globals and built-in member names. Member renaming is the option with a real edge, and it behaves exactly as it does everywhere else on this site.

We measured it two ways. Renaming a property the sample owns changes the serialised output and nothing else: with a pattern matching when, JSON.stringify({ when: d }) went from {"when":"2026-08-15T13:45:30.250Z"} to {"_0x1":"2026-08-15T13:45:30.250Z"}. The date is intact; the key is not the one your API expects. That is the documented behaviour of member renaming against a wire format, and the fix is to scope the pattern.

Renaming a name that belongs to the language is the failure mode to avoid. With a pattern matching toISOString and getTime, the sample stopped at its first line, because those are Date's own methods and a renamed call finds nothing there. This is the same generalisation this site has now measured four times over: renaming is safe when a name is only reached by code you control, and unsafe when it is part of a contract with the language, a library, or your own string-based reflection.

Practical consequence: if you enable member renaming, scope MemberRegexp to your own field names and keep built-in method names out of it. The Protect Members documentation covers the pattern syntax.

What to do with this

You do not need a date-specific test plan for the protection step. Run the tests you already have against the protected bundle; if they passed before, the measurement says they will pass after.

If a date is wrong in a protected build, the productive order is: pin the locale and time zone in every formatting call, replace === comparisons with getTime() comparisons, and check whether a date-only string is being derived from a UTC instant. Those three account for most of what gets reported.

If you use member renaming and dates cross a network boundary, check the serialised key names rather than the values. The values are the part we could not make move; the keys are the part you control with the pattern.

One thing protection genuinely does not do is make a client-side clock trustworthy. If your licensing or trial logic compares against new Date() in the browser, that comparison runs on hardware the user owns and can change. That is a design question rather than a compatibility one, and it is covered in your JavaScript expiry date runs on their clock.

Frequently asked questions

Does obfuscation change how dates are calculated?

No. We measured construction, epoch arithmetic, UTC field access, month and day normalisation, leap years and parsing against protected copies on five configurations covering both the ES5 and modern targets, two identifier-renaming presets and the string table. Every line was identical, including the ISO round trip to the millisecond.

Can protection shift my timestamps into a different time zone?

No. Time zone handling lives in the Date implementation and the ICU data on the machine running the code, neither of which a build step touches. If a protected bundle shows a different time than your development machine, pin the locale and time zone explicitly in your formatting calls and compare again; unpinned toLocaleDateString output is host-dependent before protection as well.

Why does my date come out one day off after obfuscation?

Almost always because the code derives a calendar date from a UTC instant, typically with toISOString().slice(0, 10). That gives the UTC day, which differs from the user's local day for part of every 24 hours. We measured this exact pattern and it produced the same string before and after protection, so the off-by-one predates the build step.

Does the string table break date parsing?

No. The string-table preset moves and encodes every literal in the file, including date strings, and returns the same value at the point the literal used to sit. Date.parse produced identical epoch numbers for both a full ISO string and a bare date-only string under that preset.

Will member renaming affect dates?

Only the property names around them, not the dates themselves. Renaming a field called when changed the JSON key while leaving the ISO value untouched. Renaming a name belonging to Date itself, such as getTime or toISOString, breaks the call, because those are the language's names rather than yours. Scope MemberRegexp to your own fields.

Is Date.now() safe to use in protected code?

Yes, it behaves identically; we excluded it from the diff only because a value that changes every run cannot be compared against itself. What Date.now() cannot do, protected or not, is give you a clock the user cannot change. Anything security-relevant that depends on the current time needs a server to say what the time is.

Related reading