Replace the date format option with a locale option (#1130)

This commit is contained in:
Oleksii Holub
2023-09-07 14:34:08 +03:00
committed by GitHub
parent 53b11d6c49
commit 59344cedbe
22 changed files with 288 additions and 273 deletions

View File

@@ -1,4 +1,5 @@
using System;
using System.Globalization;
using System.Linq;
using DiscordChatExporter.Core.Utils.Extensions;
@@ -19,7 +20,10 @@ public static class ImageCdn
? runes
: runes.Where(r => r.Value != 0xfe0f);
var twemojiId = string.Join("-", filteredRunes.Select(r => r.Value.ToString("x")));
var twemojiId = string.Join(
"-",
filteredRunes.Select(r => r.Value.ToString("x", CultureInfo.InvariantCulture))
);
return $"https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/{twemojiId}.svg";
}

View File

@@ -34,6 +34,12 @@ internal class ExportContext
);
}
public DateTimeOffset NormalizeDate(DateTimeOffset instant) =>
Request.IsUtcNormalizationEnabled ? instant.ToUniversalTime() : instant.ToLocalTime();
public string FormatDate(DateTimeOffset instant, string format = "g") =>
NormalizeDate(instant).ToString(format, Request.CultureInfo);
public async ValueTask PopulateChannelsAndRolesAsync(
CancellationToken cancellationToken = default
)
@@ -41,10 +47,14 @@ internal class ExportContext
await foreach (
var channel in Discord.GetGuildChannelsAsync(Request.Guild.Id, cancellationToken)
)
{
_channelsById[channel.Id] = channel;
}
await foreach (var role in Discord.GetGuildRolesAsync(Request.Guild.Id, cancellationToken))
{
_rolesById[role.Id] = role;
}
}
// Because members cannot be pulled in bulk, we need to populate them on demand
@@ -84,14 +94,6 @@ internal class ExportContext
CancellationToken cancellationToken = default
) => await PopulateMemberAsync(user.Id, user, cancellationToken);
public string FormatDate(DateTimeOffset instant) =>
Request.DateFormat switch
{
"unix" => instant.ToUnixTimeSeconds().ToString(),
"unixms" => instant.ToUnixTimeMilliseconds().ToString(),
var format => instant.ToLocalString(format)
};
public Member? TryGetMember(Snowflake id) => _membersById.GetValueOrDefault(id);
public Channel? TryGetChannel(Snowflake id) => _channelsById.GetValueOrDefault(id);

View File

@@ -39,7 +39,11 @@ public partial class ExportRequest
public bool ShouldReuseAssets { get; }
public string DateFormat { get; }
public string Locale { get; }
public CultureInfo CultureInfo { get; }
public bool IsUtcNormalizationEnabled { get; }
public ExportRequest(
Guild guild,
@@ -54,7 +58,8 @@ public partial class ExportRequest
bool shouldFormatMarkdown,
bool shouldDownloadAssets,
bool shouldReuseAssets,
string dateFormat
string locale,
bool isUtcNormalizationEnabled
)
{
Guild = guild;
@@ -67,7 +72,8 @@ public partial class ExportRequest
ShouldFormatMarkdown = shouldFormatMarkdown;
ShouldDownloadAssets = shouldDownloadAssets;
ShouldReuseAssets = shouldReuseAssets;
DateFormat = dateFormat;
Locale = locale;
IsUtcNormalizationEnabled = isUtcNormalizationEnabled;
OutputFilePath = GetOutputBaseFilePath(Guild, Channel, outputPath, Format, After, Before);
@@ -76,6 +82,8 @@ public partial class ExportRequest
AssetsDirPath = !string.IsNullOrWhiteSpace(assetsDirPath)
? FormatPath(assetsDirPath, Guild, Channel, After, Before)
: $"{OutputFilePath}_Files{Path.DirectorySeparatorChar}";
CultureInfo = CultureInfo.GetCultureInfo(Locale);
}
}

View File

