diff --git a/PluralKit.API/Controllers/v1/JsonModelExt.cs b/PluralKit.API/Controllers/v1/JsonModelExt.cs index 1e10d6d3..ac92be48 100644 --- a/PluralKit.API/Controllers/v1/JsonModelExt.cs +++ b/PluralKit.API/Controllers/v1/JsonModelExt.cs @@ -17,7 +17,7 @@ namespace PluralKit.API o.Add("description", system.DescriptionFor(ctx)); o.Add("tag", system.Tag); o.Add("avatar_url", system.AvatarUrl); - o.Add("created", DateTimeFormats.TimestampExportFormat.Format(system.Created)); + o.Add("created", system.Created.FormatExport()); o.Add("tz", system.UiTz); o.Add("description_privacy", ctx == LookupContext.ByOwner ? system.DescriptionPrivacy.ToJsonString() : null); o.Add("member_list_privacy", ctx == LookupContext.ByOwner ? system.MemberListPrivacy.ToJsonString() : null); @@ -42,8 +42,6 @@ namespace PluralKit.API public static JObject ToJson(this PKMember member, LookupContext ctx) { - var bday = member.BirthdayFor(ctx); - var created = member.CreatedFor(ctx); var includePrivacy = ctx == LookupContext.ByOwner; var o = new JObject(); @@ -52,7 +50,7 @@ namespace PluralKit.API // o.Add("color", member.ColorPrivacy.CanAccess(ctx) ? member.Color : null); o.Add("color", member.Color); o.Add("display_name", member.NamePrivacy.CanAccess(ctx) ? member.DisplayName : null); - o.Add("birthday", bday.HasValue ? DateTimeFormats.DateExportFormat.Format(bday.Value) : null); + o.Add("birthday", member.BirthdayFor(ctx)?.FormatExport()); o.Add("pronouns", member.PronounsFor(ctx)); o.Add("avatar_url", member.AvatarFor(ctx)); o.Add("description", member.DescriptionFor(ctx)); @@ -75,7 +73,7 @@ namespace PluralKit.API // o.Add("color_privacy", ctx == LookupContext.ByOwner ? (member.ColorPrivacy.LevelName()) : null); o.Add("metadata_privacy", includePrivacy ? (member.MetadataPrivacy.LevelName()) : null); - o.Add("created", created.HasValue ? DateTimeFormats.TimestampExportFormat.Format(created.Value) : null); + o.Add("created", member.CreatedFor(ctx)?.FormatExport()); if (member.ProxyTags.Count > 0) { diff --git a/PluralKit.Bot/Commands/Misc.cs b/PluralKit.Bot/Commands/Misc.cs index 3d6669e3..8229c576 100644 --- a/PluralKit.Bot/Commands/Misc.cs +++ b/PluralKit.Bot/Commands/Misc.cs @@ -83,7 +83,7 @@ namespace PluralKit.Bot { embed .AddField("Current shard", $"Shard #{shardId} (of {shardTotal} total, {shardUpTotal} are up)", true) - .AddField("Shard uptime", $"{DateTimeFormats.DurationFormat.Format(shardUptime)} ({shardInfo.DisconnectionCount} disconnections)", true) + .AddField("Shard uptime", $"{shardUptime.FormatDuration()} ({shardInfo.DisconnectionCount} disconnections)", true) .AddField("CPU usage", $"{_cpu.LastCpuMeasure:P1}", true) .AddField("Memory usage", $"{memoryUsage / 1024 / 1024} MiB", true) .AddField("Latency", $"API: {apiLatency.TotalMilliseconds:F0} ms, shard: {shardInfo.ShardLatency.Milliseconds} ms", true) diff --git a/PluralKit.Bot/Commands/Switch.cs b/PluralKit.Bot/Commands/Switch.cs index 9724d9a7..1118ed8a 100644 --- a/PluralKit.Bot/Commands/Switch.cs +++ b/PluralKit.Bot/Commands/Switch.cs @@ -103,10 +103,10 @@ namespace PluralKit.Bot // But, we do a prompt to confirm. var lastSwitchMembers = _data.GetSwitchMembers(lastTwoSwitches[0]); var lastSwitchMemberStr = string.Join(", ", await lastSwitchMembers.Select(m => m.NameFor(ctx)).ToListAsync()); - var lastSwitchTimeStr = DateTimeFormats.ZonedDateTimeFormat.Format(lastTwoSwitches[0].Timestamp.InZone(ctx.System.Zone)); - var lastSwitchDeltaStr = DateTimeFormats.DurationFormat.Format(SystemClock.Instance.GetCurrentInstant() - lastTwoSwitches[0].Timestamp); - var newSwitchTimeStr = DateTimeFormats.ZonedDateTimeFormat.Format(time); - var newSwitchDeltaStr = DateTimeFormats.DurationFormat.Format(SystemClock.Instance.GetCurrentInstant() - time.ToInstant()); + var lastSwitchTimeStr = lastTwoSwitches[0].Timestamp.FormatZoned(ctx.System); + var lastSwitchDeltaStr = (SystemClock.Instance.GetCurrentInstant() - lastTwoSwitches[0].Timestamp).FormatDuration(); + var newSwitchTimeStr = time.FormatZoned(); + var newSwitchDeltaStr = (SystemClock.Instance.GetCurrentInstant() - time.ToInstant()).FormatDuration(); // yeet var msg = await ctx.Reply($"{Emojis.Warn} This will move the latest switch ({lastSwitchMemberStr}) from {lastSwitchTimeStr} ({lastSwitchDeltaStr} ago) to {newSwitchTimeStr} ({newSwitchDeltaStr} ago). Is this OK?"); @@ -138,7 +138,7 @@ namespace PluralKit.Bot var lastSwitchMembers = _data.GetSwitchMembers(lastTwoSwitches[0]); var lastSwitchMemberStr = string.Join(", ", await lastSwitchMembers.Select(m => m.NameFor(ctx)).ToListAsync()); - var lastSwitchDeltaStr = DateTimeFormats.DurationFormat.Format(SystemClock.Instance.GetCurrentInstant() - lastTwoSwitches[0].Timestamp); + var lastSwitchDeltaStr = (SystemClock.Instance.GetCurrentInstant() - lastTwoSwitches[0].Timestamp).FormatDuration(); DiscordMessage msg; if (lastTwoSwitches.Count == 1) @@ -150,7 +150,7 @@ namespace PluralKit.Bot { var secondSwitchMembers = _data.GetSwitchMembers(lastTwoSwitches[1]); var secondSwitchMemberStr = string.Join(", ", await secondSwitchMembers.Select(m => m.NameFor(ctx)).ToListAsync()); - var secondSwitchDeltaStr = DateTimeFormats.DurationFormat.Format(SystemClock.Instance.GetCurrentInstant() - lastTwoSwitches[1].Timestamp); + var secondSwitchDeltaStr = (SystemClock.Instance.GetCurrentInstant() - lastTwoSwitches[1].Timestamp).FormatDuration(); msg = await ctx.Reply( $"{Emojis.Warn} This will delete the latest switch ({lastSwitchMemberStr}, {lastSwitchDeltaStr} ago). The next latest switch is {secondSwitchMemberStr} ({secondSwitchDeltaStr} ago). Is this okay?"); } diff --git a/PluralKit.Bot/Commands/SystemEdit.cs b/PluralKit.Bot/Commands/SystemEdit.cs index d65df1e0..31df2270 100644 --- a/PluralKit.Bot/Commands/SystemEdit.cs +++ b/PluralKit.Bot/Commands/SystemEdit.cs @@ -226,7 +226,7 @@ namespace PluralKit.Bot if (zoneStr == null) { await ctx.Reply( - $"Your current system time zone is set to **{ctx.System.UiTz}**. It is currently **{DateTimeFormats.ZonedDateTimeFormat.Format(SystemClock.Instance.GetCurrentInstant().InZone(ctx.System.Zone))}** in that time zone. To change your system time zone, type `pk;s tz `."); + $"Your current system time zone is set to **{ctx.System.UiTz}**. It is currently **{SystemClock.Instance.GetCurrentInstant().FormatZoned(ctx.System)}** in that time zone. To change your system time zone, type `pk;s tz `."); return; } @@ -235,7 +235,7 @@ namespace PluralKit.Bot var currentTime = SystemClock.Instance.GetCurrentInstant().InZone(zone); var msg = await ctx.Reply( - $"This will change the system time zone to **{zone.Id}**. The current time is **{DateTimeFormats.ZonedDateTimeFormat.Format(currentTime)}**. Is this correct?"); + $"This will change the system time zone to **{zone.Id}**. The current time is **{currentTime.FormatZoned()}**. Is this correct?"); if (!await ctx.PromptYesNo(msg)) throw Errors.TimezoneChangeCancelled; ctx.System.UiTz = zone.Id; await _data.SaveSystem(ctx.System); diff --git a/PluralKit.Bot/Commands/SystemFront.cs b/PluralKit.Bot/Commands/SystemFront.cs index ed9138d6..18887e7d 100644 --- a/PluralKit.Bot/Commands/SystemFront.cs +++ b/PluralKit.Bot/Commands/SystemFront.cs @@ -79,12 +79,12 @@ namespace PluralKit.Bot // Calculate the time between the last switch (that we iterated - ie. the next one on the timeline) and the current one var switchDuration = lastSw.Value - sw.Timestamp; stringToAdd = - $"**{membersStr}** ({DateTimeFormats.ZonedDateTimeFormat.Format(sw.Timestamp.InZone(system.Zone))}, {DateTimeFormats.DurationFormat.Format(switchSince)} ago, for {DateTimeFormats.DurationFormat.Format(switchDuration)})\n"; + $"**{membersStr}** ({sw.Timestamp.FormatZoned(system.Zone)}, {switchSince.FormatDuration()} ago, for {switchDuration.FormatDuration()})\n"; } else { stringToAdd = - $"**{membersStr}** ({DateTimeFormats.ZonedDateTimeFormat.Format(sw.Timestamp.InZone(system.Zone))}, {DateTimeFormats.DurationFormat.Format(switchSince)} ago)\n"; + $"**{membersStr}** ({sw.Timestamp.FormatZoned(system.Zone)}, {switchSince.FormatDuration()} ago)\n"; } try // Unfortunately the only way to test DiscordEmbedBuilder.Description max length is this { diff --git a/PluralKit.Bot/Errors.cs b/PluralKit.Bot/Errors.cs index ab3becfb..fe7e4d73 100644 --- a/PluralKit.Bot/Errors.cs +++ b/PluralKit.Bot/Errors.cs @@ -82,7 +82,7 @@ namespace PluralKit.Bot { public static PKError SwitchTimeInFuture => new PKError("Can't move switch to a time in the future."); public static PKError NoRegisteredSwitches => new PKError("There are no registered switches for this system."); - public static PKError SwitchMoveBeforeSecondLast(ZonedDateTime time) => new PKError($"Can't move switch to before last switch time ({DateTimeFormats.ZonedDateTimeFormat.Format(time)}), as it would cause conflicts."); + public static PKError SwitchMoveBeforeSecondLast(ZonedDateTime time) => new PKError($"Can't move switch to before last switch time ({time.FormatZoned()}), as it would cause conflicts."); public static PKError SwitchMoveCancelled => new PKError("Switch move cancelled."); public static PKError SwitchDeleteCancelled => new PKError("Switch deletion cancelled."); public static PKError TimezoneParseError(string timezone) => new PKError($"Could not parse timezone offset {timezone}. Offset must be a value like 'UTC+5' or 'GMT-4:30'."); diff --git a/PluralKit.Bot/Lists/LongRenderer.cs b/PluralKit.Bot/Lists/LongRenderer.cs index b677f573..52812025 100644 --- a/PluralKit.Bot/Lists/LongRenderer.cs +++ b/PluralKit.Bot/Lists/LongRenderer.cs @@ -22,8 +22,6 @@ namespace PluralKit.Bot public void RenderPage(DiscordEmbedBuilder eb, DateTimeZone zone, IEnumerable members, LookupContext ctx) { - string FormatTimestamp(Instant timestamp) => DateTimeFormats.ZonedDateTimeFormat.Format(timestamp.InZone(zone)); - foreach (var m in members) { var profile = $"**ID**: {m.Hid}"; @@ -32,8 +30,8 @@ namespace PluralKit.Bot if (_fields.ShowBirthday && m.BirthdayFor(ctx) != null) profile += $"\n**Birthdate**: {m.BirthdayString}"; if (_fields.ShowProxyTags && m.ProxyTags.Count > 0) profile += $"\n**Proxy tags:** {m.ProxyTagsString()}"; if (_fields.ShowMessageCount && m.MessageCountFor(ctx) is {} count && count > 0) profile += $"\n**Message count:** {count}"; - if (_fields.ShowLastMessage && m.MetadataPrivacy.TryGet(ctx, m.LastMessage, out var lastMsg)) profile += $"\n**Last message:** {FormatTimestamp(DiscordUtils.SnowflakeToInstant(lastMsg.Value))}"; - if (_fields.ShowLastSwitch && m.MetadataPrivacy.TryGet(ctx, m.LastSwitchTime, out var lastSw)) profile += $"\n**Last switched in:** {FormatTimestamp(lastSw.Value)}"; + if (_fields.ShowLastMessage && m.MetadataPrivacy.TryGet(ctx, m.LastMessage, out var lastMsg)) profile += $"\n**Last message:** {DiscordUtils.SnowflakeToInstant(lastMsg.Value).FormatZoned(zone)}"; + if (_fields.ShowLastSwitch && m.MetadataPrivacy.TryGet(ctx, m.LastSwitchTime, out var lastSw)) profile += $"\n**Last switched in:** {lastSw.Value.FormatZoned(zone)}"; if (_fields.ShowDescription && m.DescriptionFor(ctx) is {} desc) profile += $"\n\n{desc}"; if (_fields.ShowPrivacy && m.MemberVisibility == PrivacyLevel.Private) profile += "\n*(this member is hidden)*"; diff --git a/PluralKit.Bot/Services/EmbedService.cs b/PluralKit.Bot/Services/EmbedService.cs index f95aa9c6..52afa29a 100644 --- a/PluralKit.Bot/Services/EmbedService.cs +++ b/PluralKit.Bot/Services/EmbedService.cs @@ -39,7 +39,7 @@ namespace PluralKit.Bot { .WithColor(DiscordUtils.Gray) .WithTitle(system.Name ?? null) .WithThumbnail(system.AvatarUrl) - .WithFooter($"System ID: {system.Hid} | Created on {DateTimeFormats.ZonedDateTimeFormat.Format(system.Created.InZone(system.Zone))}"); + .WithFooter($"System ID: {system.Hid} | Created on {system.Created.FormatZoned(system)}"); var latestSwitch = await _data.GetLatestSwitch(system.Id); if (latestSwitch != null && system.FrontPrivacy.CanAccess(ctx)) @@ -112,7 +112,7 @@ namespace PluralKit.Bot { .WithAuthor(name, iconUrl: DiscordUtils.WorkaroundForUrlBug(avatar)) // .WithColor(member.ColorPrivacy.CanAccess(ctx) ? color : DiscordUtils.Gray) .WithColor(color) - .WithFooter($"System ID: {system.Hid} | Member ID: {member.Hid} {(member.MetadataPrivacy.CanAccess(ctx) ? $"| Created on {DateTimeFormats.ZonedDateTimeFormat.Format(member.Created.InZone(system.Zone))}":"")}"); + .WithFooter($"System ID: {system.Hid} | Member ID: {member.Hid} {(member.MetadataPrivacy.CanAccess(ctx) ? $"| Created on {member.Created.FormatZoned(system)}":"")}"); var description = ""; if (member.MemberVisibility == PrivacyLevel.Private) description += "*(this member is hidden)*\n"; @@ -149,7 +149,7 @@ namespace PluralKit.Bot { return new DiscordEmbedBuilder() .WithColor(members.FirstOrDefault()?.Color?.ToDiscordColor() ?? DiscordUtils.Gray) .AddField($"Current {"fronter".ToQuantity(members.Count, ShowQuantityAs.None)}", members.Count > 0 ? string.Join(", ", members.Select(m => m.NameFor(ctx))) : "*(no fronter)*") - .AddField("Since", $"{DateTimeFormats.ZonedDateTimeFormat.Format(sw.Timestamp.InZone(zone))} ({DateTimeFormats.DurationFormat.Format(timeSinceSwitch)} ago)") + .AddField("Since", $"{sw.Timestamp.FormatZoned(zone)} ({timeSinceSwitch.FormatDuration()} ago)") .Build(); } @@ -200,7 +200,7 @@ namespace PluralKit.Bot { var actualPeriod = breakdown.RangeEnd - breakdown.RangeStart; var eb = new DiscordEmbedBuilder() .WithColor(DiscordUtils.Gray) - .WithFooter($"Since {DateTimeFormats.ZonedDateTimeFormat.Format(breakdown.RangeStart.InZone(tz))} ({DateTimeFormats.DurationFormat.Format(actualPeriod)} ago)"); + .WithFooter($"Since {breakdown.RangeStart.FormatZoned(tz)} ({actualPeriod.FormatDuration()} ago)"); var maxEntriesToDisplay = 24; // max 25 fields allowed in embed - reserve 1 for "others" @@ -214,14 +214,15 @@ namespace PluralKit.Bot { foreach (var pair in membersOrdered) { var frac = pair.Value / actualPeriod; - eb.AddField(pair.Key?.NameFor(ctx) ?? "*(no fronter)*", $"{frac*100:F0}% ({DateTimeFormats.DurationFormat.Format(pair.Value)})"); + eb.AddField(pair.Key?.NameFor(ctx) ?? "*(no fronter)*", $"{frac*100:F0}% ({pair.Value.FormatDuration()})"); } if (membersOrdered.Count > maxEntriesToDisplay) { eb.AddField("(others)", - DateTimeFormats.DurationFormat.Format(membersOrdered.Skip(maxEntriesToDisplay) - .Aggregate(Duration.Zero, (prod, next) => prod + next.Value)), true); + membersOrdered.Skip(maxEntriesToDisplay) + .Aggregate(Duration.Zero, (prod, next) => prod + next.Value) + .FormatDuration(), true); } return Task.FromResult(eb.Build()); diff --git a/PluralKit.Core/Services/DataFileService.cs b/PluralKit.Core/Services/DataFileService.cs index 82a5f41d..ab4ab485 100644 --- a/PluralKit.Core/Services/DataFileService.cs +++ b/PluralKit.Core/Services/DataFileService.cs @@ -37,13 +37,13 @@ namespace PluralKit.Core Name = m.Name, DisplayName = m.DisplayName, Description = m.Description, - Birthday = m.Birthday != null ? DateTimeFormats.DateExportFormat.Format(m.Birthday.Value) : null, + Birthday = m.Birthday?.FormatExport(), Pronouns = m.Pronouns, Color = m.Color, AvatarUrl = m.AvatarUrl, ProxyTags = m.ProxyTags, KeepProxy = m.KeepProxy, - Created = DateTimeFormats.TimestampExportFormat.Format(m.Created), + Created = m.Created.FormatExport(), MessageCount = m.MessageCount })) members.Add(member); @@ -52,7 +52,7 @@ namespace PluralKit.Core var switchList = await _data.GetPeriodFronters(system, Instant.FromDateTimeUtc(DateTime.MinValue.ToUniversalTime()), SystemClock.Instance.GetCurrentInstant()); switches.AddRange(switchList.Select(x => new DataFileSwitch { - Timestamp = DateTimeFormats.TimestampExportFormat.Format(x.TimespanStart), + Timestamp = x.TimespanStart.FormatExport(), Members = x.Members.Select(m => m.Hid).ToList() // Look up member's HID using the member export from above })); @@ -67,7 +67,7 @@ namespace PluralKit.Core TimeZone = system.UiTz, Members = members, Switches = switches, - Created = DateTimeFormats.TimestampExportFormat.Format(system.Created), + Created = system.Created.FormatExport(), LinkedAccounts = (await _data.GetSystemAccounts(system)).ToList() }; } @@ -333,16 +333,16 @@ namespace PluralKit.Core tags.Add(new ProxyTag(Brackets[i * 2], Brackets[i * 2 + 1])); // Convert birthday from ISO timestamp format to ISO date - var convertedBirthdate = Birthday != null ? DateTimeFormats.DateExportFormat.Format( - LocalDate.FromDateTime(DateTimeFormats.TimestampExportFormat.Parse(Birthday).Value - .ToDateTimeUtc())) : null; + var convertedBirthdate = Birthday != null + ? LocalDate.FromDateTime(DateTimeFormats.TimestampExportFormat.Parse(Birthday).Value.ToDateTimeUtc()) + : (LocalDate?) null; return new DataFileMember { Id = Guid.NewGuid().ToString(), // Note: this is only ever used for lookup purposes Name = Name, AvatarUrl = AvatarUrl, - Birthday = convertedBirthdate, + Birthday = convertedBirthdate?.FormatExport(), Description = Description, ProxyTags = tags, KeepProxy = ShowBrackets, diff --git a/PluralKit.Core/Utils/DateTimeFormats.cs b/PluralKit.Core/Utils/DateTimeFormats.cs index 12983238..68a2b624 100644 --- a/PluralKit.Core/Utils/DateTimeFormats.cs +++ b/PluralKit.Core/Utils/DateTimeFormats.cs @@ -20,5 +20,12 @@ namespace PluralKit.Core { public static IPattern LocalDateTimeFormat = LocalDateTimePattern.CreateWithInvariantCulture("yyyy-MM-dd HH:mm:ss"); public static IPattern ZonedDateTimeFormat = ZonedDateTimePattern.CreateWithInvariantCulture("yyyy-MM-dd HH:mm:ss x", DateTimeZoneProviders.Tzdb); + + public static string FormatExport(this Instant instant) => TimestampExportFormat.Format(instant); + public static string FormatExport(this LocalDate date) => DateExportFormat.Format(date); + public static string FormatZoned(this ZonedDateTime zdt) => ZonedDateTimeFormat.Format(zdt); + public static string FormatZoned(this Instant i, DateTimeZone zone) => i.InZone(zone).FormatZoned(); + public static string FormatZoned(this Instant i, PKSystem sys) => i.FormatZoned(sys.Zone); + public static string FormatDuration(this Duration d) => DurationFormat.Format(d); } } \ No newline at end of file