Calculating difference between first element from iteration, which is >= current time

Hi all! May the New Year bring all of you lots of health and happiness! :slight_smile:

I am working on Ionic 4 / Angular app, but I am stuck at a point where I want to compare the first element from iteration, which is >= current date/time and after that calculate the time difference between them.

So far I managed to do this:

HTML

 <ion-content>
  <ng-container *ngFor="let item of (results | async)">
  <ng-container *ngIf="findFirstElement(item.bc) as element">
    <ion-item>{{element.Event.StartTime | date: 'HH:mm'}} - {{element.Event.Title[0].Value}}</ion-item>
   <ion-progress-bar [value]="variable"></ion-progress-bar>
  </ng-container>
 </ng-container>
<ion-content>

TS

findFirstElement(item: any[]) {
     return item.find(el => el.Event.StartTime >= this.date);
}

This helps me print out in template the first result >= this.date.

But I need to calculate the difference between them in minutes (how many minutes have already passed since the startTime). After that I will divide it over the total duration of the element. I need this for the ion-progress-bar.

The problem is I can’t seem to figure out how to pass the first element from template to TS.
After that I will use Math.round(this.diffMs / 60000) to calculate the difference in minutes, but I can’t get there.

What do you think would be the best approach here?

Thank you very much in advance!!

Remember the comparation of format 24 Hours, because in 12H maybe you can have error to calculate difference.

1 Like

Getting rid of the AsyncPipe and using date-fns.

findFirstElement references this.date. What is this here? Does each template correspond to a single event, or is there one page for all events? Is this code in a service or a page? Importantly, is this.date constant for the whole app, or does it change from component to component?

1 Like

Hi @rapropos,
Thank you for your answer!
In my case I am comparing this.date ( date = ‘2019-11-21T18:00:00’; ) to Event.StartTime from an Observable (array). This part is working as it should. I am getting the first Event.Startime which is >= this.date.
F.e. Event.StartTime = ‘2019-11-21T18:23:00’;
My problem is that I want to calculate the difference between them - in this case I want to receive the 23 minutes as a result. And I don’t mean the calculation itself, I mean the proper way of making it happen. As you can see, findFirstElement(item.bc) enters the array and gives value to the function. But this is in template. As far as I know it is not possible to make the calculation in template and I need to do it in .TS
After that I will divide the minute difference over the whole duration of the element and I will use it for the ion-progress-bar value.
So my question is what would be the proper way to do it.

And regarding your other questions - this code is in a page. I am importing the service in the typescript file of the page:

import { Component, OnInit } from '@angular/core';
import { EpgService } from './../services/epg.service';
import { Observable } from 'rxjs';

@Component({
  selector: 'app-tab4',
  templateUrl: './tab4.page.html',
  styleUrls: ['./tab4.page.scss'],
})
export class Tab4Page implements OnInit {

  results: Observable<any>;
  results2: Observable<any>;

  date = '2019-11-21T18:00:00';

  constructor(private channel: EpgService) {
  }


  findFirstElement(item: any[]) {
    return item.find(el => el.Event.StartTime >= this.date);
}

  ngOnInit() {

    this.results = this.channel.searchData();
    this.results2 = this.channel.searchData2();
   
  }

}

HTML:

<ion-header>
    <ion-toolbar>
      <ion-title class="ion-text-center">
        Now
      </ion-title>
    </ion-toolbar>
  </ion-header>

<ion-content>
  <ng-container *ngFor="let item of (results | async)">
    <ng-container *ngIf="findFirstElement(item.bc) as element">
      <ion-item>{{element.Event.StartTime | date: 'HH:mm'}} - {{element.Event.Title[0].Value}}</ion-item>
      <ion-progress-bar [value]="variable"></ion-progress-bar>
    </ng-container>
  </ng-container>
  <ng-container *ngFor="let item of (results2 | async)">
    <ng-container *ngIf="findFirstElement(item.bc) as element">
      <ion-item>{{element.Event.StartTime | date: 'HH:mm'}} - {{element.Event.Title[0].Value}}</ion-item>
      <ion-progress-bar value="0.5"></ion-progress-bar>
    </ng-container>
  </ng-container>
</ion-content>

See this example. https://jsfiddle.net/crmontiel/y9kobztj/8/

You can apply it as a function that when the ng for cycle executes, always using as the first date the first of the arrangement, and only change the second one that would be sent by the function

In this case i’m using Jquery 3.2.1

And you will have to extract the hours and minutes of the dates you bring in your arrangements.

1 Like
<ion-content>
  <ng-container *ngFor="let item of (results | async)">
  <ng-container *ngIf="findFirstElement(item.bc) as element">
    <ion-item>{{ timeCalc( YouValueInitial , YouValueDynamic ) }}  </ion-item>
   <ion-progress-bar [value]="variable"></ion-progress-bar>
  </ng-container>
 </ng-container>
<ion-content>
timeCalc(d1, d2){
	
    let date1 = new Date(d1).getTime();
    let date2 = new Date(d2).getTime();

    let msec = date2 - date1;
    let mins = Math.floor(msec / 60000);
    let hrs = Math.floor(mins / 60);
    let days = Math.floor(hrs / 24);
    
    mins = mins % 60;

    let tValue1= `In hours: ${hrs} hours,  ${mins}  minutes`
    let tValue2= `In days: ${days}  days, ${hrs} hours, ${mins} minutes`
 
   return tValue1
  // return tValue2

}
1 Like

The presence of any instead of being able to see the true structure of the data, combined with overly generic property names like results and results2 makes it hard for me to write much concretely, but here’s how I would approach this situation:
I’m changing naming conventions to put all properties in camelCase, so as to avoid conflicts with classes and interfaces in PascalCase. Event is also a very overloaded identifier in the JavaScript ecosystem, due to its usage in browser events, so I’m deliberately avoiding it in favor of Happening (but feel free to choose whatever name makes more sense to you, remembering that more specific is almost always better for readability):

export interface Happening {
  startTime: Date;
  title: string;
}

@Injectable() export class EgpService {
  allHappenings(): Observable<Happening[]> {
    // effectively, the old getResults()
  }
}
interface DisplayHappening extends Happening {
  sinceStartMinutes?: number;
  sinceStartWords?: string;
}
class Tab4Page {
  firstOpenHappening?: Happening;
  cutoff = new Date("2019-11-21T18:00:00");
  constructor(private epger: EpgService) {}
  ngOnInit(): void {
    this.epger.allHappenings().pipe(
      map(haps => haps.find(hap => isAfter(hap.startTime, this.date)),
      map(firstHap => {
        if (firstHap) {
          firstHap.sinceStartMinutes = differenceInMinutes(firstHap.startTime, this.date);
          firstHap.sinceStartWords = distanceInWords(firstHap.startTime, this.date);
        }
        return firstHap;
      })).subscribe(fh => this.firstOpenHappening = fh);
  }
} 

isAfter, differenceInMinutes, and distanceInWords are all part of date-fns. Now the template is trivial and much more performant than any approach that involves calling functions from the template, because change detection will call them zillions of times:

<ion-content>
  <ng-container *ngIf="firstOpenHappening">
    <ion-item>{{firstOpenHappening.sinceStartWords}} / {{firstOpenHappening.sinceStartMinutes}} minutes</ion-item>
  </ng-container>
</ion-content>
1 Like

Thank you very much, @crmontiel !!!
This pretty much solved my problem! I will just pass the function with the calculations I need in the ion-progress-bar value! Thanks again one more time!

1 Like

Thank you very much @rapropos for the help and the constructive remarks!
I will definitely try the date-fns approach!