using Cysharp.Text; namespace VPLink.Common.Data; /// /// Represents a plain text message builder. /// public struct PlainTextMessageBuilder : IDisposable { private Utf8ValueStringBuilder _builder; /// /// Initializes a new instance of the struct. /// public PlainTextMessageBuilder() { _builder = ZString.CreateUtf8StringBuilder(); } /// /// Appends the specified word. /// /// The word. /// The trailing whitespace trivia. public void AddWord(ReadOnlySpan word, char whitespace = ' ') { _builder.Append(word); if (whitespace != '\0') _builder.Append(whitespace); } /// /// Appends the specified word. /// /// The timestamp. /// The format. /// The trailing whitespace trivia. public void AddTimestamp(DateTimeOffset timestamp, TimestampFormat format, char whitespace = ' ') { switch (format) { case TimestampFormat.Relative: AddWord(FormatRelativeTime(timestamp), whitespace); break; case TimestampFormat.None: AddWord(timestamp.ToString("d MMM yyyy HH:mm")); AddWord("UTC", whitespace); break; case TimestampFormat.LongDate: AddWord(timestamp.ToString("dd MMMM yyyy")); AddWord("UTC", whitespace); break; case TimestampFormat.ShortDate: AddWord(timestamp.ToString("dd/MM/yyyy")); AddWord("UTC", whitespace); break; case TimestampFormat.ShortTime: AddWord(timestamp.ToString("HH:mm")); AddWord("UTC", whitespace); break; case TimestampFormat.LongTime: AddWord(timestamp.ToString("HH:mm:ss")); AddWord("UTC", whitespace); break; case TimestampFormat.ShortDateTime: AddWord(timestamp.ToString("dd MMMM yyyy HH:mm")); AddWord("UTC", whitespace); break; case TimestampFormat.LongDateTime: AddWord(timestamp.ToString("dddd, dd MMMM yyyy HH:mm")); AddWord("UTC", whitespace); break; default: AddWord($"", whitespace); break; } } /// /// Clears the builder. /// public void Clear() { _builder.Clear(); } /// public void Dispose() { _builder.Dispose(); } /// public override string ToString() { return _builder.ToString().Trim(); } private static string FormatRelativeTime(DateTimeOffset targetTime) { TimeSpan timeDifference = DateTimeOffset.Now - targetTime; bool isFuture = timeDifference.TotalMilliseconds < 0; int value; string unit; timeDifference = TimeSpan.FromMilliseconds(Math.Abs(timeDifference.TotalMilliseconds)); switch (timeDifference.TotalDays) { case >= 365: unit = "year"; value = (int)(timeDifference.TotalDays / 365); break; case >= 30: unit = "month"; value = (int)(timeDifference.TotalDays / 30); break; case >= 1: unit = "day"; value = (int)timeDifference.TotalDays; break; default: if (timeDifference.TotalHours >= 1) { unit = "hour"; value = (int)timeDifference.TotalHours; } else if (timeDifference.TotalMinutes >= 1) { unit = "minute"; value = (int)timeDifference.TotalMinutes; } else { unit = "second"; value = (int)timeDifference.TotalSeconds; } break; } string suffix = value > 1 ? "s" : ""; return isFuture ? $"in {value} {unit}{suffix}" : $"{value} {unit}{suffix} ago"; } }