|
| 1 | +--- |
| 2 | +date: 2020-02-08 |
| 3 | +title: DateTime Storage |
| 4 | +weight: 20 |
| 5 | +menu: |
| 6 | + main: |
| 7 | + parent: troubleshooting |
| 8 | +--- |
| 9 | + |
| 10 | +# DateTime Storage |
| 11 | + |
| 12 | +MySQL cannot store all the information from a `DateTime` or `DateTimeOffset` value, |
| 13 | +so the following considerations should be kept in mind when storing `DateTime` and |
| 14 | +`DateTimeOffset` values in MySQL `DATETIME` or `TIMESTAMP` columns. |
| 15 | + |
| 16 | +## MySQL Column Types |
| 17 | + |
| 18 | +There are two MySQL column types for date values: `TIMESTAMP` and `DATETIME`. |
| 19 | + |
| 20 | +The [MySQL documentation](https://dev.mysql.com/doc/refman/8.0/en/datetime.html) should be consulted |
| 21 | +to understand the behavior of these two column types, particularly around: |
| 22 | + |
| 23 | +* Range: `1970-01-01 00:00:01` to `2038-01-19 03:14:07` for `TIMESTAMP`; `1000-01-01` to `9999-12-31` for `DATETIME`. |
| 24 | +* Time zones: `TIMESTAMP` values will be converted to UTC for storage and from UTC for retrieval, which can lead to reading different values. |
| 25 | + |
| 26 | +## Range and DateTime.MinValue/MaxValue |
| 27 | + |
| 28 | +`DateTime.MinValue` and `DateTime.MaxValue` both exceed the range of a `DATETIME` column |
| 29 | +(and are well outside the range of a `TIMESTAMP` column). |
| 30 | + |
| 31 | +`DateTime.MinValue` is `0001-01-01 00:00:00`, but the minimum supported value for a `DATETIME` |
| 32 | +column is `1000-01-01 00:00:00`. In many versions of MySQL Server, you _can_ successfully |
| 33 | +insert this value; however, [it is not officially supported](https://bugs.mysql.com/bug.php?id=2106). |
| 34 | + |
| 35 | +By default, inserting `DateTime.MaxValue` into a `DATETIME` column will fail with a "datetime field |
| 36 | +overflow" error, because the timestamp is `23:59:59.9999999` but `DATETIME` can't store fractional |
| 37 | +seconds. To fix this, declare the column as `DATETIME(6)`. |
| 38 | + |
| 39 | +## DateTime Notes |
| 40 | + |
| 41 | +The `DateTime.Kind` property cannot be round-tripped. By default, all `DateTime` values read from |
| 42 | +MySQL will have a `Kind` property of `DateTimeKind.Unspecified`. |
| 43 | + |
| 44 | +A best practice is to ensure that only UTC values are stored in a `DATETIME` column, to avoid |
| 45 | +data loss when reading or comparing values across different timezones. To enforce this, |
| 46 | +set `DateTimeKind=Utc` in the connection string. When this is set, all values will be retrieved |
| 47 | +as `DateTimeKind.Utc`, and it is an error to insert `DateTimeKind.Local` values. |
| 48 | + |
| 49 | +Conversely, this connection string option can also be set to `DateTimeKind=Local` to force |
| 50 | +the storage and retrieval of only local values. |
| 51 | + |
| 52 | +## DateTimeOffset Notes |
| 53 | + |
| 54 | +It is not possible to store a `DateTimeOffset` in a `DATETIME` column. If you create a |
| 55 | +`MySqlParameter` with a `Value` holding a `DateTimeOffset`, only the `UtcDateTime` |
| 56 | +property will be stored in MySQL. The recommended approach to store and retrieve |
| 57 | +`DateTimeOffset` values is to use two columns: one for the `LocalDateTime` and one |
| 58 | +for the `Offset`. |
| 59 | + |
| 60 | +### DateTimeOffset Table Schema |
| 61 | + |
| 62 | +```sql |
| 63 | +CREATE TABLE times ( |
| 64 | + LocalDateTime DATETIME(6), |
| 65 | + Offset TIME |
| 66 | +); |
| 67 | +``` |
| 68 | + |
| 69 | +### Storing a DateTimeOffset |
| 70 | + |
| 71 | +```csharp |
| 72 | +DateTimeOffset dto; |
| 73 | +using var cmd = connection.CreateCommand(); |
| 74 | +cmd.CommandText = "insert into times(LocalDateTime, Offset) values(@LocalDateTime, @Offset);"; |
| 75 | +cmd.Parameters.AddWithValue("@LocalDateTime", dto.LocalDateTime); |
| 76 | +cmd.Parameters.AddWithValue("@Offset", dto.Offset); |
| 77 | +cmd.ExecuteNonQuery(); |
| 78 | +``` |
| 79 | + |
| 80 | +### Reading a DateTimeOffset |
| 81 | + |
| 82 | +```csharp |
| 83 | +using var cmd = connection.CreateCommand(); |
| 84 | +cmd.CommandText = "select LocalDateTime, Offset from times;"; |
| 85 | +using var reader = cmd.ExecuteReader(); |
| 86 | +while (reader.Read()) |
| 87 | +{ |
| 88 | + var dto = new DateTimeOffset(reader.GetDateTime(0), reader.GetTimeSpan(1)); |
| 89 | +} |
| 90 | +``` |
0 commit comments