Registry Parser
All articles

System timezone and Bias: the RegRipper timezone plugin

7 min read

Before you read a single timestamp in a case, you read the timezone. The TimeZoneInformation key, parsed by the RegRipper timezone plugin, tells you the offset the system was running at, and that offset is the context for every local-time artifact you are about to interpret. Skip it and you will eventually present a timeline that is off by a fixed number of hours, which is the kind of error that gets noticed on cross-examination and quietly invalidates everything around it. The ActiveTimeBias value is the one that actually matters, and the difference between it and Bias is exactly the part people get wrong.

Where it lives

The data is in the SYSTEM hive at SYSTEM\CurrentControlSet\Control\TimeZoneInformation. One key, a handful of values, no subkeys. It is small, it is fast to read, and it should be the first thing you look at when a hive lands on your desk.

The values that matter:

  • TimeZoneKeyName: the canonical Windows timezone identifier, e.g. Pacific Standard Time or Romance Standard Time. This is the unambiguous name. It maps to an IANA zone (America/Los_Angeles, Europe/Paris) and to the rules that govern DST.
  • Bias: the base offset from UTC in minutes, as a signed 32-bit integer. This is the standard-time offset for the zone, before any seasonal adjustment.
  • ActiveTimeBias: the offset that was actually in effect, in minutes. This already includes the DST adjustment if daylight time was active. This is the one you convert with.
  • StandardName / DaylightName: human-readable display strings for the two seasonal states, e.g. Pacific Standard Time and Pacific Daylight Time. Cosmetic, but useful for sanity-checking.
  • StandardBias / DaylightBias: additional minute offsets applied on top of Bias in each state. StandardBias is almost always 0; DaylightBias is typically -60.

The on-disk layout for the bias fields is little-endian, and the values are stored as REG_DWORD. Because they are signed, a zone west of UTC carries a positive bias and a zone east carries a negative one, which feels backwards until you internalize the formula below.

The RegRipper timezone plugin reads this key from the SYSTEM hive and prints the values one per line. The parser on this site surfaces TimeZoneKeyName, Bias, ActiveTimeBias, and StandardName as a four-row table — enough to establish the offset and label it. If you need DaylightBias or StandardBias explicitly, read them straight off the key in the tree view; they are right there next to the values the plugin promotes.

How Bias works

The mental model that never fails:

UTC = local time + Bias

Bias is in minutes, signed, and it is the number you add to local time to get UTC. A host in Pacific Standard Time has Bias = 480 (eight hours west, expressed as a positive number because you add it to go from local to UTC). A host in Central European Time has Bias = -60 (one hour east).

That sign convention is the trap. People reason "I'm at UTC-8, so the offset is -8" and then add a negative number, landing two hops away from the truth. Windows stores it as the value you add to local to reach UTC, so PST is +480, not -480. Trust the formula, not your intuition about the sign.

The full offset Windows actually applies is:

EffectiveBias = Bias + (DaylightBias if DST active, else StandardBias)
ActiveTimeBias = EffectiveBias

Which is why ActiveTimeBias exists as its own value: it is the precomputed sum, the offset that was genuinely in force at the moment the system last wrote it. You do not have to do the addition yourself if you trust ActiveTimeBias, and most of the time you should.

ActiveTimeBias vs Bias: the DST nuance

Bias is the zone's standard offset. ActiveTimeBias is the offset that was in effect when the value was last updated.

In winter, with no DST, the two are equal. In summer, with DST active, ActiveTimeBias = Bias + DaylightBias. For Pacific time that is 480 + (-60) = 420 — UTC-7 instead of UTC-8, which is correct: PDT is one hour closer to UTC than PST.

The reason this matters for conversion is subtle and worth stating plainly. ActiveTimeBias reflects the DST state at the time the registry value was last written, which is roughly the acquisition time of the hive, not the time of the event you are converting. If your artifact's local timestamp falls in a different DST season than the one captured in ActiveTimeBias, blindly applying ActiveTimeBias is wrong by an hour.

So: use ActiveTimeBias for events near the acquisition time. For events in another season, compute the effective bias for that date using Bias, DaylightBias, and the zone's DST transition rules (derived from TimeZoneKeyName). This is the failure mode that produces a timeline that is correct in October and an hour off in July.

Why you must establish the timezone first

