DateTimeHelper.cs 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. using System.Runtime.CompilerServices;
  2. using System.Text;
  3. namespace Lua.Standard.Internal;
  4. internal static class DateTimeHelper
  5. {
  6. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  7. public static double GetUnixTime(DateTime dateTime)
  8. {
  9. return GetUnixTime(dateTime, DateTime.UnixEpoch);
  10. }
  11. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  12. public static double GetUnixTime(DateTime dateTime, DateTime epoch)
  13. {
  14. var time = (dateTime - epoch).TotalSeconds;
  15. if (time < 0.0) return 0;
  16. return time;
  17. }
  18. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  19. public static DateTime FromUnixTime(double unixTime)
  20. {
  21. var ts = TimeSpan.FromSeconds(unixTime);
  22. return DateTime.UnixEpoch + ts;
  23. }
  24. public static DateTime ParseTimeTable(LuaThread thread, LuaTable table)
  25. {
  26. static int GetTimeField(LuaThread thread, LuaTable table, string key, bool required = true, int defaultValue = 0)
  27. {
  28. if (!table.TryGetValue(key, out var value))
  29. {
  30. if (required)
  31. {
  32. throw new LuaRuntimeException(thread.GetTraceback(), $"field '{key}' missing in date table");
  33. }
  34. else
  35. {
  36. return defaultValue;
  37. }
  38. }
  39. if (value.TryRead<double>(out var d) && MathEx.IsInteger(d))
  40. {
  41. return (int)d;
  42. }
  43. throw new LuaRuntimeException(thread.GetTraceback(), $"field '{key}' is not an integer");
  44. }
  45. var day = GetTimeField(thread, table, "day");
  46. var month = GetTimeField(thread, table, "month");
  47. var year = GetTimeField(thread, table, "year");
  48. var sec = GetTimeField(thread, table, "sec", false, 0);
  49. var min = GetTimeField(thread, table, "min", false, 0);
  50. var hour = GetTimeField(thread, table, "hour", false, 12);
  51. return new DateTime(year, month, day, hour, min, sec);
  52. }
  53. public static string StrFTime(LuaThread thread, ReadOnlySpan<char> format, DateTime d)
  54. {
  55. // reference: http://www.cplusplus.com/reference/ctime/strftime/
  56. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  57. static string? STANDARD_PATTERNS(char c)
  58. {
  59. return c switch
  60. {
  61. 'a' => "ddd",
  62. 'A' => "dddd",
  63. 'b' => "MMM",
  64. 'B' => "MMMM",
  65. 'c' => "f",
  66. 'd' => "dd",
  67. 'D' => "MM/dd/yy",
  68. 'F' => "yyyy-MM-dd",
  69. 'g' => "yy",
  70. 'G' => "yyyy",
  71. 'h' => "MMM",
  72. 'H' => "HH",
  73. 'I' => "hh",
  74. 'm' => "MM",
  75. 'M' => "mm",
  76. 'p' => "tt",
  77. 'r' => "h:mm:ss tt",
  78. 'R' => "HH:mm",
  79. 'S' => "ss",
  80. 'T' => "HH:mm:ss",
  81. 'y' => "yy",
  82. 'Y' => "yyyy",
  83. 'x' => "d",
  84. 'X' => "T",
  85. 'z' => "zzz",
  86. 'Z' => "zzz",
  87. _ => null,
  88. };
  89. }
  90. var builder = new ValueStringBuilder();
  91. bool isEscapeSequence = false;
  92. for (int i = 0; i < format.Length; i++)
  93. {
  94. char c = format[i];
  95. if (c == '%')
  96. {
  97. if (isEscapeSequence)
  98. {
  99. builder.Append('%');
  100. isEscapeSequence = false;
  101. }
  102. continue;
  103. }
  104. if (!isEscapeSequence)
  105. {
  106. builder.Append(c);
  107. continue;
  108. }
  109. if (c == 'O' || c == 'E')
  110. {
  111. continue; // no modifiers
  112. }
  113. isEscapeSequence = false;
  114. var pattern = STANDARD_PATTERNS(c);
  115. if (pattern != null)
  116. {
  117. builder.Append(d.ToString(pattern));
  118. }
  119. else if (c == 'e')
  120. {
  121. var s = d.ToString("%d");
  122. builder.Append(s.Length < 2 ? $" {s}" : s);
  123. }
  124. else if (c == 'n')
  125. {
  126. builder.Append('\n');
  127. }
  128. else if (c == 't')
  129. {
  130. builder.Append('\t');
  131. }
  132. else if (c == 'C')
  133. {
  134. // TODO: reduce allocation
  135. builder.Append((d.Year / 100).ToString());
  136. }
  137. else if (c == 'j')
  138. {
  139. builder.Append(d.DayOfYear.ToString("000"));
  140. }
  141. else if (c == 'u')
  142. {
  143. int weekDay = (int)d.DayOfWeek;
  144. if (weekDay == 0) weekDay = 7;
  145. builder.Append(weekDay.ToString());
  146. }
  147. else if (c == 'w')
  148. {
  149. int weekDay = (int)d.DayOfWeek;
  150. builder.Append(weekDay.ToString());
  151. }
  152. else if (c == 'U')
  153. {
  154. // Week number with the first Sunday as the first day of week one (00-53)
  155. builder.Append("??");
  156. }
  157. else if (c == 'V')
  158. {
  159. // ISO 8601 week number (00-53)
  160. builder.Append("??");
  161. }
  162. else if (c == 'W')
  163. {
  164. // Week number with the first Monday as the first day of week one (00-53)
  165. builder.Append("??");
  166. }
  167. else
  168. {
  169. throw new LuaRuntimeException(thread.GetTraceback(), $"bad argument #1 to 'date' (invalid conversion specifier '{format.ToString()}')");
  170. }
  171. }
  172. return builder.ToString();
  173. }
  174. }