Browse Source

Add: os.date

AnnulusGames 1 year ago
parent
commit
c9bb7abc48

+ 1 - 0
src/Lua/Standard/OpenLibExtensions.cs

@@ -87,6 +87,7 @@ public static class OpenLibExtensions
 
 
     static readonly LuaFunction[] osFunctions = [
     static readonly LuaFunction[] osFunctions = [
         ClockFunction.Instance,
         ClockFunction.Instance,
+        DateFunction.Instance,
         DiffTimeFunction.Instance,
         DiffTimeFunction.Instance,
         ExecuteFunction.Instance,
         ExecuteFunction.Instance,
         ExitFunction.Instance,
         ExitFunction.Instance,

+ 195 - 0
src/Lua/Standard/OperatingSystem/DateFunction.cs

@@ -0,0 +1,195 @@
+using System.Runtime.CompilerServices;
+using System.Text;
+
+namespace Lua.Standard.OperatingSystem;
+
+public sealed class DateFunction : LuaFunction
+{
+    public override string Name => "date";
+    public static readonly DateFunction Instance = new();
+
+    protected override ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        var format = context.HasArgument(0)
+            ? context.GetArgument<string>(0).AsSpan()
+            : "%c".AsSpan();
+
+        DateTime now;
+        if (context.HasArgument(1))
+        {
+            var time = context.GetArgument<double>(1);
+            now = DateTimeHelper.FromUnixTime(time);
+        }
+        else
+        {
+            now = DateTime.UtcNow;
+        }
+
+        var isDst = false;
+        if (format[0] == '!')
+        {
+            format = format[1..];
+        }
+        else
+        {
+            now = TimeZoneInfo.ConvertTimeFromUtc(now, TimeZoneInfo.Local);
+            isDst = now.IsDaylightSavingTime();
+        }
+
+        if (format == "*t")
+        {
+            var table = new LuaTable();
+            
+            table["year"] = now.Year;
+            table["month"] = now.Month;
+            table["day"] = now.Day;
+            table["hour"] = now.Hour;
+            table["min"] = now.Minute;
+            table["sec"] = now.Second;
+            table["wday"] = ((int)now.DayOfWeek) + 1;
+            table["yday"] = now.DayOfYear;
+            table["isdst"] = isDst;
+
+            buffer.Span[0] = table;
+        }
+        else
+        {
+            buffer.Span[0] = StrFTime(context.State, format, now);
+        }
+
+        return new(1);
+    }
+
+    static string StrFTime(LuaState state, ReadOnlySpan<char> format, DateTime d)
+    {
+        // reference: http://www.cplusplus.com/reference/ctime/strftime/
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        static string? STANDARD_PATTERNS(char c)
+        {
+            return c switch
+            {
+                'a' => "ddd",
+                'A' => "dddd",
+                'b' => "MMM",
+                'B' => "MMMM",
+                'c' => "f",
+                'd' => "dd",
+                'D' => "MM/dd/yy",
+                'F' => "yyyy-MM-dd",
+                'g' => "yy",
+                'G' => "yyyy",
+                'h' => "MMM",
+                'H' => "HH",
+                'I' => "hh",
+                'm' => "MM",
+                'M' => "mm",
+                'p' => "tt",
+                'r' => "h:mm:ss tt",
+                'R' => "HH:mm",
+                'S' => "ss",
+                'T' => "HH:mm:ss",
+                'y' => "yy",
+                'Y' => "yyyy",
+                'x' => "d",
+                'X' => "T",
+                'z' => "zzz",
+                'Z' => "zzz",
+                _ => null,
+            };
+        }
+
+        var builder = new ValueStringBuilder();
+
+        bool isEscapeSequence = false;
+
+        for (int i = 0; i < format.Length; i++)
+        {
+            char c = format[i];
+
+            if (c == '%')
+            {
+                if (isEscapeSequence)
+                {
+                    builder.Append('%');
+                    isEscapeSequence = false;
+                }
+
+                continue;
+            }
+
+            if (!isEscapeSequence)
+            {
+                builder.Append(c);
+                continue;
+            }
+
+            if (c == 'O' || c == 'E')
+            {
+                continue; // no modifiers
+            }
+
+            isEscapeSequence = false;
+
+            var pattern = STANDARD_PATTERNS(c);
+            if (pattern != null)
+            {
+                builder.Append(d.ToString(pattern));
+            }
+            else if (c == 'e')
+            {
+                var s = d.ToString("%d");
+                builder.Append(s.Length < 2 ? $" {s}" : s);
+            }
+            else if (c == 'n')
+            {
+                builder.Append('\n');
+            }
+            else if (c == 't')
+            {
+                builder.Append('\t');
+            }
+            else if (c == 'C')
+            {
+                // TODO: reduce allocation
+                builder.Append((d.Year / 100).ToString());
+            }
+            else if (c == 'j')
+            {
+                builder.Append(d.DayOfYear.ToString("000"));
+            }
+            else if (c == 'u')
+            {
+                int weekDay = (int)d.DayOfWeek;
+                if (weekDay == 0) weekDay = 7;
+                builder.Append(weekDay.ToString());
+            }
+            else if (c == 'w')
+            {
+                int weekDay = (int)d.DayOfWeek;
+                builder.Append(weekDay.ToString());
+            }
+            else if (c == 'U')
+            {
+                // Week number with the first Sunday as the first day of week one (00-53)
+                builder.Append("??");
+            }
+            else if (c == 'V')
+            {
+                // ISO 8601 week number (00-53)
+                builder.Append("??");
+            }
+            else if (c == 'W')
+            {
+                // Week number with the first Monday as the first day of week one (00-53)
+                builder.Append("??");
+            }
+            else
+            {
+                throw new LuaRuntimeException(state.GetTraceback(), $"bad argument #1 to 'date' (invalid conversion specifier '{format}')");
+            }
+        }
+
+        return builder.ToString();
+    }
+}