@@ -317,12 +317,12 @@ internal partial class HtmlMarkdownVisitor : MarkdownVisitor
)
{
var formatted = timestamp.Instant is not null
? !string.IsNullOrWhiteSpace(timestamp.Format)
? timestamp.Instant.Value.ToLocalString(timestamp.Format)
: _context.FormatDate(timestamp.Instant.Value)
? _context.FormatDate(timestamp.Instant.Value, timestamp.Format ?? "g")
: "Invalid date";
var formattedLong = timestamp.Instant?.ToLocalString("dddd, MMMM d, yyyy h:mm tt") ?? "";
var formattedLong = timestamp.Instant is not null
? _context.FormatDate(timestamp.Instant.Value, "f")
: "";
_buffer.Append(
// lang=html

View File

@@ -139,7 +139,7 @@ internal class HtmlMessageWriter : MessageWriter
Minify(
await new PostambleTemplate
{
ExportContext = Context,
Context = Context,
MessagesWritten = MessagesWritten
}.RenderAsync(cancellationToken)
)

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Encodings.Web;
using System.Text.Json;
@@ -196,7 +197,7 @@ internal class JsonMessageWriter : MessageWriter
await FormatMarkdownAsync(embed.Title ?? "", cancellationToken)
);
_writer.WriteString("url", embed.Url);
_writer.WriteString("timestamp", embed.Timestamp);
_writer.WriteString("timestamp", embed.Timestamp?.Pipe(Context.NormalizeDate));
_writer.WriteString(
"description",
await FormatMarkdownAsync(embed.Description ?? "", cancellationToken)
@@ -292,12 +293,12 @@ internal class JsonMessageWriter : MessageWriter
// Date range
_writer.WriteStartObject("dateRange");
_writer.WriteString("after", Context.Request.After?.ToDate());
_writer.WriteString("before", Context.Request.Before?.ToDate());
_writer.WriteString("after", Context.Request.After?.ToDate().Pipe(Context.NormalizeDate));
_writer.WriteString("before", Context.Request.Before?.ToDate().Pipe(Context.NormalizeDate));
_writer.WriteEndObject();
// Timestamp
_writer.WriteString("exportedAt", System.DateTimeOffset.UtcNow);
_writer.WriteString("exportedAt", Context.NormalizeDate(DateTimeOffset.UtcNow));
// Message array (start)
_writer.WriteStartArray("messages");
@@ -316,9 +317,15 @@ internal class JsonMessageWriter : MessageWriter
// Metadata
_writer.WriteString("id", message.Id.ToString());
_writer.WriteString("type", message.Kind.ToString());
_writer.WriteString("timestamp", message.Timestamp);
_writer.WriteString("timestampEdited", message.EditedTimestamp);
_writer.WriteString("callEndedTimestamp", message.CallEndedTimestamp);
_writer.WriteString("timestamp", Context.NormalizeDate(message.Timestamp));
_writer.WriteString(
"timestampEdited",
message.EditedTimestamp?.Pipe(Context.NormalizeDate)
);
_writer.WriteString(
"callEndedTimestamp",
message.CallEndedTimestamp?.Pipe(Context.NormalizeDate)
);
_writer.WriteBoolean("isPinned", message.IsPinned);
// Content

View File

@@ -1,6 +1,5 @@
@using System
@using System.Collections.Generic
@using System.Globalization
@using System.Linq
@using System.Threading.Tasks
@using DiscordChatExporter.Core.Discord.Data
@@ -20,8 +19,8 @@
ValueTask<string> ResolveAssetUrlAsync(string url) =>
Context.ResolveAssetUrlAsync(url, CancellationToken);
string FormatDate(DateTimeOffset instant) =>
Context.FormatDate(instant);
string FormatDate(DateTimeOffset instant, string format = "g") =>
Context.FormatDate(instant, format);
async ValueTask<string> FormatMarkdownAsync(string markdown) =>
Context.Request.ShouldFormatMarkdown
@@ -100,7 +99,7 @@
}
else if (message.Kind == MessageKind.Call)
{
<span>started a call that lasted @(((message.CallEndedTimestamp ?? message.Timestamp) - message.Timestamp).TotalMinutes.ToString("n0", CultureInfo.InvariantCulture)) minutes</span>
<span>started a call that lasted @(((message.CallEndedTimestamp ?? message.Timestamp) - message.Timestamp).TotalMinutes.ToString("n0", Context.Request.CultureInfo)) minutes</span>
}
else if (message.Kind == MessageKind.ChannelNameChange)
{
@@ -132,7 +131,7 @@
</span>
@* Timestamp *@
<span class="chatlog__system-notification-timestamp">
<span class="chatlog__system-notification-timestamp" title="@FormatDate(message.Timestamp, "f")">
<a href="#chatlog__message-container-@message.Id">@FormatDate(message.Timestamp)</a>
</span>
</div>
@@ -154,7 +153,7 @@
}
else
{
<div class="chatlog__short-timestamp" title="@FormatDate(message.Timestamp)">@message.Timestamp.ToLocalString("t")</div>
<div class="chatlog__short-timestamp" title="@FormatDate(message.Timestamp, "f")">@FormatDate(message.Timestamp, "t")</div>
}
</div>
@@ -194,7 +193,7 @@
@if (message.ReferencedMessage.EditedTimestamp is not null)
{
<span class="chatlog__reply-edited-timestamp" title="@FormatDate(message.ReferencedMessage.EditedTimestamp.Value)">(edited)</span>
<span class="chatlog__reply-edited-timestamp" title="@FormatDate(message.ReferencedMessage.EditedTimestamp.Value, "f")">(edited)</span>
}
</div>
}
@@ -241,7 +240,7 @@
}
@* Timestamp *@
<span class="chatlog__timestamp"><a href="#chatlog__message-container-@message.Id">@FormatDate(message.Timestamp)</a></span>
<span class="chatlog__timestamp" title="@FormatDate(message.Timestamp, "f")"><a href="#chatlog__message-container-@message.Id">@FormatDate(message.Timestamp)</a></span>
</div>
}
@@ -258,7 +257,7 @@
@* Edited timestamp *@
@if (message.EditedTimestamp is not null)
{
<span class="chatlog__edited-timestamp" title="@FormatDate(message.EditedTimestamp.Value)">(edited)</span>
<span class="chatlog__edited-timestamp" title="@FormatDate(message.EditedTimestamp.Value, "f")">(edited)</span>
}
</div>
}

