@@ -18,6 +18,11 @@ public sealed partial class TimeZoneInfo
18
18
private const string TimeZoneDirectoryEnvironmentVariable = "TZDIR" ;
19
19
private const string TimeZoneEnvironmentVariable = "TZ" ;
20
20
21
+ #if TARGET_WASI || TARGET_BROWSER
22
+ // if TZDIR is set, then the embedded TZ data will be ignored and normal unix behavior will be used
23
+ private static readonly bool UseEmbeddedTzDatabase = string . IsNullOrEmpty ( Environment . GetEnvironmentVariable ( TimeZoneDirectoryEnvironmentVariable ) ) ;
24
+ #endif
25
+
21
26
private static TimeZoneInfo GetLocalTimeZoneCore ( )
22
27
{
23
28
// Without Registry support, create the TimeZoneInfo from a TZ file
@@ -29,9 +34,31 @@ private static TimeZoneInfoResult TryGetTimeZoneFromLocalMachineCore(string id,
29
34
value = null ;
30
35
e = null ;
31
36
37
+ byte [ ] ? rawData = null ;
32
38
string timeZoneDirectory = GetTimeZoneDirectory ( ) ;
33
39
string timeZoneFilePath = Path . Combine ( timeZoneDirectory , id ) ;
34
- byte [ ] rawData ;
40
+
41
+ #if TARGET_WASI || TARGET_BROWSER
42
+ if ( UseEmbeddedTzDatabase )
43
+ {
44
+ if ( ! TryLoadEmbeddedTzFile ( timeZoneFilePath , out rawData ) )
45
+ {
46
+ e = new FileNotFoundException ( id , "Embedded TZ data not found" ) ;
47
+ return TimeZoneInfoResult . TimeZoneNotFoundException ;
48
+ }
49
+
50
+ value = GetTimeZoneFromTzData ( rawData , id ) ;
51
+
52
+ if ( value == null )
53
+ {
54
+ e = new InvalidTimeZoneException ( SR . Format ( SR . InvalidTimeZone_InvalidFileData , id , id ) ) ;
55
+ return TimeZoneInfoResult . InvalidTimeZoneException ;
56
+ }
57
+
58
+ return TimeZoneInfoResult . Success ;
59
+ }
60
+ #endif
61
+
35
62
try
36
63
{
37
64
rawData = File . ReadAllBytes ( timeZoneFilePath ) ;
@@ -74,52 +101,68 @@ private static TimeZoneInfoResult TryGetTimeZoneFromLocalMachineCore(string id,
74
101
/// <remarks>
75
102
/// Lines that start with # are comments and are skipped.
76
103
/// </remarks>
77
- private static List < string > GetTimeZoneIds ( )
104
+ private static IEnumerable < string > GetTimeZoneIds ( )
105
+ {
106
+ try
107
+ {
108
+ var fileName = Path . Combine ( GetTimeZoneDirectory ( ) , TimeZoneFileName ) ;
109
+ #if TARGET_WASI || TARGET_BROWSER
110
+ if ( UseEmbeddedTzDatabase )
111
+ {
112
+ if ( ! TryLoadEmbeddedTzFile ( fileName , out var rawData ) )
113
+ {
114
+ return Array . Empty < string > ( ) ;
115
+ }
116
+ using var blobReader = new StreamReader ( new MemoryStream ( rawData ) , Encoding . UTF8 ) ;
117
+ return ParseTimeZoneIds ( blobReader ) ;
118
+ }
119
+ #endif
120
+ using var reader = new StreamReader ( fileName , Encoding . UTF8 ) ;
121
+ return ParseTimeZoneIds ( reader ) ;
122
+ }
123
+ catch ( IOException ) { }
124
+ catch ( UnauthorizedAccessException ) { }
125
+ return Array . Empty < string > ( ) ;
126
+ }
127
+
128
+ private static List < string > ParseTimeZoneIds ( StreamReader reader )
78
129
{
79
130
List < string > timeZoneIds = new List < string > ( ) ;
80
131
81
- try
132
+ string ? zoneTabFileLine ;
133
+ while ( ( zoneTabFileLine = reader . ReadLine ( ) ) != null )
82
134
{
83
- using ( StreamReader sr = new StreamReader ( Path . Combine ( GetTimeZoneDirectory ( ) , TimeZoneFileName ) , Encoding . UTF8 ) )
135
+ if ( ! string . IsNullOrEmpty ( zoneTabFileLine ) && zoneTabFileLine [ 0 ] != '#' )
84
136
{
85
- string ? zoneTabFileLine ;
86
- while ( ( zoneTabFileLine = sr . ReadLine ( ) ) != null )
137
+ // the format of the line is "country-code \t coordinates \t TimeZone Id \t comments"
138
+
139
+ int firstTabIndex = zoneTabFileLine . IndexOf ( '\t ' ) ;
140
+ if ( firstTabIndex >= 0 )
87
141
{
88
- if ( ! string . IsNullOrEmpty ( zoneTabFileLine ) && zoneTabFileLine [ 0 ] != '#' )
142
+ int secondTabIndex = zoneTabFileLine . IndexOf ( '\t ' , firstTabIndex + 1 ) ;
143
+ if ( secondTabIndex >= 0 )
89
144
{
90
- // the format of the line is "country-code \t coordinates \t TimeZone Id \t comments"
91
-
92
- int firstTabIndex = zoneTabFileLine . IndexOf ( '\t ' ) ;
93
- if ( firstTabIndex >= 0 )
145
+ string timeZoneId ;
146
+ int startIndex = secondTabIndex + 1 ;
147
+ int thirdTabIndex = zoneTabFileLine . IndexOf ( '\t ' , startIndex ) ;
148
+ if ( thirdTabIndex >= 0 )
94
149
{
95
- int secondTabIndex = zoneTabFileLine . IndexOf ( '\t ' , firstTabIndex + 1 ) ;
96
- if ( secondTabIndex >= 0 )
97
- {
98
- string timeZoneId ;
99
- int startIndex = secondTabIndex + 1 ;
100
- int thirdTabIndex = zoneTabFileLine . IndexOf ( '\t ' , startIndex ) ;
101
- if ( thirdTabIndex >= 0 )
102
- {
103
- int length = thirdTabIndex - startIndex ;
104
- timeZoneId = zoneTabFileLine . Substring ( startIndex , length ) ;
105
- }
106
- else
107
- {
108
- timeZoneId = zoneTabFileLine . Substring ( startIndex ) ;
109
- }
150
+ int length = thirdTabIndex - startIndex ;
151
+ timeZoneId = zoneTabFileLine . Substring ( startIndex , length ) ;
152
+ }
153
+ else
154
+ {
155
+ timeZoneId = zoneTabFileLine . Substring ( startIndex ) ;
156
+ }
110
157
111
- if ( ! string . IsNullOrEmpty ( timeZoneId ) )
112
- {
113
- timeZoneIds . Add ( timeZoneId ) ;
114
- }
115
- }
158
+ if ( ! string . IsNullOrEmpty ( timeZoneId ) )
159
+ {
160
+ timeZoneIds . Add ( timeZoneId ) ;
116
161
}
117
162
}
118
163
}
119
164
}
120
165
}
121
- catch ( IOException ) { }
122
- catch ( UnauthorizedAccessException ) { }
123
166
124
167
return timeZoneIds ;
125
168
}
@@ -379,6 +422,22 @@ private static bool TryLoadTzFile(string tzFilePath, [NotNullWhen(true)] ref byt
379
422
return false ;
380
423
}
381
424
425
+ #if TARGET_WASI || TARGET_BROWSER
426
+ private static bool TryLoadEmbeddedTzFile ( string name , [ NotNullWhen ( true ) ] out byte [ ] ? rawData )
427
+ {
428
+ IntPtr bytes = Interop . Sys . GetTimeZoneData ( name , out int length ) ;
429
+ if ( bytes == IntPtr . Zero )
430
+ {
431
+ rawData = null ;
432
+ return false ;
433
+ }
434
+
435
+ rawData = new byte [ length ] ;
436
+ Marshal . Copy ( bytes , rawData , 0 , length ) ;
437
+ return true ;
438
+ }
439
+ #endif
440
+
382
441
/// <summary>
383
442
/// Gets the tzfile raw data for the current 'local' time zone using the following rules.
384
443
///
@@ -387,6 +446,10 @@ private static bool TryLoadTzFile(string tzFilePath, [NotNullWhen(true)] ref byt
387
446
/// 2. Get the default TZ from the device
388
447
/// 3. Use UTC if all else fails.
389
448
///
449
+ /// On WASI / Browser
450
+ /// 1. if TZDIR is not set, use TZ variable as id to embedded database.
451
+ /// 2. fall back to unix behavior if TZDIR is set.
452
+ ///
390
453
/// On all other platforms
391
454
/// 1. Read the TZ environment variable. If it is set, use it.
392
455
/// 2. Look for the data in /etc/localtime.
@@ -406,6 +469,11 @@ private static bool TryGetLocalTzFile([NotNullWhen(true)] out byte[]? rawData, [
406
469
{
407
470
#if TARGET_IOS || TARGET_TVOS
408
471
tzVariable = Interop . Sys . GetDefaultTimeZone ( ) ;
472
+ #elif TARGET_WASI || TARGET_BROWSER
473
+ if ( UseEmbeddedTzDatabase )
474
+ {
475
+ return false ; // use UTC
476
+ }
409
477
#else
410
478
return
411
479
TryLoadTzFile ( "/etc/localtime" , ref rawData , ref id ) ||
@@ -432,6 +500,24 @@ private static bool TryGetLocalTzFile([NotNullWhen(true)] out byte[]? rawData, [
432
500
{
433
501
tzFilePath = tzVariable ;
434
502
}
503
+
504
+ #if TARGET_WASI || TARGET_BROWSER
505
+ if ( UseEmbeddedTzDatabase )
506
+ {
507
+ // embedded database only supports relative paths
508
+ if ( tzVariable [ 0 ] == '/' )
509
+ {
510
+ return false ;
511
+ }
512
+ if ( ! TryLoadEmbeddedTzFile ( tzFilePath , out rawData ) )
513
+ {
514
+ return false ;
515
+ }
516
+ id = tzVariable ;
517
+ return true ;
518
+ }
519
+ #endif
520
+
435
521
return TryLoadTzFile ( tzFilePath , ref rawData , ref id ) ;
436
522
}
437
523
0 commit comments