Skip to content

Commit da25891

Browse files
Merge pull request #148 from dotnetprojects/add-primary-key
Parse and set DateTimeOffset Default values in Postgre
2 parents c5ac15f + 2ef7e23 commit da25891

File tree

4 files changed

+96
-0
lines changed

4 files changed

+96
-0
lines changed

src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_ChangeColumnTests.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System;
12
using System.Data;
23
using System.Threading.Tasks;
34
using DotNetProjects.Migrator.Framework;
@@ -15,4 +16,30 @@ public async Task SetUpAsync()
1516
{
1617
await BeginPostgreSQLTransactionAsync();
1718
}
19+
20+
[Test]
21+
public void ChangeColumn_DateTimeOffsetToDateTime_Success()
22+
{
23+
// Arrange
24+
var tableName = "TableName";
25+
var column1Name = "Column1";
26+
var column2Name = "Column2";
27+
var dateTimeDefaultValue = new DateTime(2025, 5, 4, 3, 2, 1, DateTimeKind.Utc);
28+
var dateTimeInsert = new DateTime(2001, 2, 3, 4, 5, 6, 7, DateTimeKind.Utc);
29+
30+
// Act
31+
Provider.AddTable(tableName,
32+
new Column(column1Name, DbType.Int32, ColumnProperty.Null),
33+
new Column(column2Name, DbType.DateTimeOffset, ColumnProperty.Null, defaultValue: dateTimeDefaultValue)
34+
);
35+
36+
Provider.Insert(table: tableName, columns: [column2Name], values: [dateTimeInsert]);
37+
38+
// Assert
39+
Provider.ChangeColumn(tableName, new Column(column2Name, DbType.DateTime2, ColumnProperty.NotNull));
40+
var column2 = Provider.GetColumnByName(tableName, column2Name);
41+
42+
Assert.That(column2.MigratorDbType, Is.EqualTo(MigratorDbType.DateTime2));
43+
Assert.That(column2.DefaultValue, Is.Null);
44+
}
1845
}

src/Migrator.Tests/Providers/PostgreSQL/PostgreSQLTransformationProvider_GetColumns_DefaultValueTests.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,15 @@ public void GetColumns_DefaultValues_Succeeds()
5555
{
5656
// Arrange
5757
var dateTimeDefaultValue = new DateTime(2000, 1, 2, 3, 4, 5, DateTimeKind.Utc);
58+
var dateTimeOffsetDefaultValue = new DateTimeOffset(2022, 2, 2, 3, 3, 4, 4, TimeSpan.FromHours(1));
5859
var guidDefaultValue = Guid.NewGuid();
5960
var decimalDefaultValue = 14.56565m;
6061

6162
const string testTableName = "MyDefaultTestTable";
6263

6364
const string dateTimeColumnName1 = "datetimecolumn1";
6465
const string dateTimeColumnName2 = "datetimecolumn2";
66+
const string dateTimeOffsetColumnName1 = "datetimeoffset1";
6567
const string decimalColumnName1 = "decimalcolumn";
6668
const string guidColumnName1 = "guidcolumn1";
6769
const string booleanColumnName1 = "booleancolumn1";
@@ -76,6 +78,7 @@ public void GetColumns_DefaultValues_Succeeds()
7678
Provider.AddTable(testTableName,
7779
new Column(dateTimeColumnName1, DbType.DateTime, dateTimeDefaultValue),
7880
new Column(dateTimeColumnName2, DbType.DateTime2, dateTimeDefaultValue),
81+
new Column(dateTimeOffsetColumnName1, DbType.DateTimeOffset, dateTimeOffsetDefaultValue),
7982
new Column(decimalColumnName1, DbType.Decimal, decimalDefaultValue),
8083
new Column(guidColumnName1, DbType.Guid, guidDefaultValue),
8184

@@ -96,6 +99,7 @@ public void GetColumns_DefaultValues_Succeeds()
9699
// Assert
97100
var dateTimeColumn1 = columns.Single(x => x.Name.Equals(dateTimeColumnName1, StringComparison.OrdinalIgnoreCase));
98101
var dateTimeColumn2 = columns.Single(x => x.Name.Equals(dateTimeColumnName2, StringComparison.OrdinalIgnoreCase));
102+
var dateTimeOffsetColumn1 = columns.Single(x => x.Name.Equals(dateTimeOffsetColumnName1, StringComparison.OrdinalIgnoreCase));
99103
var decimalColumn1 = columns.Single(x => x.Name.Equals(decimalColumnName1, StringComparison.OrdinalIgnoreCase));
100104
var guidColumn1 = columns.Single(x => x.Name.Equals(guidColumnName1, StringComparison.OrdinalIgnoreCase));
101105
var booleanColumn1 = columns.Single(x => x.Name.Equals(booleanColumnName1, StringComparison.OrdinalIgnoreCase));
@@ -108,6 +112,7 @@ public void GetColumns_DefaultValues_Succeeds()
108112

109113
Assert.That(dateTimeColumn1.DefaultValue, Is.EqualTo(dateTimeDefaultValue));
110114
Assert.That(dateTimeColumn2.DefaultValue, Is.EqualTo(dateTimeDefaultValue));
115+
Assert.That(dateTimeOffsetColumn1.DefaultValue, Is.EqualTo(dateTimeOffsetDefaultValue));
111116
Assert.That(decimalColumn1.DefaultValue, Is.EqualTo(decimalDefaultValue));
112117
Assert.That(guidColumn1.DefaultValue, Is.EqualTo(guidDefaultValue));
113118
Assert.That(booleanColumn1.DefaultValue, Is.True);

src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLDialect.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,11 @@ public override string Default(object defaultValue)
129129
var convertedString = BitConverter.ToString(byteArray).Replace("-", "").ToLower();
130130
return @$"DEFAULT E'\\x{convertedString}'";
131131
}
132+
else if (defaultValue is DateTimeOffset offset)
133+
{
134+
var convertedString = offset.ToString("yyyy-MM-dd HH:mm:ss.fffzzz");
135+
return @$"DEFAULT '{convertedString}'";
136+
}
132137

133138
return base.Default(defaultValue);
134139
}

