DateConstructor.cs 7.9 KB

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