public static DateTime LocalTime(this UserState userState)
 {
     return userState.ConvertToLocalTime(DateTime.UtcNow);
 }
        public static async Task<string> DescribeReservationAsync(
            this UserState userState,
            JToken reservation,
            int index,
            bool showOwner,
            bool showDate,
            bool showIndex,
            bool useMarkdown)
        {
            var startDate = userState.ConvertToLocalTime(reservation.StartDate());
            var duration = reservation.Value<string>("duration");

            var boatName = await BookedSchedulerCache
                .Instance[userState.ClubId]
                .GetResourceNameFromIdAsync(reservation.ResourceId());

            string owner = string.Empty;

            if (showOwner)
            {
                owner = $" {reservation.FirstName()} {reservation.LastName()}";
            }

            string partnerName = string.Empty;

            //
            // Getting the participant list is messy. When it's empty, it looks like an empty
            // array. When there are participants, it looks like an object with each key/value
            // pair consisting of the user id and the full user name. This is probably a bug so
            // we also try to handle the case that we expected (array of integer user id's).
            //
            if (reservation["participants"] is JArray)
            {
                var participants = (JArray)reservation["participants"];

                if (participants.Count > 0)
                {
                    var partnerRef = participants[0];
                    var partnerUser = await BookedSchedulerCache.Instance[userState.ClubId].GetUserAsync(partnerRef.UserId());
                    partnerName = $" w/ {partnerUser.FullName()}";
                }
            }
            else if (reservation["participants"] is JObject)
            {
                var participants = (JObject)reservation["participants"];

                foreach (var kv in participants)
                {
                    var partnerId = long.Parse(kv.Key);
                    var partnerUser = await BookedSchedulerCache.Instance[userState.ClubId].GetUserAsync(partnerId);
                    partnerName = $" w/ {partnerUser.FullName()}";
                    break;
                }
            }

            if (useMarkdown)
            {
                return string.Format(
                    "{0}**{1} {2}** {3}{4} *({5})*{6}",
                    showIndex ? $"**{index}**:  " : string.Empty,
                    showDate ? startDate.ToLocalTime().ToString("d") : string.Empty,
                    startDate.ToLocalTime().ToString("t"),
                    boatName,
                    partnerName,
                    duration,
                    owner);
            }
            else
            {
                return string.Format(
                    "{0}{1} {2} {3}{4} ({5}) {6}",
                    showIndex ? $"{index}:  " : string.Empty,
                    showDate ? startDate.ToLocalTime().ToString("d") : string.Empty,
                    startDate.ToLocalTime().ToString("t"),
                    boatName,
                    partnerName,
                    duration,
                    owner);
            }
        }
        public static async Task<string> SummarizeReservationAsync(this UserState userState, JToken reservation)
        {
            var startDate = userState.ConvertToLocalTime(reservation.StartDate());

            var boatName = await BookedSchedulerCache
                .Instance[userState.ClubId]
                .GetResourceNameFromIdAsync(reservation.ResourceId());

            return string.Format(
                "{0} {1} {2}",
                startDate.ToLocalTime().ToString("d"),
                startDate.ToLocalTime().ToString("t"),
                boatName);
        }