1
+ using System ;
2
+ using System . Reflection ;
3
+ using System . Reflection . Emit ;
4
+
5
+ namespace Dasher . TypeProviders
6
+ {
7
+ internal sealed class DateTimeOffsetProvider : ITypeProvider
8
+ {
9
+ private const int TicksPerMinute = 600000000 ;
10
+
11
+ public bool CanProvide ( Type type ) => type == typeof ( DateTimeOffset ) ;
12
+
13
+ public void Serialise ( ILGenerator ilg , LocalBuilder value , LocalBuilder packer , LocalBuilder contextLocal , DasherContext context )
14
+ {
15
+ // We need to write both the date and the offset
16
+ // - dto.DateTime always has unspecified kind (so we can just use Ticks rather than ToBinary and ignore internal flags)
17
+ // - dto.Offset is a timespan but always has integral minutes (minutes will be a smaller number than ticks so uses fewer bytes on the wire)
18
+
19
+ ilg . Emit ( OpCodes . Ldloc , packer ) ;
20
+ ilg . Emit ( OpCodes . Dup ) ;
21
+ ilg . Emit ( OpCodes . Dup ) ;
22
+
23
+ // Write the array header
24
+ ilg . Emit ( OpCodes . Ldc_I4_2 ) ;
25
+ ilg . Emit ( OpCodes . Call , typeof ( UnsafePacker ) . GetMethod ( nameof ( UnsafePacker . PackArrayHeader ) ) ) ;
26
+
27
+ // Write ticks
28
+ ilg . Emit ( OpCodes . Ldloca , value ) ;
29
+ ilg . Emit ( OpCodes . Call , typeof ( DateTimeOffset ) . GetProperty ( nameof ( DateTimeOffset . Ticks ) ) . GetMethod ) ;
30
+ ilg . Emit ( OpCodes . Call , typeof ( UnsafePacker ) . GetMethod ( nameof ( UnsafePacker . Pack ) , new [ ] { typeof ( long ) } ) ) ;
31
+
32
+ // Write offset minutes
33
+ var offset = ilg . DeclareLocal ( typeof ( TimeSpan ) ) ;
34
+ ilg . Emit ( OpCodes . Ldloca , value ) ;
35
+ ilg . Emit ( OpCodes . Call , typeof ( DateTimeOffset ) . GetProperty ( nameof ( DateTimeOffset . Offset ) ) . GetMethod ) ;
36
+ ilg . Emit ( OpCodes . Stloc , offset ) ;
37
+ ilg . Emit ( OpCodes . Ldloca , offset ) ;
38
+ ilg . Emit ( OpCodes . Call , typeof ( TimeSpan ) . GetProperty ( nameof ( TimeSpan . Ticks ) ) . GetMethod ) ;
39
+ ilg . Emit ( OpCodes . Ldc_I4 , TicksPerMinute ) ;
40
+ ilg . Emit ( OpCodes . Conv_I8 ) ;
41
+ ilg . Emit ( OpCodes . Div ) ;
42
+ ilg . Emit ( OpCodes . Conv_I2 ) ;
43
+ ilg . Emit ( OpCodes . Call , typeof ( UnsafePacker ) . GetMethod ( nameof ( UnsafePacker . Pack ) , new [ ] { typeof ( short ) } ) ) ;
44
+ }
45
+
46
+ public void Deserialise ( ILGenerator ilg , string name , Type targetType , LocalBuilder value , LocalBuilder unpacker , LocalBuilder contextLocal , DasherContext context , UnexpectedFieldBehaviour unexpectedFieldBehaviour )
47
+ {
48
+ // Ensure we have an array of two values
49
+ var arrayLength = ilg . DeclareLocal ( typeof ( int ) ) ;
50
+
51
+ ilg . Emit ( OpCodes . Ldloc , unpacker ) ;
52
+ ilg . Emit ( OpCodes . Ldloca , arrayLength ) ;
53
+ ilg . Emit ( OpCodes . Call , typeof ( Unpacker ) . GetMethod ( nameof ( Unpacker . TryReadArrayLength ) ) ) ;
54
+
55
+ // If the unpacker method failed (returned false), throw
56
+ var lbl1 = ilg . DefineLabel ( ) ;
57
+ ilg . Emit ( OpCodes . Brtrue , lbl1 ) ;
58
+ {
59
+ ilg . Emit ( OpCodes . Ldstr , $ "Expecting array header for DateTimeOffset property { name } ") ;
60
+ ilg . LoadType ( targetType ) ;
61
+ ilg . Emit ( OpCodes . Newobj , typeof ( DeserialisationException ) . GetConstructor ( new [ ] { typeof ( string ) , typeof ( Type ) } ) ) ;
62
+ ilg . Emit ( OpCodes . Throw ) ;
63
+ }
64
+ ilg . MarkLabel ( lbl1 ) ;
65
+
66
+ ilg . Emit ( OpCodes . Ldloc , arrayLength ) ;
67
+ ilg . Emit ( OpCodes . Ldc_I4_2 ) ;
68
+ ilg . Emit ( OpCodes . Ceq ) ;
69
+
70
+ var lbl2 = ilg . DefineLabel ( ) ;
71
+ ilg . Emit ( OpCodes . Brtrue , lbl2 ) ;
72
+ {
73
+ ilg . Emit ( OpCodes . Ldstr , $ "Expecting array to contain two items for DateTimeOffset property { name } ") ;
74
+ ilg . LoadType ( targetType ) ;
75
+ ilg . Emit ( OpCodes . Newobj , typeof ( DeserialisationException ) . GetConstructor ( new [ ] { typeof ( string ) , typeof ( Type ) } ) ) ;
76
+ ilg . Emit ( OpCodes . Throw ) ;
77
+ }
78
+ ilg . MarkLabel ( lbl2 ) ;
79
+
80
+ // Read ticks
81
+ var ticks = ilg . DeclareLocal ( typeof ( long ) ) ;
82
+
83
+ ilg . Emit ( OpCodes . Ldloc , unpacker ) ;
84
+ ilg . Emit ( OpCodes . Ldloca , ticks ) ;
85
+ ilg . Emit ( OpCodes . Call , typeof ( Unpacker ) . GetMethod ( nameof ( Unpacker . TryReadInt64 ) ) ) ;
86
+
87
+ // If the unpacker method failed (returned false), throw
88
+ var lbl3 = ilg . DefineLabel ( ) ;
89
+ ilg . Emit ( OpCodes . Brtrue , lbl3 ) ;
90
+ {
91
+ ilg . Emit ( OpCodes . Ldstr , $ "Expecting Int64 value for ticks component of DateTimeOffset property { name } ") ;
92
+ ilg . LoadType ( targetType ) ;
93
+ ilg . Emit ( OpCodes . Newobj , typeof ( DeserialisationException ) . GetConstructor ( new [ ] { typeof ( string ) , typeof ( Type ) } ) ) ;
94
+ ilg . Emit ( OpCodes . Throw ) ;
95
+ }
96
+ ilg . MarkLabel ( lbl3 ) ;
97
+
98
+ // Read offset
99
+ var minutes = ilg . DeclareLocal ( typeof ( short ) ) ;
100
+
101
+ ilg . Emit ( OpCodes . Ldloc , unpacker ) ;
102
+ ilg . Emit ( OpCodes . Ldloca , minutes ) ;
103
+ ilg . Emit ( OpCodes . Call , typeof ( Unpacker ) . GetMethod ( nameof ( Unpacker . TryReadInt16 ) ) ) ;
104
+
105
+ // If the unpacker method failed (returned false), throw
106
+ var lbl4 = ilg . DefineLabel ( ) ;
107
+ ilg . Emit ( OpCodes . Brtrue , lbl4 ) ;
108
+ {
109
+ ilg . Emit ( OpCodes . Ldstr , $ "Expecting Int16 value for offset component of DateTimeOffset property { name } ") ;
110
+ ilg . LoadType ( targetType ) ;
111
+ ilg . Emit ( OpCodes . Newobj , typeof ( DeserialisationException ) . GetConstructor ( new [ ] { typeof ( string ) , typeof ( Type ) } ) ) ;
112
+ ilg . Emit ( OpCodes . Throw ) ;
113
+ }
114
+ ilg . MarkLabel ( lbl4 ) ;
115
+
116
+ // Compose the final DateTimeOffset
117
+ ilg . Emit ( OpCodes . Ldloca , value ) ;
118
+ ilg . Emit ( OpCodes . Ldloc , ticks ) ;
119
+ ilg . Emit ( OpCodes . Ldloc , minutes ) ;
120
+ ilg . Emit ( OpCodes . Conv_I8 ) ;
121
+ ilg . Emit ( OpCodes . Ldc_I4 , TicksPerMinute ) ;
122
+ ilg . Emit ( OpCodes . Conv_I8 ) ;
123
+ ilg . Emit ( OpCodes . Mul ) ;
124
+ ilg . Emit ( OpCodes . Conv_I8 ) ;
125
+ ilg . Emit ( OpCodes . Call , typeof ( TimeSpan ) . GetMethod ( nameof ( TimeSpan . FromTicks ) , BindingFlags . Static | BindingFlags . Public ) ) ;
126
+ ilg . Emit ( OpCodes . Call , typeof ( DateTimeOffset ) . GetConstructor ( new [ ] { typeof ( long ) , typeof ( TimeSpan ) } ) ) ;
127
+ }
128
+ }
129
+ }
0 commit comments