Converting Siemens DATE_AND_TIME to WinCC DateTime via VBA

David Krause16 min read
HMI / SCADASiemensTutorial / How-to
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

Converting Siemens DATE_AND_TIME to WinCC DateTime via VBA

Siemens S7 PLCs expose date and time as the legacy DATE_AND_TIME (DT) data type, an 8-byte BCD structure anchored to 01.01.1990. WinCC Comfort/Advanced and WinCC Professional internal HMI tags, however, only support the DateTime data type, which is a 64-bit floating-point value anchored to 30.12.1899 (the OLE Automation epoch). When you need to mirror a PLC DT value into an HMI internal tag without binding the tag to a PLC connection, the two epochs collide and the raw read looks decades off. The fix is a single arithmetic step in a WinCC VBA script: add 32874 days. This reference documents the byte layout, the epoch math, two production-ready VBA routines, and the verification steps to confirm the conversion is correct.

Reference: Siemens Support Entry 109775660 documents the day-offset rule applied when WinCC reads S7 DATE_AND_TIME values. Always cross-check the entry ID against the installed TIA Portal / WinCC version because the exact behavior of internal DateTime has evolved between WinCC V13, V15, V16, V17, V18, V19 and V20.

1. Problem Statement: DT vs. DateTime Mismatch in WinCC

When a WinCC tag is bound to a PLC connection pointing at an S7 DATE_AND_TIME tag, the WinCC driver transparently converts the 8-byte BCD value to an internal 64-bit DateTime and the screen shows the correct timestamp. As soon as the engineer attempts to use a WinCC internal tag (one without a PLC connection) of data type DateTime populated by a VBA script, the displayed value drifts by approximately 90 years. The cause is that the VBA path does not inherit the driver's automatic conversion and the engineer is now responsible for moving between two epochs manually.

Symptoms reported in the field include:

  • Time stamp displays 01.01.1990 instead of the current date.
  • Time stamp shows year 2079 or 2080 (off by exactly 89 years, 9 months from the BCD base).
  • Time stamp shows year 1900 (off by exactly 90 years in the negative direction).
  • Date portion is correct but the time of day is shifted by hours.
  • Script crashes with type-mismatch error 13 because the raw DT bytes were assigned to a String.

2. Siemens DATE_AND_TIME (DT) Format Specification

Per the Siemens TIA Portal reference for the DT data type, DATE_AND_TIME occupies 8 bytes and stores the value in BCD. The BCD layout is fixed and the field engine cannot reorder or reformat the bytes.

Byte Contents (BCD) Range Example for 23.05.2024 14:35:07.482
0 Year, hundreds + tens 19…20 16#20
1 Year, thousands + units 90…99 (for 1990…1999), 00…89 (for 2000…2089) 16#24
2 Month 01…12 16#05
3 Day 01&hellips31 16#23
4 Hour 00…23 16#14
5 Minute 00…59 16#35
6 Second 00…59 16#07
7 ms high nibble (BCD digit ×100) + ms mid nibble (BCD digit ×10) 00…09 (high), 00…09 (mid) 16#48
8 ms low nibble (BCD digit ×1) + weekday (low 3 bits) 00…09 (ms), 1…7 (Sun…Sat) 16#22 (=ms=2, weekday=2 = Monday)

The base year is 1990. Years 1990…1999 are encoded as 90…99 in the second byte; years 2000…2089 are encoded as 00…89. The day-of-week encoding follows ISO 8601 loosely: 1 = Sunday, 2 = Monday, …, 7 = Saturday. Reserve bit 7 of byte 8 (the high bit of the weekday nibble) is always 0 on S7-1500; some legacy S7-300 firmware writes a status flag there that the converter must mask off.

Reading the BCD bytes in WinCC: if the DT is read as a 64-bit raw value (e.g. via SmartTags("GLOB_DB_DT").Read returning a Variant containing an array of bytes), the array index in WinCC VBA is 1-based, so the year-low byte is at index 2 and the weekday nibble is at index 9.

3. WinCC DateTime Internals and the OLE Epoch

WinCC internal tags of type DateTime store values as 64-bit IEEE-754 floating-point numbers representing days since 30.12.1899 00:00:00. The fractional part encodes the time of day: 0.5 = 12:00:00 noon, 0.25 = 06:00:00, and so on. This is the same epoch used by VBA's Date and DateSerial functions, by COM VT_DATE, and by the Win32 SYSTEMTIME conversions performed inside the WinCC runtime. The epoch was chosen by Microsoft to maintain backward compatibility with the 1900 date system in Lotus 1-2-3.

