DateTimeSerializer.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  1. /* Copyright 2010-present MongoDB Inc.
  2. *
  3. * Licensed under the Apache License, Version 2.0 (the "License");
  4. * you may not use this file except in compliance with the License.
  5. * You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software
  10. * distributed under the License is distributed on an "AS IS" BASIS,
  11. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. * See the License for the specific language governing permissions and
  13. * limitations under the License.
  14. */
  15. using System;
  16. using System.Globalization;
  17. using System.IO;
  18. using MongoDB.Bson.IO;
  19. using MongoDB.Bson.Serialization.Attributes;
  20. using MongoDB.Bson.Serialization.Options;
  21. namespace MongoDB.Bson.Serialization.Serializers
  22. {
  23. /// <summary>
  24. /// Represents a serializer for DateTimes.
  25. /// </summary>
  26. public class DateTimeSerializer : StructSerializerBase<DateTime>, IRepresentationConfigurable<DateTimeSerializer>
  27. {
  28. // private constants
  29. private static class Flags
  30. {
  31. public const long DateTime = 1;
  32. public const long Ticks = 2;
  33. }
  34. // private static fields
  35. private static readonly DateTimeSerializer __dateOnlyInstance = new DateTimeSerializer(true);
  36. private static readonly DateTimeSerializer __localInstance = new DateTimeSerializer(DateTimeKind.Local);
  37. private static readonly DateTimeSerializer __utcInstance = new DateTimeSerializer(DateTimeKind.Utc);
  38. // private fields
  39. private readonly bool _dateOnly;
  40. private readonly SerializerHelper _helper;
  41. private readonly Int64Serializer _int64Serializer = new Int64Serializer();
  42. private readonly DateTimeKind _kind;
  43. private readonly BsonType _representation;
  44. // constructors
  45. /// <summary>
  46. /// Initializes a new instance of the <see cref="DateTimeSerializer"/> class.
  47. /// </summary>
  48. public DateTimeSerializer()
  49. : this(DateTimeKind.Utc, BsonType.DateTime)
  50. {
  51. }
  52. /// <summary>
  53. /// Initializes a new instance of the <see cref="DateTimeSerializer"/> class.
  54. /// </summary>
  55. /// <param name="dateOnly">if set to <c>true</c> [date only].</param>
  56. public DateTimeSerializer(bool dateOnly)
  57. : this(dateOnly, BsonType.DateTime)
  58. {
  59. }
  60. /// <summary>
  61. /// Initializes a new instance of the <see cref="DateTimeSerializer"/> class.
  62. /// </summary>
  63. /// <param name="dateOnly">if set to <c>true</c> [date only].</param>
  64. /// <param name="representation">The representation.</param>
  65. public DateTimeSerializer(bool dateOnly, BsonType representation)
  66. : this(dateOnly, DateTimeKind.Utc, representation)
  67. {
  68. }
  69. /// <summary>
  70. /// Initializes a new instance of the <see cref="DateTimeSerializer"/> class.
  71. /// </summary>
  72. /// <param name="representation">The representation.</param>
  73. public DateTimeSerializer(BsonType representation)
  74. : this(DateTimeKind.Utc, representation)
  75. {
  76. }
  77. /// <summary>
  78. /// Initializes a new instance of the <see cref="DateTimeSerializer"/> class.
  79. /// </summary>
  80. /// <param name="kind">The kind.</param>
  81. public DateTimeSerializer(DateTimeKind kind)
  82. : this(kind, BsonType.DateTime)
  83. {
  84. }
  85. /// <summary>
  86. /// Initializes a new instance of the <see cref="DateTimeSerializer"/> class.
  87. /// </summary>
  88. /// <param name="kind">The kind.</param>
  89. /// <param name="representation">The representation.</param>
  90. public DateTimeSerializer(DateTimeKind kind, BsonType representation)
  91. : this(false, kind, representation)
  92. {
  93. }
  94. private DateTimeSerializer(bool dateOnly, DateTimeKind kind, BsonType representation)
  95. {
  96. switch (representation)
  97. {
  98. case BsonType.DateTime:
  99. case BsonType.Document:
  100. case BsonType.Int64:
  101. case BsonType.String:
  102. break;
  103. default:
  104. var message = string.Format("{0} is not a valid representation for a DateTimeSerializer.", representation);
  105. throw new ArgumentException(message);
  106. }
  107. _dateOnly = dateOnly;
  108. _kind = kind;
  109. _representation = representation;
  110. _helper = new SerializerHelper
  111. (
  112. new SerializerHelper.Member("DateTime", Flags.DateTime),
  113. new SerializerHelper.Member("Ticks", Flags.Ticks)
  114. );
  115. }
  116. // public static properties
  117. /// <summary>
  118. /// Gets an instance of DateTimeSerializer with DateOnly=true.
  119. /// </summary>
  120. public static DateTimeSerializer DateOnlyInstance
  121. {
  122. get { return __dateOnlyInstance; }
  123. }
  124. /// <summary>
  125. /// Gets an instance of DateTimeSerializer with Kind=Local.
  126. /// </summary>
  127. public static DateTimeSerializer LocalInstance
  128. {
  129. get { return __localInstance; }
  130. }
  131. /// <summary>
  132. /// Gets an instance of DateTimeSerializer with Kind=Utc.
  133. /// </summary>
  134. public static DateTimeSerializer UtcInstance
  135. {
  136. get { return __utcInstance; }
  137. }
  138. // public properties
  139. /// <summary>
  140. /// Gets whether this DateTime consists of a Date only.
  141. /// </summary>
  142. public bool DateOnly
  143. {
  144. get { return _dateOnly; }
  145. }
  146. /// <summary>
  147. /// Gets the DateTimeKind (Local, Unspecified or Utc).
  148. /// </summary>
  149. public DateTimeKind Kind
  150. {
  151. get { return _kind; }
  152. }
  153. /// <summary>
  154. /// Gets the external representation.
  155. /// </summary>
  156. /// <value>
  157. /// The representation.
  158. /// </value>
  159. public BsonType Representation
  160. {
  161. get { return _representation; }
  162. }
  163. // public methods
  164. /// <summary>
  165. /// Deserializes a value.
  166. /// </summary>
  167. /// <param name="context">The deserialization context.</param>
  168. /// <param name="args">The deserialization args.</param>
  169. /// <returns>A deserialized value.</returns>
  170. public override DateTime Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
  171. {
  172. var bsonReader = context.Reader;
  173. DateTime value;
  174. var bsonType = bsonReader.GetCurrentBsonType();
  175. switch (bsonType)
  176. {
  177. case BsonType.DateTime:
  178. // use an intermediate BsonDateTime so MinValue and MaxValue are handled correctly
  179. value = new BsonDateTime(bsonReader.ReadDateTime()).ToUniversalTime();
  180. break;
  181. case BsonType.Document:
  182. value = default(DateTime);
  183. _helper.DeserializeMembers(context, (elementName, flag) =>
  184. {
  185. switch (flag)
  186. {
  187. case Flags.DateTime: bsonReader.SkipValue(); break; // ignore value (use Ticks instead)
  188. case Flags.Ticks: value = new DateTime(_int64Serializer.Deserialize(context), DateTimeKind.Utc); break;
  189. }
  190. });
  191. break;
  192. case BsonType.Int64:
  193. value = DateTime.SpecifyKind(new DateTime(bsonReader.ReadInt64()), DateTimeKind.Utc);
  194. break;
  195. case BsonType.String:
  196. if (_dateOnly)
  197. {
  198. value = DateTime.SpecifyKind(DateTime.ParseExact(bsonReader.ReadString(), "yyyy-MM-dd", null), DateTimeKind.Utc);
  199. }
  200. else
  201. {
  202. value = JsonConvert.ToDateTime(bsonReader.ReadString());
  203. }
  204. break;
  205. default:
  206. throw CreateCannotDeserializeFromBsonTypeException(bsonType);
  207. }
  208. if (_dateOnly)
  209. {
  210. if (value.TimeOfDay != TimeSpan.Zero)
  211. {
  212. throw new FormatException("TimeOfDay component for DateOnly DateTime value is not zero.");
  213. }
  214. value = DateTime.SpecifyKind(value, _kind); // not ToLocalTime or ToUniversalTime!
  215. }
  216. else
  217. {
  218. switch (_kind)
  219. {
  220. case DateTimeKind.Local:
  221. case DateTimeKind.Unspecified:
  222. value = DateTime.SpecifyKind(BsonUtils.ToLocalTime(value), _kind);
  223. break;
  224. case DateTimeKind.Utc:
  225. value = BsonUtils.ToUniversalTime(value);
  226. break;
  227. }
  228. }
  229. return value;
  230. }
  231. /// <summary>
  232. /// Serializes a value.
  233. /// </summary>
  234. /// <param name="context">The serialization context.</param>
  235. /// <param name="args">The serialization args.</param>
  236. /// <param name="value">The object.</param>
  237. public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, DateTime value)
  238. {
  239. var bsonWriter = context.Writer;
  240. DateTime utcDateTime;
  241. if (_dateOnly)
  242. {
  243. if (value.TimeOfDay != TimeSpan.Zero)
  244. {
  245. throw new BsonSerializationException("TimeOfDay component is not zero.");
  246. }
  247. utcDateTime = DateTime.SpecifyKind(value, DateTimeKind.Utc); // not ToLocalTime
  248. }
  249. else
  250. {
  251. utcDateTime = BsonUtils.ToUniversalTime(value);
  252. }
  253. var millisecondsSinceEpoch = BsonUtils.ToMillisecondsSinceEpoch(utcDateTime);
  254. switch (_representation)
  255. {
  256. case BsonType.DateTime:
  257. bsonWriter.WriteDateTime(millisecondsSinceEpoch);
  258. break;
  259. case BsonType.Document:
  260. bsonWriter.WriteStartDocument();
  261. bsonWriter.WriteDateTime("DateTime", millisecondsSinceEpoch);
  262. bsonWriter.WriteInt64("Ticks", utcDateTime.Ticks);
  263. bsonWriter.WriteEndDocument();
  264. break;
  265. case BsonType.Int64:
  266. bsonWriter.WriteInt64(utcDateTime.Ticks);
  267. break;
  268. case BsonType.String:
  269. if (_dateOnly)
  270. {
  271. bsonWriter.WriteString(value.ToString("yyyy-MM-dd"));
  272. }
  273. else
  274. {
  275. if (value == DateTime.MinValue || value == DateTime.MaxValue)
  276. {
  277. // serialize MinValue and MaxValue as Unspecified so we do NOT get the time zone offset
  278. value = DateTime.SpecifyKind(value, DateTimeKind.Unspecified);
  279. }
  280. else if (value.Kind == DateTimeKind.Unspecified)
  281. {
  282. // serialize Unspecified as Local se we get the time zone offset
  283. value = DateTime.SpecifyKind(value, DateTimeKind.Local);
  284. }
  285. bsonWriter.WriteString(JsonConvert.ToString(value));
  286. }
  287. break;
  288. default:
  289. var message = string.Format("'{0}' is not a valid DateTime representation.", _representation);
  290. throw new BsonSerializationException(message);
  291. }
  292. }
  293. /// <summary>
  294. /// Returns a serializer that has been reconfigured with the specified dateOnly value.
  295. /// </summary>
  296. /// <param name="dateOnly">if set to <c>true</c> the values will be required to be Date's only (zero time component).</param>
  297. /// <returns>
  298. /// The reconfigured serializer.
  299. /// </returns>
  300. public DateTimeSerializer WithDateOnly(bool dateOnly)
  301. {
  302. if (dateOnly == _dateOnly)
  303. {
  304. return this;
  305. }
  306. else
  307. {
  308. return new DateTimeSerializer(dateOnly, _representation);
  309. }
  310. }
  311. /// <summary>
  312. /// Returns a serializer that has been reconfigured with the specified dateOnly value and representation.
  313. /// </summary>
  314. /// <param name="dateOnly">if set to <c>true</c> the values will be required to be Date's only (zero time component).</param>
  315. /// <param name="representation">The representation.</param>
  316. /// <returns>
  317. /// The reconfigured serializer.
  318. /// </returns>
  319. public DateTimeSerializer WithDateOnly(bool dateOnly, BsonType representation)
  320. {
  321. if (dateOnly == _dateOnly && representation == _representation)
  322. {
  323. return this;
  324. }
  325. else
  326. {
  327. return new DateTimeSerializer(dateOnly, representation);
  328. }
  329. }
  330. /// <summary>
  331. /// Returns a serializer that has been reconfigured with the specified DateTimeKind value.
  332. /// </summary>
  333. /// <param name="kind">The DateTimeKind.</param>
  334. /// <returns>
  335. /// The reconfigured serializer.
  336. /// </returns>
  337. public DateTimeSerializer WithKind(DateTimeKind kind)
  338. {
  339. if (kind == _kind && _dateOnly == false)
  340. {
  341. return this;
  342. }
  343. else
  344. {
  345. return new DateTimeSerializer(kind, _representation);
  346. }
  347. }
  348. /// <summary>
  349. /// Returns a serializer that has been reconfigured with the specified DateTimeKind value and representation.
  350. /// </summary>
  351. /// <param name="kind">The DateTimeKind.</param>
  352. /// <param name="representation">The representation.</param>
  353. /// <returns>
  354. /// The reconfigured serializer.
  355. /// </returns>
  356. public DateTimeSerializer WithKind(DateTimeKind kind, BsonType representation)
  357. {
  358. if (kind == _kind && representation == _representation && _dateOnly == false)
  359. {
  360. return this;
  361. }
  362. else
  363. {
  364. return new DateTimeSerializer(kind, representation);
  365. }
  366. }
  367. /// <summary>
  368. /// Returns a serializer that has been reconfigured with the specified representation.
  369. /// </summary>
  370. /// <param name="representation">The representation.</param>
  371. /// <returns>The reconfigured serializer.</returns>
  372. public DateTimeSerializer WithRepresentation(BsonType representation)
  373. {
  374. if (representation == _representation)
  375. {
  376. return this;
  377. }
  378. else
  379. {
  380. if (_dateOnly)
  381. {
  382. return new DateTimeSerializer(_dateOnly, representation);
  383. }
  384. else
  385. {
  386. return new DateTimeSerializer(_kind, representation);
  387. }
  388. }
  389. }
  390. // explicit interface implementations
  391. IBsonSerializer IRepresentationConfigurable.WithRepresentation(BsonType representation)
  392. {
  393. return WithRepresentation(representation);
  394. }
  395. }
  396. }