Skip to content

Commit a228af6

Browse files
committed
PostgreSql: TimeZoneInfo as DefaultTimeZone
no loading data from server
1 parent b9a1654 commit a228af6

File tree

3 files changed

+29
-76
lines changed

3 files changed

+29
-76
lines changed

Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/DriverFactory.cs

Lines changed: 8 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
using Xtensive.Orm;
1414
using Xtensive.Sql.Info;
1515
using Xtensive.Sql.Drivers.PostgreSql.Resources;
16-
using System.Collections.Generic;
1716

1817
namespace Xtensive.Sql.Drivers.PostgreSql
1918
{
@@ -67,9 +66,9 @@ protected override SqlDriver CreateDriver(string connectionString, SqlDriverConf
6766
else
6867
OpenConnectionFast(connection, configuration, false).GetAwaiter().GetResult();
6968
var version = GetVersion(configuration, connection);
70-
var serverTimezones = GetServerTimeZones(connection, false).GetAwaiter().GetResult();
7169
var defaultSchema = GetDefaultSchema(connection);
72-
return CreateDriverInstance(connectionString, version, defaultSchema, serverTimezones, connection.Timezone);
70+
var defaultTimeZoneInfo = PostgreSqlHelper.GetTimeZoneInfoForServerTimeZone(connection.Timezone);
71+
return CreateDriverInstance(connectionString, version, defaultSchema, defaultTimeZoneInfo);
7372
}
7473

7574
/// <inheritdoc/>
@@ -87,9 +86,9 @@ protected override async Task<SqlDriver> CreateDriverAsync(
8786
else
8887
await OpenConnectionFast(connection, configuration, true, token).ConfigureAwait(false);
8988
var version = GetVersion(configuration, connection);
90-
var serverTimezones = await GetServerTimeZones(connection, true, token).ConfigureAwait(false);
9189
var defaultSchema = await GetDefaultSchemaAsync(connection, token: token).ConfigureAwait(false);
92-
return CreateDriverInstance(connectionString, version, defaultSchema, serverTimezones, connection.Timezone);
90+
var defaultTimeZoneInfo = PostgreSqlHelper.GetTimeZoneInfoForServerTimeZone(connection.Timezone);
91+
return CreateDriverInstance(connectionString, version, defaultSchema, defaultTimeZoneInfo);
9392
}
9493
}
9594

@@ -103,7 +102,7 @@ private static Version GetVersion(SqlDriverConfiguration configuration, NpgsqlCo
103102

104103
private static SqlDriver CreateDriverInstance(
105104
string connectionString, Version version, DefaultSchemaInfo defaultSchema,
106-
Dictionary<string, TimeSpan> timezones, string defaultTimeZone)
105+
TimeZoneInfo defaultTimeZone)
107106
{
108107
var coreServerInfo = new CoreServerInfo {
109108
ServerVersion = version,
@@ -116,7 +115,6 @@ private static SqlDriver CreateDriverInstance(
116115
var pgsqlServerInfo = new PostgreServerInfo() {
117116
InfinityAliasForDatesEnabled = InfinityAliasForDatesEnabled,
118117
LegacyTimestampBehavior = LegacyTimestamptBehaviorEnabled,
119-
ServerTimeZones = timezones,
120118
DefaultTimeZone = defaultTimeZone
121119
};
122120

@@ -204,44 +202,7 @@ await SqlHelper.NotifyConnectionInitializingAsync(accessors,
204202
}
205203
}
206204

207-
private static async ValueTask<Dictionary<string, TimeSpan>> GetServerTimeZones(NpgsqlConnection connection, bool isAsync, CancellationToken token = default)
208-
{
209-
var resultZones = new Dictionary<string, TimeSpan>();
210-
211-
var command = connection.CreateCommand();
212-
command.CommandText = "SELECT \"name\", \"abbrev\", \"utc_offset\", \"is_dst\" FROM pg_timezone_names";
213-
if (isAsync) {
214-
await using(command)
215-
await using(var reader = await command.ExecuteReaderAsync()) {
216-
while(await reader.ReadAsync()) {
217-
ReadTimezoneRow(reader, resultZones);
218-
}
219-
}
220-
}
221-
else {
222-
using (command)
223-
using (var reader = command.ExecuteReader()) {
224-
while (reader.Read()) {
225-
ReadTimezoneRow(reader, resultZones);
226-
}
227-
}
228-
}
229-
return resultZones;
230-
231-
232-
static void ReadTimezoneRow(NpgsqlDataReader reader, Dictionary<string, TimeSpan> zones)
233-
{
234-
var name = reader.GetString(0);
235-
var abbrev = reader.GetString(1);
236-
var utcOffset = PostgreSqlHelper.ResurrectTimeSpanFromNpgsqlInterval(reader.GetFieldValue<NpgsqlTypes.NpgsqlInterval>(2));
237-
238-
_ = zones.TryAdd(name, utcOffset);
239-
//flatten results for search convinience
240-
if (!string.IsNullOrEmpty(abbrev)) {
241-
_ = zones.TryAdd(abbrev, utcOffset);
242-
}
243-
}
244-
}
205+
#region Helpers
245206

246207
private static bool SetOrGetExistingDisableInfinityAliasForDatesSwitch(bool valueToSet) =>
247208
GetSwitchValueOrSet(Orm.PostgreSql.WellKnown.DateTimeToInfinityConversionSwitchName, valueToSet);
@@ -260,6 +221,8 @@ private static bool GetSwitchValueOrSet(string switchName, bool valueToSet)
260221
}
261222
}
262223

