Convert DateTimeType to a more user-friendly string

Hello,
I have some Xiaomi Aquara Sensors in my house, I use them to track temperature and humidity in the rooms. I check them “live” using openHAB Adroid app, and I check the history using grafana and a dedicated tablet with a custom theme in HABPanel.

I use some rules to track each sensor’s last update time.

EXAMPLE ITEM

Number:Temperature      HTP_Camera_Temperature      "Temperatura [%.1f %unit%]"   <temperature>       (gTempHumPresCamera, gTemperatures)       { channel="mihome:sensor_weather_v1:04cf8ca16192:158d00036b498d:temperature", ga="thermostatTemperatureAmbient" }
Number:Dimensionless    HTP_Camera_Humidity         "Umidità [%.1f %%]"           <humidity>          (gTempHumPresCamera, gHumidities)         { channel="mihome:sensor_weather_v1:04cf8ca16192:158d00036b498d:humidity", ga="thermostatHumidityAmbient" }

DateTime                HTP_Camera_LastUpdate       "Aggiornato [%1$td/%1$tm %1$tH:%1$tM]"

EXAMPLE RULE

rule "Xiaomi Sensor (Camera) State Changed"
	when
		Item HTP_Camera_Temperature received update or 
		Item HTP_Camera_Humidity received update
	then
		HTP_Camera_LastUpdate.postUpdate( new DateTimeType() )
	end

everything works fine and i get the last update time formatted as:
19/05 10:37

What I would like to achieve is to format the update time so that if today is 19/05 it writes
today 10:37
or otherwise something like:
12 minutes ago

I thought of using a rule that checks all the update times at a given interval of minutes (using cron) and formats all the outputs accordingly using a string item instead of a DateTime, but it seems like a poor solution to me, also not very reliable.

Is this possible in a different and better way?

In what context? I’d guess this is for display in one UI or another, not data storage.

A solution -

Hey @rossko57
Thank you for the super fast answer! :slightly_smiling_face:
You are correct, I use it to display the “last update” in openHAB Android app in a more readable way, I will consider to use it also in HABPanel.

The solution you linked to works like a charm. It is a bit heavy (takes 2-3 seconds to load the page in the app), but I can live with that!

When using the locales version ( moment-with-locales.min.js ) the waiting time with a few sensors becomes quite long, in my case around 8-10s for 6 sensors - because in this case the transform/relative.js becomes a huge 354KB file!

In case anyone needs localization, what I did to partially solve this issue is:

  • I downloaded the moment-with-locales.js (the non-minified version)
  • I erased all unnecessary languages (they are at the end of the file, starting with hooks.defineLocale...)
  • I saved it and minified it using an online tool
  • I pasted the minified code in the relative.js code

This way relative.js size goes down to 61KB. Still pretty heavy but ok for me.

BTW having a similar feature built-in in openHAB would be great!

Thank you!

1 Like

In case anyone needs it, to use the newly formatted times inside HABPanel you need to use something like:

{{getItem('HTP_Camera_LastUpdate').transformedState}}

As visible in the modal, on the right of the title:

!
!

1 Like

Hi! Please share the modal window code, done beautifully!

Hello @Olymp
I’ll do my best to extrapolate the modal code as you asked, but keep in mind there are several files involved and it is difficult to extrapolate all the code and css rules needed. Let me explain a bit how it is all set up, this may help you understand the code/find the right snippets. I’m not a programmer, I know html & css and a bit of js, but am a UI Designer myself, so I had to find a way to implement my design at my best, using and modifying pieces of code I found around here, with a lot of trial and error - the code is pretty messy. My design started with the intention to slightly modify the fantastic Matrix Theme for HABPanel - but I ended up basically rewriting everything from scratch, it was easier than modifying something that was more complicated than what I needed.
In HABPanel I customized pretty heavily the additional css. The sizes of many elements are given in px, my main focus was to make it look nice on a specific tablet, not to go with a full responsive approach. I had started using SVGs but endend up converting everything to minified PNGs because my tablet was suffering and lagging with all those SVGs.

My HABPanel theme is managed with a single big widget, the “tabs” (lights, rollershutters, temperatures management, power management, and system) are all inside a slider so you can tap the single icons on the top right of the interface or just swype left/right over the house map to cycle between the tabs. This is done with an Angular carousel. When you click one of the red circles inside the temperatures tab, they open a modal.

This is the code:

<div class="bolloTemperatura TempSala" ng-click="openModal('modalSala.html', true, 'lg')">
...
</div>

