@@ -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 = string . IsNullOrEmpty ( Environment . GetEnvironmentVariable ( TimeZoneDirectoryEnvironmentVariable ) ) ;
24+ #endif
25+
2126 private static TimeZoneInfo GetLocalTimeZoneCore ( )
2227 {
2328 // Without Registry support, create the TimeZoneInfo from a TZ file
@@ -29,9 +34,31 @@ private static TimeZoneInfoResult TryGetTimeZoneFromLocalMachineCore(string id,
2934 value = null ;
3035 e = null ;
3136
37+ byte [ ] ? rawData = null ;
3238 string timeZoneDirectory = GetTimeZoneDirectory ( ) ;
3339 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+
3562 try
3663 {
3764 rawData = File . ReadAllBytes ( timeZoneFilePath ) ;
@@ -74,52 +101,68 @@ private static TimeZoneInfoResult TryGetTimeZoneFromLocalMachineCore(string id,
74101 /// <remarks>
75102 /// Lines that start with # are comments and are skipped.
76103 /// </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 )
78129 {
79130 List < string > timeZoneIds = new List < string > ( ) ;
80131
81- try
132+ string ? zoneTabFileLine ;
133+ while ( ( zoneTabFileLine = reader . ReadLine ( ) ) != null )
82134 {
83- using ( StreamReader sr = new StreamReader ( Path . Combine ( GetTimeZoneDirectory ( ) , TimeZoneFileName ) , Encoding . UTF8 ) )
135+ if ( ! string . IsNullOrEmpty ( zoneTabFileLine ) && zoneTabFileLine [ 0 ] != '#' )
84136 {
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 )
87141 {
88- if ( ! string . IsNullOrEmpty ( zoneTabFileLine ) && zoneTabFileLine [ 0 ] != '#' )
142+ int secondTabIndex = zoneTabFileLine . IndexOf ( '\t ' , firstTabIndex + 1 ) ;
143+ if ( secondTabIndex >= 0 )
89144 {
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 )
94149 {
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+ }
110157
111- if ( ! string . IsNullOrEmpty ( timeZoneId ) )
112- {
113- timeZoneIds . Add ( timeZoneId ) ;
114- }
115- }
158+ if ( ! string . IsNullOrEmpty ( timeZoneId ) )
159+ {
160+ timeZoneIds . Add ( timeZoneId ) ;
116161 }
117162 }
118163 }
119164 }
120165 }
121- catch ( IOException ) { }
122- catch ( UnauthorizedAccessException ) { }
123166
124167 return timeZoneIds ;
125168 }
@@ -379,6 +422,22 @@ private static bool TryLoadTzFile(string tzFilePath, [NotNullWhen(true)] ref byt
379422 return false ;
380423 }
381424
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+
382441 /// <summary>
383442 /// Gets the tzfile raw data for the current 'local' time zone using the following rules.
384443 ///
@@ -387,6 +446,10 @@ private static bool TryLoadTzFile(string tzFilePath, [NotNullWhen(true)] ref byt
387446 /// 2. Get the default TZ from the device
388447 /// 3. Use UTC if all else fails.
389448 ///
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+ ///
390453 /// On all other platforms
391454 /// 1. Read the TZ environment variable. If it is set, use it.
392455 /// 2. Look for the data in /etc/localtime.
@@ -406,6 +469,11 @@ private static bool TryGetLocalTzFile([NotNullWhen(true)] out byte[]? rawData, [
406469 {
407470#if TARGET_IOS || TARGET_TVOS
408471 tzVariable = Interop . Sys . GetDefaultTimeZone ( ) ;
472+ #elif TARGET_WASI || TARGET_BROWSER
473+ if ( UseEmbeddedTzDatabase )
474+ {
475+ return false ; // use UTC
476+ }
409477#else
410478 return
411479 TryLoadTzFile ( "/etc/localtime" , ref rawData , ref id ) ||
@@ -432,6 +500,24 @@ private static bool TryGetLocalTzFile([NotNullWhen(true)] out byte[]? rawData, [
432500 {
433501 tzFilePath = tzVariable ;
434502 }
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+
435521 return TryLoadTzFile ( tzFilePath , ref rawData , ref id ) ;
436522 }
437523
0 commit comments