src/Migrator/Providers/Impl/PostgreSQL/PostgreSQLTransformationProvider.cs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -762,6 +762,65 @@ public override Column[] GetColumns(string table)
762762
throw new NotImplementedException($"Cannot parse {columnInfo.ColumnDefault} in column '{column.Name}'");
763763
}
764764
}
765+
else if (column.MigratorDbType == MigratorDbType.DateTimeOffset)
766+
{
767+
if (columnInfo.ColumnDefault.StartsWith("'"))
768+
{
769+
var match = stripSingleQuoteRegEx.Match(columnInfo.ColumnDefault);
770+
771+
if (!match.Success)
772+
{
773+
throw new NotImplementedException($"Cannot parse {columnInfo.ColumnDefault} in column '{column.Name}'");
774+
}
775+
776+
var singleQuoteString = match.Value;
777+
778+
// 1) Normalize "Z" at the end → "+00:00"
779+
singleQuoteString = Regex.Replace(singleQuoteString, @"Z$", "+00:00");
780+
781+
// 2) Normalize offset at the end of the string
782+
// Cases handled:
783+
// +HH → +HH:00
784+
// +HHMM → +HH:MM
785+
// +HH:MM → stays unchanged
786+
// -HH / -HHMM → same logic
787+
singleQuoteString = Regex.Replace(
788+
singleQuoteString,
789+
@"([+-])(\d{2})(?::?(\d{2}))?$",
790+
m =>
791+
{
792+
var sign = m.Groups[1].Value; // "+" or "-"
793+
var hh = m.Groups[2].Value; // hours
794+
var hasMm = m.Groups[3].Success; // minutes present?
795+
var mm = hasMm ? m.Groups[3].Value : "00";
796+
return $"{sign}{hh}:{mm}";
797+
}
798+
);
799+
800+
// 3) Parse using multiple possible formats
801+
// Supports both space and "T" separator, with/without milliseconds
802+
var formats = new[]
803+
{
804+
"yyyy-MM-dd HH:mm:ss.fffzzz", // space separator, with ms
805+
"yyyy-MM-dd HH:mm:sszzz", // space separator, no ms
806+
"yyyy-MM-ddTHH:mm:ss.fffzzz", // ISO8601, with ms
807+
"yyyy-MM-ddTHH:mm:sszzz" // ISO8601, no ms
808+
};
809+
810+
var dateTimeOffset = DateTimeOffset.ParseExact(
811+
singleQuoteString,
812+
formats,
813+
CultureInfo.InvariantCulture,
814+
DateTimeStyles.None
815+
);
816+
817+
column.DefaultValue = dateTimeOffset;
818+
}
819+
else
820+
{
821+
throw new NotImplementedException($"Cannot parse {columnInfo.ColumnDefault} in column '{column.Name}'");
822+
}
823+
}
765824
else
766825
{
767826
throw new NotImplementedException($"{nameof(DbType)} {column.MigratorDbType} not implemented.");

0 commit comments

Comments
 (0)