DateConstructor.cs 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. using System;
  2. using System.Globalization;
  3. using Jint.Collections;
  4. using Jint.Native.Function;
  5. using Jint.Native.Object;
  6. using Jint.Runtime;
  7. using Jint.Runtime.Descriptors;
  8. using Jint.Runtime.Interop;
  9. namespace Jint.Native.Date
  10. {
  11. /// <summary>
  12. /// https://tc39.es/ecma262/#sec-date-constructor
  13. /// </summary>
  14. public sealed class DateConstructor : FunctionInstance, IConstructor
  15. {
  16. internal static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
  17. private static readonly string[] DefaultFormats = {
  18. "yyyy-MM-dd",
  19. "yyyy-MM",
  20. "yyyy"
  21. };
  22. private static readonly string[] SecondaryFormats = {
  23. "yyyy-MM-ddTHH:mm:ss.FFF",
  24. "yyyy-MM-ddTHH:mm:ss",
  25. "yyyy-MM-ddTHH:mm",
  26. // Formats used in DatePrototype toString methods
  27. "ddd MMM dd yyyy HH:mm:ss 'GMT'K",
  28. "ddd MMM dd yyyy",
  29. "HH:mm:ss 'GMT'K",
  30. // standard formats
  31. "yyyy-M-dTH:m:s.FFFK",
  32. "yyyy/M/dTH:m:s.FFFK",
  33. "yyyy-M-dTH:m:sK",
  34. "yyyy/M/dTH:m:sK",
  35. "yyyy-M-dTH:mK",
  36. "yyyy/M/dTH:mK",
  37. "yyyy-M-d H:m:s.FFFK",
  38. "yyyy/M/d H:m:s.FFFK",
  39. "yyyy-M-d H:m:sK",
  40. "yyyy/M/d H:m:sK",
  41. "yyyy-M-d H:mK",
  42. "yyyy/M/d H:mK",
  43. "yyyy-M-dK",
  44. "yyyy/M/dK",
  45. "yyyy-MK",
  46. "yyyy/MK",
  47. "yyyyK",
  48. "THH:mm:ss.FFFK",
  49. "THH:mm:ssK",
  50. "THH:mmK",
  51. "THHK"
  52. };
  53. private static readonly JsString _functionName = new JsString("Date");
  54. internal DateConstructor(
  55. Engine engine,
  56. Realm realm,
  57. FunctionPrototype functionPrototype,
  58. ObjectPrototype objectPrototype)
  59. : base(engine, realm, _functionName)
  60. {
  61. _prototype = functionPrototype;
  62. PrototypeObject = new DatePrototype(engine, realm, this, objectPrototype);
  63. _length = new PropertyDescriptor(7, PropertyFlag.Configurable);
  64. _prototypeDescriptor = new PropertyDescriptor(PrototypeObject, PropertyFlag.AllForbidden);
  65. }
  66. public DatePrototype PrototypeObject { get; }
  67. protected override void Initialize()
  68. {
  69. const PropertyFlag lengthFlags = PropertyFlag.Configurable;
  70. const PropertyFlag propertyFlags = PropertyFlag.Configurable | PropertyFlag.Writable;
  71. var properties = new PropertyDictionary(3, checkExistingKeys: false)
  72. {
  73. ["parse"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "parse", Parse, 1, lengthFlags), propertyFlags),
  74. ["UTC"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "UTC", Utc, 7, lengthFlags), propertyFlags),
  75. ["now"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "now", Now, 0, lengthFlags), propertyFlags)
  76. };
  77. SetProperties(properties);
  78. }
  79. /// <summary>
  80. /// https://tc39.es/ecma262/#sec-date.parse
  81. /// </summary>
  82. private JsValue Parse(JsValue thisObj, JsValue[] arguments)
  83. {
  84. var date = TypeConverter.ToString(arguments.At(0));
  85. var negative = date.StartsWith("-");
  86. if (negative)
  87. {
  88. date = date.Substring(1);
  89. }
  90. var startParen = date.IndexOf('(');
  91. if (startParen != -1)
  92. {
  93. // informative text
  94. date = date.Substring(0, startParen);
  95. }
  96. date = date.Trim();
  97. if (!DateTime.TryParseExact(date, DefaultFormats, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out var result))
  98. {
  99. if (!DateTime.TryParseExact(date, SecondaryFormats, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out result))
  100. {
  101. if (!DateTime.TryParse(date, Engine.Options.Culture, DateTimeStyles.AdjustToUniversal, out result))
  102. {
  103. if (!DateTime.TryParse(date, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out result))
  104. {
  105. // unrecognized dates should return NaN (15.9.4.2)
  106. return JsNumber.DoubleNaN;
  107. }
  108. }
  109. }
  110. }
  111. return FromDateTime(result, negative);
  112. }
  113. private static JsValue Utc(JsValue thisObj, JsValue[] arguments)
  114. {
  115. var y = TypeConverter.ToNumber(arguments.At(0));
  116. var m = TypeConverter.ToNumber(arguments.At(1, JsNumber.PositiveZero));
  117. var dt = TypeConverter.ToNumber(arguments.At(2, JsNumber.PositiveOne));
  118. var h = TypeConverter.ToNumber(arguments.At(3, JsNumber.PositiveZero));
  119. var min = TypeConverter.ToNumber(arguments.At(4, JsNumber.PositiveZero));
  120. var s = TypeConverter.ToNumber(arguments.At(5, JsNumber.PositiveZero));
  121. var milli = TypeConverter.ToNumber(arguments.At(6, JsNumber.PositiveZero));
  122. var yInteger = TypeConverter.ToInteger(y);
  123. if (!double.IsNaN(y) && 0 <= yInteger && yInteger <= 99)
  124. {
  125. y = yInteger + 1900;
  126. }
  127. var finalDate = DatePrototype.MakeDate(
  128. DatePrototype.MakeDay(y, m, dt),
  129. DatePrototype.MakeTime(h, min, s, milli));
  130. return TimeClip(finalDate);
  131. }
  132. private static JsValue Now(JsValue thisObj, JsValue[] arguments)
  133. {
  134. return System.Math.Floor((DateTime.UtcNow - Epoch).TotalMilliseconds);
  135. }
  136. public override JsValue Call(JsValue thisObject, JsValue[] arguments)
  137. {
  138. return PrototypeObject.ToString(Construct(Arguments.Empty, thisObject), Arguments.Empty);
  139. }
  140. ObjectInstance IConstructor.Construct(JsValue[] arguments, JsValue newTarget) => Construct(arguments, newTarget);
  141. /// <summary>
  142. /// https://tc39.es/ecma262/#sec-date
  143. /// </summary>
  144. private ObjectInstance Construct(JsValue[] arguments, JsValue newTarget)
  145. {
  146. double dv;
  147. if (arguments.Length == 0 || newTarget.IsUndefined())
  148. {
  149. dv = FromDateTime(DateTime.UtcNow);
  150. }
  151. else if (arguments.Length == 1)
  152. {
  153. if (arguments[0] is DateInstance date)
  154. {
  155. return Construct(date.DateValue);
  156. }
  157. var v = TypeConverter.ToPrimitive(arguments[0]);
  158. if (v.IsString())
  159. {
  160. return Construct(((JsNumber) Parse(Undefined, Arguments.From(v)))._value);
  161. }
  162. dv = TimeClip(TypeConverter.ToNumber(v));
  163. }
  164. else
  165. {
  166. var y = TypeConverter.ToNumber(arguments.At(0));
  167. var m = TypeConverter.ToNumber(arguments.At(1));
  168. var dt = TypeConverter.ToNumber(arguments.At(2, JsNumber.PositiveOne));
  169. var h = TypeConverter.ToNumber(arguments.At(3, JsNumber.PositiveZero));
  170. var min = TypeConverter.ToNumber(arguments.At(4, JsNumber.PositiveZero));
  171. var s = TypeConverter.ToNumber(arguments.At(5, JsNumber.PositiveZero));
  172. var milli = TypeConverter.ToNumber(arguments.At(6, JsNumber.PositiveZero));
  173. var yInteger = TypeConverter.ToInteger(y);
  174. if (!double.IsNaN(y) && 0 <= yInteger && yInteger <= 99)
  175. {
  176. y += 1900;
  177. }
  178. var finalDate = DatePrototype.MakeDate(
  179. DatePrototype.MakeDay(y, m, dt),
  180. DatePrototype.MakeTime(h, min, s, milli));
  181. dv = TimeClip(PrototypeObject.Utc(finalDate));
  182. }
  183. var o = OrdinaryCreateFromConstructor(
  184. newTarget,
  185. static intrinsics => intrinsics.Date.PrototypeObject,
  186. static (engine, realm, _) => new DateInstance(engine));
  187. o.DateValue = dv;
  188. return o;
  189. }
  190. public DateInstance Construct(DateTimeOffset value)
  191. {
  192. return Construct(value.UtcDateTime);
  193. }
  194. public DateInstance Construct(DateTime value)
  195. {
  196. return Construct(FromDateTime(value));
  197. }
  198. public DateInstance Construct(double time)
  199. {
  200. var instance = new DateInstance(_engine)
  201. {
  202. _prototype = PrototypeObject,
  203. DateValue = TimeClip(time)
  204. };
  205. return instance;
  206. }
  207. private static double TimeClip(double time)
  208. {
  209. if (double.IsInfinity(time) || double.IsNaN(time))
  210. {
  211. return double.NaN;
  212. }
  213. if (System.Math.Abs(time) > 8640000000000000)
  214. {
  215. return double.NaN;
  216. }
  217. return TypeConverter.ToInteger(time) + 0;
  218. }
  219. private double FromDateTime(DateTime dt, bool negative = false)
  220. {
  221. var convertToUtcAfter = (dt.Kind == DateTimeKind.Unspecified);
  222. var dateAsUtc = dt.Kind == DateTimeKind.Local
  223. ? dt.ToUniversalTime()
  224. : DateTime.SpecifyKind(dt, DateTimeKind.Utc);
  225. double result;
  226. if (negative)
  227. {
  228. var zero = (Epoch - DateTime.MinValue).TotalMilliseconds;
  229. result = zero - TimeSpan.FromTicks(dateAsUtc.Ticks).TotalMilliseconds;
  230. result *= -1;
  231. }
  232. else
  233. {
  234. result = (dateAsUtc - Epoch).TotalMilliseconds;
  235. }
  236. if (convertToUtcAfter)
  237. {
  238. result = PrototypeObject.Utc(result);
  239. }
  240. return System.Math.Floor(result);
  241. }
  242. }
  243. }