then you have the code for the modal itself:

 <style> 
 	.modal .modal-dialog.modal-lg {
 		width: 800px;
 		height: 555px;
 		padding: 0;
 	}
 	.modal .modal-content {
 		width: 800px;
 		height: 555px;
 		padding: 0;
 		background-color: #161719;
 		color: #FFF;
 	}
 	.modal .modal-dialog {
 		margin-bottom: 0px !important;
 	}
 	.modal h3 {
 		font-size: 37px;
 		line-height: 37px;
 		text-transform: uppercase;
 		margin: 16px 0 0 25px;
 		font-family: 'Montserrat_T';
 	}
 	.modal .updated {
 		font-size: 12px;
 		line-height: 12px;
 		color: #777777;
 		text-transform: uppercase;
 		margin: 38px 0 0 15px;
 		font-family: 'Montserrat_M';
 	}
 	.modal a.modalCloseBtn {
 		display:block;
 		position: relative;
 		width: 19px;
 		height: 19px;
 		float: right;
 		background-image: url(/static/ALB-matrix-reloaded-theme/png/icons/close_19x19@2x-min.png);
 		background-size: contain;
 		background-color: transparent;
 		background-repeat: no-repeat;
 		border: 0;
 		padding: 0;
 		margin: 25px 25px 0 0;
 	}
 	.modal .modalDetails {
 		padding: 15px 0 0 25px;
 		overflow: hidden;
 		position: absolute;
 		top: 55px;
 	}
 	.modal .modalDetails .modalDetailsIcon {
 		width: 24px;
 		height: 50px;
 		clear: both;
 		display: block;
 	}
 	.modal .modalDetails .temp{
 		font-size: 40px;
 		line-height: 40px;
 		margin: 5px 0 0 11px;
 		color: #FFF;
 		min-width: 120px;
 	}
 	.modal .modalDetails .temp sup.deg {
 		top: -17px;
 		font-size: 16px;
 		line-height: 16px;
 		color: #999;
 	}
 	.modal .humLevel .hum {
 		margin-bottom: 9px;
 	}
 	.modal .humLevel img {
 		display: block;
 		float: left;
 	}
 	.modal .humLevel span {
 		display: block;
 		float: left;
 		margin: 2px 0 0 5px;
 	}
 	.modal .periodi{
 		overflow: hidden;
 		margin: 4px 25px 0 0;
 		/* clear: right; */
 		float: right;
 		padding: 0;
 	}
 	.modal a.periodo {
 		display: block;
 		float: left;
 		margin: 10px 0 0 -1px;
 		border: 1px solid #FFF;
 		color: #FFF;
 		padding: 5px 7px;
 	}
 	.modal a.periodo:first-child {
 		margin-left: 0;
 	}
 	.modal a.periodo.active {
 		background-color: #FF6363;
 		color: #FFF;
 				border: 1px solid #FF6363;
 	}
 	.patchTitoloGrafico {
 		position: absolute;
 		width: 100%;
 		height: 30px;
 		margin-top: 75px;
 		z-index: 99;
 		background-color: #161719;
 	} 
 	.modalGraphContainer{
 		min-width: 116px;
 		min-height: 116px;
 		background-image: url(/static/ALB-matrix-reloaded-theme/gif/loader.gif);
 		background-color: transparent;
 		background-repeat: no-repeat;
 		background-position: 360px 210px;
 		background-size: 10%;
 	}
 </style>

<!-- ---------- iFrame SALA ---------- -->
<script type="text/ng-template" id="modalSala.html">
	<div class="modalContainer">
		<div class="modalHeader overflowHidden">
			<h3 class="red floatLeft">Sala</h3>
			<div class="updated floatLeft">Ultimo aggiornamento: {{getItem('HTP_Sala_LastUpdate').transformedState}}</div>
			<a ng-click="$close()" class="modalCloseBtn floatRight"></a>
		</div>
		
		<div class="patchTitoloGrafico"></div>
		<div class="modalGraphContainer">
			<iframe id="iframeSala" class="grafanaIframe" src="/static/ALB-matrix-reloaded-theme/iframeSala.html" width="798px" height="490px" frameborder="0" style="margin-top: 10px;"></iframe>
		</div>
	</div>

	<div class="modalDetails">
		<img class="modalDetailsIcon floatLeft" src="/static/ALB-matrix-reloaded-theme/png/controls/termometro@2x-min.png" />
		<div class="temp floatLeft">{{(getItem('HTP_Sala_Temperature').state.replace(" °C", "") | number : 1).replace(",", ".")}}<sup class="deg">&deg;C</sup></div>
		<div class="humLevel floatLeft">	
			<div class="hum overflowHidden"><img class="icon_20x20" src="/static/ALB-matrix-reloaded-theme/png/icons/humidity_20x20@2x-min.png" /><span>    UMIDIT&Agrave;&nbsp;{{getItem('HTP_Sala_Humidity').state.replace(" %", "") | number : 0}}%</span></div>
			<!-- 0-30=DRY // 30-50=COMFORT // 50-100=WET -->
			<div class="level overflowHidden" ng-if="getItem('HTP_Sala_Humidity').state.replace(' %', '') <= 30"><img class="icon_20x20" src="/static/ALB-matrix-reloaded-theme/png/icons/    dry_20x20@2x-min.png" /><span>DRY</span></div>
			<div class="level overflowHidden" ng-if="getItem('HTP_Sala_Humidity').state.replace(' %', '') > 30 && getItem('HTP_Sala_Humidity').state.replace(' %', '') < 55"><img class="icon_20x20"     src="/static/ALB-matrix-reloaded-theme/png/icons/comfort_20x20@2x-min.png" /><span>COMFORT</span></div>
			<div class="level overflowHidden" ng-if="getItem('HTP_Sala_Humidity').state.replace(' %', '') >= 55"><img class="icon_20x20" src="/static/ALB-matrix-reloaded-theme/png/icons/    wet_20x20@2x-min.png" /><span>WET</span></div>
		</div>
	</div>
