2025 © Francesco Costantino

What if thousands of users were living in the past... or the future?

September 3, 2025

What if thousands of users were living in the past... or the future?

What if thousands of users were living in the past... or the future?

This sounds like sci-fi, but it was a real problem we faced with our HbbTV app. For an application that depends on perfect timing, we were in a time-travel paradox.

The Problem

We were seeing bizarre bugs that we couldn't replicate. The breakthrough came when we started logging the internal clock of every TV running our app. Sending this data to our ELK stack revealed a chaotic picture: thousands of Smart TVs across Italy were out of sync. Without proper NTP configuration, their clocks were drifting by several minutes, creating a nightmare for our time-sensitive features.

Our users weren't time travelers; their TVs just had the wrong time.

The Solution

To fix this, we decided to become the master of time ourselves. Here's how we implemented the solution using Angular:

// time-sync.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpResponse } from '@angular/common/http';
import { Observable, map, tap } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class TimeSyncService {
  private timeOffset: number = 0;

  constructor(private http: HttpClient) {
    this.synchronizeTime();
  }

  private synchronizeTime() {
    this.http.get('/api/time', { observe: 'response' })
      .subscribe((response: HttpResponse<any>) => {
        const serverTime = new Date(response.headers.get('Date')).getTime();
        const localTime = new Date().getTime();
        this.timeOffset = serverTime - localTime;
        console.log(`Time offset: ${this.timeOffset}ms`);
      });
  }

  public getCurrentTime(): Date {
    return new Date(Date.now() + this.timeOffset);
  }
}

And how to use it in components:

// app.component.ts
import { Component, OnInit } from '@angular/core';
import { TimeSyncService } from './time-sync.service';

@Component({
  selector: 'app-root',
  template: `
    <div>
      <h2>Correct time: {{ currentTime | date:'medium' }}</h2>
      <h2>Device local time: {{ deviceTime | date:'medium' }}</h2>
    </div>
  `
})
export class AppComponent implements OnInit {
  currentTime: Date;
  deviceTime: Date;

  constructor(private timeSync: TimeSyncService) {
    setInterval(() => {
      this.currentTime = this.timeSync.getCurrentTime();
      this.deviceTime = new Date();
    }, 1000);
  }
}

How It Works

Our TimeSyncService does several important things:

  1. On application startup, it makes an HTTP request to our server
  2. Extracts the Date header from the HTTP response as the "ground truth"
  3. Calculates the difference (offset) between server time and device time
  4. Provides a getCurrentTime() method that always returns the correct time

To handle edge cases, we also implemented a retry and fallback strategy:

// retry-strategy.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { retryWhen, delay, take } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class TimeRetryStrategy {
  private readonly MAX_RETRIES = 3;
  private readonly RETRY_DELAY = 1000;
  private readonly FALLBACK_SERVERS = [
    '/api/time',
    '/api/fallback-time',
    'https://backup-timeserver.com/time'
  ];

  constructor(private http: HttpClient) {}

  public getTimeWithRetry() {
    return this.http.get(this.FALLBACK_SERVERS[0])
      .pipe(
        retryWhen(errors => 
          errors.pipe(
            delay(this.RETRY_DELAY),
            take(this.MAX_RETRIES)
          )
        )
      );
  }
}

Results

The result? All time-related bugs vanished instantly. Our HbbTV app now works flawlessly on all Smart TVs, regardless of their local time settings.

Lessons Learned

This experience reinforced a golden rule of development: the client-side is the wild west. Building strong monitoring and observability is the only way to tame it.

Emerged Best Practices:

  1. Never trust the client device's clock
  2. Always implement robust logging
  3. Use ELK or similar tools for pattern analysis
  4. Always have a fallback plan