Add 12-hour time format (implements #2)

This commit is contained in:
Noi 2023-01-14 21:07:27 -08:00
parent 7a7bc097e8
commit 09ba9644dc
8 changed files with 184 additions and 15 deletions

View file

@ -25,16 +25,20 @@ public class CommandsBase : InteractionModuleBase<ShardedInteractionContext> {
/// <summary>
/// Returns a string displaying the current time in the given time zone.
/// The result begins with four numbers for sorting purposes. Must be trimmed before output.
/// The result begins with six numbers for sorting purposes. Must be trimmed before output.
/// </summary>
protected static string TzPrint(string zone) {
protected static string TzPrint(string zone, bool use12hr) {
var tzdb = DateTimeZoneProviders.Tzdb;
DateTimeZone tz = tzdb.GetZoneOrNull(zone)!;
if (tz == null) throw new Exception("Encountered unknown time zone: " + zone);
var now = SystemClock.Instance.GetCurrentInstant().InZone(tz);
var sortpfx = now.ToString("MMdd", DateTimeFormatInfo.InvariantInfo);
var fullstr = now.ToString("dd'-'MMM' 'HH':'mm' 'x' (UTC'o<g>')'", DateTimeFormatInfo.InvariantInfo);
var sortpfx = now.ToString("MMddHH", DateTimeFormatInfo.InvariantInfo);
string fullstr;
if (use12hr) {
var ap = now.ToString("tt", DateTimeFormatInfo.InvariantInfo).ToLowerInvariant();
fullstr = now.ToString($"MMM' 'dd', 'hh':'mm'{ap} 'x' (UTC'o<g>')'", DateTimeFormatInfo.InvariantInfo);
} else fullstr = now.ToString("dd'-'MMM', 'HH':'mm' 'x' (UTC'o<g>')'", DateTimeFormatInfo.InvariantInfo);
return $"{sortpfx}● `{fullstr}`";
}

View file

@ -0,0 +1,28 @@
using Discord.Interactions;
namespace WorldTime.Commands;
[Group("config", HelpSettings)]
public class ConfigCommands : CommandsBase {
internal const string HelpSettings = "Configuration commands for World Time.";
internal const string HelpUse12 = "Sets whether to use the 12-hour (AM/PM) format in time zone listings.";
[RequireGuildContext]
[SlashCommand("use-12hour", HelpUse12)]
public async Task Cmd12Hour([Summary(description: "True to enable, False to disable.")] bool setting) {
if (!IsUserAdmin((SocketGuildUser)Context.User)) {
await RespondAsync(ErrNotAllowed, ephemeral: true);
return;
}
using var db = DbContext;
var gs = db.GuildSettings.Where(r => r.GuildId == Context.Guild.Id).SingleOrDefault();
if (gs == null) {
gs = new() { GuildId = Context.Guild.Id };
db.Add(gs);
}
gs.Use12HourTime = setting;
await db.SaveChangesAsync();
await RespondAsync($":white_check_mark: Time listing set to **{(setting ? "AM/PM" : "24 hour")}** format.");
}
}

View file

@ -2,12 +2,16 @@
using System.Text;
namespace WorldTime.Commands;
public class ApplicationCommands : CommandsBase {
const string EmbedHelpField1 = $"`/help` - {HelpHelp}\n"
public class UserCommands : CommandsBase {
const string EmbedHelpField1 =
$"`/help` - {HelpHelp}\n"
+ $"`/list` - {HelpList}\n"
+ $"`/set` - {HelpSet}\n"
+ $"`/remove` - {HelpRemove}";
const string EmbedHelpField2 = $"`/set-for` - {HelpSetFor}\n`/remove-for` - {HelpRemoveFor}";
const string EmbedHelpField2 =
$"`/config use-12hour` - {ConfigCommands.HelpUse12}\n"
+ $"`/set-for` - {HelpSetFor}\n"
+ $"`/remove-for` - {HelpRemoveFor}";
#region Help strings
const string HelpHelp = "Displays a list of available bot commands.";
@ -67,23 +71,25 @@ public class ApplicationCommands : CommandsBase {
await RespondAsync(":x: Nothing to show. Register your time zones with the bot using the `/set` command.");
return;
}
// Order times by popularity to limit how many are shown, group by printed name
var sortedlist = new SortedDictionary<string, List<ulong>>();
var ampm = db.GuildSettings.Where(s => s.GuildId == Context.Guild.Id).SingleOrDefault()?.Use12HourTime ?? false;
foreach ((string area, List<ulong> users) in userlist.OrderByDescending(o => o.Value.Count).Take(20)) {
// Filter further to top 20 distinct timezones, even if they are not displayed in the final result
var areaprint = TzPrint(area);
var areaprint = TzPrint(area, ampm);
if (!sortedlist.ContainsKey(areaprint)) sortedlist.Add(areaprint, new List<ulong>());
sortedlist[areaprint].AddRange(users);
}
const int MaxSingleLineLength = 750;
const int MaxSingleOutputLength = 900;
const int MaxSingleOutputLength = 3000;
// Build zone listings with users
var outputlines = new List<string>();
foreach ((string area, List<ulong> users) in sortedlist) {
var buffer = new StringBuilder();
buffer.Append(area[4..] + ": ");
buffer.Append(area[6..] + ": ");
bool empty = true;
foreach (var userid in users) {
var userinstance = Context.Guild.GetUser(userid);
@ -137,7 +143,8 @@ public class ApplicationCommands : CommandsBase {
return;
}
var resulttext = TzPrint(result)[4..] + ": " + FormatName(parameter);
var ampm = db.GuildSettings.Where(s => s.GuildId == Context.Guild.Id).SingleOrDefault()?.Use12HourTime ?? false;
var resulttext = TzPrint(result, ampm)[6..] + ": " + FormatName(parameter);
await RespondAsync(embed: new EmbedBuilder().WithDescription(resulttext).Build());
}

View file

@ -17,6 +17,7 @@ public class BotDatabaseContext : DbContext {
}
public DbSet<UserEntry> UserEntries { get; set; } = null!;
public DbSet<GuildConfiguration> GuildSettings { get; set; } = null!;
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
@ -24,9 +25,8 @@ public class BotDatabaseContext : DbContext {
.UseSnakeCaseNamingConvention();
protected override void OnModelCreating(ModelBuilder modelBuilder) {
modelBuilder.Entity<UserEntry>(entity => {
entity.HasKey(e => new { e.GuildId, e.UserId }).HasName("userdata_pkey");
});
modelBuilder.Entity<UserEntry>().HasKey(e => new { e.GuildId, e.UserId }).HasName("userdata_pkey");
modelBuilder.Entity<GuildConfiguration>().Property(p => p.Use12HourTime).HasDefaultValue(false);
}
#region Helper methods / abstractions

View file

@ -0,0 +1,9 @@
using System.ComponentModel.DataAnnotations;
namespace WorldTime.Data;
public class GuildConfiguration {
[Key]
public ulong GuildId { get; set; }
public bool Use12HourTime { get; set; }
}

View file

@ -0,0 +1,69 @@
// <auto-generated />
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using WorldTime.Data;
#nullable disable
namespace WorldTime.Data.Migrations
{
[DbContext(typeof(BotDatabaseContext))]
[Migration("20230115041447_Add12HrSetting")]
partial class Add12HrSetting
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "7.0.1")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("WorldTime.Data.GuildConfiguration", b =>
{
b.Property<decimal>("GuildId")
.ValueGeneratedOnAdd()
.HasColumnType("numeric(20,0)")
.HasColumnName("guild_id");
b.Property<bool>("Use12HourTime")
.ValueGeneratedOnAdd()
.HasColumnType("boolean")
.HasDefaultValue(false)
.HasColumnName("use12hour_time");
b.HasKey("GuildId")
.HasName("pk_guild_settings");
b.ToTable("guild_settings", (string)null);
});
modelBuilder.Entity("WorldTime.Data.UserEntry", b =>
{
b.Property<long>("GuildId")
.HasColumnType("bigint")
.HasColumnName("guild_id");
b.Property<long>("UserId")
.HasColumnType("bigint")
.HasColumnName("user_id");
b.Property<string>("TimeZone")
.IsRequired()
.HasColumnType("text")
.HasColumnName("zone");
b.HasKey("GuildId", "UserId")
.HasName("userdata_pkey");
b.ToTable("userdata", (string)null);
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,33 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace WorldTime.Data.Migrations
{
/// <inheritdoc />
public partial class Add12HrSetting : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "guild_settings",
columns: table => new
{
guildid = table.Column<decimal>(name: "guild_id", type: "numeric(20,0)", nullable: false),
use12hourtime = table.Column<bool>(name: "use12hour_time", type: "boolean", nullable: false, defaultValue: false)
},
constraints: table =>
{
table.PrimaryKey("pk_guild_settings", x => x.guildid);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "guild_settings");
}
}
}

View file

@ -16,11 +16,30 @@ namespace WorldTime.Data.Migrations
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "6.0.6")
.HasAnnotation("ProductVersion", "7.0.1")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("WorldTime.Data.GuildConfiguration", b =>
{
b.Property<decimal>("GuildId")
.ValueGeneratedOnAdd()
.HasColumnType("numeric(20,0)")
.HasColumnName("guild_id");
b.Property<bool>("Use12HourTime")
.ValueGeneratedOnAdd()
.HasColumnType("boolean")
.HasDefaultValue(false)
.HasColumnName("use12hour_time");
b.HasKey("GuildId")
.HasName("pk_guild_settings");
b.ToTable("guild_settings", (string)null);
});
modelBuilder.Entity("WorldTime.Data.UserEntry", b =>
{
b.Property<long>("GuildId")