@@ -18,6 +18,11 @@ public sealed partial class TimeZoneInfo
1818 private const string TimeZoneDirectoryEnvironmentVariable = "TZDIR" ;
1919 private const string TimeZoneEnvironmentVariable = "TZ" ;
2020
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+
2126 private static TimeZoneInfo GetLocalTimeZoneCore ( )
2227 {
2328 // Without Registry support, create the TimeZoneInfo from a TZ file
@@ -29,9 +34,30 @@ private static TimeZoneInfoResult TryGetTimeZoneFromLocalMachineCore(string id,
2934 value = null ;
3035 e = null ;
3136
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+
3259 string timeZoneDirectory = GetTimeZoneDirectory ( ) ;
3360 string timeZoneFilePath = Path . Combine ( timeZoneDirectory , id ) ;
34- byte [ ] rawData ;
3561 try
3662 {
3763 rawData = File . ReadAllBytes ( timeZoneFilePath ) ;
@@ -74,52 +100,68 @@ private static TimeZoneInfoResult TryGetTimeZoneFromLocalMachineCore(string id,
74100 /// <remarks>
75101 /// Lines that start with # are comments and are skipped.
76102 /// </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 )
78128 {
79129 List < string > timeZoneIds = new List < string > ( ) ;
80130
81- try
131+ string ? zoneTabFileLine ;
132+ while ( ( zoneTabFileLine = reader . ReadLine ( ) ) != null )
82133 {
83- using ( StreamReader sr = new StreamReader ( Path . Combine ( GetTimeZoneDirectory ( ) , TimeZoneFileName ) , Encoding . UTF8 ) )
134+ if ( ! string . IsNullOrEmpty ( zoneTabFileLine ) && zoneTabFileLine [ 0 ] != '#' )
84135 {
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 )
87140 {
88- if ( ! string . IsNullOrEmpty ( zoneTabFileLine ) && zoneTabFileLine [ 0 ] != '#' )
141+ int secondTabIndex = zoneTabFileLine . IndexOf ( '\t ' , firstTabIndex + 1 ) ;
142+ if ( secondTabIndex >= 0 )
89143 {
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 )
94148 {
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+ }
110156
111- if ( ! string . IsNullOrEmpty ( timeZoneId ) )
112- {
113- timeZoneIds . Add ( timeZoneId ) ;
114- }
115- }
157+ if ( ! string . IsNullOrEmpty ( timeZoneId ) )
158+ {
159+ timeZoneIds . Add ( timeZoneId ) ;
116160 }
117161 }
118162 }
119163 }
120164 }
121- catch ( IOException ) { }
122- catch ( UnauthorizedAccessException ) { }
123165
124166 return timeZoneIds ;
125167 }
@@ -379,6 +421,22 @@ private static bool TryLoadTzFile(string tzFilePath, [NotNullWhen(true)] ref byt
379421 return false ;
380422 }
381423
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+
382440 /// <summary>
383441 /// Gets the tzfile raw data for the current 'local' time zone using the following rules.
384442 ///
@@ -387,6 +445,10 @@ private static bool TryLoadTzFile(string tzFilePath, [NotNullWhen(true)] ref byt
387445 /// 2. Get the default TZ from the device
388446 /// 3. Use UTC if all else fails.
389447 ///
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+ ///
390452 /// On all other platforms
391453 /// 1. Read the TZ environment variable. If it is set, use it.
392454 /// 2. Look for the data in /etc/localtime.
@@ -406,6 +468,11 @@ private static bool TryGetLocalTzFile([NotNullWhen(true)] out byte[]? rawData, [
406468 {
407469#if TARGET_IOS || TARGET_TVOS
408470 tzVariable = Interop . Sys . GetDefaultTimeZone ( ) ;
471+ #elif TARGET_WASI || TARGET_BROWSER
472+ if ( UseEmbeddedTzDatabase )
473+ {
474+ return false ; // use UTC
475+ }
409476#else
410477 return
411478 TryLoadTzFile ( "/etc/localtime" , ref rawData , ref id ) ||
@@ -419,6 +486,17 @@ private static bool TryGetLocalTzFile([NotNullWhen(true)] out byte[]? rawData, [
419486 {
420487 return false ;
421488 }
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
422500
423501 // Otherwise, use the path from the env var. If it's not absolute, make it relative
424502 // to the system timezone directory
0 commit comments