DateConstructor.cs 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. using System;
  2. using System.Globalization;
  3. using Jint.Native.Function;
  4. using Jint.Native.Object;
  5. using Jint.Runtime;
  6. using Jint.Runtime.Descriptors;
  7. using Jint.Runtime.Interop;
  8. namespace Jint.Native.Date
  9. {
  10. public sealed class DateConstructor : FunctionInstance, IConstructor
  11. {
  12. internal static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
  13. private static readonly string[] DefaultFormats = {
  14. "yyyy-MM-ddTHH:mm:ss.FFF",
  15. "yyyy-MM-ddTHH:mm:ss",
  16. "yyyy-MM-ddTHH:mm",
  17. "yyyy-MM-dd",
  18. "yyyy-MM",
  19. "yyyy"
  20. };
  21. private static readonly string[] SecondaryFormats = {
  22. // Formats used in DatePrototype toString methods
  23. "ddd MMM dd yyyy HH:mm:ss 'GMT'K",
  24. "ddd MMM dd yyyy",
  25. "HH:mm:ss 'GMT'K",
  26. // standard formats
  27. "yyyy-M-dTH:m:s.FFFK",
  28. "yyyy/M/dTH:m:s.FFFK",
  29. "yyyy-M-dTH:m:sK",
  30. "yyyy/M/dTH:m:sK",
  31. "yyyy-M-dTH:mK",
  32. "yyyy/M/dTH:mK",
  33. "yyyy-M-d H:m:s.FFFK",
  34. "yyyy/M/d H:m:s.FFFK",
  35. "yyyy-M-d H:m:sK",
  36. "yyyy/M/d H:m:sK",
  37. "yyyy-M-d H:mK",
  38. "yyyy/M/d H:mK",
  39. "yyyy-M-dK",
  40. "yyyy/M/dK",
  41. "yyyy-MK",
  42. "yyyy/MK",
  43. "yyyyK",
  44. "THH:mm:ss.FFFK",
  45. "THH:mm:ssK",
  46. "THH:mmK",
  47. "THHK"
  48. };
  49. public DateConstructor(Engine engine) : base(engine, "Date", null, null, false)
  50. {
  51. }
  52. public static DateConstructor CreateDateConstructor(Engine engine)
  53. {
  54. var obj = new DateConstructor(engine);
  55. obj.Extensible = true;
  56. // The value of the [[Prototype]] internal property of the Date constructor is the Function prototype object
  57. obj.Prototype = engine.Function.PrototypeObject;
  58. obj.PrototypeObject = DatePrototype.CreatePrototypeObject(engine, obj);
  59. obj.SetOwnProperty("length", new PropertyDescriptor(7, PropertyFlag.AllForbidden));
  60. // The initial value of Date.prototype is the Date prototype object
  61. obj.SetOwnProperty("prototype", new PropertyDescriptor(obj.PrototypeObject, PropertyFlag.AllForbidden));
  62. return obj;
  63. }
  64. public void Configure()
  65. {
  66. FastAddProperty("parse", new ClrFunctionInstance(Engine, "parse", Parse, 1), true, false, true);
  67. FastAddProperty("UTC", new ClrFunctionInstance(Engine, "utc", Utc, 7), true, false, true);
  68. FastAddProperty("now", new ClrFunctionInstance(Engine, "now", Now, 0), true, false, true);
  69. }
  70. private JsValue Parse(JsValue thisObj, JsValue[] arguments)
  71. {
  72. var date = TypeConverter.ToString(arguments.At(0));
  73. if (!DateTime.TryParseExact(date, DefaultFormats, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out var result))
  74. {
  75. if (!DateTime.TryParseExact(date, SecondaryFormats, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out result))
  76. {
  77. if (!DateTime.TryParse(date, Engine.Options._Culture, DateTimeStyles.AdjustToUniversal, out result))
  78. {
  79. if (!DateTime.TryParse(date, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out result))
  80. {
  81. // unrecognized dates should return NaN (15.9.4.2)
  82. return JsNumber.DoubleNaN;
  83. }
  84. }
  85. }
  86. }
  87. return FromDateTime(result);
  88. }
  89. private JsValue Utc(JsValue thisObj, JsValue[] arguments)
  90. {
  91. return TimeClip(ConstructTimeValue(arguments, useUtc: true));
  92. }
  93. private static JsValue Now(JsValue thisObj, JsValue[] arguments)
  94. {
  95. return System.Math.Floor((DateTime.UtcNow - Epoch).TotalMilliseconds);
  96. }
  97. public override JsValue Call(JsValue thisObject, JsValue[] arguments)
  98. {
  99. return PrototypeObject.ToString(Construct(Arguments.Empty), Arguments.Empty);
  100. }
  101. /// <summary>
  102. /// http://www.ecma-international.org/ecma-262/5.1/#sec-15.9.3
  103. /// </summary>
  104. /// <param name="arguments"></param>
  105. /// <returns></returns>
  106. public ObjectInstance Construct(JsValue[] arguments)
  107. {
  108. if (arguments.Length == 0)
  109. {
  110. return Construct(DateTime.UtcNow);
  111. }
  112. else if (arguments.Length == 1)
  113. {
  114. var v = TypeConverter.ToPrimitive(arguments[0]);
  115. if (v.IsString())
  116. {
  117. return Construct(((JsNumber) Parse(Undefined, Arguments.From(v)))._value);
  118. }
  119. return Construct(TypeConverter.ToNumber(v));
  120. }
  121. else
  122. {
  123. return Construct(ConstructTimeValue(arguments, useUtc: false));
  124. }
  125. }
  126. private double ConstructTimeValue(JsValue[] arguments, bool useUtc)
  127. {
  128. if (arguments.Length < 2)
  129. {
  130. ExceptionHelper.ThrowArgumentOutOfRangeException(nameof(arguments), "There must be at least two arguments.");
  131. }
  132. var y = TypeConverter.ToNumber(arguments[0]);
  133. var m = (int)TypeConverter.ToInteger(arguments[1]);
  134. var dt = arguments.Length > 2 ? (int)TypeConverter.ToInteger(arguments[2]) : 1;
  135. var h = arguments.Length > 3 ? (int)TypeConverter.ToInteger(arguments[3]) : 0;
  136. var min = arguments.Length > 4 ? (int)TypeConverter.ToInteger(arguments[4]) : 0;
  137. var s = arguments.Length > 5 ? (int)TypeConverter.ToInteger(arguments[5]) : 0;
  138. var milli = arguments.Length > 6 ? (int)TypeConverter.ToInteger(arguments[6]) : 0;
  139. for (int i = 2; i < arguments.Length; i++)
  140. {
  141. if (double.IsNaN(TypeConverter.ToNumber(arguments[i])))
  142. {
  143. return double.NaN;
  144. }
  145. }
  146. if (!double.IsNaN(y) && 0 <= TypeConverter.ToInteger(y) && TypeConverter.ToInteger(y) <= 99)
  147. {
  148. y += 1900;
  149. }
  150. var finalDate = DatePrototype.MakeDate(
  151. DatePrototype.MakeDay(y, m, dt),
  152. DatePrototype.MakeTime(h, min, s, milli));
  153. return TimeClip(useUtc ? finalDate : PrototypeObject.Utc(finalDate));
  154. }
  155. public DatePrototype PrototypeObject { get; private set; }
  156. public DateInstance Construct(DateTimeOffset value)
  157. {
  158. return Construct(value.UtcDateTime);
  159. }
  160. public DateInstance Construct(DateTime value)
  161. {
  162. var instance = new DateInstance(Engine)
  163. {
  164. Prototype = PrototypeObject,
  165. PrimitiveValue = FromDateTime(value),
  166. Extensible = true
  167. };
  168. return instance;
  169. }
  170. public DateInstance Construct(double time)
  171. {
  172. var instance = new DateInstance(Engine)
  173. {
  174. Prototype = PrototypeObject,
  175. PrimitiveValue = TimeClip(time),
  176. Extensible = true
  177. };
  178. return instance;
  179. }
  180. public static double TimeClip(double time)
  181. {
  182. if (double.IsInfinity(time) || double.IsNaN(time))
  183. {
  184. return double.NaN;
  185. }
  186. if (System.Math.Abs(time) > 8640000000000000)
  187. {
  188. return double.NaN;
  189. }
  190. return TypeConverter.ToInteger(time);
  191. }
  192. public double FromDateTime(DateTime dt)
  193. {
  194. var convertToUtcAfter = (dt.Kind == DateTimeKind.Unspecified);
  195. var dateAsUtc = dt.Kind == DateTimeKind.Local
  196. ? dt.ToUniversalTime()
  197. : DateTime.SpecifyKind(dt, DateTimeKind.Utc);
  198. var result = (dateAsUtc - Epoch).TotalMilliseconds;
  199. if (convertToUtcAfter)
  200. {
  201. result = PrototypeObject.Utc(result);
  202. }
  203. return System.Math.Floor(result);
  204. }
  205. }
  206. }