Duration, Date and Time¶
Duration¶
The library SysTimeCore supports functions for handling the difference (delta) between two points in time (duration) provided by the CODESYS runtime system.
The SysTime
alias type is defined to handle ULINT
values (0 - 18.446.744.073.709.551.615).
xOK := (SIZEOF(SysTime) = 8 (* Bytes *)) AND (SIZEOF(ULINT) = 8 (* Bytes *));
Note
In the following examples the prefix st
was selected for variables of type SysTime
.
See: Naming Conventions for variables of type UDINT
, ULINT
, TIME
and LTIME
.
SysTimeGetMs¶
UDINT
in a resolution of one millisecond ([ms]).Note
The value range of the data type TIME
corresponds to the value range of the data type UDINT
with a resolution of one millisecond.
Note
Instead of the function SysTimeGetMs
, the operator TIME()
can also be used.
VAR
tDelta, tStart, tEnd : TIME;
END_VAR
tStart := TO_TIME(SysTimeGetMs());
(* ... lengthy operation ... *)
tEnd := TO_TIME(SysTimeGetMs());
tDelta := tEnd - tStart (* ms *);
The code example in the last section has the same effect as the code example in the next section.
tStart := TIME();
(* ... lengthy operation ... *)
tDelta := TIME() - tStart (* ms *);
SysTimeGetUs¶
SysTime
in a resolution of one microsecond ([µs]).VAR
stDelta, stStart, stEnd : SysTime;
END_VAR
SysTimeGetUs(stStart);
(* ... lengthy operation ... *)
SysTimeGetUs(stEnd);
stDelta := stEnd - stStart (* µs *);
SysTimeGetNs¶
SysTime
in a resolution of one nanosecond ([ns]).Note
The value range of the data type LTIME
corresponds to the value range of the data type SysTime
and ULINT
with a resolution of one nanosecond.
Note
Instead of the function SysTimeGetNs
, the operator LTIME()
should be used.
VAR
ltDelta : LTIME;
stStart, stEnd : SysTime;
END_VAR
SysTimeGetNs(stStart);
(* ... lengthy operation ... *)
SysTimeGetNs(stEnd);
ltDelta := TO_LTIME(stEnd - stStart) (* ns *);
The code example in the last section has the same effect as the code example in the next section.
VAR
ltDelta, ltStart : LTIME;
END_VAR
ltStart := LTIME();
(* lengthy operation *)
ltDelta := LTIME() - ltStart (* ns *);
Timer¶
The handling of a time duration at the basis of the data types TIME
and LTIME
are also the kernel
of the timer function blocks in the libraries standard (for TIME
) and standard64 (for LTIME
).
Date and Time¶
Problems with Localtime¶
The major problem dealing with the localtime is that certain times of a day may occur twice in a year. For example, in the US/Eastern timezone on the last Sunday morning in October, the following sequence happens:
01:00 EDT occurs
1 hour later, instead of 2:00am the clock is turned back 1 hour and 01:00 happens again (this time 01:00 EST)
In fact, every instant between 01:00 and 02:00 occurs twice. This means that if a variable of type “TIME” is written in the “US/Eastern” time zone, it can no longer be decided whether it was written shortly before or after the transition from summer time to winter time.
The best and simplest solution is to stick with using UTC. Note that some other timezones are commonly thought of as the same (GMT, Greenwich, Universal, etc.). The definition of UTC is distinct from these other timezones, and they are not equivalent.
“UTC” is Coordinated Universal Time. It is a successor to, but distinct from, Greenwich Mean Time (GMT) and the various definitions of Universal Time. UTC is currently the worldwide standard for regulating clocks and time measurement.
All other timezones are defined relative to UTC, and include offsets like UTC+0800 - hours to add or subtract from UTC to derive the local time. No daylight saving time occurs in UTC, making it a useful timezone to perform date arithmetic without worrying about the confusion and ambiguities caused by daylight saving time transitions, your country changing its timezone, or mobile applications that roam through multiple timezones.
Note
It is therefore very important to remember that it is not just the current time that is relevant, but also the values of date and time zone. Otherwise, it is not possible to associate a specific point in time with a specific event. We can only omit the time zone value and work with just the time and date value if UTC is the associated time zone.
For this the data types DATE_AND_TIME
or better LDATE_AND_TIME
in the UTC time zone are a good choice.
The SysTimeRTC Library provides a set of functions to deal with this issues and helps to handle variables of the types
(L)DATE
, (L)TIME_OF_DAY
and (L)DATE_AND_TIME
correctly.
The Data Types¶
CODESYS is following the IEC 61131-3 standard and implements therefore some data types:
DATE
: Seconds since Thursday, 1.1.1970 00:00:00, managed in a 32 Bit data type likeUDINT
TIME_OF_DAY
orTOD
: Milliseconds since 00:00:00.000, managed in a 32 Bit data type likeUDINT
(Modulo TIME#1d)DATE_AND_TIME
orDT
: Seconds since Thursday, 1.1.1970 00:00:00, managed in a 32 Bit data type likeUDINT
Starting with data type DATE_AND_TIME
, variables of the other data types can be assigned accordingly.
A variable of type DATE_AND_TIME
can be initialized with a current value with the help of the CODESYS
runtime system’s functions SysTimeRtcGet
.
See: Naming Conventions for variables of type UDINT
, DATE
, TIME_OF_DAY
and DATE_AND_TIME
.
VAR
udiUTC_DateAndTime : UDINT;
Result : RTS_IEC_RESULT;
dtNow : DATE_AND_TIME;
todNow : TIME_OF_DAY;
datNow : DATE;
END_VAR
udiUTC_DateAndTime := TO_UDINT(SysTimeRtcGet(Result)); // UDINT#1528268918
dtNow := TO_DT(udiUTC_DateAndTime); // DT#2018-6-6-7:8:38
todNow := TO_TOD(dtNow); // TOD#7:8:38
datNow := TO_DATE(dtNow); // D#2018-6-6
A further edition of IEC 61131-3 has added some additional data types. This is also the case in CODESYS.
LDATE
: Nanoseconds since Thursday, 1.1.1970 00:00:00, managed in a 64 Bit data type likeLINT
LTIME_OF_DAY
orLTOD
: Nanoseconds since 00:00:00, managed in a 64 Bit data type likeULINT
(Modulo LTIME#1d)LDATE_AND_TIME
orLDT
: Nanoseconds since Thursday, 1.1.1970 00:00:00, managed in a 64 Bit data type likeLINT
Starting with data type LDATE_AND_TIME
, variables of the other data types can be assigned accordingly.
A variable of type LDATE_AND_TIME
can be initialized with a current value with the help of the CODESYS
runtime system’s functions SysTimeRtcHighResGet
.
See: Naming Conventions for variables of type ULINT
, LDATE
, LTIME_OF_DAY
and LDATE_AND_TIME
.
VAR
stUTC_DateAndTime : SYSTIME;
Result : RTS_IEC_RESULT;
ldtNow : LDATE_AND_TIME;
ltodNow : LTIME_OF_DAY;
ldatNow : LDATE;
END_VAR
Result := SysTimeRtcHighResGet(stUTC_DateAndTime); // UlINT#1738934115369
ldtNow := TO_LDT(stUTC_DateAndTime * LTIME#1ms); // LDT#2025-2-7-13:15:15.369000000
ltodNow := TO_LTOD(ldtNow); // LTOD#13:15:15.369000000
ldatNow := TO_LDATE(ldtNow); // LD#2025-2-7
Note
LINT
now also allows to operate on dates that occurred before 1 January 1970.ldatApollo11 : LDATE := LD#1969-07-21; // LINT#-14169600000000000
The CODESYS runtime system specifies some additional data types:
Standard
udiTimeStamp
(UDINT
) Seconds since Thursday, 1.1.1970 00:00:00High Resolution
uliTimeStamp
(ULINT
) Milliseconds since Thursday, 1.1.1970 00:00:00.000SysTimeDate
(STRUCT
)wYear : UINT; // Year (e.g. 2006) wMonth : UINT; // Month (1..12: January = 1, December = 12) wDay : UINT; // Day of month (1..31) wHour : UINT; // Hours after midnight (0..23) wMinute : UINT; // Minutes after hour (0..59) wSecond : UINT; // Seconds after minute (0..59) wMilliseconds : UINT; // Milliseconds after second (0..999). Optional! wDayOfWeek : UINT; // Day of week (1..7: Monday = 1, Sunday = 7 wYday : UINT; // Day of year (1..365): January 1 = 1, December 31 = 364/365
See: SYSTEMTIME structure
Getting Date and Time (UTC)¶
The CODESYS runtime system provides the following functions for getting the current date and time in the UTC timezone:
SysTimeRtcGet: Returns the date and time in seconds since Thursday, 1.1.1970 00:00:00, managed in a 32 Bit data type like
UDINT
SysTimeRtcHighResGet: Returns the date and time in milliseconds since Thursday, 1.1.1970 00:00:00.000, managed in a 64 Bit data type like
SysTime
Converting from UTC to Localtime¶
Note
The concept “local timezone” in the context of the CODESYS runtime system is based on the time zone witch the local controller is configured for. This is not always the time zone suitable for displaying the current time. Some times it is necessary to use the timezone which the CODESYS development environment is configured for. Some times there are other requirements to use a completely different time zone.
With the SysTimeRtcGetTimezone2
the name of the “local timezone” can be queried.
In order to meet these conflicting requirements, the util.library offers the option of using the functions for converting time and date data with a flexible specification of a freely selectable time zone.
The CODESYS runtime system provides the following functions for converting the current date and time from the UTC timezone to the local timezone:
SysTimeRtcConvertHighResToLocal: Converts a high resolution Time format
SysTime
to Localtime in theSysTimeDate
format.SysTimeRtcConvertDateToHighRes: Converts the time given by time in the SysTimeDate format into a High Resolution Time of format
SysTime
.
Note
In the following examples the prefix std
was selected for variables of type SysTimeDate
.
VAR
stUTC_Timestamp : SysTime;
stLocal_TimeStamp : SysTime;
stdNow : SysTimeDate;
Result : RTS_IEC_RESULT;
dtNow : DATE_AND_TIME;
todNow : TIME_OF_DAY;
datNow : DATE;
END_VAR
Result := SysTimeRtcHighResGet(stUTC_Timestamp); // ULINT#1528273494913
Result := SysTimeRtcConvertHighResToLocal(stUTC_Timestamp, stdNow);
// stdNow.wYear = UINT#2018
// stdNow.wMonth = UINT#6
// stdNowy.wDay = UINT#6
// stdNow.wHour = UINT#10
// stdNow.wMinute = UINT#24
// stdNow.wSecond = UINT#54
// stdNow.wMilliseconds = UINT#913
// stdNow.wDayOfWeek = UINT#3
// stdNow.wYday = UINT#157
Result := SysTimeRtcConvertDateToHighRes(stdNow, stLocal_TimeStamp); // ULINT#1528280694913
dtNow := TO_DT(stLocal_TimeStamp / TIME#1ms); // DT#2018-6-6-10:24:54
todNow := TO_TOD(stLocal_TimeStamp MOD TO_ULINT(T#1D)); // TOD#10:24:54.913
datToday := TO_DATE(dtNow); // D#2018-6-6
Converting from Localtime to UTC¶
The CODESYS runtime system provides the following function for converting the current date and time from the local timezone to the UTC timezone:
SysTimeRtcConvertLocalToHighRes: Converts the date and time given by
SysTimeDate
structure (localtime) into a high resolution time of typeSysTime
(UTC)
VAR
stdNew : SysTimeDate;
stUTC_Timestamp : SysTime;
END_VAR
// stdNew.wYear = UINT#2018
// stdNew.wMonth = UINT#6
// stdNew.wDay = UINT#6
// stdNew.wHour = UINT#10
// stdNew.wMinute = UINT#24
// stdNew.wSecond = UINT#54
// stdNew.wMilliseconds = UINT#913
Result := SysTimeRtcConvertLocalToHighRes(stdNew, stUTC_Timestamp); // ULINT#1528273494913
Support from the Util.library¶
To handle the local time, it is necessary to always specify the time and date and the time zone that is currently valid. This makes it possible to convert the local time to Coordinated Universal Time and vice versa. UTC is Coordinated Universal Time. It is a successor to, but distinct from, Greenwich Mean Time (GMT) and the various definitions of Universal Time. UTC is now the worldwide standard for regulating clocks and time measurement.
All other timezones are defined relative to UTC, and include offsets like UTC+0800 - hours to add or subtract from UTC to derive the local time. No daylight saving time occurs in UTC, making it a useful timezone to perform date arithmetic without worrying about the confusion and ambiguities caused by daylight saving time transitions, your country changing its timezone, or mobile computers that roam through multiple timezones.
The following snippet exposes the definition of UTC and CentralEuropeTime/CentralEuropeSommerTime.
VAR_GLOBAL CONSTANT
/// Coordinated Universal Time
gc_tzTimeZoneUTC : TimeZone := (asgPeriod := [(sName:='UTC')]);
/// Central Europe Time
gc_tzTimeZoneCET : TimeZone :=
(
iBias := 60 (* T#1M => minutes *),
asgPeriod := [
( (* (CEST -> CET) - Last Sunday in Oktober at 03:00:00.000 (CEST) *)
sName:='CET',
dtDate := (uiMonth := 10, eWeekday := WEEKDAY.SUNDAY, uiDay := 5, uiHour := 3)
),( (* (CET -> CEST) - Last Sunday in March at 02:00:00.000 (CET) *)
sName := 'CEST',
dtDate := (uiMonth := 3, eWeekday := WEEKDAY.SUNDAY, uiDay := 5, uiHour := 2),
iBias := 60 (* T#1M => minutes *)
)]
);
END_VAR
Note
The Bias element represents the offset from the Coordinated Universal Time (UTC). This value is in minutes.
The offset growing positive in eastern direction starting from the prime meridian.
The offset is growing negative in western direction starting from the prime meridian.

With the data structure TimeZone
it is possible to specify every
timezone of the world and so the functions below
can handle the local time conversion and the switching from standard to day light saving period’s if necessary.
Example
With the following expressions the difference between the UTC time zone and an other timezone instance can be calculated.
iBiasUTC_Standard := gc_tzTimeZoneCET.iBias;
iBiasUTC_Daylight := gc_tzTimeZoneCET.iBias + gc_tzTimeZoneCET.asgPeriod[PERIOD.DAYLIGHT].iBias;
Note
The time zone which the current computer is configured for, is not always the time zone suitable for displaying the current time. Therefore in the respective application, the possibility should be provided, to be able to select the “correct” time zone that is suitable for the related output option (WebVisu, LogFiles, Email, …).
In version 3.5.14.0 and higher, the Util library provides the following functions:
Conversion from UTC timestamp (e.g. from SysTimeRtcHighResGet
) to the current period and the local date and time related to the TimeZone
parameter.
FUNCTION PUBLIC LocalDateTime : ULINT
VAR_IN_OUT CONSTANT
tzTimeZone : TimeZone;
END_VAR
VAR_INPUT
uliDateTime : ULINT;
END_VAR
VAR_OUTPUT
eErrorID : ERROR;
ePeriod : PERIOD; (* 1 = ``PERIOD.STANDARD``, 2 = ``PERIOD.DAYLIGHT`` *)
END_VAR
Splitting a timestamp into the components of a point in time.
FUNCTION PUBLIC SplitDateTime : ERROR
VAR_INPUT
uliDateTime : ULINT;
END_VAR
VAR_OUTPUT
uiYear : YEAR; (* 1970..2106 *)
uiMonth : MONTH;
uiDay : DAY;
uiHour : HOUR;
uiMinute : MINUTE;
uiSecond : SECOND;
uiMilliseconds : MILLISECOND;
eWeekday : WEEKDAY;
END_VAR
Joining the components of a point in time to a timestamp.
FUNCTION PUBLIC JoinDateTime : ULINT
VAR_INPUT
uiYear : YEAR; (* 1970..2106 *)
uiMonth : MONTH;
uiDay : DAY;
uiHour : HOUR;
uiMinute : MINUTE;
uiSecond : SECOND;
uiMilliseconds : MILLISECOND;
END_VAR
VAR_OUTPUT
eErrorID : ERROR := ERROR.NO_ERROR;
END_VAR
Separates a timestamp in its IEC data typed parts.
FUNCTION PUBLIC SeparateDateTime : ERROR
VAR_INPUT
uliDateTime : ULINT;
END_VAR
VAR_OUTPUT
eWeekDay : WEEKDAY;
datDate : DATE;
todTime : TIME_OF_DAY;
END_VAR
Combines the IEC data typed parts to a timestamp.
FUNCTION PUBLIC CombineDateTime : ULINT
VAR_INPUT
datDate : DATE;
todTime : TIME_OF_DAY;
END_VAR
VAR_OUTPUT
eErrorID : ERROR;
END_VAR
Calculates the appropriate values of the ISO week date parts that matches the parameter uliDateTime
FUNCTION PUBLIC WeekOfYear : ERROR
VAR_INPUT
uliDateTime : ULINT;
END_VAR
VAR_OUTPUT
uiYear : YEAR;
uiWeek: WEEK;
eWeekday : WEEKDAY;
END_VAR
Combines the ISO week date parts to a timestamp
FUNCTION DateTimeFromWeek : ULINT
VAR_INPUT
uiYear : YEAR;
uiWeek : WEEK;
eWeekday : WEEKDAY;
END_VAR
VAR_OUTPUT
eErrorID : ERROR;
END_VAR
Calculates the appropriate value of the WEEKDAY enum that matches the parameter datDate
.
FUNCTION PUBLIC DayOfWeek : WEEKDAY
VAR_INPUT
datDate : DATE;
END_VAR
VAR_OUTPUT
eErrorID : ERROR;
END_VAR
Checks whether the year (uiYear
) passed represents a leap year.
FUNCTION PUBLIC IsLeapYear : BOOL
VAR_INPUT
uiYear : YEAR; (* 1970..2106 *)
END_VAR
VAR_OUTPUT
eErrorID : ERROR;
END_VAR
Special Applications¶
Handling of the clock time at application level, if it is not possible to change the clock time in the runtime system.
In this case we can read the current clock time from the runtime system, but we are not allowed to change the clock time in the runtime system. Either we do not have the necessary access rights or changing the time could have unwanted consequences for the other processes running parallel to the runtime system on the current controller.
A possible solution would then be as follows:
We create an application specific function block that implements the interface
IDateTimeProvider
.We add another input to this function block. This offset would then be used to calculate an application-specific clock time from the clock time that we previously read from the runtime system.
The variable which supplies the input of the function block with the current offset should be able to keep its value even after restarting the controller. CODESYS provides various mechanisms for this purpose.
{attribute 'no_explicit_call' := 'Use method like GetDateTime'}
FUNCTION_BLOCK FINAL AdjustableTimeProvider IMPLEMENTS Util.IDateTimeProvider
VAR_INPUT
liOffset : LINT;
END_VAR
METHOD FINAL GetDateTime : ULINT
VAR_OUTPUT
eErrorID : Util.ERROR;
END_VAR
IF liOffset >= 0 THEN
GetDateTime := Util.GetDateTime(eErrorID=>eErrorID) + TO_ULINT(liOffset);
ELSE
GetDateTime := Util.GetDateTime(eErrorID=>eErrorID) - TO_ULINT(-liOffset);
END_IF
Take a look to the application exaple:

PROGRAM PLC_PRG
VAR
ATP : AdjustableTimeProvider;
TS : Util.TimerSwitch := (itfDateTimeProvider:=ATP);
liOffset : LINT := TO_LINT(TIME#1H);
END_VAR
The application clock time is exactly one hour ahead of the realtime clock inside the runtime system.
If the application will change the value of uliOffset
the the application clock time will be changing accordingly.
Another example with the help of some other time supporting functions of the Util library:
VAR
ATP : AdjustableTimeProvider;
liOffset : LINT := TO_LINT(TIME#1H);
datDate : DATE;
todTime : TIME_OF_DAY;
END_VAR
ATP.liOffset := liOffset;
UTIL.SeparateDateTime(
Util.LocalDateTime(
tzTimeZone := Util.TSW.gc_tzTimeZoneCET,
uliDateTime:= ATP.GetDateTime()
),
datDate => datDate,
todTime => todTime
);
Handling of the clock time at application level, if it is a point in time in the further past
When handling date and time, there is sometimes a requirement to be able to display the local time valid at that point in time. For example, it must be possible today to assign the time valid at that time to an event that took place two weeks ago, although a changeover to daylight saving time has taken place in between.
Thus it is necessary not only to store the UTC time stamp for a specific event but also the offset between local time and UTC valid at that time.