The Siemens DT base (01.01.1990) and the WinCC DateTime base (30.12.1899) are separated by exactly 32 874 calendar days. That integer is the single most important constant in this conversion; every working VBA routine eventually uses it as a literal or as a named constant.

4. Why +32874: The Day-Offset Calculation

Counting days from 30.12.1899 (WinCC epoch) to 01.01.1990 (Siemens DT epoch):

  • 30.12.1899 → 30.12.1989 is exactly 32 873 full days.
  • Add one more day to reach 31.12.1989.
  • One more day lands on 01.01.1990.
  • Total: 32 874 + 1 = 32 875 days if you count both endpoints inclusively, but the standard serial-date convention counts the difference, giving 32 874 days.

Any time the driver (or your script) represents a Siemens DT as a day count since 01.01.1990, you must add 32 874 to land on the WinCC epoch. If the driver or your parser already returns a day count since 30.12.1899, the offset is zero. Always identify which epoch your intermediate value uses before applying the constant.

Source representation Required operation to reach WinCC DateTime
BCD byte array, 8 bytes (raw PLC value) Full parse: extract year/mo/day/hr/min/sec, build DateSerial/TimeSerial, sum.
Days since 01.01.1990 (DInt or Real) Add 32874.
Days since 30.12.1899 (OLE date) None, use directly.
String in ISO format "yyyy-MM-dd HH:mm:ss" Use CDate() in VBA, no offset needed.

5. Prerequisites

  • WinCC Comfort/Advanced V16 or later, or WinCC Professional V16 or later (TIA Portal). Earlier versions (V13/V14/V15) use the same math but require the legacy HMIRuntime object model in some edge cases.
  • Project with the VBA option enabled in the TIA Portal project properties (Runtime Settings → Scripts → Enable VBA).
  • Internal HMI tag dt_Internal of data type DateTime.
  • PLC tag "DB_HMI".dt_PLC of data type Date_And_Time exposed via a WinCC connection. If the PLC uses a CPU-local tag (e.g. %DB5.DBD0 as raw bytes), the conversion must start from the byte array, not from a numeric.
  • Trigger event: a scheduled task, a value-change event on a discrete tag, or a button click that runs the script.

6. Conversion Method A: Direct Numeric Offset via VBA

Use this method when the WinCC driver already exposes the PLC DATE_AND_TIME as a numeric Long / Double representing days since 01.01.1990 (this is the default for tags connected through the S7-1500 driver in TIA Portal V17+ when the HMI tag is typed as Long in an intermediate calculation step). The script reads the numeric, adds 32 874, and writes the result to the internal DateTime tag.

  1. Open the WinCC project in TIA Portal.
  2. Add an internal tag dt_Internal of type DateTime.
  3. Open the project tree → VBA macros → create a new module modDateConvert.
  4. Paste the following routine and wire it to the trigger event of your choice (typically the same trigger that currently updates the screen):
' modDateConvert - Convert PLC DT (days since 1990-01-01) to WinCC DateTime
' Constants
Public Const SIEMENS_DT_EPOCH_OFFSET As Long = 32874   ' days from 30.12.1899 to 01.01.1990

Public Sub ConvertPlcDT_ToInternalDateTime()
    Dim vRaw As Variant
    Dim dDays As Double
    Dim dWinCC As Double
    Dim oTag As HMITag
    
    ' 1. Read the PLC DT value (numeric: days since 01.01.1990)
    vRaw = SmartTags("DB_HMI_dummy")
    If IsEmpty(vRaw) Or IsNull(vRaw) Then Exit Sub
    
    ' 2. Coerce to Double; the driver may hand back Integer, Long or Double
    dDays = CDbl(vRaw)
    
    ' 3. Add the offset to land on the OLE 30.12.1899 epoch
    dWinCC = dDays + SIEMENS_DT_EPOCH_OFFSET
    
    ' 4. Write to the internal DateTime tag
    SmartTags("dt_Internal") = dWinCC
End Sub
Type-coercion safety: VBA's CDate() can also be used in step 4: SmartTags("dt_Internal") = CDate(dWinCC). Both forms write a valid WinCC DateTime; the direct Double form is marginally faster because it avoids a region/locale lookup.

