Set last tripped/alarm date, or last action date to an item - best practice?

You can use the Expire binding for this as well, but will want a separate Timer Item.

Here is an example from my rules (slightly simplified).

Contact vFrontDoor "Front Door is [MAP(en.map):%s]"
  { mqtt="<[chimera:entry_sensors/main/front_door:state:default]" }

Switch vFrontDoor_Timer
  { expire="1h,command=OFF" }
rule "Set Door Open Timer"
when
  Item vFrontDoor changed 
then
  if(vFrontDoor.state == OPEN) vFrontDoor_Timer.sendCommand(ON)
  else vFrontDoor_Timer.postUpdate(OFF)
end

rule "Timer expired for a door"
when
  Item vFrontDoor_Timer received command OFF
then
  aAlert.sendCommand("The front door has been open for over an hour")
  
  if(vTimeOfDay.state.toString == "NIGHT" || vTimeOfDay.state.toString == "BED") {
  	timer.sendCommand(ON) // reschedule the timer
  }
end

If you have a lot of doors and windows you want to track, you might find my full entry rules to be informative. Note that the rules below require persistence to be set up with everyChange to work.

You can find my Time of Day rules and Items here and my Alerting Items and Rules here though the names of my Items have changed since I wrote that last one.

For an explanation of how I access the Timer and LastUpdate Items from the Door Item’s name see this.

Group:Contact:OR(OPEN,CLOSED) gDoorSensors "The doors are [MAP(en.map):%s]"

Group:DateTime:MAX gDoorsLast "The last door event was [%1$tm/%1$td %1$tH:%1$tM]"

Group:Switch:OR(ON, OFF) gDoorsTimers

Switch aGarageOpener1 "Garage Door Opener 1"
  
Contact vGarageOpener1 "Garage Door Opener 1 is [MAP(en.map):%s]"
  (gDoorSensors)
  { mqtt="<[chimera:entry_sensors/main/garage/door1:state:default]" }
  
Switch vGarageOpener1_Timer
  (gDoorsTimers)
  { expire="1h,command=OFF" }
  
DateTime vGarageOpener1_LastUpdate "Garage Door Opener 1 [%1$tm/%1$td %1$tH:%1$tM]"
  (gDoorsLast)
  
Switch aGarageOpener2 "Garage Door Opener 2"
  
Contact vGarageOpener2 "Garage Door Opener 2 is [MAP(en.map):%s]"
  (gDoorSensors)
  { mqtt="<[chimera:entry_sensors/main/garage/door2:state:default]" }

Switch vGarageOpener2_Timer
  (gDoorsTimers)
  { expire="1h,command=OFF" }

DateTime vGarageOpener2_LastUpdate "Garage Door Opener 2 [%1$tm/%1$td %1$tH:%1$tM]"
  (gDoorsLast)
  
Contact vFrontDoor "Front Door is [MAP(en.map):%s]"
  (gDoorSensors)
  { mqtt="<[chimera:entry_sensors/main/front_door:state:default]" }

Switch vFrontDoor_Timer
  (gDoorsTimers)
  { expire="1h,command=OFF" }
  
DateTime vFrontDoor_LastUpdate "Front Door [%1$tm/%1$td %1$tH:%1$tM]"
  (gDoorsLast)
  
Contact vBackDoor "Back Door is [MAP(en.map):%s]"
  (gDoorSensors)
  { mqtt="<[chimera:entry_sensors/main/back_door:state:default]" }

Switch vBackDoor_Timer
  (gDoorsTimers)
  { expire="1h,command=OFF" }

DateTime vBackDoor_LastUpdate "Back Door [%1$tm/%1$td %1$tH:%1$tM]"
  (gDoorsLast)
  
Contact vGarageDoor "Garage Door is [MAP(en.map):%s]"
  (gDoorSensors)
  { mqtt="<[chimera:entry_sensors/main/garage_door:state:default]" }

Switch vGarageDoor_Timer
  (gDoorsTimers)
  { expire="1h,command=OFF" }
  
DateTime vGarageDoor_LastUpdate "Garage Door [%1$tm/%1$td %1$tH:%1$tM]"
  (gDoorsLast)
import org.eclipse.xtext.xbase.lib.Functions

val logName = "entry"

val Functions$Function1<SwitchItem, Boolean> openGarage = [ opener |
	var topic = "NA"
	switch opener.name{
		case "aGarageOpener1": topic = "actuators/garage1"
		case "aGarageOpener2": topic = "actuators/garage2"
	}
	
	logInfo("entry", "Publishing ON to " + topic)
	publish("chimera", topic, "ON")
	
	// TODO send an alert if cerberos is down
	true	
]
 
rule "Garage Opener 1 Triggered"
when
  Item aGarageOpener1 received command
then
  openGarage.apply(aGarageOpener1)
end

rule "Garage Opener 2 Triggered"
when
  Item aGarageOpener2 received command
then
  openGarage.apply(aGarageOpener2)
end

rule "Keep track of the last time a door was opened or closed"
when
  Item vGarageOpener1 changed or
  Item vGarageOpener2 changed or
  Item vFrontDoor changed or
  Item vBackDoor changed or
  Item vGarageDoor changed
then
  Thread::sleep(100)
  val door = gDoorSensors.members.filter[s|s.lastUpdate("mapdb") != null].sortBy[lastUpdate("mapdb")].last as ContactItem
  
  // Update LastUpdate
  val last = gDoorsLast.members.filter[dt | dt.name == door.name+"_LastUpdate"].head as DateTimeItem
  last.postUpdate(new DateTimeType)

  // Set/cancel the Timer  
  val timer = gDoorsTimers.members.filter[t | t.name == door.name+"_Timer"].head as SwitchItem
  if(door.state == OPEN) timer.sendCommand(ON)
  else timer.postUpdate(OFF)
  
  // Log and alert
  val StringBuilder msg = new StringBuilder
  val doorName = transform("MAP", "en.map", door.name)
  
  msg.append(doorName)
  msg.append(" was")
  msg.append(if(door.state == OPEN) " opened" else " closed")
  
  var alert = false
  if(vTimeOfDay.state.toString == "NIGHT" || vTimeOfDay.state.toString == "BED"){
  	alert = true
  	msg.append(" and it is night")
  }
  
  if(vPresent.state == OFF) {
  	alert = true
  	msg.append(" and no one is home")
  }
  
  if(alert){
  	msg.append("!")
  	aAlert.sendCommand(msg.toString)
  }
  
  logInfo(logName, msg.toString)
end

rule "Timer expired for a door"
when
  Item vGarageOpener1_Timer received command OFF or
  Item vGarageOpener2_Timer received command OFF or
  Item vFrontDoor_Timer received command OFF or
  Item vBackDoor_Timer received command OFF or
  Item vGarageDoor_Timer received command OFF
then
  Thread::sleep(100)
  val timer = gDoorsTimers.members.sortBy[lastUpdate("mapdb")].last as SwitchItem
  val doorName = transform("MAP", "en.map", timer.name)  

  aAlert.sendCommand(doorName + " has been open for over an hour")
  
  if(vTimeOfDay.state.toString == "NIGHT" || vTimeOfDay.state.toString == "BED") {
  	timer.sendCommand(ON) // reschedule the timer
  }
end