View File

@@ -91,9 +91,7 @@ internal partial class PlainTextMarkdownVisitor : MarkdownVisitor
{
_buffer.Append(
timestamp.Instant is not null
? !string.IsNullOrWhiteSpace(timestamp.Format)
? timestamp.Instant.Value.ToLocalString(timestamp.Format)
: _context.FormatDate(timestamp.Instant.Value)
? _context.FormatDate(timestamp.Instant.Value, timestamp.Format ?? "g")
: "Invalid date"
);

View File

@@ -1,7 +1,9 @@
@inherits RazorBlade.HtmlTemplate
@using System
@inherits RazorBlade.HtmlTemplate
@functions {
public required ExportContext ExportContext { get; init; }
public required ExportContext Context { get; init; }
public required long MessagesWritten { get; init; }
}
@@ -14,7 +16,8 @@
<!--/wmm:ignore-->
<div class="postamble">
<div class="postamble__entry">Exported @MessagesWritten.ToString("n0") message(s)</div>
<div class="postamble__entry">Exported @MessagesWritten.ToString("n0", Context.Request.CultureInfo) message(s)</div>
<div class="postamble__entry">Timezone: UTC@((Context.Request.IsUtcNormalizationEnabled ? 0 : TimeZoneInfo.Local.BaseUtcOffset.TotalHours).ToString("+#.#;-#.#;+0", Context.Request.CultureInfo))</div>
</div>
</body>

View File

@@ -21,8 +21,8 @@
ValueTask<string> ResolveAssetUrlAsync(string url) =>
Context.ResolveAssetUrlAsync(url, CancellationToken);
string FormatDate(DateTimeOffset instant) =>
Context.FormatDate(instant);
string FormatDate(DateTimeOffset instant, string format = "g") =>
Context.FormatDate(instant, format);
async ValueTask<string> FormatMarkdownAsync(string markdown) =>
Context.Request.ShouldFormatMarkdown

View File

@@ -340,16 +340,13 @@ internal static partial class MarkdownParser
)
);
var format = m.Groups[2].Value switch
var format = m.Groups[2].Value.NullIfWhiteSpace() switch
{
"t" => "h:mm tt",
"T" => "h:mm:ss tt",
"d" => "MM/dd/yyyy",
"D" => "MMMM dd, yyyy",
"f" => "MMMM dd, yyyy h:mm tt",
"F" => "dddd, MMMM dd, yyyy h:mm tt",
// Relative format is ignored because it doesn't make much sense in a static export
_ => null
// Ignore the 'relative' format because it doesn't make sense in a static export
"r" => null,
"R" => null,
// Discord's date formats are (mostly) compatible with .NET's date formats
var f => f
};
return new TimestampNode(instant, format);

View File

@@ -1,10 +0,0 @@
using System;
using System.Globalization;
namespace DiscordChatExporter.Core.Utils.Extensions;
public static class DateExtensions
{
public static string ToLocalString(this DateTimeOffset instant, string format) =>
instant.ToLocalTime().ToString(format, CultureInfo.InvariantCulture);
}