To run the routine when the PLC updates the DT, schedule it on the same trigger. In WinCC Comfort/Advanced, right-click the internal tag in the HMI tags editor and select Properties → Events → Value change → and bind it to the ConvertPlcDT_ToInternalDateTime macro. For WinCC Professional, attach the same macro to the PLC tag's OnValueChanged event in the Graphics Designer.

7. Conversion Method B: Full BCD Parse to Date

Use this method when the PLC tag is a raw byte array (e.g. an S7-300 DATE_AND_TIME of type DT read through an Area Pointer or a non-integrated connection) or when you want maximum portability across firmware versions. The script walks the 8-byte BCD structure, decodes each nibble, and constructs a WinCC DateTime with DateSerial / TimeSerial.

' modDateConvert - Decode 8-byte BCD DATE_AND_TIME into WinCC DateTime
Public Sub DecodePlcDTBytes_ToInternalDateTime()
    Dim vRaw As Variant
    Dim b() As Byte
    Dim iYear As Integer, iMonth As Integer, iDay As Integer
    Dim iHour As Integer, iMin As Integer, iSec As Integer
    Dim iMs As Integer, iDow As Integer
    Dim dtResult As Date
    
    ' 1. Read the raw byte array exposed by the driver
    vRaw = SmartTags("DB_HMI_dummy")
    If Not IsArray(vRaw) Then
        SmartTags("dt_Internal") = 0          ' driver returned a scalar; abort safely
        Exit Sub
    End If
    b = vRaw
    
    ' 2. Decode year (BCD bytes 0 and 1, big-endian)
    '    b(0) = hundreds+tens nibble, b(1) = thousands+units nibble
    iYear = 2000 + (BCD(b(0)) * 100) + (BCD(b(1)) Mod 100)
    If iYear > 2089 Then iYear = iYear - 100   ' wrap for the rare >2089 case
    
    ' 3. Decode month and day (BCD bytes 2 and 3)
    iMonth = BCD(b(2))
    iDay = BCD(b(3))
    
    ' 4. Decode time of day (BCD bytes 4, 5, 6)
    iHour = BCD(b(4))
    iMin  = BCD(b(5))
    iSec  = BCD(b(6))
    
    ' 5. Decode milliseconds and day-of-week (BCD bytes 7 and 8)
    '    b(7) high nibble = ms*100, low nibble = ms*10
    '    b(8) high nibble = ms*1, low nibble = weekday (1..7)
    iMs = (BCD_HiNibble(b(7)) * 100) + (BCD_LoNibble(b(7)) * 10) + BCD_HiNibble(b(8))
    iDow = BCD_LoNibble(b(8)) And &H07       ' mask off any reserved high bit
    
    ' 6. Build a VBA Date and write to internal tag (no offset needed;
    '    DateSerial already returns a 30.12.1899-anchored serial number).
    dtResult = DateSerial(iYear, iMonth, iDay) + _
               TimeSerial(iHour, iMin, iSec) + _
               (iMs / 86400000#)
    
    SmartTags("dt_Internal") = dtResult
    SmartTags("iWeekday")   = iDow            ' optional internal tag, Integer
End Sub

Private Function BCD(ByVal b As Byte) As Integer
    BCD = (((b And &H F0) \ & H10) * 10) + (b And &H0F)
End Function

Private Function BCD_HiNibble(ByVal b As Byte) As Integer
    BCD_HiNibble = (b And &HF0) \ &H10
End Function

Private Function BCD_LoNibble(ByVal b As Byte) As Integer
    BCD_LoNibble = b And &H0F
End Function
BCD sanity check: if any decoded field exceeds its valid range (month > 12, day > 31, hour > 23, minute > 59, second > 59, ms > 999, dow > 7) the input is not a valid Siemens DT; abort and write 0 to dt_Internal to avoid an out-of-range DateSerial call which raises error 5 in VBA.

Method B has the advantage of being self-documenting: every byte of the BCD structure is visible in the code, so a maintainer can verify the conversion against the Siemens TIA Portal DATE_AND_TIME reference without external context.

8. Alternative: Skip VBA Entirely by Using DTL

On S7-1500 (firmware V2.0 or later) and TIA Portal V14 SP1 or later, the DTL data type is supported. DTL stores date and time in a structure with individual INT and DINT fields (year, month, day, hour, minute, second, nanosecond, weekday) and is mapped natively to WinCC DateTime through the integrated S7-1500 connection. The complete conversion collapses to a single tag-pointing exercise; no VBA is required. Reserve this approach for greenfield projects or for PLC programs you can modify without affecting other consumers of the legacy DT field.

Conversion in the PLC from DT to DTL is done with the standard library function DT_TO_DTL (defined in the Siemens IEC Standard Functions extended library). Once the variable is DTL, bind it to a WinCC tag of type DateTime; the driver handles the rest.

9. Verification Procedure

After deploying either method, verify the conversion is correct using the following steps. Each step has a binary pass/fail criterion so commissioning engineers can sign off unambiguously.

  1. Static check (offline): set the PLC clock to a known value, e.g. 15.03.2024 09:30:00. Trigger the VBA routine manually from the WinCC runtime by clicking a button bound to ConvertPlcDT_ToInternalDateTime. The internal tag dt_Internal must display 15.03.2024 09:30:00 in the WinCC tag simulator.
  2. Epoch boundary check: force the PLC to write 01.01.1990 00:00:00.482 to the DT field. The internal tag must display 01.01.1990 00:00:00 with the milliseconds in the source reflected in the lower three digits (482).
  3. Leap-year check: force the PLC to 29.02.2024 23:59:59. The internal tag must display 29.02.2024 23:59:59 (non-leap-year 29.02 must be rejected by the sanity filter).
  4. Round-trip check: subtract the offset (32 874) from the internal tag value with the formula Round((dt_Internal - 32874) * 86400); the result in seconds since 01.01.1990 must equal the PLC DT converted to seconds since 01.01.1990. Mismatches > 1 second indicate a BCD parse error.
  5. Daylight-saving check: if the PLC uses local time without DST adjustment, confirm that the displayed time matches the PLC's local-time view, not the UTC view, to avoid confusion during DST transitions.
  6. Performance check: trigger the routine 1 000 times in a tight loop and confirm the runtime of the conversion is < 50 ms on the target panel (Comfort Panels typically < 5 ms; WinCC Runtime Professional on a PC typically < 2 ms).

10. Troubleshooting Matrix

Symptom Likely root cause Diagnostic Fix
Internal tag shows 01.01.1990 for any input PLC tag not updating; VBA never reads a non-zero value Add a MsgBox in the VBA routine to display the raw read Verify the WinCC connection is online and the PLC connection name matches SmartTags(...) exactly
Internal tag shows the correct date but with year 1900 Subtracted 32874 instead of adding it (sign error) Check the line dWinCC = dDays + SIEMENS_DT_EPOCH_OFFSET Change the sign to plus
Internal tag shows date 89 years 9 months in the future Offset accidentally applied twice or value already in OLE epoch received the offset Read the raw vRaw and print it; if it is already a serial near today's date, remove the + 32874 Use Method B (full BCD parse) to remove ambiguity
Runtime error 13 (type mismatch) at SmartTags("dt_Internal") = ... Internal tag is configured as String or Integer instead of DateTime Open HMI tags editor and inspect the data type of dt_Internal Change the data type to DateTime
Runtime error 5 (invalid procedure call) inside DateSerial Decoded month or day out of range; BCD parse fed a non-DT value Add Debug.Print of every decoded field before DateSerial Add the BCD sanity filter described in Method B; reject the value rather than letting DateSerial raise
Internal tag updates correctly but weekday field is wrong by 1 Day-of-week nibble has the reserved high bit set (legacy S7-300 firmware) Inspect byte 8 in the diagnostics trace Apply the And &H07 mask in the weekday decode step
Conversion drifts by 1 hour twice a year PLC and HMI are using different time bases (local vs. UTC) and one of them applies DST Compare the PLC's Time function to the HMI's Now at the moment of the trigger Standardize on a single time base (UTC recommended) on both sides
Internal tag stays at the previous value after the trigger fires VBA event not bound; the macro exists but is never called Open the trigger object's event list and verify the macro name is selected Re-bind the macro to the trigger event

11. Field-Proven Caveats

  • Locale side effect: the DateSerial/CDate path is sensitive to the HMI panel's regional settings. On a panel configured for German (de-DE), CDate("03/04/2024") interprets the string as 3 April 2024; on English (en-US) the same string is 4 March 2024. Method B avoids this risk by using numeric DateSerial(y, m, d) exclusively.
  • Variant caching: VBA caches the value of SmartTags(...) within a single Sub only if the underlying tag changes during execution. The cache is invalidated at the next Sub boundary, so calling SmartTags("dt_Internal") in a loop always re-reads.
  • 32-bit overflow: the offset 32 874 plus today's serial (~45 600 for mid-2024) totals ~78 474, which fits comfortably in a 32-bit Long (max 2 147 483 647). If you ever compute seconds-since-epoch instead of days, the result is ~6.78 billion and overflows a 32-bit Long on TIA Portal VBA; coerce to Double immediately.
  • Trigger latency: on Comfort Panels, a value-change event fires within 100–250 ms of the underlying update. For high-resolution timestamps (1 ms granularity required), prefer a cyclic trigger at 100 ms or 250 ms rather than a value-change event.
  • Watchdog on the PLC side: the S7-1500 RD_SYS_T and S7-300 READ_CLK instructions write the entire DT atomically; the WinCC driver, however, reads the 8 bytes non-atomically. Under heavy CPU load, a value-change event can fire between byte 3 and byte 4 of the read, producing an inconsistent timestamp. Read the value twice and keep the result only if both reads match.

12. Quick-Reference Constants

Name Value Meaning
SIEMENS_DT_EPOCH_OFFSET 32874 Days from OLE epoch (30.12.1899) to Siemens DT epoch (01.01.1990)
SIEMENS_DT_BASE_YEAR 1990 First year representable in 2-byte BCD year field
SIEMENS_DT_MAX_YEAR 2089 Last year representable in 2-byte BCD year field
OLE_MS_PER_DAY 86400000 Milliseconds in a day, used to convert ms to fractional day
DT_BYTE_COUNT 8 Length in bytes of a Siemens DATE_AND_TIME

Why do I need to add 32874 days when reading a Siemens DATE_AND_TIME into a WinCC internal DateTime tag?

Siemens DATE_AND_TIME is anchored to 01.01.1990; WinCC DateTime (and VBA Date) is anchored to 30.12.1899. The two epochs are 32 874 days apart. If the driver hands you days-since-1990, add 32874; if it hands you the OLE serial directly, do nothing. Method A uses the offset; Method B builds a Date with DateSerial and never needs the offset.

Can I avoid VBA entirely and just bind the WinCC tag directly to the PLC DATE_AND_TIME?

Yes, if the tag is allowed to be a WinCC external tag with a connection to the PLC. The driver performs the conversion automatically. Internal tags (no PLC connection) cannot receive driver conversion, which is why VBA is used in the scenario above. Alternatively, on S7-1500 firmware V2.0+ and TIA Portal V14 SP1+, change the PLC data type to DTL and bind it to a WinCC DateTime tag; the driver handles the conversion natively without VBA.

My internal tag shows the correct date but the time is shifted by exactly 1 hour twice a year. What is wrong?

One side is applying daylight-saving time and the other is not. Confirm the PLC's time base (UTC vs. local) by inspecting the project properties or the time-master configuration, then match it on the HMI. The most robust setup is to keep the PLC in UTC and let the HMI display local time, so DST transitions are handled at the display layer only.

What is the byte order of the 8-byte BCD structure inside an S7-300 DB of type DATE_AND_TIME?

Year is stored big-endian across bytes 0 and 1 (hundreds-and-tens nibble first, then thousands-and-units), followed by month (byte 2), day (byte 3), hour (byte 4), minute (byte 5), second (byte 6). Bytes 7 and 8 contain the three BCD digits of milliseconds (4 bits each, big-endian) plus the day-of-week (1…7) in the low nibble of byte 8. This layout is documented in the TIA Portal reference for the DT data type.

My VBA script throws runtime error 13 (type mismatch) on the SmartTags write. What is the cause?

The internal tag is not configured as DateTime. Open the HMI tags editor, select dt_Internal, and change the data type to DateTime (not String, not Integer). After the change, recompile and download the runtime. A Double serial value written to a String or Integer tag is the typical cause of error 13.

Does this offset work on TIA Portal V13 / V14 / V15 or only on the latest V20?

The 32 874-day offset is a property of the calendar between 30.12.1899 and 01.01.1990 and does not change with TIA Portal versions. The conversion logic and the <offset, parse, write> pattern have been stable from WinCC Flexible 2008 through TIA Portal V20. The internal object model (SmartTags) and the HMI tag DateTime data type have also remained consistent across these versions.

Back to blog