Most forensic timestamps are stored in UTC. FILETIME values — UserAssist LastRun, BAM execution times, USBSTOR device install times — are 64-bit UTC and need no timezone to interpret. For those, the timezone is irrelevant to the conversion itself.

But two categories make the timezone load-bearing:

SYSTEMTIME-based artifacts store local time. The SYSTEMTIME structure has no timezone field — it is wall-clock time as the machine saw it. The classic example is the NetworkList plugin: DateCreated and DateLastConnected under Signatures\Unmanaged are SYSTEMTIME blobs in local time. To put a "first connected to this network" event on a UTC timeline, you must add the bias. Without TimeZoneInformation, that timestamp is uninterpretable in any absolute sense.

Any tool that renders local time inherits the host's timezone, or worse, the analyst's. Event Viewer on the examiner's workstation shows event times in the examiner's local zone unless told otherwise. A spreadsheet of "log times" pasted from a triage box may already have a hidden offset baked in. The only defense is to know the source system's offset, record it, and normalize everything to UTC explicitly.

The discipline is simple: read TimeZoneInformation, write down TimeZoneKeyName and ActiveTimeBias, and treat every local-time artifact as suspect until you have applied the bias on the record.

A worked conversion

Suppose the SYSTEM hive gives you:

TimeZoneKeyName: Pacific Standard Time
Bias:            480
ActiveTimeBias:  420
StandardName:    Pacific Standard Time
DaylightBias:    -60

ActiveTimeBias = 420 tells you the host was on PDT (UTC-7) when the hive was written. Now NetworkList hands you a DateLastConnected of:

2026-06-15 09:30:00  (local, SYSTEMTIME)

June is DST season, so PDT applies and ActiveTimeBias = 420 is the right offset for this date. Apply the formula:

UTC = local + bias(minutes)
UTC = 2026-06-15 09:30:00 + 420 minutes
UTC = 2026-06-15 16:30:00 UTC

The device last connected to that network at 16:30 UTC. Put that on the timeline, never the 09:30 local value, and annotate it as converted from PDT.

Now flip the season. A second NetworkList entry reads 2026-01-10 22:00:00 local. January is standard time, so the correct offset is Bias = 480, not ActiveTimeBias = 420:

UTC = 2026-01-10 22:00:00 + 480 minutes
UTC = 2026-01-11 06:00:00 UTC

Note the date rollover, and note that using ActiveTimeBias here would have put it at 05:00 — an hour wrong, because the hive was acquired in summer and that value carries the summer offset. This is the entire reason the DST nuance is worth a paragraph.

The CurrentControlSet gotcha

CurrentControlSet is not a real key on disk. The SYSTEM hive stores numbered control sets — ControlSet001, ControlSet002 — and a Select key whose Current value points at the one that was active. When Windows is running it exposes the active set as the CurrentControlSet symlink, but an offline hive has no such symlink. A tool that hard-codes ControlSet001 is guessing, and on a machine that has failed over to a different set, guessing wrong gives you a stale TimeZoneInformation from a previous configuration.

The plugin resolves this the correct way: it reads Select\Current, builds the real control-set path (ControlSet00N), and reads TimeZoneInformation from there. Every SYSTEM-hive plugin on this site routes through the same control-set resolution, so Bias comes from the set that was actually in force, not a hard-coded number. When you cross-check against another tool, confirm it resolved the control set rather than assuming 001; a mismatch in ActiveTimeBias between two tools is very often this and nothing more.

Tools

  • RegRipper's timezone plugin (source) reads TimeZoneInformation and prints TimeZoneKeyName, Bias, ActiveTimeBias, and the standard/daylight names. It is the canonical reference for what the key contains. See the RegRipper plugins reference for the wider plugin set.
  • Eric Zimmerman's Registry Explorer / RECmd will dump the same values; the Bias field shows as a signed DWORD.
  • You can analyze a SYSTEM hive in your browser and read TimeZoneInformation directly, with control-set resolution handled for you, before you touch any local-time artifact.

The habit

Make timezone the first plugin you run on any SYSTEM hive, and write the offset at the top of the case notes where every later conversion can see it. The single most common timeline error in DFIR is not a misread artifact — it is a correctly read local timestamp presented as if it were UTC. TimeZoneInformation is a three-second read that prevents an entire category of mistakes. Then go convert your NetworkList timestamps, because those are the ones that depend on it.