| // Copyright (c) 2015, the timezone project authors. Please see the AUTHORS |
| // file for details. All rights reserved. Use of this source code is governed |
| // by a BSD-style license that can be found in the LICENSE file. |
| |
| /// TimeZone db file. |
| library; |
| |
| import 'dart:collection'; |
| import 'dart:typed_data'; |
| import 'location.dart'; |
| import 'location_database.dart'; |
| |
| /// Serialize TimeZone Database |
| List<int> tzdbSerialize(LocationDatabase db) { |
| final locationsInBytes = <List<int>>[]; |
| var bufferLength = 0; |
| |
| for (final l |
| in db.locations.values.toList() |
| ..sort((l, r) => l.name.compareTo(r.name))) { |
| final List<int> b = _serializeLocation(l); |
| locationsInBytes.add(b); |
| bufferLength += 8 + b.length; |
| bufferLength = _align(bufferLength, 8); |
| } |
| |
| final r = Uint8List(bufferLength); |
| final rb = ByteData.sublistView(r); |
| |
| var offset = 0; |
| for (final b in locationsInBytes) { |
| final length = _align(b.length, 8); |
| rb.setUint32(offset, length); |
| r.setAll(offset + 8, b); |
| offset += 8 + length; |
| } |
| |
| return r; |
| } |
| |
| /// Deserialize TimeZone Database |
| Iterable<Location> tzdbDeserialize(List<int> rawData) sync* { |
| final data = rawData is Uint8List ? rawData : Uint8List.fromList(rawData); |
| final bdata = ByteData.sublistView(data); |
| |
| var offset = 0; |
| while (offset < data.length) { |
| final length = bdata.getUint32(offset); |
| // u32 _pad; |
| assert((length % 8) == 0); |
| offset += 8; |
| |
| yield _deserializeLocation( |
| Uint8List.sublistView(data, offset, offset + length), |
| ); |
| offset += length; |
| } |
| } |
| |
| Uint8List _serializeLocation(Location location) { |
| var offset = 0; |
| |
| final abbreviations = <String>[]; |
| final abbreviationsIndex = HashMap<String, int>(); |
| final zoneAbbreviationOffsets = <int>[]; |
| |
| // The number of bytes of all abbreviations. |
| var abbreviationsLength = 0; |
| for (final z in location.zones) { |
| final ai = abbreviationsIndex.putIfAbsent(z.abbreviation, () { |
| final ret = abbreviations.length; |
| abbreviationsLength += z.abbreviation.length + 1; // abbreviation + '\0' |
| abbreviations.add(z.abbreviation); |
| return ret; |
| }); |
| |
| zoneAbbreviationOffsets.add(ai); |
| } |
| |
| final name = location.name; |
| |
| final nameOffset = 32; |
| final nameLength = name.length; |
| final abbreviationsOffset = nameOffset + nameLength; |
| final zonesOffset = _align(abbreviationsOffset + abbreviationsLength, 4); |
| final zonesLength = location.zones.length; |
| final transitionsOffset = _align(zonesOffset + zonesLength * 8, 8); |
| final transitionsLength = location.transitionAt.length; |
| |
| final bufferLength = _align(transitionsOffset + transitionsLength * 9, 8); |
| |
| final result = Uint8List(bufferLength); |
| final buffer = ByteData.sublistView(result); |
| |
| // write header |
| buffer.setUint32(0, nameOffset); |
| buffer.setUint32(4, nameLength); |
| buffer.setUint32(8, abbreviationsOffset); |
| buffer.setUint32(12, abbreviationsLength); |
| buffer.setUint32(16, zonesOffset); |
| buffer.setUint32(20, zonesLength); |
| buffer.setUint32(24, transitionsOffset); |
| buffer.setUint32(28, transitionsLength); |
| |
| // write name |
| for (var i = 0; i < name.length; i++) { |
| final char = name.codeUnitAt(i); |
| if (char <= 0x7f) { |
| buffer.setUint8(nameOffset + i, char); |
| } else { |
| throw FormatException('Not ASCII', name, i); |
| } |
| } |
| |
| // Write abbreviations. |
| offset = abbreviationsOffset; |
| for (final a in abbreviations) { |
| for (final c in a.codeUnits) { |
| buffer.setUint8(offset++, c); |
| } |
| buffer.setUint8(offset++, 0); |
| } |
| |
| // write zones |
| offset = zonesOffset; |
| for (var i = 0; i < location.zones.length; i++) { |
| final zone = location.zones[i]; |
| buffer.setInt32(offset, zone.offset.inSeconds); // convert to sec |
| buffer.setUint8(offset + 4, zone.isDst ? 1 : 0); |
| buffer.setUint8(offset + 5, zoneAbbreviationOffsets[i]); |
| offset += 8; |
| } |
| |
| // write transitions |
| offset = transitionsOffset; |
| for (final tAt in location.transitionAt) { |
| final t = (tAt / 1000).floorToDouble(); |
| buffer.setFloat64(offset, t.toDouble()); // convert to sec |
| offset += 8; |
| } |
| |
| for (final tZone in location.transitionZone) { |
| buffer.setUint8(offset, tZone); |
| offset += 1; |
| } |
| |
| return result; |
| } |
| |
| Location _deserializeLocation(Uint8List data) { |
| final bdata = ByteData.sublistView(data); |
| var offset = 0; |
| |
| // Header |
| // |
| // struct { |
| // u32 nameOffset; |
| // u32 nameLength; |
| // u32 abbrsOffset; |
| // u32 abbrsLength; |
| // u32 zonesOffset; |
| // u32 zonesLength; |
| // u32 transitionsOffset; |
| // u32 transitionsLength; |
| // } header; |
| final nameOffset = bdata.getUint32(0); |
| final nameLength = bdata.getUint32(4); |
| final abbreviationsOffset = bdata.getUint32(8); |
| final abbreviationsLength = bdata.getUint32(12); |
| final zonesOffset = bdata.getUint32(16); |
| final zonesLength = bdata.getUint32(20); |
| final transitionsOffset = bdata.getUint32(24); |
| final transitionsLength = bdata.getUint32(28); |
| |
| assert(_allAscii(data, nameOffset, nameOffset + nameLength)); |
| final name = String.fromCharCodes(data, nameOffset, nameOffset + nameLength); |
| final abbreviations = <String>[]; |
| final zones = <TimeZone>[]; |
| final transitionAt = <int>[]; |
| final transitionZone = <int>[]; |
| |
| // Abbreviations |
| // |
| // \0 separated strings |
| offset = abbreviationsOffset; |
| final abbreviationsEnd = abbreviationsOffset + abbreviationsLength; |
| for (var i = abbreviationsOffset; i < abbreviationsEnd; i++) { |
| if (data[i] == 0) { |
| assert(_allAscii(data, offset, i)); |
| final abbreviation = String.fromCharCodes(data, offset, i); |
| abbreviations.add(abbreviation); |
| offset = i + 1; |
| } |
| } |
| |
| // TimeZones |
| // |
| // struct { |
| // i32 offset; // in seconds |
| // u8 isDst; |
| // u8 abbrIndex; |
| // u8 _pad[2]; |
| // } zones[zonesLength]; // at zoneOffset |
| offset = zonesOffset; |
| assert((offset % 4) == 0); |
| for (var i = 0; i < zonesLength; i++) { |
| final zoneOffset = Duration(seconds: bdata.getInt32(offset)); |
| final zoneIsDst = bdata.getUint8(offset + 4); |
| final zoneAbbreviationIndex = bdata.getUint8(offset + 5); |
| offset += 8; |
| zones.add( |
| TimeZone( |
| zoneOffset, |
| isDst: zoneIsDst == 1, |
| abbreviation: abbreviations[zoneAbbreviationIndex], |
| ), |
| ); |
| } |
| |
| // Transitions |
| // |
| // f64 transitionAt[transitionsLength]; // in seconds |
| // u8 transitionZone[transitionLength]; // at (transitionsOffset + (transitionsLength * 8)) |
| offset = transitionsOffset; |
| assert((offset % 8) == 0); |
| for (var i = 0; i < transitionsLength; i++) { |
| transitionAt.add(bdata.getFloat64(offset).toInt() * 1000); // convert to ms |
| offset += 8; |
| } |
| |
| for (var i = 0; i < transitionsLength; i++) { |
| transitionZone.add(bdata.getUint8(offset)); |
| offset += 1; |
| } |
| |
| return Location(name, transitionAt, transitionZone, zones); |
| } |
| |
| int _align(int offset, int boundary) { |
| assert(boundary > 1 && _isPowerOf2(boundary)); |
| final mask = boundary - 1; |
| return (offset + mask) & ~mask; |
| } |
| |
| bool _isPowerOf2(int value) => value > 0 && (value & (value - 1)) == 0; |
| |
| bool _allAscii(Uint8List bytes, int start, int end) { |
| var bits = 0; |
| for (var i = start; i < end; i++) { |
| bits |= bytes[i]; |
| } |
| return bits <= 0x7f; |
| } |