224+
#endregion
225+
263226
static DriverFactory()
264227
{
265228
// Starging from Npgsql 6.0 they broke compatibility by forcefully replacing

Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/PostgreServerInfo.cs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
using System;
66
using System.Collections.Generic;
7+
using System.Text.RegularExpressions;
78

89
namespace Xtensive.Sql.Drivers.PostgreSql
910
{
@@ -27,13 +28,10 @@ internal sealed class PostgreServerInfo
2728
public bool LegacyTimestampBehavior { get; init; }
2829

2930
/// <summary>
30-
/// Contains server timezone names and their base Utc offset (including abbreviations).
31+
/// Gets the <see cref="TimeZoneInfo"/> to which <see cref="DateTimeOffset"/> values
32+
/// will be converted on reading from database.
33+
/// <see langword="null"/> if no local equivalent of server time zone.
3134
/// </summary>
32-
public IReadOnlyDictionary<string, TimeSpan> ServerTimeZones { get; init; }
33-
34-
/// <summary>
35-
/// Gets time zone of connection after connection initialization script was executed.
36-
/// </summary>
37-
public string DefaultTimeZone { get; init; }
35+
public TimeZoneInfo DefaultTimeZone { get; init; }
3836
}
3937
}

Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/TypeMapper.cs

Lines changed: 16 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,11 @@
55
// Created: 2009.06.23
66

77
using System;
8-
using System.Collections.Generic;
98
using System.Data;
109
using System.Data.Common;
1110
using System.Security;
1211
using Npgsql;
1312
using NpgsqlTypes;
14-
using Xtensive.Orm.PostgreSql;
1513
using Xtensive.Reflection.PostgreSql;
1614

1715

@@ -27,7 +25,7 @@ internal class TypeMapper : Sql.TypeMapper
2725
private const long TimeSpanMaxValueAdjustedTicks = 9223372036854775800;
2826

2927
protected readonly bool legacyTimestampBehaviorEnabled;
30-
protected readonly TimeSpan? defaultTimeZone;
28+
protected readonly TimeZoneInfo defaultTimeZone;
3129

3230
public override bool IsParameterCastRequired(Type type)
3331
{
@@ -158,7 +156,7 @@ public override void BindDateTime(DbParameter parameter, object value)
158156
// in Npgsql 6+, though both types have the same range of values and resolution.
159157
//
160158
// If no explicit type declared it seems to be identified by DateTime value's Kind,
161-
// so now we have to unbox-box value to change kind of value because of this "talanted" person.
159+
// so now we have to unbox-box value to change kind of value.
162160
nativeParameter.NpgsqlDbType = NpgsqlDbType.Timestamp;
163161
nativeParameter.Value = value is null
164162
? DBNull.Value
@@ -180,7 +178,6 @@ public override void BindDateTimeOffset(DbParameter parameter, object value)
180178
nativeParameter.NpgsqlDbType = NpgsqlDbType.TimestampTz;
181179

182180
// Manual switch to universal time is required by Npgsql from now on,
183-
// Npgsql team "untaught" the library to do it.
184181
nativeParameter.NpgsqlValue = value is null
185182
? DBNull.Value
186183
: value is DateTimeOffset dateTimeOffset
@@ -300,23 +297,22 @@ public override object ReadDateTimeOffset(DbDataReader reader, int index)
300297
return value;
301298
}
302299
else {
303-
// Here, we try to apply connection timezone to the values we read.
304-
// To not get it from internal connection of DbDataReader
305-
// we assume that time zone switch happens (if happens)
306-
// in DomainConfiguration.ConnectionInitializationSql and
307-
// we cache time zone of native connection after the script
308-
// has been executed.
309-
310-
return (defaultTimeZone.HasValue)
311-
? value.ToOffset(defaultTimeZone.Value)
312-
: value.ToLocalTime();
300+
// Here, we try to apply connection time zone (if it was recongized on client-side)
301+
// to the values we read.
302+
// If any time zone change happens, we assume that DomainConfiguration.ConnectionInitializationSql
303+
// is used to make such change and we cache time zone info on first connection in driver factory
304+
// after initialization has happened.
305+
// If connection time zone has not been recognized we transform value to local offset
306+
// on the assumption that database server is usually in the same region.
307+
return defaultTimeZone is not null
308+
? TimeZoneInfo.ConvertTime(value, defaultTimeZone)
309+
: (object) value.ToLocalTime();
313310
}
314311
}
315312

316-
internal protected ArgumentException ValueNotOfTypeError(string typeName)
317-
{
318-
return new ArgumentException($"Value is not of '{typeName}' type.");
319-
}
313+
internal protected ArgumentException ValueNotOfTypeError(string typeName) =>
314+
new($"Value is not of '{typeName}' type.");
315+
320316

321317
// Constructors
322318

@@ -325,11 +321,7 @@ public TypeMapper(PostgreSql.Driver driver)
325321
{
326322
var postgreServerInfo = driver.PostgreServerInfo;
327323
legacyTimestampBehaviorEnabled = postgreServerInfo.LegacyTimestampBehavior;
328-
defaultTimeZone = postgreServerInfo.ServerTimeZones.TryGetValue(
329-
PostgreSqlHelper.TryGetZoneFromPosix(postgreServerInfo.DefaultTimeZone), out var offset)
330-
? offset
331-
: null;
332-
;
324+
defaultTimeZone = postgreServerInfo.DefaultTimeZone;
333325
}
334326
}
335327
}

0 commit comments

Comments
 (0)