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 *));
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.
Returns a timer tick value (point in time) of type UDINT in a resolution of one millisecond ([ms]).
This results in a value range of 4.294.967.296ms ≈ 4.294.967s ≈ 7.582m ≈ 1.193h ≈ 49d
The value range of the data type TIME corresponds to the value range of the data type UDINT with a resolution of one millisecond.
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 *);
Returns a timer tick value (point in time) of type SysTime in a resolution of one microsecond ([µs]).
This results in a value range of 18.446.744.073.709.551.615µs ≈ 18.446.744.073.709.551ms ≈ 18.446.744.073.709s ≈ 307.445.734.561m ≈ 5.124.095.576h ≈ 213.503.982d ≈ 599.730y
VAR
stDelta, stStart, stEnd : SysTime;
END_VAR
SysTimeGetUs(stStart);
(* ... lengthy operation ... *)
SysTimeGetUs(stEnd);
stDelta := stEnd - stStart (* µs *);
Returns a timer tick value (point in time) of type SysTime in a resolution of one nanosecond ([ns]).
This results in a value range of 18.446.744.073.709.551.615ns ≈ 18.446.744.073.709.551µs ≈ 18.446.744.073.709ms ≈ 18.446.744.073s ≈ 307.445.734m ≈ 5.124.095h ≈ 213.503d ≈ 599y
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.
Instead of the function SysTimeGetNs, the operator LTIME() can also 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 *);
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.
The SysTimeRTC Library provides a set of functions to deal with this issues and helps to handle variables of the types DATE, TIME_OF_DAY and DATE_AND_TIME correctly.
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 like UDINT
TIME_OF_DAY or TOD: Milliseconds since 00:00:00.000, managed in a 32 Bit data type like UDINT
DATE_AND_TIME or DT: Seconds since Thursday, 1.1.1970 00:00:00, managed in a 32 Bit data type like UDINT
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 and SysTimeRTCConvertUTCToLocal.
See: Naming Conventions for variables of type UDINT, DATE, TIME_OF_DAY and DATE_AND_TIME.
VAR
udiUTC_DateAndTime : UDINT;
udiLocal_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
Result := SysTimeRTCConvertUTCToLocal(udiUTC_DateAndTime, udiLocal_DateAndTime); // UDINT#1528276118
dtNow := TO_DT(udiLocal_DateAndTime); // DT#2018-6-6-9:8:38
todNow := TO_TOD(dtNow); // TOD#9:8:38
datNow := TO_DATE(dtNow); // D#2018-6-6
The CODESYS runtime system specifies some additional data types:
Standard udiTimeStamp (UDINT) Seconds since Thursday, 1.1.1970 00:00:00
High Resolution uliTimeStamp (ULINT) Milliseconds since Thursday, 1.1.1970 00:00:00.000
SysTimeDate (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
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
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.
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:
SysTimeRtcConvertDateToHighRes:
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 / 1000 (* ms *)); // 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
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 type SysTime (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
// Writing a high resolution value of type ``SysTime`` to the realtime clock (RTC) device.
Result := SysTimeRtcHighResSet(stUTC_Timestamp);
To handle the local time, it is neccecary 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
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;
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
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.