</script>

Inside the modal, there is an iFrame (iframeSala.html) which is a simple html page:

<!-- UPDATED: 2020-05-19 -->

<!DOCTYPE html>
<html>
<head>
	<script src="http://code.jquery.com/jquery-3.4.1.min.js" type="text/javascript"></script>
	<meta http-equiv="Content-type" CONTENT="text/html; charset=utf-8">
	<style>
		html, body {
			background-color: #161719;
		}
		.container iframe {
			width: 100%;
			height: 400px;
		}
		.container {
			width: 98%;
			margin: 0 auto;
			border: 0;
		}
		
		div.periodi{
			overflow: hidden;
			margin: 4px 25px 0 0;
			/* clear: right; */
			float: right;
			padding: 0;
		}
		a.periodo {
			float: left;
			margin: 10px 0 0 -1px;
			border: 1px solid #555;
			color: #FFF;
			padding: 10px 9px;
			text-decoration: none;
			font-family: arial;
			font-size: 13px;
			line-height: 13px;
		}
		a.periodo:first-child {
			margin-left: 0;
		}
		a.periodo.active {
			background-color: #FF6363;
			color: #FFF;
			border: 1px solid #FF6363;
			position: relative;
			z-index: 999;
		}
	</style>

</head>

<body>
	<div class="periodi">
		<a class="periodo periodo1"        href="http://192.168.2.150:3000/d-solo/mROk6Xigz/temperature?panelId=2&orgId=1&from=now-3h&to=now"  target="iframeSala">3 ORE</a>
		<a class="periodo periodo2"        href="http://192.168.2.150:3000/d-solo/mROk6Xigz/temperature?panelId=2&orgId=1&from=now-6h&to=now"  target="iframeSala">6 ORE</a>
		<a class="periodo periodo3 active" href="http://192.168.2.150:3000/d-solo/mROk6Xigz/temperature?panelId=2&orgId=1&from=now-12h&to=now" target="iframeSala">12 ORE</a>
		<a class="periodo periodo4"        href="http://192.168.2.150:3000/d-solo/mROk6Xigz/temperature?panelId=2&orgId=1&from=now-24h&to=now" target="iframeSala">24 ORE</a>
		<a class="periodo periodo5"        href="http://192.168.2.150:3000/d-solo/mROk6Xigz/temperature?panelId=2&orgId=1&from=now-2d&to=now"  target="iframeSala">2 GIORNI</a>
		<a class="periodo periodo6"        href="http://192.168.2.150:3000/d-solo/mROk6Xigz/temperature?panelId=2&orgId=1&from=now-7d&to=now"  target="iframeSala">1 SETTIMANA</a>
	</div>
	<div class="modalGraphContainer">
		<iframe id="iframeSala" name="iframeSala" class="grafanaIframe" src="http://192.168.2.150:3000/d-solo/mROk6Xigz/temperature?panelId=2&orgId=1&from=now-12h&to=now" width="790px" height="    410px" frameborder="0" style="margin-top: 10px;"></iframe>
	</div>

</body>


<script type="text/javascript"> 
	$(document).ready(function(){
		$("a.periodo").click(function(){
			$(this).parent().children().removeClass("active");
			$(this).addClass("active");
		});
	});
</script>

</html>

As you can see some css rules right now are next to the code. Not the best solution, I know, but easier to test quickly (all this temperatures tab is still WIP) before moving them to the main css file.
On the main css file there are other rules impacting the final layout, and it’s quite complicated to extrapolate each single rule that may be needed in your environment. It would also probably break your layout. So be aware that these code snippets won’t work out of the box, you will have to tweak heavily the code, or I suggest you take them just as a track to point you in the right direction to write your own widget, probably with a much cleaner code! :grin:

I hope it can help you somehow. Pardon the messy code, as I said I did my best. For sure everything could be done in a much cleaner, easier way. In my case I chose to go for a ugly code that works, instead of scratching my head on sophisticated code that I am not able to manage.

1 Like

Thank! This is exactly what I need. I am not familiar with js, html, css :wink: Your example will help me a lot!

For me, this is the perfect option! This is a chance to at least understand something :slight_smile:

I’ll be back if you don’t mind …

No problem, as soon as I have an answer!