@@ -193,6 +193,7 @@ private sealed class AndroidTzData
193193 private string [ ] _ids ;
194194 private int [ ] _byteOffsets ;
195195 private int [ ] _lengths ;
196+ private bool [ ] _isBackwards ;
196197 private string _tzFileDir ;
197198 private string _tzFilePath ;
198199
@@ -230,7 +231,7 @@ public AndroidTzData()
230231 foreach ( var tzFileDir in tzFileDirList )
231232 {
232233 string tzFilePath = Path . Combine ( tzFileDir , TimeZoneFileName ) ;
233- if ( LoadData ( tzFilePath ) )
234+ if ( LoadData ( tzFileDir , tzFilePath ) )
234235 {
235236 _tzFileDir = tzFileDir ;
236237 _tzFilePath = tzFilePath ;
@@ -241,10 +242,62 @@ public AndroidTzData()
241242 throw new TimeZoneNotFoundException ( SR . TimeZoneNotFound_ValidTimeZoneFileMissing ) ;
242243 }
243244
245+ // On some versions of Android, the tzdata file may still contain backward timezone ids.
246+ // We attempt to use tzlookup.xml, which is available on some versions of Android to help
247+ // validate non-backward timezone ids
248+ // tzlookup.xml is an autogenerated file that contains timezone ids in this form:
249+ //
250+ // <timezones ianaversion="2019b">
251+ // <countryzones>
252+ // <country code="au" default="Australia/Sydney" everutc="n">
253+ // <id alts="Australia/ACT,Australia/Canberra,Australia/NSW">Australia/Sydney</id>
254+ // ...
255+ // ...
256+ // <id>Australia/Eucla</id>
257+ // </country>
258+ // <country ...>
259+ // ...
260+ // ...
261+ // ...
262+ // </country>
263+ // </countryzones>
264+ // </timezones>
265+ //
266+ // Once the timezone cache is populated with the IDs, we reference tzlookup id tags
267+ // to determine if an id is backwards and label it as such if they are.
268+ private void FilterBackwardIDs ( string tzFileDir , out HashSet < string > tzLookupIDs )
269+ {
270+ tzLookupIDs = new HashSet < string > ( ) ;
271+ try
272+ {
273+ using ( StreamReader sr = File . OpenText ( Path . Combine ( tzFileDir , "tzlookup.xml" ) ) )
274+ {
275+ string ? tzLookupLine ;
276+ while ( sr . Peek ( ) >= 0 )
277+ {
278+ if ( ! ( tzLookupLine = sr . ReadLine ( ) ) ! . AsSpan ( ) . TrimStart ( ) . StartsWith ( "<id" , StringComparison . Ordinal ) )
279+ continue ;
280+
281+ int idStart = tzLookupLine ! . IndexOf ( '>' ) + 1 ;
282+ int idLength = tzLookupLine . LastIndexOf ( "</" , StringComparison . Ordinal ) - idStart ;
283+ if ( idStart <= 0 || idLength < 0 )
284+ {
285+ // Either the start tag <id ... > or the end tag </id> are not found
286+ continue ;
287+ }
288+ string id = tzLookupLine . Substring ( idStart , idLength ) ;
289+ tzLookupIDs . Add ( id ) ;
290+ }
291+ }
292+ }
293+ catch { }
294+ }
295+
244296 [ MemberNotNullWhen ( true , nameof ( _ids ) ) ]
245297 [ MemberNotNullWhen ( true , nameof ( _byteOffsets ) ) ]
246298 [ MemberNotNullWhen ( true , nameof ( _lengths ) ) ]
247- private bool LoadData ( string path )
299+ [ MemberNotNullWhen ( true , nameof ( _isBackwards ) ) ]
300+ private bool LoadData ( string tzFileDir , string path )
248301 {
249302 if ( ! File . Exists ( path ) )
250303 {
@@ -254,7 +307,7 @@ private bool LoadData(string path)
254307 {
255308 using ( FileStream fs = File . OpenRead ( path ) )
256309 {
257- LoadTzFile ( fs ) ;
310+ LoadTzFile ( tzFileDir , fs ) ;
258311 }
259312 return true ;
260313 }
@@ -266,15 +319,16 @@ private bool LoadData(string path)
266319 [ MemberNotNull ( nameof ( _ids ) ) ]
267320 [ MemberNotNull ( nameof ( _byteOffsets ) ) ]
268321 [ MemberNotNull ( nameof ( _lengths ) ) ]
269- private void LoadTzFile ( Stream fs )
322+ [ MemberNotNull ( nameof ( _isBackwards ) ) ]
323+ private void LoadTzFile ( string tzFileDir , Stream fs )
270324 {
271325 const int HeaderSize = 24 ;
272326 Span < byte > buffer = stackalloc byte [ HeaderSize ] ;
273327
274328 ReadTzDataIntoBuffer ( fs , 0 , buffer ) ;
275329
276330 LoadHeader ( buffer , out int indexOffset , out int dataOffset ) ;
277- ReadIndex ( fs , indexOffset , dataOffset ) ;
331+ ReadIndex ( tzFileDir , fs , indexOffset , dataOffset ) ;
278332 }
279333
280334 private void LoadHeader ( Span < byte > buffer , out int indexOffset , out int dataOffset )
@@ -303,23 +357,25 @@ private void LoadHeader(Span<byte> buffer, out int indexOffset, out int dataOffs
303357 [ MemberNotNull ( nameof ( _ids ) ) ]
304358 [ MemberNotNull ( nameof ( _byteOffsets ) ) ]
305359 [ MemberNotNull ( nameof ( _lengths ) ) ]
306- private void ReadIndex ( Stream fs , int indexOffset , int dataOffset )
360+ [ MemberNotNull ( nameof ( _isBackwards ) ) ]
361+ private void ReadIndex ( string tzFileDir , Stream fs , int indexOffset , int dataOffset )
307362 {
308363 int indexSize = dataOffset - indexOffset ;
309364 const int entrySize = 52 ; // Data entry size
310365 int entryCount = indexSize / entrySize ;
311-
312366 _byteOffsets = new int [ entryCount ] ;
313367 _ids = new string [ entryCount ] ;
314368 _lengths = new int [ entryCount ] ;
315-
369+ _isBackwards = new bool [ entryCount ] ;
370+ FilterBackwardIDs ( tzFileDir , out HashSet < string > tzLookupIDs ) ;
316371 for ( int i = 0 ; i < entryCount ; ++ i )
317372 {
318373 LoadEntryAt ( fs , indexOffset + ( entrySize * i ) , out string id , out int byteOffset , out int length ) ;
319374
320375 _byteOffsets [ i ] = byteOffset + dataOffset ;
321376 _ids [ i ] = id ;
322377 _lengths [ i ] = length ;
378+ _isBackwards [ i ] = ! tzLookupIDs . Contains ( id ) ;
323379
324380 if ( length < 24 ) // Header Size
325381 {
@@ -372,7 +428,25 @@ private void LoadEntryAt(Stream fs, long position, out string id, out int byteOf
372428
373429 public string [ ] GetTimeZoneIds ( )
374430 {
375- return _ids ;
431+ int numTimeZoneIDs = 0 ;
432+ for ( int i = 0 ; i < _ids . Length ; i ++ )
433+ {
434+ if ( ! _isBackwards [ i ] )
435+ {
436+ numTimeZoneIDs ++ ;
437+ }
438+ }
439+ string [ ] nonBackwardsTZIDs = new string [ numTimeZoneIDs ] ;
440+ var index = 0 ;
441+ for ( int i = 0 ; i < _ids . Length ; i ++ )
442+ {
443+ if ( ! _isBackwards [ i ] )
444+ {
445+ nonBackwardsTZIDs [ index ] = _ids [ i ] ;
446+ index ++ ;
447+ }
448+ }
449+ return nonBackwardsTZIDs ;
376450 }
377451
378452 public string GetTimeZoneDirectory ( )
0 commit comments