diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7d37fa4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,176 @@ +# Created by http://www.gitignore.io + +### VisualStudio ### +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +x64/ +build/ +bld/ +[Bb]in/ +[Oo]bj/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +#NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding addin-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +*.ncrunch* +_NCrunch_* +.*crunch*.local.xml + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml + +# NuGet Packages Directory +packages/ +!packages/repositories.config + +# Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets +# This line needs to be after the ignore of the build folder (and the packages folder if the line above has been uncommented) +!packages/build/ + +# Windows Azure Build Output +csx/ +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ diff --git a/.tfignore b/.tfignore new file mode 100644 index 0000000..e37a9f1 --- /dev/null +++ b/.tfignore @@ -0,0 +1 @@ +\.git \ No newline at end of file diff --git a/NCuid.Tests/Base36ConverterTests.cs b/NCuid.Tests/Base36ConverterTests.cs new file mode 100644 index 0000000..000e26e --- /dev/null +++ b/NCuid.Tests/Base36ConverterTests.cs @@ -0,0 +1,21 @@ +using Xunit; + +namespace Cuid.Tests +{ + public class Base36ConverterTests + { + [Fact] + public void DecodeTest() + { + Assert.Equal(0, Base36Converter.Decode("")); + Assert.Equal(-1, Base36Converter.Decode("%")); + Assert.Equal(1412823931503067241, Base36Converter.Decode("AQF8AA0006EH")); + } + + [Fact] + public void EncodeTest() + { + Assert.Equal("AQF8AA0006EH", Base36Converter.Encode(1412823931503067241)); + } + } +} diff --git a/NCuid.Tests/CuidTests.cs b/NCuid.Tests/CuidTests.cs new file mode 100644 index 0000000..4116b66 --- /dev/null +++ b/NCuid.Tests/CuidTests.cs @@ -0,0 +1,53 @@ +using System; +using System.Diagnostics; +using Xunit; + +namespace Cuid.Tests +{ + public class CuidTests + { + [Fact] + public void GenerateTest() + { + Assert.Equal(Cuid.Generate().Length, 25); + } + + [Fact] + public void CuidsAreShorterThanGuids() + { + Assert.True( + Guid.NewGuid().ToString().Replace("-", string.Empty).Length > Cuid.Generate().Length + ); + } + + [Fact] + public void CuidsAreSlowerThanNativeGuids() + { + var toGen = Math.Pow(36, 4) +1; + + var sw = new Stopwatch(); + + sw.Start(); + for (double i = 0; i < toGen; i++) + { + Cuid.Generate(); + } + sw.Stop(); + + var elapsedCuid = sw.ElapsedTicks; + + sw.Reset(); + + sw.Start(); + for (double i = 0; i < toGen; i++) + { + Guid.NewGuid(); + } + sw.Stop(); + + var elapsedGuid = sw.ElapsedTicks; + + Assert.False(elapsedGuid > elapsedCuid); + } + } +} diff --git a/NCuid.Tests/DateTimeExtensionsTests.cs b/NCuid.Tests/DateTimeExtensionsTests.cs new file mode 100644 index 0000000..1ddf983 --- /dev/null +++ b/NCuid.Tests/DateTimeExtensionsTests.cs @@ -0,0 +1,23 @@ +using System; +using Xunit; +namespace Cuid.Tests +{ + public class DateTimeExtensionsTests + { + [Fact] + public void FromUnixTimeTest() + { + var dt = new DateTime(1987, 07, 03, 18, 00, 00); + Assert.Equal(dt.ToUnixTime(), 552333600); + } + + [Fact] + public void ToUnixTimeTest() + { + var dt = new DateTime(1987, 07, 03, 18, 00, 00); + + Assert.Equal(dt, 552333600.FromUnixTime()); + Assert.Equal(dt, 552333600L.FromUnixTime()); + } + } +} diff --git a/NCuid.Tests/NCuid.Tests.csproj b/NCuid.Tests/NCuid.Tests.csproj new file mode 100644 index 0000000..21a0b26 --- /dev/null +++ b/NCuid.Tests/NCuid.Tests.csproj @@ -0,0 +1,98 @@ + + + + Debug + AnyCPU + {1CD3A779-B529-497C-A186-F0637E316D2A} + Library + Properties + NCuid.Tests + NCuid.Tests + v4.5 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + ..\packages\xunit.1.9.2\lib\net20\xunit.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + {C9033C45-5859-4420-8D44-15A43BBC53EA} + NCuid + + + + + + + False + + + False + + + False + + + False + + + + + + + + \ No newline at end of file diff --git a/NCuid.Tests/Properties/AssemblyInfo.cs b/NCuid.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..543024d --- /dev/null +++ b/NCuid.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// Les informations générales relatives à un assembly dépendent de +// l'ensemble d'attributs suivant. Changez les valeurs de ces attributs pour modifier les informations +// associées à un assembly. +[assembly: AssemblyTitle("Cuid.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Cuid.Tests")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// L'affectation de la valeur false à ComVisible rend les types invisibles dans cet assembly +// aux composants COM. Si vous devez accéder à un type dans cet assembly à partir de +// COM, affectez la valeur true à l'attribut ComVisible sur ce type. +[assembly: ComVisible(false)] + +// Le GUID suivant est pour l'ID de la typelib si ce projet est exposé à COM +[assembly: Guid("d6d097b8-9e8e-4ca0-895f-214931f45a38")] + +// Les informations de version pour un assembly se composent des quatre valeurs suivantes : +// +// Version principale +// Version secondaire +// Numéro de build +// Révision +// +// Vous pouvez spécifier toutes les valeurs ou vous pouvez définir par défaut les numéros de build et de révision +// en utilisant '*', comme indiqué ci-dessous : +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/NCuid.Tests/StringExtensionsTests.cs b/NCuid.Tests/StringExtensionsTests.cs new file mode 100644 index 0000000..d9d9ee7 --- /dev/null +++ b/NCuid.Tests/StringExtensionsTests.cs @@ -0,0 +1,25 @@ +using Xunit; +namespace Cuid.Tests +{ + public class StringExtensionsTests + { + [Fact] + public void SliceTest() + { + Assert.Equal("eac", "Peaceful".Slice(1, 4)); + Assert.Equal(" morning is upon u", "The morning is upon us.".Slice(3, -2)); + + const string s = "0123456789_"; + Assert.Equal("0", s.Slice(0, 1)); + Assert.Equal("01", s.Slice(0, 2)); + Assert.Equal("1", s.Slice(1, 2)); + Assert.Equal("89_", s.Slice(8, 11)); + } + + [Fact] + public void ReverseTest() + { + Assert.Equal("CBA", "ABC".Reverse()); + } + } +} diff --git a/NCuid.Tests/packages.config b/NCuid.Tests/packages.config new file mode 100644 index 0000000..3eb1f07 --- /dev/null +++ b/NCuid.Tests/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/NCuid.sln b/NCuid.sln new file mode 100644 index 0000000..b368227 --- /dev/null +++ b/NCuid.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.30110.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NCuid.Tests", "NCuid.Tests\NCuid.Tests.csproj", "{1CD3A779-B529-497C-A186-F0637E316D2A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NCuid", "NCuid\NCuid.csproj", "{C9033C45-5859-4420-8D44-15A43BBC53EA}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1CD3A779-B529-497C-A186-F0637E316D2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1CD3A779-B529-497C-A186-F0637E316D2A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1CD3A779-B529-497C-A186-F0637E316D2A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1CD3A779-B529-497C-A186-F0637E316D2A}.Release|Any CPU.Build.0 = Release|Any CPU + {C9033C45-5859-4420-8D44-15A43BBC53EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C9033C45-5859-4420-8D44-15A43BBC53EA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C9033C45-5859-4420-8D44-15A43BBC53EA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C9033C45-5859-4420-8D44-15A43BBC53EA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/NCuid/Base36Converter.cs b/NCuid/Base36Converter.cs new file mode 100644 index 0000000..016a35f --- /dev/null +++ b/NCuid/Base36Converter.cs @@ -0,0 +1,52 @@ +using System; +using System.Text; + +namespace Cuid +{ + public static class Base36Converter + { + private const string Clist = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + private static readonly char[] Clistarr = Clist.ToCharArray(); + + public static long Decode(string inputString) + { + long result = 0; + var pow = 0; + + for (var i = inputString.Length - 1; i >= 0; i--) + { + var c = inputString[i]; + var pos = Clist.IndexOf(c); + + if (pos > -1) + { + result += pos*(long)Math.Pow(Clist.Length, pow); + } + else + { + return -1; + } + + pow++; + } + return result; + } + + public static string Encode(long inputNumber) + { + return Encode((ulong)inputNumber); + } + + public static string Encode(ulong inputNumber) + { + var sb = new StringBuilder(); + do + { + sb.Append(Clistarr[inputNumber % (ulong)Clist.Length]); + inputNumber /= (ulong)Clist.Length; + } while (inputNumber != 0); + + return sb.ToString().Reverse(); + } + } +} \ No newline at end of file diff --git a/NCuid/Cuid.cs b/NCuid/Cuid.cs new file mode 100644 index 0000000..f413634 --- /dev/null +++ b/NCuid/Cuid.cs @@ -0,0 +1,69 @@ +using System; +using System.Diagnostics; +using System.Linq; + +namespace Cuid +{ + public class Cuid + { + private static ulong _globalCounter; + private const int BlockSize = 4; + private const int Base = 36; + private static readonly ulong DiscreteValues = (ulong)Math.Pow(Base, BlockSize); + + //private static string Pad(string num, int size) + //{ + // var s = "0000 0 0000" + num; + // return s.Substring(s.Length - size); + //} + + private static string Pad(string num, int size) + { + var s = "000000000" + num; + return s.Substring(s.Length-size); + } + + private static string RandomBlock(Random rnd) + { + var number = (long)(rnd.NextDouble() * DiscreteValues); + number <<= 0; + + var r = Pad(Base36Converter.Encode(number), + BlockSize + ); + + return r; + } + + public static string Generate() + { + var ts = Base36Converter.Encode(DateTime.Now.ToUnixMilliTime()); + var gen = new Random(); + var rnd = RandomBlock(gen) + RandomBlock(gen); + var fingerprint = FingerPrint(); + + _globalCounter = (_globalCounter < DiscreteValues) + ? _globalCounter + : 0; + + var counter = Pad(Base36Converter.Encode(_globalCounter), BlockSize); + + _globalCounter++; + + return ("c" + ts + counter + fingerprint + rnd).ToLowerInvariant(); + } + + public static string FingerPrint() + { + const int padding = 2; + + var pid = Pad(Base36Converter.Encode((Process.GetCurrentProcess().Id)), padding); + var hostname = Environment.MachineName; + var length = hostname.Length; + var inputNumber = hostname.Split().Aggregate(length + 36, (prev, c) => prev + c[0]); + + var hostId = Pad(Base36Converter.Encode(inputNumber), padding); + return pid + hostId; + } + } +} diff --git a/NCuid/DateTimeExtensions.cs b/NCuid/DateTimeExtensions.cs new file mode 100644 index 0000000..f69c496 --- /dev/null +++ b/NCuid/DateTimeExtensions.cs @@ -0,0 +1,29 @@ +using System; + +namespace Cuid +{ + public static class DateTimeExtensions + { + private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + public static DateTime FromUnixTime(this int unixTime) + { + return Epoch.AddSeconds(unixTime); + } + + public static DateTime FromUnixTime(this long unixTime) + { + return Epoch.AddSeconds(unixTime); + } + + public static long ToUnixTime(this DateTime date) + { + return Convert.ToInt64((date - Epoch).TotalSeconds); + } + + public static long ToUnixMilliTime(this DateTime date) + { + return Convert.ToInt64((date - Epoch).TotalMilliseconds); + } + } +} diff --git a/NCuid/NCuid.csproj b/NCuid/NCuid.csproj new file mode 100644 index 0000000..4b959fc --- /dev/null +++ b/NCuid/NCuid.csproj @@ -0,0 +1,56 @@ + + + + + Debug + AnyCPU + {C9033C45-5859-4420-8D44-15A43BBC53EA} + Library + Properties + NCuid + NCuid + v4.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/NCuid/Properties/AssemblyInfo.cs b/NCuid/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..0b49ef9 --- /dev/null +++ b/NCuid/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// Les informations générales relatives à un assembly dépendent de +// l'ensemble d'attributs suivant. Changez les valeurs de ces attributs pour modifier les informations +// associées à un assembly. +[assembly: AssemblyTitle("Cuid.NET")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Cuid.NET")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// L'affectation de la valeur false à ComVisible rend les types invisibles dans cet assembly +// aux composants COM. Si vous devez accéder à un type dans cet assembly à partir de +// COM, affectez la valeur true à l'attribut ComVisible sur ce type. +[assembly: ComVisible(false)] + +// Le GUID suivant est pour l'ID de la typelib si ce projet est exposé à COM +[assembly: Guid("ca82e0f7-976a-4f3c-bb29-1640af785b97")] + +// Les informations de version pour un assembly se composent des quatre valeurs suivantes : +// +// Version principale +// Version secondaire +// Numéro de build +// Révision +// +// Vous pouvez spécifier toutes les valeurs ou indiquer les numéros de build et de révision par défaut +// en utilisant '*', comme indiqué ci-dessous : +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/NCuid/StringExtensions.cs b/NCuid/StringExtensions.cs new file mode 100644 index 0000000..de614f7 --- /dev/null +++ b/NCuid/StringExtensions.cs @@ -0,0 +1,27 @@ +using System; + +namespace Cuid +{ + public static class StringExtensions + { + /// + /// Get the string slice between the two indexes. + /// Inclusive for start index, exclusive for end index. + /// + public static string Slice(this string source, int start, int end) + { + if (end < 0) // Keep this for negative end support + { + end += source.Length; + } + return source.Substring(start, end - start); // Return Substring of length + } + + public static string Reverse(this string s) + { + var charArray = s.ToCharArray(); + Array.Reverse(charArray); + return new string(charArray); + } + } +} \ No newline at end of file