diff --git a/Directory.Build.targets b/Directory.Build.targets
index 6e7b0d1aa..180821c63 100644
--- a/Directory.Build.targets
+++ b/Directory.Build.targets
@@ -24,6 +24,8 @@
+
+
diff --git a/Discord.Net.sln b/Discord.Net.sln
index 8e8cebaa2..04a83acce 100644
--- a/Discord.Net.sln
+++ b/Discord.Net.sln
@@ -7,6 +7,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{381B0F15-BA2
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Discord.Net.Core", "src\Core\Discord.Net.Core.csproj", "{57A52C6A-337D-4165-A42D-94FAC87B2807}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{B960E106-DC21-4A28-9C28-6AA0B49346BB}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Discord.Net.Core.UnitTests", "test\Discord.Net.Core.UnitTests\Discord.Net.Core.UnitTests.csproj", "{1A1D1A6F-F9DD-4A14-9F93-9B4C88F0B534}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -32,8 +36,21 @@ Global
{57A52C6A-337D-4165-A42D-94FAC87B2807}.Release|x64.Build.0 = Release|Any CPU
{57A52C6A-337D-4165-A42D-94FAC87B2807}.Release|x86.ActiveCfg = Release|Any CPU
{57A52C6A-337D-4165-A42D-94FAC87B2807}.Release|x86.Build.0 = Release|Any CPU
+ {1A1D1A6F-F9DD-4A14-9F93-9B4C88F0B534}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1A1D1A6F-F9DD-4A14-9F93-9B4C88F0B534}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1A1D1A6F-F9DD-4A14-9F93-9B4C88F0B534}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {1A1D1A6F-F9DD-4A14-9F93-9B4C88F0B534}.Debug|x64.Build.0 = Debug|Any CPU
+ {1A1D1A6F-F9DD-4A14-9F93-9B4C88F0B534}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {1A1D1A6F-F9DD-4A14-9F93-9B4C88F0B534}.Debug|x86.Build.0 = Debug|Any CPU
+ {1A1D1A6F-F9DD-4A14-9F93-9B4C88F0B534}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1A1D1A6F-F9DD-4A14-9F93-9B4C88F0B534}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1A1D1A6F-F9DD-4A14-9F93-9B4C88F0B534}.Release|x64.ActiveCfg = Release|Any CPU
+ {1A1D1A6F-F9DD-4A14-9F93-9B4C88F0B534}.Release|x64.Build.0 = Release|Any CPU
+ {1A1D1A6F-F9DD-4A14-9F93-9B4C88F0B534}.Release|x86.ActiveCfg = Release|Any CPU
+ {1A1D1A6F-F9DD-4A14-9F93-9B4C88F0B534}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{57A52C6A-337D-4165-A42D-94FAC87B2807} = {381B0F15-BA2C-4E23-BE68-015462861AF0}
+ {1A1D1A6F-F9DD-4A14-9F93-9B4C88F0B534} = {B960E106-DC21-4A28-9C28-6AA0B49346BB}
EndGlobalSection
EndGlobal
diff --git a/src/Core/Snowflake.cs b/src/Core/Snowflake.cs
new file mode 100644
index 000000000..34074dce4
--- /dev/null
+++ b/src/Core/Snowflake.cs
@@ -0,0 +1,70 @@
+using System;
+
+namespace Discord
+{
+ ///
+ /// Utilities for reading and writing Discord snowflakes.
+ ///
+ ///
+ public static class Snowflake
+ {
+ ///
+ /// The offset, in milliseconds, from the Unix epoch which represents
+ /// the Discord Epoch.
+ ///
+ public const ulong DiscordEpochOffset = 1420070400000UL;
+
+ ///
+ /// Calculates the time a given snowflake was created.
+ ///
+ ///
+ /// The snowflake to calculate the creation time of.
+ ///
+ ///
+ /// A representing the creation time, in
+ /// UTC, of the snowflake.
+ ///
+ ///
+ /// This sample demonstrates how to identify when a Discord user was
+ /// created.
+ ///
+ /// IUser user = await GetUserAsync();
+ /// var snowflake = user.Id;
+ /// var created = Snowflake.GetCreatedTime(snowflake);
+ /// Console.WriteLine($"The user {user.Name} was created at {created}");
+ ///
+ ///
+ public static DateTimeOffset GetCreatedTime(ulong snowflake)
+ => DateTimeOffset.FromUnixTimeMilliseconds(
+ (long)((snowflake >> 22) + DiscordEpochOffset));
+
+ ///
+ /// Calculates the smallest possible snowflake for a given creation
+ /// time.
+ ///
+ ///
+ /// The time to generate a snowflake for.
+ ///
+ ///
+ /// A snowflake representing the smallest possible snowflake for the
+ /// given creation time.
+ ///
+ ///
+ /// This sample demonstrates how to check if a user was created before
+ /// a certain date.
+ ///
+ /// IUser user = await GetUserAsync();
+ /// var desiredTime = DateTimeOffset.UtcNow.AddDays(-7);
+ /// var minimumSnowflake = Snowflake.GetSnowflake(desiredTime);
+ ///
+ /// if (user.Id <= minimumSnowflake)
+ /// Console.WriteLine($"The user {user.Name} was created at least 7 days ago");
+ /// else
+ /// Console.WriteLine($"The user {user.Name} was created less than 7 days ago");
+ ///
+ ///
+ public static ulong GetSnowflake(DateTimeOffset time)
+ => ((ulong)time.ToUnixTimeMilliseconds()
+ - DiscordEpochOffset) << 22;
+ }
+}
diff --git a/test/Directory.Build.props b/test/Directory.Build.props
new file mode 100644
index 000000000..79e8a93f9
--- /dev/null
+++ b/test/Directory.Build.props
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+ $(MSBuildAllProjects);$(MSBuildThisFileDirectory)..\Directory.Build.props
+ test
+
+
+
+
+
+ false
+ trx
+ $(BaseArtifactsPath)tst/$(Configuration)/
+
+
+
+
+
+
+
+
+
diff --git a/test/Directory.Build.targets b/test/Directory.Build.targets
new file mode 100644
index 000000000..cb93612f1
--- /dev/null
+++ b/test/Directory.Build.targets
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+ $(MSBuildAllProjects);$(MSBuildThisFileDirectory)..\Directory.Build.targets
+
+
+
+
+
diff --git a/test/Discord.Net.Core.UnitTests/Discord.Net.Core.UnitTests.csproj b/test/Discord.Net.Core.UnitTests/Discord.Net.Core.UnitTests.csproj
new file mode 100644
index 000000000..c7d579df1
--- /dev/null
+++ b/test/Discord.Net.Core.UnitTests/Discord.Net.Core.UnitTests.csproj
@@ -0,0 +1,13 @@
+
+
+
+ netcoreapp3.1
+
+ false
+
+
+
+
+
+
+
diff --git a/test/Discord.Net.Core.UnitTests/SnowflakeTests.cs b/test/Discord.Net.Core.UnitTests/SnowflakeTests.cs
new file mode 100644
index 000000000..380b9f3bb
--- /dev/null
+++ b/test/Discord.Net.Core.UnitTests/SnowflakeTests.cs
@@ -0,0 +1,85 @@
+using System;
+using System.Collections.Generic;
+using Xunit;
+
+namespace Discord.UnitTests
+{
+ public class SnowflakeTests
+ {
+ private static IEnumerable