Global timer for all users

How would you go about creating a global timer all users use? Such as a tournament and every 24 hours the timer resets. How would you make all users see the same time and not base it off the client side?

I’d think to use a service like playfab or app42, but how would this work through them anyway?

Thanks

The following script will retrieve the current UTC date from an API (I’ve arbitrarily chosen worldclockapi, but you can use another one if you want, you will have to change some code if you do this) and update a UI label to show the remaining time before midnight of the next day.

using System;
using System.Collections;
using UnityEngine;
using UnityEngine.Networking;

public class GlobalTimer : MonoBehaviour
{
    /// <summary>
    /// The UI text that will show the remaining time
    /// </summary>
    // Drag & drop the gameObject holding the text component in the inspector
    [SerializeField]
    private UnityEngine.UI.Text timerText;

    /// <summary>
    /// The date of the end of the event
    /// </summary>
    private DateTime eventEndTime;

    /// <summary>
    /// The time offset between the server date and the real date
    /// </summary>
    private TimeSpan timeOffset;

    /// <summary>
    /// Flag to indicate wether the dates have been retrieved
    /// </summary>
    private bool datesRetrieved;

    /// <summary>
    /// Awake is called when the script instance is being loaded
    /// </summary>
    private void Awake()
    {
        StartCoroutine( RetrieveCurrentUTC( OnDateRetrieved ) );
    }

    /// <summary>
    /// Updates is called once per frame
    /// </summary>
    void Update()
    {
        if ( !datesRetrieved )
            return;

        TimeSpan remainingTime = eventEndTime.Subtract( DateTime.UtcNow.Add( timeOffset ) );
        timerText.text = remainingTime.ToString( "hh\\:mm\\:ss" );
    }

    // Retrieve the dates if the application has been paused
    // because maybe, the user changed its device date
    private void OnApplicationPause( bool pause )
    {
        datesRetrieved = false;
        if ( !pause )
            StartCoroutine( RetrieveCurrentUTC( OnDateRetrieved ) );
    }

    // Retrieve the dates if the application has lost focus
    // because maybe, the user changed its device date
    private void OnApplicationFocus( bool focus )
    {
        datesRetrieved = false;
        if ( focus )
            StartCoroutine( RetrieveCurrentUTC( OnDateRetrieved ) );
    }

    /// <summary>
    /// Function called when the UTC date has been retrieved
    /// </summary>
    /// <param name="utc"></param>
    private void OnDateRetrieved( DateTime utc )
    {
        // Compute the time offset between the real date and the client date
        timeOffset = utc.Subtract( DateTime.UtcNow );

        // Change this line according to your needs
        // Here, the event resets every day at midnight UTC
        // You may need to retrieve the date from your server if the events end at specific times
        eventEndTime = utc.AddDays( 1 ).Date;

        // Turn the flag to true to allow the UI to be updated
        datesRetrieved = true;
    }

    /// <summary>
    /// Retrieves the real current UTC date using a web request to a server
    /// </summary>
    /// <param name="onSuccess">Function to call if the date is correctly retrieved</param>
    /// <param name="onError">Function to call if an error occurs</param>
    /// <returns></returns>
    IEnumerator RetrieveCurrentUTC( Action<DateTime> onSuccess, Action<string> onError = null )
    {
        string api = "http://worldclockapi.com/api/json/utc/now";
        UnityWebRequest request = UnityWebRequest.Get( api );
        var operation = request.SendWebRequest();
        yield return operation;
        if( request.isHttpError || request.isNetworkError )
        {
            if ( onError != null )
                onError( request.error );
            yield break;
        }

        string responseDate = request.GetResponseHeader("Date");
        string apiResponse = request.downloadHandler.text;

        if ( onSuccess != null )
        {
            onSuccess( ParseAPIDate( apiResponse ) );
            // OR
            //onSuccess( ParseResponseDate( responseDate ) );
        }
    }

    /// <summary>
    /// Parses the date fetched from the API
    /// </summary>
    /// <param name="input"></param>
    /// <returns>The parsed date</returns>
    /// <remarks>You will have to change the body of this function if you want to use another API</remarks>
    DateTime ParseAPIDate( string input )
    {
        System.Text.RegularExpressions.Regex regex = new System.Text.RegularExpressions.Regex( "\"currentDateTime\":\"([0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}(:[0-9]{2})?Z)\"" );
        var match = regex.Match( input );
        if ( match.Success )
            return DateTime.Parse( match.Groups[1].Value ).ToUniversalTime();

        return DateTime.UtcNow;
    }

    /// <summary>
    /// Parses the date retrieved thanks to the headers of a web request
    /// </summary>
    /// <param name="input"></param>
    /// <returns>The parsed date</returns>
    /// <remarks>You may need to change the body of this function according to the format returned by the server</remarks>
    DateTime ParseResponseDate( string input )
    {
        return DateTime.ParseExact( input, "ddd, dd MMM yyyy HH:mm:ss Z", System.Globalization.CultureInfo.InvariantCulture ).ToUniversalTime();
    }
}