DateTime Conversion (openHAB 3.x)

Tags: #<Tag:0x00007f43380e16b8> #<Tag:0x00007f43380e15f0> #<Tag:0x00007f43380e1500>

Because openHAB 2.x rules use Jodatime instead of Java Time API, there is still the old DateTime Conversion thread for openHAB 2.x.

Overview

In openHab 3.x there are different ways to handle Date/Time values.

  • DateTimeType
    A DateTime Item carries a DateTimeType.

  • Java Time
    By default openHAB 3.x Rules DSL use java.time API to represent date and time.
    Principle java date-time concepts are LocalDateTime (date and time without any offset or time-zone) and ZonedDateTime (“full” date-time with time-zone and resolved offset from UTC/Greenwich).

  • Epoch
    The lowest common denominator when working with time is to get at the epoc value. Epoc is the number of milliseconds that has passed since 1 January 1970 GMT and stored in a long. With epoc, one can compare two dates together, convert a Joda DateTime to a DateTimeType and visa versa. With epoc you can represent a duration.

  • String
    In certain cases it is needed to convert date/time values to a human readable.

This flow chart is intended to help you finding the code needed for conversion.
The numbers of the flow chart refer to the numbers of the following list.

#1 Get DateTimeType from String

String must be ISO 8601 formatted like this: “yyyy-MM-dd’T’HH:mm:ss.SSSZ”

val DateTimeType MyDateTimeTypeFromString = DateTimeType.valueOf(MyString) 

#2 Get DateTimeType from Epoch

(Preliminary: not yet tested)

Since no direct conversion is known, this code uses conversion #6 (Epoch to JavaTime) and then #3 (JavaTime to DateTimeType)

val long MyEpochMilliseconds = 999999000  // create a epoch value to work with`

val MyJavaTimeFromEpoch = Instant.ofEpochMilli(MyEpochMilliseconds).atZone(ZoneId.systemDefault()) // #6 convert epoch to JavaTime

val MyDateTimeTypeFromJavaTime = new DateTimeType(MyJavaTimeFromEpoch) // #3 convert JavaTime to DateTimeType

#3 Get DateTimeType from Java Time

Variant A (from LocalDateTime)

val LocalDateTime MyJavaLocalDateTime = new LocalDateTime() // create a LocalDateTime value to work with`
val DateTimeType MyDateTimeTypeFromJavaLocalDateTime = new DateTimeType(MyJavaLocalDateTime ) // conversion

Variant B

Because JavaTime and DateTimeType both accept ISO 8601 formatted date/time strings it is easy to convert from one to the other using their toString.
Regarding to the flow chart this code converts JavaTime => #11 => to String => #1 => to DateTimeType

val DateTimeType MyDateTimeTypeFromJavaTime = DateTimeType.valueOf(MyJavaTime.toLocalDateTime().toString())

Timestamp (DateTimeType)

val DateTimeType MyDateTimeTypeTimestamp = DateTimeType.valueOf(now.toLocalDateTime().toString())

#4 Get Java Time from DateTimeType

val MyJavaTimeFromDateTimeItem = (MyDateTimeItem.state as DateTimeType).getZonedDateTime()

More info about Java ZonedDateTime Class

#5 Get Java Time from String

String must be ISO 8601 formatted like this: “yyyy-MM-dd’T’HH:mm:ss.SSSZ”

val MyZonedDateTimeFromString = ZonedDateTime.parse("2020-12-25T00:00:00.000Z").withZoneSameInstant(ZoneId.systemDefault())

#6 Get Java Time from Epoch

(Preliminary: not yet tested)

Variant A

val long MyMilliseconds = 999999000  // create a value we can work with`

val MyJavaTimeFromEpoch_VariantA = Instant.ofEpochMilli(MyMilliseconds).atZone(ZoneId.systemDefault()) // #6

#7 Get Epoch from Java Time

(Preliminary: not yet tested)

Variant A

val MyEpochFromJavaTime_VariantA = MyJavaTime.toInstant.toEpochMilli

