Client LuaCsForBarotrauma
SerializableDateTime.cs
1 #nullable enable
2 using System;
3 using System.Globalization;
4 using System.Linq;
5 
6 namespace Barotrauma
7 {
8  public readonly struct SerializableTimeZone
9  {
13  public readonly TimeSpan Value;
14 
15  private readonly int hours;
16  private readonly int minutes;
17  private readonly char sign;
18 
19  public SerializableTimeZone(TimeSpan value)
20  {
21  Value = new TimeSpan(
22  hours: value.Hours,
23  minutes: value.Minutes,
24  seconds: 0);
25 
26  hours = Math.Abs(value.Hours);
27  minutes = Math.Abs(value.Minutes);
28  sign = Value.Ticks < 0 ? '-' : '+';
29  }
30 
31  public override string ToString()
32  => (hours, minutes) switch
33  {
34  (0, 0) => "UTC",
35  (_, 0) => $"UTC{sign}{hours}",
36  (_, < 10) => $"UTC{sign}{hours}:0{minutes}",
37  _ => $"UTC{sign}{hours}:{minutes}"
38  };
39 
40  public override int GetHashCode()
41  => HashCode.Combine(Value.Ticks < 0, hours, minutes);
42 
43  public static SerializableTimeZone FromDateTime(DateTime dateTime)
44  {
45  if (dateTime.Kind == DateTimeKind.Unspecified)
46  {
47  throw new InvalidOperationException(
48  $"Cannot determine timezone for {nameof(DateTime)} " +
49  $"of unspecified kind");
50  }
51  var utcDateTime = dateTime.ToUniversalTime();
52  return new SerializableTimeZone(dateTime - utcDateTime);
53  }
54 
56  => FromDateTime(DateTime.Now);
57 
58  public static Option<SerializableTimeZone> Parse(string str)
59  {
60  if (!str.StartsWith("UTC", StringComparison.OrdinalIgnoreCase))
61  {
62  return Option<SerializableTimeZone>.None();
63  }
64  string timeZoneStr = str[3..];
65  bool negative = timeZoneStr.StartsWith("-");
66  bool valid = negative || timeZoneStr.StartsWith("+");
67 
68  if (!valid) { return Option<SerializableTimeZone>.None(); }
69 
70  timeZoneStr = str[4..];
71 
72  TimeSpan makeTimeSpan(int hours, int minutes)
73  => new TimeSpan(
74  ticks: (hours * TimeSpan.TicksPerHour + minutes * TimeSpan.TicksPerMinute)
75  * (negative ? -1L : 1L));
76 
77  if (timeZoneStr.IndexOf(':') is var hrMinSeparator && hrMinSeparator > 0)
78  {
79  if (int.TryParse(timeZoneStr[..hrMinSeparator], out int timeZoneHours)
80  && int.TryParse(timeZoneStr[(hrMinSeparator + 1)..], out int timeZoneMinutes))
81  {
82  return Option<SerializableTimeZone>.Some(
83  new SerializableTimeZone(makeTimeSpan(timeZoneHours, timeZoneMinutes)));
84  }
85  }
86  else if (int.TryParse(timeZoneStr, out int timeZoneHours))
87  {
88  return Option<SerializableTimeZone>.Some(
89  new SerializableTimeZone(makeTimeSpan(timeZoneHours, 0)));
90  }
91  return Option<SerializableTimeZone>.None();
92  }
93  }
94 
99  public readonly struct SerializableDateTime : IComparable<SerializableDateTime>
100  {
101  public bool Equals(SerializableDateTime other)
102  => ToUtc().value.Equals(other.ToUtc().value);
103 
104  public override bool Equals(object? obj)
105  => obj is SerializableDateTime other && Equals(other);
106 
107  private static DateTime UnixEpoch(DateTimeKind kind)
108  => new DateTime(1970, 1, 1, 0, 0, 0, kind);
109 
110  private readonly DateTime value;
112 
113  public SerializableDateTime(DateTime value) : this(value, default)
114  {
115  if (value.Kind == DateTimeKind.Unspecified)
116  {
117  throw new Exception($"Timezone required when constructing {nameof(SerializableDateTime)} " +
118  $"from {nameof(DateTime)} of unspecified kind");
119  }
121  }
122 
123  public SerializableDateTime(DateTime value, SerializableTimeZone timeZone)
124  {
125  this.value = new DateTime(
126  value.Year, value.Month, value.Day,
127  value.Hour, value.Minute, value.Second,
128  DateTimeKind.Unspecified);
129  TimeZone = timeZone;
130  }
131 
133  => new SerializableDateTime(DateTime.Now);
134 
136  => new SerializableDateTime(DateTime.UtcNow);
137 
139  => new SerializableDateTime(
140  DateTime.SpecifyKind(value - TimeZone.Value, DateTimeKind.Utc));
141 
143  => new SerializableDateTime(
144  new DateTime(ticks: value.Ticks) - TimeZone.Value + SerializableTimeZone.LocalTimeZone.Value,
146 
147  public long Ticks => value.Ticks;
148 
149  public DateTime ToUtcValue() => ToUtc().value;
150  public DateTime ToLocalValue() => ToLocal().value;
151 
152  public static SerializableDateTime FromLocalUnixTime(long unixTime)
153  => new SerializableDateTime(UnixEpoch(DateTimeKind.Local) + TimeSpan.FromSeconds(unixTime));
154 
155  public static SerializableDateTime FromUtcUnixTime(long unixTime)
156  => new SerializableDateTime(UnixEpoch(DateTimeKind.Utc) + TimeSpan.FromSeconds(unixTime));
157 
158  public long ToUnixTime()
159  => (value - UnixEpoch(value.Kind)).Ticks / TimeSpan.TicksPerSecond;
160 
161  private static string MakeString(params (long Value, string Suffix)[] parts)
162  => string.Join(' ',
163  parts.Select(p => $"{p.Value.ToString().PadLeft(2, '0')}{p.Suffix}"));
164 
165  public override string ToString()
166  => MakeString(
167  // Let's go out of our way to tag
168  // the year, month and day so nobody
169  // gets confused about the meaning of
170  // each number
171  (value.Year, "Y"),
172  (value.Month, "M"),
173  (value.Day, "D"),
174 
175  (value.Hour, "HR"),
176  (value.Minute, "MIN"),
177  (value.Second, "SEC"))
178  + $" {TimeZone}";
179 
180  public string ToLocalUserString()
181  => ToLocalValue().ToString(CultureInfo.InvariantCulture);
182 
183  public override int GetHashCode()
184  => HashCode.Combine(
185  value.Year, value.Month, value.Day,
186  value.Hour, value.Minute, value.Second,
188 
189  public static Option<SerializableDateTime> Parse(string str)
190  {
191  if (long.TryParse(str, out long unixTime)
192  && unixTime > 0
193  && unixTime < (DateTime.MaxValue - UnixEpoch(DateTimeKind.Utc)).TotalSeconds)
194  {
195  return Option<SerializableDateTime>.Some(FromUtcUnixTime(unixTime));
196  }
197 
198  string[] split = str.Split(' ');
199 
200  int year = 0; int month = 0; int day = 0;
201  int hour = 0; int minute = 0; int second = 0;
202  SerializableTimeZone timeZone = default;
203  foreach (var part in split)
204  {
205  if (SerializableTimeZone.Parse(part).TryUnwrap(out var parsedTimeZone))
206  {
207  timeZone = parsedTimeZone;
208  continue;
209  }
210 
211  Identifier suffix = string.Join("", part.Where(char.IsLetter)).ToIdentifier();
212  if (!part.EndsWith(suffix.Value)) { continue; }
213  if (!int.TryParse(
214  part[..^suffix.Value.Length],
215  NumberStyles.Integer,
216  CultureInfo.InvariantCulture,
217  out int value))
218  {
219  continue;
220  }
221 
222  if (suffix == "Y") { year = value; }
223  else if (suffix == "M") { month = value; }
224  else if (suffix == "D") { day = value; }
225  else if (suffix == "HR") { hour = value; }
226  else if (suffix == "MIN") { minute = value; }
227  else if (suffix == "SEC") { second = value; }
228  }
229 
230  if (year > 0 && month > 0 && day > 0)
231  {
232  return Option<SerializableDateTime>.Some(
234  new DateTime(year, month, day, hour, minute, second),
235  timeZone));
236  }
237 
238  return Option<SerializableDateTime>.None();
239  }
240 
241  public int CompareTo(SerializableDateTime other)
242  => ToUtc().value.CompareTo(other.ToUtc().value);
243 
244  public static bool operator <(in SerializableDateTime a, in SerializableDateTime b)
245  => a.CompareTo(b) < 0;
246 
247  public static bool operator >(in SerializableDateTime a, in SerializableDateTime b)
248  => a.CompareTo(b) > 0;
249 
251  => a.CompareTo(b) == 0;
252 
254  => !(a == b);
255 
256  public static SerializableDateTime operator +(in SerializableDateTime dt, in TimeSpan ts)
257  => new SerializableDateTime(dt.value + ts, dt.TimeZone);
258 
259  public static SerializableDateTime operator -(in SerializableDateTime dt, in TimeSpan ts)
260  => new SerializableDateTime(dt.value - ts, dt.TimeZone);
261 
262  public static TimeSpan operator -(in SerializableDateTime a, in SerializableDateTime b)
263  => a.ToUtc().value - b.ToUtc().value;
264  }
265 }
DateTime wrapper that tries to offer a reliable string representation that's also human-friendly
SerializableDateTime ToUtc()
static SerializableDateTime FromUtcUnixTime(long unixTime)
static SerializableDateTime UtcNow
static bool operator!=(in SerializableDateTime a, in SerializableDateTime b)
static bool operator==(in SerializableDateTime a, in SerializableDateTime b)
static bool operator>(in SerializableDateTime a, in SerializableDateTime b)
static SerializableDateTime FromLocalUnixTime(long unixTime)
SerializableDateTime ToLocal()
override bool Equals(object? obj)
int CompareTo(SerializableDateTime other)
static SerializableDateTime LocalNow
readonly SerializableTimeZone TimeZone
SerializableDateTime(DateTime value, SerializableTimeZone timeZone)
bool Equals(SerializableDateTime other)
static SerializableDateTime operator-(in SerializableDateTime dt, in TimeSpan ts)
static bool operator<(in SerializableDateTime a, in SerializableDateTime b)
static SerializableDateTime operator+(in SerializableDateTime dt, in TimeSpan ts)
static Option< SerializableDateTime > Parse(string str)
static SerializableTimeZone FromDateTime(DateTime dateTime)
static SerializableTimeZone LocalTimeZone
static Option< SerializableTimeZone > Parse(string str)
readonly TimeSpan Value
Diff from UTC