/
AdvancedGuidGenerator.cs
123 lines (107 loc) · 5.24 KB
/
AdvancedGuidGenerator.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
using System;
namespace HermaFx.Utils
{
public static class AdvancedGuidGenerator
{
private static readonly DateTime UnixStartTime = new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc);
// XXX: Values added for GenerateCombEx
// From private DateTime.TicksPerMillisecond
private const long TicksPerMillisecond = 10000;
private static DateTime MinDateTimeValue { get; } = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
private static int NumDateBytes = 6;
// note: Guids generated by CombGuidGenerator are only considered ascending by SQL Server which compares Guids in an unusual way
// to generate Guids considered ascending by MongoDB use the AscendingGuidGenerator
public static Guid GenerateComb()
{
return GenerateComb(Guid.NewGuid(), DateTime.UtcNow);
}
// See: http://www.informit.com/articles/article.aspx?p=25862&seqNum=7
public static Guid GenerateComb(Guid id, DateTime timestamp)
{
var result = id.ToByteArray();
var span = new TimeSpan(timestamp.Ticks - UnixStartTime.Ticks);
var timeOfDay = timestamp.TimeOfDay;
#if false
byte[] days = BitConverter.GetBytes(span.Days);
byte[] time = BitConverter.GetBytes((long)(timeOfDay.TotalMilliseconds / 3.333333));
Array.Reverse(bytes);
Array.Reverse(array);
Array.Copy(bytes, (int)bytes.Length - 2, result, (int)result.Length - 6, 2);
Array.Copy(array, (int)array.Length - 4, result, (int)result.Length - 4, 4);
#else
var days = BitConverter.GetBytes(span.Days);
var time = BitConverter.GetBytes((int)(timestamp.TimeOfDay.Ticks * 300 / TimeSpan.TicksPerSecond)); // convert from .NET resolution to SQL Server resolution
Array.Copy(days, 0, result, 10, 2);
Array.Copy(time, 0, result, 12, 4);
if (BitConverter.IsLittleEndian)
{
Array.Reverse(result, 10, 2);
Array.Reverse(result, 12, 4);
}
#endif
return new Guid(result);
}
#region GenerateCombForPostgreSQL
/*
* FROM: https://github.com/richardtallent/RT.Comb/blob/main/src/RT.Comb/CombProvider/PostgreSqlCombProvider.cs
*
* Copyright 2015-2020 Richard S. Tallent, II
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to
* do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// IDateTimeStrategy is required to provide the bytes we need in network byte order, and that's what we want
// in PostgreSQL as well. However, internally, GUID bytes for Data1 and Data2 are stored in little endian
// order, so we need to reverse one or both of those so the bytes we want are re-reversed to the correct
// order by Npgsql's GUID data type handler.
private static void SwapByteOrderForStringOrder(byte[] input)
{
Array.Reverse(input, 0, 4); // Swap around the first 4 bytes
if (input.Length == 4) return;
Array.Reverse(input, 4, 2); // Swap around the next 2 bytes
}
// We purposefully are not using the FromUnixTimeMilliseconds and ToUnixTimeMilliseconds to remain compatible with .NET 4.5.1
//(long)(timestamp.ToUniversalTime() - MinDateTimeValue).TotalMilliseconds;
//public DateTime FromUnixTimeMilliseconds(long ms) => MinDateTimeValue.AddMilliseconds(ms);
private static long ToUnixTimeMilliseconds(DateTime timestamp) => (timestamp.Ticks - MinDateTimeValue.Ticks) / TicksPerMillisecond;
private static byte[] DateTimeToBytes(DateTime timestamp)
{
var ms = ToUnixTimeMilliseconds(timestamp);
var msBytes = BitConverter.GetBytes(ms);
if (BitConverter.IsLittleEndian) Array.Reverse(msBytes);
var result = new byte[NumDateBytes];
var index = msBytes.GetUpperBound(0) + 1 - NumDateBytes;
Array.Copy(msBytes, index, result, 0, NumDateBytes);
return result;
}
/// <summary>
/// Generate CombGuid specially recommended for PostgreSQL databases
/// </summary>
/// <param name="id"></param>
/// <param name="timestamp"></param>
/// <returns></returns>
public static Guid GenerateCombEx(Guid id, DateTime timestamp)
{
var gbytes = id.ToByteArray();
var dbytes = DateTimeToBytes(timestamp);
Array.Copy(dbytes, 0, gbytes, 0, NumDateBytes);
SwapByteOrderForStringOrder(gbytes);
return new Guid(gbytes);
}
/// <summary>
/// Generate CombGuid specially recommended for PostgreSQL databases
/// </summary>
/// <returns></returns>
public static Guid GenerateCombEx()
{
return GenerateCombEx(Guid.NewGuid(), DateTime.UtcNow);
}
#endregion
}
}