Variant B

val MyEpochFromJavaTime_VariantB = MyJavaTime.toEpochSecond * 1000

Timestamp

val MyEpochTimestamp_VariantA = now.toInstant.toEpochMilli
val MyEpochTimestamp_VariantB = now.toEpochSecond * 1000

#8 Get Epoch from DateTimeType

val Number MyEpochFromDateTimeTypeItem = (MyDateTimeTypeItem.state as DateTimeType).zonedDateTime.toInstant.toEpochMilli

#9 Get Epoch from String

String must be ISO 8601 formatted like this: “yyyy-MM-dd’T’HH:mm:ss.SSSZ”

val Number MyEpochFromString = new DateTime(MyDateTimeString).millis

#10 Get String from Epoch

Variant A
Regarding to the flow chart this code converts from Epoch => #6 => to Java Time => #11 => to String. This means formatting can be done like shown in #11

ToDo …

Variant B

Here is an option utilizing SimpleDateFormat:

import java.text.SimpleDateFormat
import java.util.Date

val SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ")
val String MyStringFromEpoch_VariantB = sdf.format(new Date(MyEpoch))

#11 Get String from Java Time

Variant A

val String MyStringFromJavaTime = MyJavaTime.toLocalDateTime().toString()

Timestamp (String)

val String MyStringTimestamp = now.toLocalDateTime().toString()

#12 Get String from DateTimeType

Formatting can be done like shown here.
For more details look at this.

val String MyStringFromDateTimeType = MyDateTimeType_item.state.format("%1$td.%1$tm.%1$ty %1$tH:%1$tM")
23 Likes

I used the following two steps in 3.0.0.M2 to convert a Java Time (now) to a DateTimeType (no imports needed):
# 11 Get String from Java Time
val String dts = now.toLocalDateTime().toString()

Then, I converted the string to a DateTimeType:
# 3 Get DateTimeType from Java Time (using String)
val DateTimeType dt = DateTimeType.valueOf(dts)

2 Likes

Because I’m still on oh 2.4 yet, I can’t test it. But I think you did.
Thanks a lot!

Thought I would share this as my upgrade to OH3 broke all my timers that used " now.plusMillis ".
I wasn’t able to find a function that would use milliseconds as OH2 did but I was ok with using seconds. I converted everything from milliseconds to seconds and in a way it makes it easier to read and calculate. If you need to get the fractional seconds, you can use plusNanos instead…

I essentially went from this that would now present me with an error in VSS

   
rule "Test plusSeconds"
when
    Item testSwitch changed
then
    logInfo("testing.rules", "*****************  Begin!  ********************" )   
   
    testSwitch_Timer_off = createTimer(now.plusMillis(5000)) [|
                logInfo("testing.rules", "1 - 2 - 3 - 4 - 5 seconds have passed" )   	
    ]

   logInfo("testing.rules", "******************  All good things come to an end!  ********************" )   
end

The error…

to the following…


rule "Test plusSeconds"
when
    Item testSwitch changed
then
    logInfo("testing.rules", "******************  Begin!  ********************" )   
   
    testSwitch_Timer_off = createTimer(now.plusSeconds(5)) [|
                logInfo("testing.rules", "1 - 2 - 3 - 4 - 5 seconds have passed" )   	
    ]

   logInfo("testing.rules", "******************  All good things come to an end!  ********************" )   
end

Anyway hope that helps someone…

and finally some info on the Java ZonedDateTime Class in case that helps.

4 Likes

Can anyone give me a hint so I can do conversion #4?
I’ve tried to use datetime parser to convert a datetimetype to a Java time, but it won’t work.
I want to use now.isAfter(item state).
Example (works in OH 2.5.x):

val morning=new DateTime(Morning_Time.state.toString)
val day=new DateTime(Day_Time.state.toString)
var curr=""
if (now.isAfter(morning) && now.isBefore(day)) { curr="Morning"}

