Problem Description
When archiving tag values from WinCC Runtime V7.4 SP1 to Microsoft SQL Server 2014 via VBScript (or C script), the most common failure is a conversion error at the moment the INSERT statement touches a datetime, date, datetime2, or smalldatetime column. The WinCC runtime sends a date string that the SQL parser cannot interpret, and the batch is rejected with one of the following messages:
Msg 241, Level 16, State 1, Line 1: Conversion failed when converting date and/or time from character string.Msg 242, Level 16, State 3, Line 1: The conversion of a varchar data type to a datetime data type resulted in an out-of-range value.Msg 296, Level 16, State 3, Line 1: The conversion of char data type to smalldatetime data type resulted in an out-of-range value for smalldatetime value.
A typical installation logs an integer power reading (kW) once per hour from a power meter. The tag archive delivers the value fine, but the timestamp column throws the error above. The fault is reproducible and, more importantly, intermittent when the regional settings of the WinCC station, the SQL Server login, and the default language of the database user drift out of alignment.
Root Cause: Date Format vs. Date Order
SQL Server parses character-string → datetime conversions through two independent mechanisms:
- The connection's language (set by
SET LANGUAGEor by the login's default language). - The session's active DATEFORMAT setting (set by
SET DATEFORMAT, which overrides the language default).
WinCC's built-in VBScript functions (Date, Time, Now, FormatDateTime) return strings in the regional short-date format of the runtime PC. On a German Windows installation that is dd.mm.yyyy; on a US installation M/d/yyyy; on a UK installation dd/mm/yyyy. SQL Server's default SET LANGUAGE US_ENGLISH expects mdy in most builds, so a value like 05/12/2019 is read as month 5, day 12 on a US host and day 5, month 12 on a German host. When the runtime sends 2019-12-05 thinking it is year-day-month, SQL Server interprets it as month 13, day 5 and raises Msg 242.
| Runtime Windows locale | VBScript Now output |
SQL Server us_english parses as |
Result |
|---|---|---|---|
| de-DE | 05.12.2019 14:05:09 | Not convertible (period not allowed) | Msg 241 |
| en-GB | 05/12/2019 14:05:09 | May 12, 2019 | Wrong value, no error |
| en-US | 12/5/2019 2:05:09 PM | Dec 5, 2019 | OK |
| nl-NL | 5-12-2019 14:05 | May 12, 2019 (dash ignored) | Wrong value |
Because the failure depends on the combination of runtime locale, SQL login default language, and DATEFORMAT, a system that ran for years can break when one of the three is changed (for example, applying a Windows cumulative update, switching the SQL login, or installing a language pack on the engineering station).
Why the "SET LANGUAGE" Workaround Is Fragile
Setting the SQL Server login default language to match the WinCC station is a frequent recommendation, and it often appears to work because the same engineer touches the same machine for years. It fails under three documented conditions:
-
Connection pooling reuses a session with the previous
DATEFORMAT. When WinCC opens the second connection in the pool, the residualSET DATEFORMAT dmyfrom a prior call is still active because the connection was not reset. Usesp_reset_connectionin the connection string or setSET DATEFORMATexplicitly in every batch. -
The login's default language is overridden by an explicit
SET LANGUAGEin a stored procedure or trigger invoked by theINSERTpath. This is common when the application callsmaster.dbo.xp_execresultsetor runs inside a job step. -
Database compatibility level is below 130.
datetime2and theFORMAT()function have stricter parsing rules than the legacydatetimetype, so a string that worked ondatetimein SQL 2014 can fail ondatetime2after a migration to SQL 2017+.
SET LANGUAGE alone. Always combine it with SET DATEFORMAT at the top of the archiving batch, or — preferably — eliminate implicit string conversion entirely.
Solution 1 — Build the Date in VBScript (Recommended for Simple Logs)
For a single-process WinCC station logging one tag at a fixed cadence, build the timestamp from integer components. Integer components have no regional interpretation, so the string mm/dd/yyyy hh:nn:ss is delivered exactly as written, in the same order that us_english expects.
' WinCC V7.4 SP1 — Global Script (VBS) action
' Trigger: Tag "Trigger_Log" change, cyclic every 3600 s
Dim oConn, oRs, sSQL, sErr
Dim MyYear, MyMonth, MyDay, MyTime, Datum
MyYear = Year(Date)
MyMonth = Month(Date)
MyDay = Day(Date)
MyTime = Time
' Always emit mm/dd/yyyy hh:nn:ss — unambiguous for us_english
Datum = Right("0" & MyMonth, 2) & "/" & _
Right("0" & MyDay, 2) & "/" & _
MyYear & " " & MyTime
Set oConn = CreateObject("ADODB.Connection")
oConn.ConnectionString = _
"Provider=SQLOLEDB;" & _
"Data Source=.\WINCC;" & _
"Initial Catalog=EnergyArchive;" & _
"User ID=wincc_logger;" & _
"Password=********;" & _
"AutoTranslate=False;" & _
"Language=us_english;"
oConn.Open
sSQL = "INSERT INTO dbo.PowerLog (LogTime, kW) VALUES ('" & _
Datum & "', " & CStr(CLng(SmartTag("KW_Actual"))) & ")"
oConn.Execute sSQL, , adExecuteNoRecords
oConn.Close
Set oConn = Nothing
Key points of the snippet:
-
Right("0" & MyMonth, 2)zero-pads single-digit months to04instead of4; SQL Server accepts both, but the padded form is required fordatetime2columns that are stored with millisecond precision inODBC canonical (yyyy-mm-dd hh:mm:ss.fff)form. - The connection string explicitly sets
Language=us_englishso the OLE DB provider issuesSET LANGUAGE 'us_english'on every new physical connection. -
AutoTranslate=Falseprevents the OLE DB layer from converting character sets on a non-ANSI driver (CP1252 vs. CP850 on legacy installations).
Solution 2 — Use ISO 8601 Format and Set DATEFORMAT
SQL Server always recognises the ISO 8601 literals yyyy-mm-ddThh:mm:ss and yyyy-mm-dd hh:mm:ss regardless of language, provided the date order is preserved. This is the format Microsoft documents in Date and Time Data Types and Functions (Transact-SQL):
-- ISO 8601 is always parsed in ymd order by SQL Server
SET DATEFORMAT ymd;
INSERT INTO dbo.PowerLog (LogTime, kW)
VALUES ('2019-12-05T14:05:09', 412);
From VBScript this becomes:
Dim d, t, iso
d = Year(Date) & "-" & Right("0" & Month(Date), 2) & "-" & Right("0" & Day(Date), 2)
t = Right("0" & Hour(Time), 2) & ":" & Right("0" & Minute(Time), 2) & ":" & Right("0" & Second(Time), 2)
iso = d & "T" & t
sSQL = "SET DATEFORMAT ymd; INSERT INTO dbo.PowerLog (LogTime, kW) VALUES ('" & iso & "', " & _
CStr(CLng(SmartTag("KW_Actual"))) & ")"
Combining the explicit SET DATEFORMAT ymd with the ISO literal makes the script independent of the SQL login's default language. The OLE DB Language= parameter in the connection string is then optional.
Solution 3 — Use the Server Clock (GETDATE() / SYSDATETIME())
If the SQL Server and the WinCC runtime PC are time-synchronised (recommended: same NTP source, max ±1 s skew), the most robust approach is to not send the timestamp from WinCC at all. Let the database stamp the row with GETDATE(), SYSDATETIME(), or SYSUTCDATETIME(). This removes every locale-related variable from the equation.
-- Table definition with a default
CREATE TABLE dbo.PowerLog (
LogID INT IDENTITY(1,1) PRIMARY KEY,
LogTime DATETIME2(3) NOT NULL
CONSTRAINT DF_PowerLog_LogTime DEFAULT (SYSDATETIME()),
kW INT NOT NULL
);
' WinCC VBS — only the value is sent, the server fills the timestamp
oConn.Execute "INSERT INTO dbo.PowerLog (kW) VALUES (" & _
CStr(CLng(SmartTag("KW_Actual"))) & ")", , adExecuteNoRecords
Function reference per Microsoft T-SQL date/time documentation:
| Function | Return type | Precision | Time zone |
|---|---|---|---|
GETDATE() |
datetime |
~3.33 ms (rounded to .000, .003, .007) | Server local |
SYSDATETIME() |
datetime2(7) |
100 ns | Server local |
SYSUTCDATETIME() |
datetime2(7) |
100 ns | UTC |
SYSDATETIMEOFFSET() |
datetimeoffset(7) |
100 ns | Local + offset |
Solution 4 — Convert Explicitly in the INSERT
When you must pass the timestamp as a string but cannot guarantee SET DATEFORMAT, wrap the value in CONVERT with an explicit style. The CONVERT style is parsed before the language lookup, so the string is interpreted purely by its pattern. Reference the complete style list in the T-SQL date and time documentation.
-- Style 120 = ODBC canonical 'yyyy-mm-dd hh:mm:ss'
INSERT INTO dbo.PowerLog (LogTime, kW)
VALUES (CONVERT(datetime2(3), '2019-12-05 14:05:09', 120), 412);
-- Style 101 = US 'mm/dd/yyyy'
INSERT INTO dbo.PowerLog (LogTime, kW)
VALUES (CONVERT(datetime2(3), '12/05/2019 14:05:09', 101), 412);
-- Style 104 = German 'dd.mm.yyyy'
INSERT INTO dbo.PowerLog (LogTime, kW)
VALUES (CONVERT(datetime2(3), '05.12.2019 14:05:09', 104), 412);
| Style | Pattern | When to use |
|---|---|---|
| 101 | mm/dd/yyyy | US locale WinCC |
| 103 | dd/mm/yyyy | UK, Australia, most of EU |
| 104 | dd.mm.yyyy | Germany, Austria, Switzerland |
| 105 | dd-mm-yyyy | Italy, Netherlands |
| 120 | yyyy-mm-dd hh:mm:ss | Universal — ODBC canonical |
| 121 | yyyy-mm-dd hh:mm:ss.nnn | Universal with milliseconds |
Solution 5 — Switch the Column to a Non-Ambiguous Type
Store the timestamp as datetime2 or datetimeoffset and let WinCC send the value as a numeric OLE Automation date (a Double holding days since 1899-12-30). This bypasses all string parsing and is the cleanest solution for high-rate logs.
' WinCC VBS — send as numeric OLE Automation date
Dim dValue
dValue = CDbl(DateAdd("d", 0, DateSerial(Year(Date), Month(Date), Day(Date)))) + _
(Timer / 86400)
oConn.Execute "INSERT INTO dbo.PowerLog (LogTime, kW) " & _
"VALUES (CAST(" & CStr(dValue) & " AS datetime2(3)), " & _
CStr(CLng(SmartTag("KW_Actual"))) & ")"
On the SQL side, wrap the value with CAST(... AS datetime2(3)) so the OLE DB provider knows to use the numeric → datetime conversion path. datetime2 accepts the full range of Double values that VBScript can produce (1753-01-01 through 9999-12-31).
Connection String Reference for WinCC V7.4 SP1 → SQL Server 2014
Use the SQLOLEDB or SQLNCLI11 provider. The latter is required when connecting to SQL Server 2012+ with native client features.
Provider=SQLNCLI11;
Data Source=<Server>\<Instance>;
Initial Catalog=<Database>;
User ID=<Login>;
Password=<Password>;
AutoTranslate=False;
Language=us_english;
DataTypeCompatibility=80;
MARS Connection=True;
Connect Timeout=15;
| Parameter | Value | Purpose |
|---|---|---|
Provider |
SQLNCLI11 | Native Client 11, ships with SQL 2012 / WinCC V7.4 DVD |
AutoTranslate |
False | Prevents code-page conversion of the timestamp string |
Language |
us_english | Sets SET LANGUAGE on connect |
DataTypeCompatibility |
80 | Disables datetime → datetime2 auto-promotion for legacy data |
MARS Connection |
True | Allows WinCC background tags and scripts to share a pool safely |
Verification Procedure
- Open SQL Server Management Studio on the WinCC station and run
SELECT GETDATE(). Compare the displayed format to what the WinCC VBScript action generates. They must match byte-for-byte (with the same date order and same time separator). - Execute a one-shot test from the WinCC VBScript debugger (Global Script → Debug) using the snippet from Solution 1 and a fixed value:
oConn.Execute "INSERT INTO dbo.PowerLog (LogTime, kW) VALUES ('12/05/2019 14:05:09', 0)" - Inspect the inserted row with
SELECT LogID, LogTime, kW FROM dbo.PowerLog ORDER BY LogID DESC. TheLogTimecolumn must read2019-12-05 14:05:09.000, not2019-05-12. - Trigger the cyclic logging action for ten cycles and check that
SELECT COUNT(*) FROM dbo.PowerLog WHERE LogTime > DATEADD(MINUTE, -1, GETDATE())returns ten rows. - Force a date-format stress test: change the SQL login's default language from
us_englishtoBritishand verify that Solution 2 (ISO 8601 +SET DATEFORMAT ymd) still inserts the correct timestamp.
Troubleshooting Matrix
| Symptom | SQL error code | Likely cause | Fix |
|---|---|---|---|
| First INSERT works, later ones fail | 241 | Pooled connection retained previous DATEFORMAT
|
Add SET DATEFORMAT ymd in every batch or use Solution 5 |
| All INSERTs fail after SQL restart | 242 | Login default language changed to a non-us_english locale |
Switch to ISO 8601 literals (Solution 2) |
| Dates appear as a different day | None (silent corruption) | Day/month swap on en-GB or de-DE runtime | Build date from integer components (Solution 1) |
| SSMS insert works, WinCC insert fails | 241 | SSMS uses ANSI_QUERY, WinCC ODBC uses OLE DB | Add DataTypeCompatibility=80 or use SQLNCLI11
|
| Error: "String or binary data would be truncated" | 8152 | Tag value contains text characters from operator entry | Wrap value in CStr() and add explicit length check |
| Datetime stored 1 hour off twice a year | None | DST transition, local datetime used | Use SYSUTCDATETIME() + display-time conversion |
| Bulk insert from 50 tags fails only for some | 241 | Some tags defined as TEXT in WinCC |
Map them to nvarchar(255) in SQL |
| Works in WinCC V6, fails in V7.4 | 241 | New SQLNCLI11 driver enforces DATEFORMAT more strictly |
Add explicit SET DATEFORMAT in batch |
Best-Practice Recommendations for the Production Deployment
-
Use
datetime2(3)for the timestamp column. The legacydatetimetype rounds to .000, .003, or .007 seconds, which corrupts millisecond-resolution logs. -
Use
SYSDATETIME()on the server side whenever the runtime and the database are time-synchronised. This is the only solution that survives a WinCC station rebuild, a Windows locale change, and a SQL Server collation change simultaneously. -
Set the SQL login's default language to
us_englishon principle, but do not rely on it. Always combine with an explicit format. -
Use a stored procedure instead of inline
INSERT. The procedure encapsulates the format logic and is auditable:
CREATE PROCEDURE dbo.usp_LogPower
@LogTime DATETIME2(3) = NULL,
@kW INT
AS
BEGIN
SET NOCOUNT ON;
SET DATEFORMAT ymd;
IF @LogTime IS NULL SET @LogTime = SYSDATETIME();
INSERT INTO dbo.PowerLog (LogTime, kW) VALUES (@LogTime, @kW);
END
-
Log the exact error inside the VBScript with
oConn.Errors(0).Descriptionand route it to WinCC's alarm logging. The native error 241 is far more diagnostic in context than a silent gap in the database. -
Disable pooled connection reuse for archive writes by appending
;OLE DB Services=-4to the connection string when you cannot guaranteeSET DATEFORMATon every batch. This forces a fresh session for every write. - Document the WinCC runtime locale in the project header comment. Six months from now nobody will remember that the project started life on a German Windows installation.
FAQ
What is the single most reliable way to log a timestamp from WinCC V7.4 SP1 to SQL Server?
Send only the measured value from WinCC and let SQL Server stamp the row with SYSDATETIME() as a column default. This eliminates every locale-related parsing issue and is the only approach that survives a Windows language change on the runtime PC.
Why does my SET LANGUAGE workaround stop working after a few weeks?
Connection pooling in the SQLOLEDB / SQLNCLI11 providers reuses a physical connection across calls. The previous DATEFORMAT is retained unless the application explicitly issues a SET DATEFORMAT at the start of every batch. Always combine the login default language with an explicit SET DATEFORMAT ymd in the same batch.
Which SQL Server data type should I use for a WinCC archive timestamp?
Use datetime2(3) for millisecond resolution, or datetimeoffset(3) if the plant spans time zones. Avoid the legacy datetime type — it rounds to 0.003 s and silently drops the sub-second portion of the timestamp.
How do I write an unambiguous date string from VBScript regardless of regional settings?
Build the date from integer components: Datum = Right("0" & Month(Date), 2) & "/" & Right("0" & Day(Date), 2) & "/" & Year(Date) & " " & Time. Then issue SET DATEFORMAT mdy in the same batch. The integer components carry no regional bias, so the output is identical on any Windows locale.
Can I use the ODBC canonical format yyyy-mm-dd hh:mm:ss with the SQLOLEDB provider?
Yes. SQL Server always parses the ODBC canonical literal in ymd order regardless of SET LANGUAGE or SET DATEFORMAT. The format is documented under style 120 in the T-SQL date and time reference.