@@ -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 = Environment . GetEnvironmentVariable ( TimeZoneDirectoryEnvironmentVariable ) == null ;
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,30 @@ private static TimeZoneInfoResult TryGetTimeZoneFromLocalMachineCore(string id,
29
34
value = null ;
30
35
e = null ;
31
36
37
+ byte [ ] ? rawData = null ;
38
+ #if TARGET_WASI || TARGET_BROWSER
39
+ if ( UseEmbeddedTzDatabase )
40
+ {
41
+ if ( ! TryLoadEmbeddedTzFile ( id , ref rawData ) )
42
+ {
43
+ e = new FileNotFoundException ( id , "Embedded TZ data not found" ) ;
44
+ return TimeZoneInfoResult . TimeZoneNotFoundException ;
45
+ }
46
+
47
+ value = GetTimeZoneFromTzData ( rawData , id ) ;
48
+
49
+ if ( value == null )
50
+ {
51
+ e = new InvalidTimeZoneException ( SR . Format ( SR . InvalidTimeZone_InvalidFileData , id , id ) ) ;
52
+ return TimeZoneInfoResult . InvalidTimeZoneException ;
53
+ }
54
+
55
+ return TimeZoneInfoResult . Success ;
56
+ }
57
+ #endif
58
+
32
59
string timeZoneDirectory = GetTimeZoneDirectory ( ) ;
33
60
string timeZoneFilePath = Path . Combine ( timeZoneDirectory , id ) ;
34
- byte [ ] rawData ;
35
61
try
36
62
{
37
63
rawData = File . ReadAllBytes ( timeZoneFilePath ) ;
@@ -74,52 +100,68 @@ private static TimeZoneInfoResult TryGetTimeZoneFromLocalMachineCore(string id,
74
100
/// <remarks>
75
101
/// Lines that start with # are comments and are skipped.
76
102
/// </remarks>
77
- private static List < string > GetTimeZoneIds ( )
103
+ private static IEnumerable < string > GetTimeZoneIds ( )
104
+ {
105
+ #if TARGET_WASI || TARGET_BROWSER
106
+ byte [ ] ? rawData = null ;
107
+ if ( UseEmbeddedTzDatabase )
108
+ {
109
+ if ( ! TryLoadEmbeddedTzFile ( TimeZoneFileName , ref rawData ) )
110
+ {
111
+ return Array . Empty < string > ( ) ;
112
+ }
113
+ using var reader = new StreamReader ( new MemoryStream ( rawData ) , Encoding . UTF8 ) ;
114
+ return ParseTimeZoneIds ( reader ) ;
115
+ }
116
+ #endif
117
+ try
118
+ {
119
+ using var reader = new StreamReader ( Path . Combine ( GetTimeZoneDirectory ( ) , TimeZoneFileName ) , Encoding . UTF8 ) ;
120
+ return ParseTimeZoneIds ( reader ) ;
121
+ }
122
+ catch ( IOException ) { }
123
+ catch ( UnauthorizedAccessException ) { }
124
+ return Array . Empty < string > ( ) ;
125
+ }
126
+
127
+ private static List < string > ParseTimeZoneIds ( StreamReader reader )
78
128
{
79
129
List < string > timeZoneIds = new List < string > ( ) ;
80
130
81
- try
131
+ string ? zoneTabFileLine ;
132
+ while ( ( zoneTabFileLine = reader . ReadLine ( ) ) != null )
82
133
{
83
- using ( StreamReader sr = new StreamReader ( Path . Combine ( GetTimeZoneDirectory ( ) , TimeZoneFileName ) , Encoding . UTF8 ) )
134
+ if ( ! string . IsNullOrEmpty ( zoneTabFileLine ) && zoneTabFileLine [ 0 ] != '#' )
84
135
{
85
- string ? zoneTabFileLine ;
86
- while ( ( zoneTabFileLine = sr . ReadLine ( ) ) != null )
136
+ // the format of the line is "country-code \t coordinates \t TimeZone Id \t comments"
137
+
138
+ int firstTabIndex = zoneTabFileLine . IndexOf ( '\t ' ) ;
139
+ if ( firstTabIndex >= 0 )
87
140
{
88
- if ( ! string . IsNullOrEmpty ( zoneTabFileLine ) && zoneTabFileLine [ 0 ] != '#' )
141
+ int secondTabIndex = zoneTabFileLine . IndexOf ( '\t ' , firstTabIndex + 1 ) ;
142
+ if ( secondTabIndex >= 0 )
89
143
{
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 )
144
+ string timeZoneId ;
145
+ int startIndex = secondTabIndex + 1 ;
146
+ int thirdTabIndex = zoneTabFileLine . IndexOf ( '\t ' , startIndex ) ;
147
+ if ( thirdTabIndex >= 0 )
94
148
{
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
- }
149
+ int length = thirdTabIndex - startIndex ;
150
+ timeZoneId = zoneTabFileLine . Substring ( startIndex , length ) ;
151
+ }
152
+ else
153
+ {
154
+ timeZoneId = zoneTabFileLine . Substring ( startIndex ) ;
155
+ }
110
156
111
- if ( ! string . IsNullOrEmpty ( timeZoneId ) )
112
- {
113
- timeZoneIds . Add ( timeZoneId ) ;
114
- }
115
- }
157
+ if ( ! string . IsNullOrEmpty ( timeZoneId ) )
158
+ {
159
+ timeZoneIds . Add ( timeZoneId ) ;
116
160
}
117
161
}
118
162
}
119
163
}
120
164
}
121
- catch ( IOException ) { }
122
- catch ( UnauthorizedAccessException ) { }
123
165
124
166
return timeZoneIds ;
125
167
}
@@ -379,6 +421,22 @@ private static bool TryLoadTzFile(string tzFilePath, [NotNullWhen(true)] ref byt
379
421
return false ;
380
422
}
381
423
424
+ #if TARGET_WASI || TARGET_BROWSER
425
+ private static bool TryLoadEmbeddedTzFile ( string name , [ NotNullWhen ( true ) ] ref byte [ ] ? rawData )
426
+ {
427
+ IntPtr bytes = Interop . Sys . GetTimeZoneData ( name , out int length ) ;
428
+ if ( bytes == IntPtr . Zero )
429
+ {
430
+ rawData = null ;
431
+ return false ;
432
+ }
433
+
434
+ rawData = new byte [ length ] ;
435
+ Marshal . Copy ( bytes , rawData , 0 , length ) ;
436
+ return true ;
437
+ }
438
+ #endif
439
+
382
440
/// <summary>
383
441
/// Gets the tzfile raw data for the current 'local' time zone using the following rules.
384
442
///
@@ -387,6 +445,10 @@ private static bool TryLoadTzFile(string tzFilePath, [NotNullWhen(true)] ref byt
387
445
/// 2. Get the default TZ from the device
388
446
/// 3. Use UTC if all else fails.
389
447
///
448
+ /// On WASI / Browser
449
+ /// 0. if TZDIR is not set, use TZ variable as id to embedded database.
450
+ /// 1. fall back to unix behavior if TZDIR is set.
451
+ ///
390
452
/// On all other platforms
391
453
/// 1. Read the TZ environment variable. If it is set, use it.
392
454
/// 2. Look for the data in /etc/localtime.
@@ -406,6 +468,11 @@ private static bool TryGetLocalTzFile([NotNullWhen(true)] out byte[]? rawData, [
406
468
{
407
469
#if TARGET_IOS || TARGET_TVOS
408
470
tzVariable = Interop . Sys . GetDefaultTimeZone ( ) ;
471
+ #elif TARGET_WASI || TARGET_BROWSER
472
+ if ( UseEmbeddedTzDatabase )
473
+ {
474
+ return false ; // use UTC
475
+ }
409
476
#else
410
477
return
411
478
TryLoadTzFile ( "/etc/localtime" , ref rawData , ref id ) ||
@@ -419,6 +486,17 @@ private static bool TryGetLocalTzFile([NotNullWhen(true)] out byte[]? rawData, [
419
486
{
420
487
return false ;
421
488
}
489
+ #if TARGET_WASI || TARGET_BROWSER
490
+ if ( UseEmbeddedTzDatabase )
491
+ {
492
+ if ( ! TryLoadEmbeddedTzFile ( tzVariable , ref rawData ) )
493
+ {
494
+ return false ;
495
+ }
496
+ id = tzVariable ;
497
+ return true ;
498
+ }
499
+ #endif
422
500
423
501
// Otherwise, use the path from the env var. If it's not absolute, make it relative
424
502
// to the system timezone directory
0 commit comments