Because now is in OH3 Java time, this won’t work anymore.
I’ve tried to change it to this:

val morning=ZonedDateTime.parse(Morning_Time.state.toString, DateTimeFormatter.ISO_OFFSET_DATE_TIME)
val day=ZonedDateTime.parse(Day_Time.state.toString, DateTimeFormatter.ISO_OFFSET_DATE_TIME)
var curr=""
if (now.isAfter(morning) && now.isBefore(day)) { curr="Morning"}

But I get the error that it can’t be parse correctly.

Thanks to @rlkoshak I was able to figure this out for my use case. Here are two examples that work for me.

val day_start = (AstroSunData_Rise_StartTime.state as DateTimeType).getZonedDateTime()
val evening_start = (AstroSunData_Set_StartTime.state as DateTimeType).getZonedDateTime()
3 Likes

Thank you and @rlkoshak! It works.

1 Like

@ljsquare
maybe you can share the solution here ?

This is the solution:

A post was split to a new topic: Time conversion

#5:

val MyZoneDateTime = ZoneDateTime.parse("2020-12-25T00:00:00.000Z").withZoneSameInstant(ZoneId.systemDefault())
1 Like

#4 Get Java Time from DateTimeType
Tested in OH3, works fine:
val MyJavaTimeFromDateTimeItem = (MyDateTimeItem.state as DateTimeType).getZonedDateTime()

3 Likes

Hello,

I had in OH 2.5 the following rule - that calculates my power consuption for one day:

rule “Strom Verbrauch Tag”
when
tem homematic_stecker_Energiezaehler_kwh received update
then
if(homematic_stecker_Energiezaehler_kwh.state instanceof Number){
Stromzaehler_Verbrauch_Tag.postUpdate(homematic_stecker_Energiezaehler_kwh.deltaSince(now.withTimeAtStartOfDay, “influxdb”) as Number)
}

Unfortunately i am not a programmer. Could someone please give me a hint on how to recreate the rule for OH3?

Thanks

KR
Michael

2 Likes

thanks - now it works

I replaced

now.withTimeAtStartOfDay

with

ZonedDateTime.now().with(LocalTime.MIDNIGHT)

2 Likes

Hello,
I didn’t understand Java at all.
I need to convert

now.getWeekOfWeekyear

in oh3 format because now one rule dont work properly.

Many thanks

Hi,

When rewriting my rules for OH3, I encountered a problem with the conversion between DateTimeType and Java Time: for some reason, when I convert Java Time to String, the name of my timezone is added to the string, e.g. 2021-01-01T07:00+01:00[Europe/Amsterdam]. This probably has to do with the time zone configuration of the Java environment. Obviously, this leads to problems when the string is parsed.

I also had a problem the other way around. When a DateTimeType was converted to a String, the string was missing a : in the time zone identifier:

DateTimeType to String: "2021-01-01T07:00:00.000+0100"
Java Time expects:      "2021-01-01T07:00:00.000+01:00"

I was able to solve both issues by using a formatter:

  val formatter = java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ")

  val morning_start = ZonedDateTime.now().with(LocalTime.MIDNIGHT).plusHours(7) // 7:00

  // Java time to DateTimeType:
  vMorning_Time.postUpdate(DateTimeType.valueOf(morning_start.format(formatter))) 

  // DateTimeType to Java time:
  val day_start = ZonedDateTime.parse(vSunrise_Time.state.toString, formatter) 

I hope this helps someone. I’d think this method is more robust in all cases, so it might be recommended to do this, even if you don’t encounter problems yet.

3 Likes

You can use the .toLocaleDateTime method to get the time with the time zone. Then you don’t the formatter.
See:

And

#7

now.toInstant.toEpochMilli
now.toEpochSecond * 1000
1 Like

#6

val zdt = Instant.ofEpochMilli(XXX).atZone(ZoneId.systemDefault())
val zdt = ZonedDateTime.ofInstant(Instant.ofEpochMilli(XXX), ZoneId.systemDefault())
1 Like