├── .gitattributes ├── .github └── workflows │ └── test-package.yml ├── .gitignore ├── AUTHORS ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── lib ├── browser.dart ├── data │ ├── latest.dart │ ├── latest.tzf │ ├── latest_10y.dart │ ├── latest_10y.tzf │ ├── latest_all.dart │ └── latest_all.tzf ├── src │ ├── date_time.dart │ ├── env.dart │ ├── exceptions.dart │ ├── location.dart │ ├── location_database.dart │ ├── tools.dart │ ├── tzdata │ │ ├── zicfile.dart │ │ └── zone_tab.dart │ └── tzdb.dart ├── standalone.dart ├── timezone.dart └── tzdata.dart ├── pubspec.yaml ├── test ├── data │ ├── US │ │ └── Eastern │ └── zone1970.tab ├── datetime_browser_test.dart ├── datetime_browser_test.html ├── datetime_test.dart ├── datetime_test_no_database.dart ├── two_way_test.dart ├── zicfile_test.dart └── zone_tab_test.dart └── tool ├── encode_dart.dart ├── encode_tzf.dart ├── get.dart ├── refresh.sh ├── test_random.dart └── zone_dump.dart /.gitattributes: -------------------------------------------------------------------------------- 1 | lib/data/* binary -------------------------------------------------------------------------------- /.github/workflows/test-package.yml: -------------------------------------------------------------------------------- 1 | name: Dart CI 2 | 3 | on: 4 | # Run on PRs and pushes to the default branch. 5 | push: 6 | branches: [ master ] 7 | pull_request: 8 | branches: [ master ] 9 | schedule: 10 | - cron: "0 0 * * 0" 11 | 12 | env: 13 | PUB_ENVIRONMENT: bot.github 14 | 15 | jobs: 16 | # Check code formatting and static analysis on a single OS (linux) 17 | # against Dart dev channel. 18 | analyze: 19 | runs-on: ubuntu-latest 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | sdk: [dev] 24 | steps: 25 | - uses: actions/checkout@v2 26 | - uses: dart-lang/setup-dart@v1.0 27 | with: 28 | sdk: ${{ matrix.sdk }} 29 | - id: install 30 | name: Install dependencies 31 | run: dart pub get 32 | - name: Check formatting 33 | run: dart format --output=none --set-exit-if-changed . 34 | if: always() && steps.install.outcome == 'success' 35 | - name: Analyze code 36 | run: dart analyze --fatal-infos 37 | if: always() && steps.install.outcome == 'success' 38 | 39 | # Run tests on a matrix consisting of two dimensions: 40 | # 1. OS: ubuntu-latest, (macos-latest, windows-latest) 41 | # 2. release channel: dev 42 | test: 43 | needs: analyze 44 | runs-on: ${{ matrix.os }} 45 | strategy: 46 | fail-fast: false 47 | matrix: 48 | # Add macos-latest and/or windows-latest if relevant for this package. 49 | os: [ubuntu-latest] 50 | sdk: [3.7.0, dev] 51 | steps: 52 | - uses: actions/checkout@v2 53 | - uses: dart-lang/setup-dart@v1.0 54 | with: 55 | sdk: ${{ matrix.sdk }} 56 | - id: install 57 | name: Install dependencies 58 | run: dart pub get 59 | - name: Run VM tests 60 | run: dart test --platform vm 61 | if: always() && steps.install.outcome == 'success' 62 | 63 | document: 64 | needs: analyze 65 | runs-on: ${{ matrix.os }} 66 | strategy: 67 | fail-fast: false 68 | matrix: 69 | os: [ubuntu-latest] 70 | steps: 71 | - uses: actions/checkout@v2 72 | - uses: dart-lang/setup-dart@v1.0 73 | with: 74 | sdk: dev 75 | - id: install 76 | name: Install dependencies 77 | run: | 78 | dart pub get 79 | dart pub global activate dartdoc 80 | - name: Verify dartdoc 81 | run: dart pub global run dartdoc \ 82 | --no-generate-docs \ 83 | --errors=unresolved-doc-reference,ambiguous-doc-reference,ambiguous-reexport,broken-link,deprecated,no-library-level-docs,unknown-directive,unknown-macro 84 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # https://www.dartlang.org/tools/private-files.html 2 | 3 | build/ 4 | .dart_tool 5 | packages 6 | .packages 7 | .pub 8 | pubspec.lock 9 | 10 | .buildlog 11 | *.js_ 12 | *.js.deps 13 | *.js.map 14 | *.dart.js 15 | 16 | # Eclipse 17 | .project 18 | 19 | # IntelliJ 20 | *.iml 21 | *.ipr 22 | *.iws 23 | .idea/ 24 | 25 | # Mac 26 | .DS_Store 27 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # Below is a list of people and organizations that have contributed 2 | # to the timezone project. Names should be added to the list like 3 | # so: 4 | # 5 | # Name/Organization 6 | 7 | Boris Kaul -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.10.1 2 | 3 | - Time zone database updated to 2025b. For your convenience here is the 4 | announcement for [2025a], [2025b]. 5 | - Added a `native` getter for `TZDateTime`. Thanks @klondikedragon! 6 | 7 | [2025a]: https://lists.iana.org/hyperkitty/list/tz-announce@iana.org/thread/MWII7R3HMCEDNUCIYQKSSTYYR7UWK4OQ/ 8 | [2025b]: https://lists.iana.org/hyperkitty/list/tz-announce@iana.org/thread/6JVHNHLB6I2WAYTQ75L6KEPEQHFXAJK3/ 9 | 10 | # 0.10.0 11 | 12 | - Update time zone-updating script to use `rearguard.zi`. 13 | - Convert `browser.dart` to use `package:http` instead of `dart:html` for HTTP 14 | requests. 15 | - Time zone database updated to 2024b. For your convenience here is the 16 | announcement for [2024b]. 17 | 18 | [2024b]: https://lists.iana.org/hyperkitty/list/tz-announce@iana.org/thread/IZ7AO6WRE3W3TWBL5IR6PMQUL433BQIE/ 19 | 20 | # 0.9.4 21 | 22 | - Support cross-isolate issues by overriding `hashCode` and `operator ==` on 23 | class `Location`. (see #147) 24 | - Fix incorrect DST transition. (see #166) 25 | 26 | # 0.9.3 27 | 28 | - Time zone database updated to 2024a. For your convenience here are the 29 | announcements for [2023d], [2024a]. 30 | 31 | [2023d]: https://mm.icann.org/pipermail/tz-announce/2023-December.txt 32 | [2024a]: https://mm.icann.org/pipermail/tz-announce/2024-February.txt 33 | 34 | 35 | # 0.9.2 36 | 37 | - Time zone database updated to 2023c. For your convenience here are the 38 | announcements for [2023a], [2023b], [2023c]. 39 | 40 | [2023a]: http://mm.icann.org/pipermail/tz-announce/2023-March/000077.html 41 | [2023b]: http://mm.icann.org/pipermail/tz-announce/2023-March/000078.html 42 | [2023c]: http://mm.icann.org/pipermail/tz-announce/2023-March/000079.html 43 | 44 | # 0.9.1 45 | 46 | - Time zone database updated to 2022g. For your convenience here are the 47 | announcements for [2022d], [2022e], [2022f], [2022g]. 48 | 49 | [2022d]: http://mm.icann.org/pipermail/tz-announce/2022-September/000073.html 50 | [2022e]: http://mm.icann.org/pipermail/tz-announce/2022-October/000074.html 51 | [2022f]: http://mm.icann.org/pipermail/tz-announce/2022-October/000075.html 52 | [2022g]: http://mm.icann.org/pipermail/tz-announce/2022-November/000076.html 53 | 54 | # 0.9.0 55 | 56 | - Time zone database updated to 2022c. For your convenience here are the 57 | announcements for [2022a], [2022b], [2022c]. 58 | - Removed named database files in `lib/data` (for example, `lib/data/2021e.tzf`). 59 | The only supported database files are all now named `latest_*`. 60 | 61 | [2022a]: http://mm.icann.org/pipermail/tz-announce/2022-March/000070.html 62 | [2022b]: http://mm.icann.org/pipermail/tz-announce/2022-August/000071.html 63 | [2022c]: http://mm.icann.org/pipermail/tz-announce/2022-August/000072.html 64 | 65 | # 0.8.1 66 | 67 | - Time zone database updated to 2021e. For your convenience here are the 68 | announcements for [2021b], [2021c], [2021d], [2021e]. 69 | - Fixed encoding script to not skip a few missing time zones. 70 | 71 | [2021b]: http://mm.icann.org/pipermail/tz-announce/2021-September/000066.html 72 | [2021c]: http://mm.icann.org/pipermail/tz-announce/2021-October/000067.html 73 | [2021d]: http://mm.icann.org/pipermail/tz-announce/2021-October/000068.html 74 | [2021e]: http://mm.icann.org/pipermail/tz-announce/2021-October/000069.html 75 | 76 | # 0.8.0 77 | 78 | - Time zone database updated to 2021a. For your convenience here is the 79 | announcement for [2021a]. 80 | - Time zone databases encoded with UTF-16 instead of base64. 81 | - **Breaking change**: Remove `tool/encode.dart` in favor of 82 | `tool/encode_dart.dart`. 83 | 84 | [2021a]: https://mm.icann.org/pipermail/tz-announce/2021-January/000065.html 85 | 86 | 87 | # 0.7.0 88 | 89 | - **Breaking change**: Change some of TimeZone's constructor parameters to be 90 | named instead of positional. 91 | - **Breaking change**: Rename `TimeZone.abbr` to `TimeZone.abbreviation`. 92 | - Deprecate `LocationDatabase.isEmpty` in favor of 93 | `LocationDatabase.isInitialized`. 94 | - Removed `new` usage from examples and fixed a typo in the `TZDateTime.from` 95 | example. 96 | - Migrate to Dart's null safety language feature. 97 | 98 | # 0.6.1 99 | 100 | - Updated the `get` script (now `encode_tzf`) to work with a `zoneinfo` 101 | directory (as created by the `zic` tool) as input. Fetching and compiling this 102 | directory is now done by a bash script (`refresh.sh`) using standard tools. 103 | 104 | This allows pointing the tool at a custom `zoneinfo` directory. 105 | 106 | # 0.6.0 107 | 108 | - Stopping internal versioning of time zone data. Only the latest data will be 109 | included, as there is no use case for using an outdated version. 110 | - Renaming the `_2015_2025` database to `_10y` for it to have a stable name. 111 | In the past `latest_2010-2020.tzf` had to be renamed to 112 | `latest_2015-2025.tzf`. 113 | 114 | # 0.5.9 115 | 116 | - Time zone database updated to 2020d. For your convenience here is the 117 | announcement for [2020d]. 118 | 119 | [2020d]: https://mm.icann.org/pipermail/tz-announce/2020-October/000062.html 120 | 121 | # 0.5.8 122 | 123 | - Time zone database updated to 2020b. For your convenience here is the 124 | announcement for [2020b]. 125 | 126 | [2020b]: https://mm.icann.org/pipermail/tz-announce/2020-October/000059.html 127 | 128 | # 0.5.7 129 | 130 | - Time zone database updated to 2020a. For your convenience here is the 131 | announcement for [2020a]. 132 | - Earlier null checking on some TZDateTime constructor arguments. 133 | - Many internal changes; should not affect API. 134 | 135 | [2020a]: http://mm.icann.org/pipermail/tz-announce/2020-April/000058.html 136 | 137 | # 0.5.6 138 | 139 | - Time zone database updated to 2019c. For your convenience here is the 140 | announcement for [2019c]. 141 | - Dart-importable databases made available in `lib/data`. README.md has more 142 | details. 143 | 144 | [2019c]: http://mm.icann.org/pipermail/tz-announce/2019-September/000057.html 145 | 146 | # 0.5.5 147 | 148 | - Time zone database updated to 2019b. For your convenience here is the 149 | announcement for [2019b]. 150 | - Convenience database symlinks added for convenience at 151 | - `lib/data/latest.tzf` 152 | - `lib/data/latest_2015-2025.tzf` 153 | - `lib/data/latest_all.tzf` 154 | 155 | [2019b]: http://mm.icann.org/pipermail/tz-announce/2019-July/000056.html 156 | 157 | # 0.5.4 158 | 159 | - TZDateTime.utc is accessible before time zone database is initialized (thanks 160 | @jsmarr). 161 | - Fix dropping microseconds when creating TZDateTime (thanks @jsmarr). 162 | 163 | # 0.5.3 164 | 165 | - Time zone database updated to 2019a. For your convenience here is the 166 | announcement for [2019a]. 167 | 168 | [2019a]: http://mm.icann.org/pipermail/tz-announce/2019-March/000055.html 169 | 170 | # 0.5.2 171 | 172 | - Time zone database updated to 2018i. For your convenience here are the 173 | announcements for [2018h] and [2018i]. 174 | 175 | [2018h]: http://mm.icann.org/pipermail/tz-announce/2018-December/000053.html 176 | [2018i]: http://mm.icann.org/pipermail/tz-announce/2018-December/000054.html 177 | 178 | # 0.5.1 179 | 180 | - Time zone database updated to 2018g. For your convenience here are the 181 | announcements for [2018d], [2018e], [2018f], and [2018g]. 182 | 183 | [2018d]: http://mm.icann.org/pipermail/tz-announce/2018-March/000049.html 184 | [2018e]: http://mm.icann.org/pipermail/tz-announce/2018-May/000050.html 185 | [2018f]: http://mm.icann.org/pipermail/tz-announce/2018-October/000051.html 186 | [2018g]: http://mm.icann.org/pipermail/tz-announce/2018-October/000052.html 187 | 188 | # 0.5.0 189 | 190 | - Support a package-directory-free environment. In Dart 1.19, timezone is now 191 | compatible with `pub get --no-packages-dir`. 192 | - **Breaking:** Remove initializeTimeZoneSync method; it is incompatible with 193 | the async method for resolving package URIs. 194 | - Fix all strong mode _errors_ (thanks @har79). 195 | - Add microsecond support (thanks @har79). 196 | - Improve interaction between TZDateTime and native DateTime (thanks @har79). 197 | - Fix TimeZone's `==` (thanks @har79). 198 | - Many new dartdoc comments (thanks @har79). 199 | - Fix for calling `new TZDateTime.from()` with a non-UTC DateTime object 200 | (thanks @tomaine2002). 201 | - Support Dart 2. 202 | - Time zone database updated to 2018c. For your convenience here are the 203 | announcements for [2015c], [2015d], [2015e], [2015f], [2015g], [2016a], 204 | [2016b], [2016c], [2016d], [2016e], [2016f], [2016g], [2016h], [2016i], 205 | [2017a], [2017b], [2017c], and [2018c]. 206 | 207 | [2015c]: http://mm.icann.org/pipermail/tz-announce/2015-April/000030.html 208 | [2015d]: http://mm.icann.org/pipermail/tz-announce/2015-April/000031.html 209 | [2015e]: http://mm.icann.org/pipermail/tz-announce/2015-June/000032.html 210 | [2015f]: http://mm.icann.org/pipermail/tz-announce/2015-August/000033.html 211 | [2015g]: http://mm.icann.org/pipermail/tz-announce/2015-October/000034.html 212 | [2016a]: http://mm.icann.org/pipermail/tz-announce/2016-January/000035.html 213 | [2016b]: http://mm.icann.org/pipermail/tz-announce/2016-March/000036.html 214 | [2016c]: http://mm.icann.org/pipermail/tz-announce/2016-March/000037.html 215 | [2016d]: http://mm.icann.org/pipermail/tz-announce/2016-April/000038.html 216 | [2016e]: http://mm.icann.org/pipermail/tz-announce/2016-June/000039.html 217 | [2016f]: http://mm.icann.org/pipermail/tz-announce/2016-July/000040.html 218 | [2016g]: http://mm.icann.org/pipermail/tz-announce/2016-September/000041.html 219 | [2016h]: http://mm.icann.org/pipermail/tz-announce/2016-October/000042.html 220 | [2016i]: http://mm.icann.org/pipermail/tz-announce/2016-November/000043.html 221 | [2017a]: http://mm.icann.org/pipermail/tz-announce/2017-February/000045.html 222 | [2017b]: http://mm.icann.org/pipermail/tz-announce/2017-March/000046.html 223 | [2017c]: http://mm.icann.org/pipermail/tz-announce/2017-October/000047.html 224 | [2018c]: http://mm.icann.org/pipermail/tz-announce/2018-January/000048.html 225 | 226 | # 0.4.3 227 | 228 | - Fix Dart 1.14 incompatibility further. 229 | 230 | # 0.4.2 231 | 232 | - Bad pub publish. Ignore. 233 | 234 | # 0.4.1 235 | 236 | - Fix Dart 1.14 incompatibility with packageRoot returning null. 237 | 238 | # 0.4.0 239 | 240 | - Remove usage of tuple package. 241 | - Upgrade unittest package to test. 242 | - Fix database URL for "latest" database. 243 | - Add tool/dartfmt for formatting source. 244 | 245 | # 0.3.1 246 | 247 | - `generate_data_subset` script is removed. It will be available as a 248 | separate package. 249 | 250 | # 0.3.0 251 | 252 | - Time zone database updated to 2015b. 253 | - Removed local location detection heuristics (didn't worked properly). 254 | Local location is initialized with UTC location by default, use 255 | `setLocalLocation` to change local location. 256 | - Time zone database format is changed; data is aligned. 257 | 258 | # 0.2.5 259 | 260 | - Fixed bug with String formatting (invalid offsets for minutes). 261 | 262 | # 0.2.4 263 | 264 | - Fixed bug with Calendar-type constructor. 265 | 266 | # 0.2.3 267 | 268 | - Added `initializeTimeZoneSync` function for standalone environments. 269 | - Fixed bug with script path on Windows. 270 | 271 | # 0.2.2 272 | 273 | - TimeZone database updated to "2014j". 274 | - "args" and "path" moved from dev dependencies to dependencies. 275 | 276 | # 0.2.1 277 | 278 | - `tzfile` library renamed to `tzdata`. 279 | - Added `zone1970.tab` parser to `tzdata` library. 280 | - Removed `package:collection` dependency. 281 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, timezone project authors. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 17 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TimeZone 2 | 3 | This package provides the [IANA time zone database] and time zone aware 4 | `DateTime` class, [`TZDateTime`]. 5 | 6 | The current time zone database version is [2025b]. See [the announcement] for 7 | details. 8 | 9 | You can update to the current IANA time zone database by running 10 | `tool/refresh.sh`. 11 | 12 | 13 | ## Initialization 14 | 15 | [`TimeZone`] objects require time zone data, so the first step is to load 16 | one of our [time zone databases](#databases). 17 | 18 | We provide three different APIs to load a database: one which is embedded 19 | into a Dart library, one for browsers, and one for standalone environments. 20 | 21 | ### Database variants 22 | 23 | We offer three different variants of the IANA database: 24 | 25 | - **default**: doesn't contain deprecated and historical zones with some 26 | exceptions like "US/Eastern" and "Etc/UTC"; this is about 75% the size of the 27 | **all** database. 28 | - **all**: contains all data from the [IANA time zone database]. 29 | - **10y**: default database truncated to contain historical data from 5 years 30 | ago until 5 years in the future; this database is about 25% the size of the 31 | default database. 32 | 33 | ### Initialization from Dart library 34 | 35 | This is the recommended way to initialize a time zone database for non-browser 36 | environments. Each Dart library found in `lib/data`, for example 37 | `lib/data/latest.dart`, contains a single no-argument function, 38 | `initializeTimeZones`. 39 | 40 | ```dart 41 | import 'package:timezone/data/latest.dart' as tz; 42 | void main() { 43 | tz.initializeTimeZones(); 44 | } 45 | ``` 46 | 47 | To initialize the **all** database variant, `import 48 | 'package:timezone/data/latest_all.dart'`. To initialize the **10y** 49 | database variant, `import 'package:timezone/data/latest_10y.dart'`. 50 | 51 | ### Initialization for browser environment 52 | 53 | Import `package:timezone/browser.dart` library and run async function 54 | `Future initializeTimeZone([String path])`. 55 | 56 | ```dart 57 | import 'package:timezone/browser.dart' as tz; 58 | 59 | Future setup() async { 60 | await tz.initializeTimeZone(); 61 | var detroit = tz.getLocation('America/Detroit'); 62 | var now = tz.TZDateTime.now(detroit); 63 | } 64 | ``` 65 | 66 | To initialize the **all** database variant, call 67 | `initializeTimeZone('packages/timezone/data/latest_all.tzf')`. To initialize 68 | the **10y** database variant, call 69 | `initializeTimeZone('packages/timezone/data/latest_10y.tzf')`. 70 | 71 | ### Initialization for standalone environment 72 | 73 | Import `package:timezone/standalone.dart` library and run async function 74 | `Future initializeTimeZone([String path])`. 75 | 76 | ```dart 77 | import 'package:timezone/standalone.dart' as tz; 78 | 79 | Future setup() async { 80 | await tz.initializeTimeZone(); 81 | var detroit = tz.getLocation('America/Detroit'); 82 | var now = tz.TZDateTime.now(detroit); 83 | } 84 | ``` 85 | 86 | Note: This method likely will not work in a Flutter environment. 87 | 88 | To initialize the **all** database variant, call 89 | `initializeTimeZone('data/latest_all.tzf')`. To initialize the **10y** 90 | database variant, call `initializeTimeZone('data/latest_10y.tzf')`. 91 | 92 | ### Local Location 93 | 94 | By default, when library is initialized, local location will be `UTC`. 95 | 96 | To overwrite local location you can use `setLocalLocation(Location 97 | location)` function. 98 | 99 | ```dart 100 | Future setup() async { 101 | await tz.initializeTimeZone(); 102 | var detroit = tz.getLocation('America/Detroit'); 103 | tz.setLocalLocation(detroit); 104 | } 105 | ``` 106 | 107 | 108 | ## API 109 | 110 | ### Library Namespace 111 | 112 | The public interfaces expose several top-level functions. It is recommended 113 | then to import the libraries with a prefix (the prefix `tz` is common), or to 114 | import specific members via a `show` clause. 115 | 116 | ### Location 117 | 118 | > Each location in the database represents a national region where all 119 | > clocks keeping local time have agreed since 1970. Locations are 120 | > identified by continent or ocean and then by the name of the 121 | > location, which is typically the largest city within the region. For 122 | > example, America/New_York represents most of the US eastern time 123 | > zone; America/Phoenix represents most of Arizona, which uses 124 | > mountain time without daylight saving time (DST); America/Detroit 125 | > represents most of Michigan, which uses eastern time but with 126 | > different DST rules in 1975; and other entries represent smaller 127 | > regions like Starke County, Indiana, which switched from central to 128 | > eastern time in 1991 and switched back in 2006. 129 | > 130 | > [The tz database](https://www.iana.org/time-zones) 131 | 132 | #### Get location by tz database/Olson name 133 | 134 | ```dart 135 | final detroit = tz.getLocation('America/Detroit'); 136 | ``` 137 | 138 | See [Wikipedia list] for more database entry names. 139 | 140 | We don't provide any functions to get locations by time zone abbreviations 141 | because of the ambiguities. 142 | 143 | > Alphabetic time zone abbreviations should not be used as unique identifiers 144 | > for UTC offsets as they are ambiguous in practice. For example, "EST" denotes 145 | > 5 hours behind UTC in English-speaking North America, but it denotes 10 or 11 146 | > hours ahead of UTC in Australia; and French-speaking North Americans prefer 147 | > "HNE" to "EST". 148 | > 149 | > [The tz database](https://www.iana.org/time-zones) 150 | 151 | ### TimeZone 152 | 153 | TimeZone objects represents time zone and contains offset, DST flag, and name 154 | in the abbreviated form. 155 | 156 | ```dart 157 | var timeInUtc = DateTime.utc(1995, 1, 1); 158 | var timeZone = detroit.timeZone(timeInUtc.millisecondsSinceEpoch); 159 | ``` 160 | 161 | ### TimeZone aware DateTime 162 | 163 | The `TZDateTime` class implements the `DateTime` interface from `dart:core`, 164 | and contains information about location and time zone. 165 | 166 | ```dart 167 | var date = tz.TZDateTime(detroit, 2014, 11, 17); 168 | ``` 169 | 170 | #### Converting DateTimes between time zones 171 | 172 | To convert between time zones, just create a new `TZDateTime` object using 173 | `from` constructor and pass `Location` and `DateTime` to the constructor. 174 | 175 | ```dart 176 | var localTime = tz.DateTime(2010, 1, 1); 177 | var detroitTime = tz.TZDateTime.from(localTime, detroit); 178 | ``` 179 | 180 | This constructor supports any objects that implement `DateTime` interface, so 181 | you can pass a native `DateTime` object or our `TZDateTime`. 182 | 183 | ### Listing known time zones 184 | 185 | After initializing the time zone database, the `timeZoneDatabase` top-level 186 | member contains all of the known time zones. Examples: 187 | 188 | ```dart 189 | import 'package:timezone/timezone.dart' as tz; 190 | import 'package:timezone/data/latest.dart' as tz; 191 | 192 | void main() { 193 | tz.initializeTimeZones(); 194 | var locations = tz.timeZoneDatabase.locations; 195 | print(locations.length); // => 429 196 | print(locations.keys.first); // => "Africa/Abidjan" 197 | print(locations.keys.last); // => "US/Pacific" 198 | } 199 | ``` 200 | 201 | ## Time Zone databases 202 | 203 | We are using [IANA Time Zone Database](http://www.iana.org/time-zones) 204 | to build our databases. 205 | 206 | We currently build three different database variants: 207 | 208 | - default (doesn't contain deprecated and historical zones with some exceptions 209 | like US/Eastern). 361kb 210 | - all (contains all data from the [IANA time zone database]). 443kb 211 | - 10y (default database that contains historical data from the last and future 5 212 | years). 85kb 213 | 214 | ### Updating Time Zone databases 215 | 216 | Script for updating Time Zone database, it will automatically download the 217 | [IANA time zone database] and compile into our native format. 218 | 219 | ```sh 220 | $ chmod +x tool/refresh.sh 221 | $ tool/refresh.sh 222 | ``` 223 | 224 | Note, on Windows, you may need to follow [these 225 | steps](https://github.com/srawlins/timezone/issues/60#issuecomment-638411716) 226 | which use WSL. 227 | 228 | [2025b]: https://data.iana.org/time-zones/releases/tzdb-2025b.tar.lz 229 | [IANA time zone database]: https://www.iana.org/time-zones 230 | [Wikipedia list]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones 231 | [`TZDateTime`]: https://pub.dartlang.org/documentation/timezone/latest/timezone.standalone/TZDateTime-class.html 232 | [`TimeZone`]: https://pub.dartlang.org/documentation/timezone/latest/timezone.standalone/TimeZone-class.html 233 | [the announcement]: https://lists.iana.org/hyperkitty/list/tz-announce@iana.org/thread/6JVHNHLB6I2WAYTQ75L6KEPEQHFXAJK3/ 234 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:lints/recommended.yaml 2 | 3 | analyzer: 4 | errors: 5 | # Just need to clean up existing violations. 6 | non_constant_identifier_names: ignore 7 | todo: ignore 8 | language: 9 | strict-casts: true 10 | strict-inference: true 11 | strict-raw-types: true 12 | 13 | linter: 14 | rules: 15 | - always_declare_return_types 16 | - always_use_package_imports 17 | - avoid_catches_without_on_clauses 18 | - avoid_dynamic_calls 19 | - avoid_positional_boolean_parameters 20 | - avoid_unused_constructor_parameters 21 | - directives_ordering 22 | - lines_longer_than_80_chars 23 | - package_api_docs 24 | - prefer_asserts_in_initializer_lists 25 | - prefer_if_elements_to_conditional_expressions 26 | - unawaited_futures 27 | - unnecessary_library_directive 28 | - unnecessary_parenthesis 29 | - unreachable_from_main 30 | -------------------------------------------------------------------------------- /lib/browser.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the timezone project authors. Please see the AUTHORS 2 | // file for details. All rights reserved. Use of this source code is governed 3 | // by a BSD-style license that can be found in the LICENSE file. 4 | 5 | /// TimeZone initialization for browser environments. 6 | /// 7 | /// ```dart 8 | /// import 'package:timezone/browser.dart'; 9 | /// 10 | /// initializeTimeZone().then((_) { 11 | /// final detroit = getLocation('America/Detroit'); 12 | /// final now = TZDateTime.now(detroit); 13 | /// }); 14 | library timezone.browser; 15 | 16 | import 'package:http/browser_client.dart' as browser; 17 | import 'package:timezone/timezone.dart'; 18 | 19 | export 'package:timezone/timezone.dart' 20 | show 21 | getLocation, 22 | setLocalLocation, 23 | TZDateTime, 24 | Location, 25 | TimeZone, 26 | timeZoneDatabase; 27 | 28 | /// Path to the Time Zone default database. 29 | const String tzDataDefaultPath = 30 | 'packages/timezone/data/$tzDataDefaultFilename'; 31 | 32 | /// Initialize Time Zone database. 33 | /// 34 | /// Throws [TimeZoneInitException] when something is wrong. 35 | /// 36 | /// ```dart 37 | /// import 'package:timezone/browser.dart'; 38 | /// 39 | /// initializeTimeZone().then(() { 40 | /// final detroit = getLocation('America/Detroit'); 41 | /// final detroitNow = TZDateTime.now(detroit); 42 | /// }); 43 | /// ``` 44 | Future initializeTimeZone([String path = tzDataDefaultPath]) async { 45 | final client = browser.BrowserClient(); 46 | try { 47 | final response = await client.get( 48 | Uri.parse(path), 49 | headers: {'Accept': 'application/octet-stream'}, 50 | ); 51 | if (response.statusCode == 200) { 52 | initializeDatabase(response.bodyBytes); 53 | } else { 54 | throw TimeZoneInitException( 55 | 'Request failed with status: ${response.statusCode}', 56 | ); 57 | } 58 | } on TimeZoneInitException { 59 | rethrow; 60 | } on Exception catch (e) { 61 | throw TimeZoneInitException(e.toString()); 62 | } finally { 63 | client.close(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /lib/data/latest.tzf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/srawlins/timezone/64db18e450c10247e23ee36546865007b01cf135/lib/data/latest.tzf -------------------------------------------------------------------------------- /lib/data/latest_10y.tzf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/srawlins/timezone/64db18e450c10247e23ee36546865007b01cf135/lib/data/latest_10y.tzf -------------------------------------------------------------------------------- /lib/data/latest_all.tzf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/srawlins/timezone/64db18e450c10247e23ee36546865007b01cf135/lib/data/latest_all.tzf -------------------------------------------------------------------------------- /lib/src/date_time.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the timezone project authors. Please see the AUTHORS 2 | // file for details. All rights reserved. Use of this source code is governed 3 | // by a BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:timezone/src/env.dart'; 6 | import 'package:timezone/src/location.dart'; 7 | 8 | /// TimeZone aware DateTime. 9 | class TZDateTime implements DateTime { 10 | /// Maximum value for time instants. 11 | static const int maxMillisecondsSinceEpoch = 8640000000000000; 12 | 13 | /// Minimum value for time instants. 14 | static const int minMillisecondsSinceEpoch = -maxMillisecondsSinceEpoch; 15 | 16 | /// Returns the native [DateTime] object. 17 | static DateTime _toNative(DateTime t) => t is TZDateTime ? t.native : t; 18 | 19 | /// Converts a [_localDateTime] into a correct [DateTime]. 20 | static DateTime _utcFromLocalDateTime(DateTime local, Location location) { 21 | // Adapted from https://github.com/JodaOrg/joda-time/blob/main/src/main/java/org/joda/time/DateTimeZone.java#L951 22 | // Get the offset at local (first estimate). 23 | final localInstant = local.millisecondsSinceEpoch; 24 | final localTimezone = location.lookupTimeZone(localInstant); 25 | final localOffset = localTimezone.timeZone.offset; 26 | 27 | // Adjust localInstant using the estimate and recalculate the offset. 28 | final adjustedInstant = localInstant - localOffset; 29 | final adjustedTimezone = location.lookupTimeZone(adjustedInstant); 30 | final adjustedOffset = adjustedTimezone.timeZone.offset; 31 | 32 | var milliseconds = localInstant - adjustedOffset; 33 | 34 | // If the offsets differ, we must be near a DST boundary 35 | if (localOffset != adjustedOffset) { 36 | // We need to ensure that time is always after the DST gap 37 | // this happens naturally for positive offsets, but not for negative. 38 | // If we just use adjustedOffset then the time is pushed back before the 39 | // transition, whereas it should be on or after the transition 40 | if (localOffset - adjustedOffset < 0 && 41 | adjustedOffset != 42 | location 43 | .lookupTimeZone(localInstant - adjustedOffset) 44 | .timeZone 45 | .offset) { 46 | milliseconds = adjustedInstant; 47 | } 48 | } 49 | 50 | // Ensure original microseconds are preserved regardless of TZ shift. 51 | final microsecondsSinceEpoch = 52 | Duration(milliseconds: milliseconds, microseconds: local.microsecond) 53 | .inMicroseconds; 54 | return DateTime.fromMicrosecondsSinceEpoch(microsecondsSinceEpoch, 55 | isUtc: true); 56 | } 57 | 58 | /// Native [DateTime] used as a Calendar object. 59 | /// 60 | /// Represents the same date and time as this [TZDateTime], but in the UTC 61 | /// time zone. For example, for a [TZDateTime] representing 62 | /// 2000-03-17T12:00:00-0700, this will store the [DateTime] representing 63 | /// 2000-03-17T12:00:00Z. 64 | final DateTime _localDateTime; 65 | 66 | /// Native [DateTime] used as canonical, UTC representation. 67 | /// 68 | /// Represents the same moment as this [TZDateTime]. 69 | final DateTime native; 70 | 71 | /// The number of milliseconds since 72 | /// the "Unix epoch" 1970-01-01T00:00:00Z (UTC). 73 | /// 74 | /// This value is independent of the time zone. 75 | /// 76 | /// This value is at most 77 | /// 8,640,000,000,000,000ms (100,000,000 days) from the Unix epoch. 78 | /// In other words: [:millisecondsSinceEpoch.abs() <= 8640000000000000:]. 79 | @override 80 | int get millisecondsSinceEpoch => native.millisecondsSinceEpoch; 81 | 82 | /// The number of microseconds since the "Unix epoch" 83 | /// 1970-01-01T00:00:00Z (UTC). 84 | /// 85 | /// This value is independent of the time zone. 86 | /// 87 | /// This value is at most 8,640,000,000,000,000,000us (100,000,000 days) from 88 | /// the Unix epoch. In other words: 89 | /// microsecondsSinceEpoch.abs() <= 8640000000000000000. 90 | /// 91 | /// Note that this value does not fit into 53 bits (the size of a IEEE 92 | /// double). A JavaScript number is not able to hold this value. 93 | @override 94 | int get microsecondsSinceEpoch => native.microsecondsSinceEpoch; 95 | 96 | /// [Location] 97 | final Location location; 98 | 99 | /// [TimeZone] 100 | final TimeZone timeZone; 101 | 102 | /// True if this [TZDateTime] is set to UTC time. 103 | /// 104 | /// ```dart 105 | /// final dDay = TZDateTime.utc(1944, 6, 6); 106 | /// assert(dDay.isUtc); 107 | /// ``` 108 | /// 109 | @override 110 | bool get isUtc => _isUtc(location); 111 | 112 | static bool _isUtc(Location l) => identical(l, UTC); 113 | 114 | /// True if this [TZDateTime] is set to Local time. 115 | /// 116 | /// ```dart 117 | /// final dDay = TZDateTime.local(1944, 6, 6); 118 | /// assert(dDay.isLocal); 119 | /// ``` 120 | /// 121 | bool get isLocal => identical(location, local); 122 | 123 | /// Constructs a [TZDateTime] instance specified at [location] time zone. 124 | /// 125 | /// For example, 126 | /// to create a new TZDateTime object representing April 29, 2014, 6:04am 127 | /// in America/Detroit: 128 | /// 129 | /// ```dart 130 | /// final detroit = getLocation('America/Detroit'); 131 | /// 132 | /// final annularEclipse = TZDateTime(location, 133 | /// 2014, DateTime.APRIL, 29, 6, 4); 134 | /// ``` 135 | TZDateTime(Location location, int year, 136 | [int month = 1, 137 | int day = 1, 138 | int hour = 0, 139 | int minute = 0, 140 | int second = 0, 141 | int millisecond = 0, 142 | int microsecond = 0]) 143 | : this.from( 144 | _utcFromLocalDateTime( 145 | DateTime.utc(year, month, day, hour, minute, second, 146 | millisecond, microsecond), 147 | location), 148 | location); 149 | 150 | /// Constructs a [TZDateTime] instance specified in the UTC time zone. 151 | /// 152 | /// ```dart 153 | /// final dDay = TZDateTime.utc(1944, TZDateTime.JUNE, 6); 154 | /// ``` 155 | TZDateTime.utc(int year, 156 | [int month = 1, 157 | int day = 1, 158 | int hour = 0, 159 | int minute = 0, 160 | int second = 0, 161 | int millisecond = 0, 162 | int microsecond = 0]) 163 | : this(UTC, year, month, day, hour, minute, second, millisecond, 164 | microsecond); 165 | 166 | /// Constructs a [TZDateTime] instance specified in the local time zone. 167 | /// 168 | /// ```dart 169 | /// final dDay = TZDateTime.utc(1944, TZDateTime.JUNE, 6); 170 | /// ``` 171 | TZDateTime.local(int year, 172 | [int month = 1, 173 | int day = 1, 174 | int hour = 0, 175 | int minute = 0, 176 | int second = 0, 177 | int millisecond = 0, 178 | int microsecond = 0]) 179 | : this(local, year, month, day, hour, minute, second, millisecond, 180 | microsecond); 181 | 182 | /// Constructs a [TZDateTime] instance with current date and time in the 183 | /// [location] time zone. 184 | /// 185 | /// ```dart 186 | /// final detroit = getLocation('America/Detroit'); 187 | /// 188 | /// final thisInstant = TZDateTime.now(detroit); 189 | /// ``` 190 | TZDateTime.now(Location location) : this.from(DateTime.now(), location); 191 | 192 | /// Constructs a new [TZDateTime] instance with the given 193 | /// [millisecondsSinceEpoch]. 194 | /// 195 | /// The constructed [TZDateTime] represents 196 | /// 1970-01-01T00:00:00Z + [millisecondsSinceEpoch] ms in the given 197 | /// time zone [location]. 198 | TZDateTime.fromMillisecondsSinceEpoch( 199 | Location location, int millisecondsSinceEpoch) 200 | : this.from( 201 | DateTime.fromMillisecondsSinceEpoch(millisecondsSinceEpoch, 202 | isUtc: true), 203 | location); 204 | 205 | TZDateTime.fromMicrosecondsSinceEpoch( 206 | Location location, int microsecondsSinceEpoch) 207 | : this.from( 208 | DateTime.fromMicrosecondsSinceEpoch(microsecondsSinceEpoch, 209 | isUtc: true), 210 | location); 211 | 212 | /// Constructs a new [TZDateTime] instance from the given [DateTime] 213 | /// in the specified [location]. 214 | /// 215 | /// ```dart 216 | /// final laTime = TZDateTime(la, 2010, 1, 1); 217 | /// final detroitTime = TZDateTime.from(laTime, detroit); 218 | /// ``` 219 | TZDateTime.from(DateTime other, Location location) 220 | : this._( 221 | _toNative(other).toUtc(), 222 | location, 223 | _isUtc(location) 224 | ? TimeZone.UTC 225 | : location.timeZone(other.millisecondsSinceEpoch)); 226 | 227 | TZDateTime._(this.native, this.location, this.timeZone) 228 | : _localDateTime = 229 | _isUtc(location) ? native : native.add(_timeZoneOffset(timeZone)); 230 | 231 | /// Constructs a new [TZDateTime] instance based on [formattedString]. 232 | /// 233 | /// Throws a [FormatException] if the input cannot be parsed. 234 | /// 235 | /// The function parses a subset of ISO 8601 236 | /// which includes the subset accepted by RFC 3339. 237 | /// 238 | /// The result is always in the time zone of the provided location. 239 | /// 240 | /// Examples of accepted strings: 241 | /// 242 | /// * `"2012-02-27 13:27:00"` 243 | /// * `"2012-02-27 13:27:00.123456z"` 244 | /// * `"20120227 13:27:00"` 245 | /// * `"20120227T132700"` 246 | /// * `"20120227"` 247 | /// * `"+20120227"` 248 | /// * `"2012-02-27T14Z"` 249 | /// * `"2012-02-27T14+00:00"` 250 | /// * `"-123450101 00:00:00 Z"`: in the year -12345. 251 | /// * `"2002-02-27T14:00:00-0500"`: Same as `"2002-02-27T19:00:00Z"` 252 | static TZDateTime parse(Location location, String formattedString) { 253 | return TZDateTime.from(DateTime.parse(formattedString), location); 254 | } 255 | 256 | /// Returns this DateTime value in the UTC time zone. 257 | /// 258 | /// Returns [this] if it is already in UTC. 259 | @override 260 | TZDateTime toUtc() => isUtc ? this : TZDateTime.from(native, UTC); 261 | 262 | /// Returns this DateTime value in the local time zone. 263 | /// 264 | /// Returns [this] if it is already in the local time zone. 265 | @override 266 | TZDateTime toLocal() => isLocal ? this : TZDateTime.from(native, local); 267 | 268 | static String _fourDigits(int n) { 269 | var absN = n.abs(); 270 | var sign = n < 0 ? "-" : ""; 271 | if (absN >= 1000) return "$n"; 272 | if (absN >= 100) return "${sign}0$absN"; 273 | if (absN >= 10) return "${sign}00$absN"; 274 | return "${sign}000$absN"; 275 | } 276 | 277 | static String _threeDigits(int n) { 278 | if (n >= 100) return "$n"; 279 | if (n >= 10) return "0$n"; 280 | return "00$n"; 281 | } 282 | 283 | static String _twoDigits(int n) { 284 | if (n >= 10) return "$n"; 285 | return "0$n"; 286 | } 287 | 288 | /// Returns a human-readable string for this instance. 289 | /// 290 | /// The returned string is constructed for the time zone of this instance. 291 | /// The `toString()` method provides a simply formatted string. 292 | /// It does not support internationalized strings. 293 | /// Use the [intl](http://pub.dartlang.org/packages/intl) package 294 | /// at the pub shared packages repo. 295 | @override 296 | String toString() => _toString(iso8601: false); 297 | 298 | /// Returns an ISO-8601 full-precision extended format representation. 299 | /// 300 | /// The format is yyyy-MM-ddTHH:mm:ss.mmmuuuZ for UTC time, and 301 | /// yyyy-MM-ddTHH:mm:ss.mmmuuu±hhmm for local/non-UTC time, where: 302 | /// 303 | /// * yyyy is a, possibly negative, four digit representation of the year, 304 | /// if the year is in the range -9999 to 9999, otherwise it is a signed 305 | /// six digit representation of the year. 306 | /// * MM is the month in the range 01 to 12, 307 | /// * dd is the day of the month in the range 01 to 31, 308 | /// * HH are hours in the range 00 to 23, 309 | /// * mm are minutes in the range 00 to 59, 310 | /// * ss are seconds in the range 00 to 59 (no leap seconds), 311 | /// * mmm are milliseconds in the range 000 to 999, and 312 | /// * uuu are microseconds in the range 001 to 999. If microsecond equals 0, 313 | /// then this part is omitted. 314 | /// 315 | ///The resulting string can be parsed back using parse. 316 | @override 317 | String toIso8601String() => _toString(iso8601: true); 318 | 319 | String _toString({bool iso8601 = true}) { 320 | var offset = timeZone.offset; 321 | 322 | var y = _fourDigits(year); 323 | var m = _twoDigits(month); 324 | var d = _twoDigits(day); 325 | var sep = iso8601 ? "T" : " "; 326 | var h = _twoDigits(hour); 327 | var min = _twoDigits(minute); 328 | var sec = _twoDigits(second); 329 | var ms = _threeDigits(millisecond); 330 | var us = microsecond == 0 ? "" : _threeDigits(microsecond); 331 | 332 | if (isUtc) { 333 | return "$y-$m-$d$sep$h:$min:$sec.$ms${us}Z"; 334 | } else { 335 | var offSign = offset.sign >= 0 ? '+' : '-'; 336 | offset = offset.abs() ~/ 1000; 337 | var offH = _twoDigits(offset ~/ 3600); 338 | var offM = _twoDigits((offset % 3600) ~/ 60); 339 | 340 | return "$y-$m-$d$sep$h:$min:$sec.$ms$us$offSign$offH$offM"; 341 | } 342 | } 343 | 344 | /// Returns a new [TZDateTime] instance with [duration] added to [this]. 345 | @override 346 | TZDateTime add(Duration duration) => 347 | TZDateTime.from(native.add(duration), location); 348 | 349 | /// Returns a new [TZDateTime] instance with [duration] subtracted from 350 | /// [this]. 351 | @override 352 | TZDateTime subtract(Duration duration) => 353 | TZDateTime.from(native.subtract(duration), location); 354 | 355 | /// Returns a [Duration] with the difference between [this] and [other]. 356 | @override 357 | Duration difference(DateTime other) => native.difference(_toNative(other)); 358 | 359 | /// Returns true if [other] is a [TZDateTime] at the same moment and in the 360 | /// same [Location]. 361 | /// 362 | /// ```dart 363 | /// final detroit = getLocation('America/Detroit'); 364 | /// final dDayUtc = TZDateTime.utc(1944, DateTime.JUNE, 6); 365 | /// final dDayLocal = TZDateTime(detroit, 1944, DateTime.JUNE, 6); 366 | /// 367 | /// assert(dDayUtc.isAtSameMomentAs(dDayLocal) == false); 368 | /// ```` 369 | /// 370 | /// See [isAtSameMomentAs] for a comparison that adjusts for time zone. 371 | @override 372 | bool operator ==(Object other) { 373 | return identical(this, other) || 374 | other is TZDateTime && 375 | native.isAtSameMomentAs(other.native) && 376 | location == other.location; 377 | } 378 | 379 | /// Returns true if [this] occurs before [other]. 380 | /// 381 | /// The comparison is independent of whether the time is in UTC or in other 382 | /// time zone. 383 | /// 384 | /// ```dart 385 | /// final berlinWallFell = TZDateTime(UTC, 1989, 11, 9); 386 | /// final moonLanding = TZDateTime(UTC, 1969, 7, 20); 387 | /// 388 | /// assert(berlinWallFell.isBefore(moonLanding) == false); 389 | /// ``` 390 | @override 391 | bool isBefore(DateTime other) => native.isBefore(_toNative(other)); 392 | 393 | /// Returns true if [this] occurs after [other]. 394 | /// 395 | /// The comparison is independent of whether the time is in UTC or in other 396 | /// time zone. 397 | /// 398 | /// ```dart 399 | /// final berlinWallFell = TZDateTime(UTC, 1989, 11, 9); 400 | /// final moonLanding = TZDateTime(UTC, 1969, 7, 20); 401 | /// 402 | /// assert(berlinWallFell.isAfter(moonLanding) == true); 403 | /// ``` 404 | @override 405 | bool isAfter(DateTime other) => native.isAfter(_toNative(other)); 406 | 407 | /// Returns true if [this] occurs at the same moment as [other]. 408 | /// 409 | /// The comparison is independent of whether the time is in UTC or in other 410 | /// time zone. 411 | /// 412 | /// ```dart 413 | /// final berlinWallFell = TZDateTime(UTC, 1989, 11, 9); 414 | /// final moonLanding = TZDateTime(UTC, 1969, 7, 20); 415 | /// 416 | /// assert(berlinWallFell.isAtSameMomentAs(moonLanding) == false); 417 | /// ``` 418 | @override 419 | bool isAtSameMomentAs(DateTime other) => 420 | native.isAtSameMomentAs(_toNative(other)); 421 | 422 | /// Compares this [TZDateTime] object to [other], 423 | /// returning zero if the values occur at the same moment. 424 | /// 425 | /// This function returns a negative integer 426 | /// if this [TZDateTime] is smaller (earlier) than [other], 427 | /// or a positive integer if it is greater (later). 428 | @override 429 | int compareTo(DateTime other) => native.compareTo(_toNative(other)); 430 | 431 | @override 432 | int get hashCode => native.hashCode; 433 | 434 | /// The abbreviated time zone name—for example, 435 | /// [:"CET":] or [:"CEST":]. 436 | @override 437 | String get timeZoneName => timeZone.abbreviation; 438 | 439 | /// The time zone offset, which is the difference between time at [location] 440 | /// and UTC. 441 | /// 442 | /// The offset is positive for time zones east of UTC. 443 | /// 444 | /// Note, that JavaScript, Python and C return the difference between UTC and 445 | /// local time. Java, C# and Ruby return the difference between local time and 446 | /// UTC. 447 | @override 448 | Duration get timeZoneOffset => _timeZoneOffset(timeZone); 449 | 450 | static Duration _timeZoneOffset(TimeZone timeZone) => 451 | Duration(milliseconds: timeZone.offset); 452 | 453 | /// The year. 454 | @override 455 | int get year => _localDateTime.year; 456 | 457 | /// The month [1..12]. 458 | @override 459 | int get month => _localDateTime.month; 460 | 461 | /// The day of the month [1..31]. 462 | @override 463 | int get day => _localDateTime.day; 464 | 465 | /// The hour of the day, expressed as in a 24-hour clock [0..23]. 466 | @override 467 | int get hour => _localDateTime.hour; 468 | 469 | /// The minute [0...59]. 470 | @override 471 | int get minute => _localDateTime.minute; 472 | 473 | /// The second [0...59]. 474 | @override 475 | int get second => _localDateTime.second; 476 | 477 | /// The millisecond [0...999]. 478 | @override 479 | int get millisecond => _localDateTime.millisecond; 480 | 481 | /// The microsecond [0...999]. 482 | @override 483 | int get microsecond => _localDateTime.microsecond; 484 | 485 | /// The day of the week. 486 | /// 487 | /// In accordance with ISO 8601 488 | /// a week starts with Monday, which has the value 1. 489 | @override 490 | int get weekday => _localDateTime.weekday; 491 | } 492 | -------------------------------------------------------------------------------- /lib/src/env.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the timezone project authors. Please see the AUTHORS 2 | // file for details. All rights reserved. Use of this source code is governed 3 | // by a BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:timezone/src/location.dart'; 6 | import 'package:timezone/src/location_database.dart'; 7 | import 'package:timezone/src/tzdb.dart'; 8 | 9 | /// File name of the Time Zone default database. 10 | const String tzDataDefaultFilename = 'latest.tzf'; 11 | 12 | final _UTC = Location('UTC', [minTime], [0], [TimeZone.UTC]); 13 | 14 | final _database = LocationDatabase(); 15 | late Location _local; 16 | 17 | /// Global TimeZone database 18 | LocationDatabase get timeZoneDatabase => _database; 19 | 20 | /// UTC Location 21 | Location get UTC => _UTC; 22 | 23 | /// Local Location 24 | /// 25 | /// By default it is instantiated with UTC [Location] 26 | Location get local => _local; 27 | 28 | /// Find [Location] by its name. 29 | /// 30 | /// ```dart 31 | /// final detroit = getLocation('America/Detroit'); 32 | /// ``` 33 | Location getLocation(String locationName) { 34 | return _database.get(locationName); 35 | } 36 | 37 | /// Set local [Location] 38 | /// 39 | /// ```dart 40 | /// final detroit = getLocation('America/Detroit') 41 | /// setLocalLocation(detroit); 42 | /// ``` 43 | void setLocalLocation(Location location) { 44 | _local = location; 45 | } 46 | 47 | /// Initialize Time zone database. 48 | void initializeDatabase(List rawData) { 49 | _database.clear(); 50 | 51 | for (final l in tzdbDeserialize(rawData)) { 52 | _database.add(l); 53 | } 54 | 55 | _local = _UTC; 56 | } 57 | -------------------------------------------------------------------------------- /lib/src/exceptions.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the timezone project authors. Please see the AUTHORS 2 | // file for details. All rights reserved. Use of this source code is governed 3 | // by a BSD-style license that can be found in the LICENSE file. 4 | 5 | class TimeZoneInitException implements Exception { 6 | final String msg; 7 | 8 | TimeZoneInitException(this.msg); 9 | 10 | @override 11 | String toString() => msg; 12 | } 13 | 14 | class LocationNotFoundException implements Exception { 15 | final String msg; 16 | 17 | LocationNotFoundException(this.msg); 18 | 19 | @override 20 | String toString() => msg; 21 | } 22 | -------------------------------------------------------------------------------- /lib/src/location.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the timezone project authors. Please see the AUTHORS 2 | // file for details. All rights reserved. Use of this source code is governed 3 | // by a BSD-style license that can be found in the LICENSE file. 4 | 5 | /// TimeZone Location Info. 6 | /// 7 | /// Most of this code were taken from the go standard library 8 | /// [http://golang.org/src/pkg/time/zoneinfo.go](time/zoneinfo.go) 9 | /// and ported to Dart. 10 | library timezone.src.location; 11 | 12 | /// Maximum value for time instants. 13 | const int maxTime = 8640000000000000; 14 | 15 | /// Minimum value for time instants. 16 | const int minTime = -maxTime; 17 | 18 | /// A [Location] maps time instants to the zone in use at that time. 19 | /// Typically, the Location represents the collection of time offsets 20 | /// in use in a geographical area, such as CEST and CET for central Europe. 21 | class Location { 22 | /// [Location] name. 23 | final String name; 24 | 25 | /// Transition time, in milliseconds since 1970 UTC. 26 | final List transitionAt; 27 | 28 | /// The index of the zone that goes into effect at that time. 29 | final List transitionZone; 30 | 31 | /// [TimeZone]s at this [Location]. 32 | final List zones; 33 | 34 | /// [TimeZone] for the current time. 35 | TimeZone get currentTimeZone => 36 | timeZone(DateTime.now().millisecondsSinceEpoch); 37 | 38 | // Most lookups will be for the current time. 39 | // To avoid the binary search through tx, keep a 40 | // static one-element cache that gives the correct 41 | // zone for the time when the Location was created. 42 | // if cacheStart <= t <= cacheEnd, 43 | // lookup can return cacheZone. 44 | // The units for cacheStart and cacheEnd are milliseconds 45 | // since January 1, 1970 UTC, to match the argument 46 | // to lookup. 47 | static final int _cacheNow = DateTime.now().millisecondsSinceEpoch; 48 | int _cacheStart = 0; 49 | int _cacheEnd = 0; 50 | late TimeZone _cacheZone; 51 | 52 | Location(this.name, this.transitionAt, this.transitionZone, this.zones) { 53 | // Fill in the cache with information about right now, 54 | // since that will be the most common lookup. 55 | for (var i = 0; i < transitionAt.length; i++) { 56 | final tAt = transitionAt[i]; 57 | 58 | if ((tAt <= _cacheNow) && 59 | ((i + 1 == transitionAt.length) || 60 | (_cacheNow < transitionAt[i + 1]))) { 61 | _cacheStart = tAt; 62 | _cacheEnd = maxTime; 63 | if (i + 1 < transitionAt.length) { 64 | _cacheEnd = transitionAt[i + 1]; 65 | } 66 | _cacheZone = zones[transitionZone[i]]; 67 | } 68 | } 69 | } 70 | 71 | /// translate instant in time expressed as milliseconds since 72 | /// January 1, 1970 00:00:00 UTC to this [Location]. 73 | int translate(int millisecondsSinceEpoch) { 74 | return millisecondsSinceEpoch + timeZone(millisecondsSinceEpoch).offset; 75 | } 76 | 77 | /// translate instant in time expressed as milliseconds since 78 | /// January 1, 1970 00:00:00 to UTC. 79 | int translateToUtc(int millisecondsSinceEpoch) { 80 | final t = lookupTimeZone(millisecondsSinceEpoch); 81 | final tz = t.timeZone; 82 | final start = t.start; 83 | final end = t.end; 84 | 85 | var utc = millisecondsSinceEpoch; 86 | 87 | if (tz.offset != 0) { 88 | utc -= tz.offset; 89 | 90 | if (utc < start) { 91 | utc = 92 | millisecondsSinceEpoch - lookupTimeZone(start - 1).timeZone.offset; 93 | } else if (utc >= end) { 94 | utc = millisecondsSinceEpoch - lookupTimeZone(end).timeZone.offset; 95 | } 96 | } 97 | 98 | return utc; 99 | } 100 | 101 | /// lookup for [TimeZone] and its boundaries for an instant in time expressed 102 | /// as milliseconds since January 1, 1970 00:00:00 UTC. 103 | TzInstant lookupTimeZone(int millisecondsSinceEpoch) { 104 | if (zones.isEmpty) { 105 | return const TzInstant(TimeZone.UTC, minTime, maxTime); 106 | } 107 | 108 | if (millisecondsSinceEpoch >= _cacheStart && 109 | millisecondsSinceEpoch < _cacheEnd) { 110 | return TzInstant(_cacheZone, _cacheStart, _cacheEnd); 111 | } 112 | 113 | if (transitionAt.isEmpty || millisecondsSinceEpoch < transitionAt[0]) { 114 | final zone = _firstZone(); 115 | final start = minTime; 116 | final end = transitionAt.isEmpty ? maxTime : transitionAt.first; 117 | return TzInstant(zone, start, end); 118 | } 119 | 120 | // Binary search for entry with largest millisecondsSinceEpoch <= sec. 121 | var lo = 0; 122 | var hi = transitionAt.length; 123 | var end = maxTime; 124 | 125 | while (hi - lo > 1) { 126 | final m = lo + (hi - lo) ~/ 2; 127 | final at = transitionAt[m]; 128 | 129 | if (millisecondsSinceEpoch < at) { 130 | end = at; 131 | hi = m; 132 | } else { 133 | lo = m; 134 | } 135 | } 136 | 137 | return TzInstant(zones[transitionZone[lo]], transitionAt[lo], end); 138 | } 139 | 140 | /// timeZone method returns [TimeZone] in use at an instant in time expressed 141 | /// as milliseconds since January 1, 1970 00:00:00 UTC. 142 | TimeZone timeZone(int millisecondsSinceEpoch) { 143 | return lookupTimeZone(millisecondsSinceEpoch).timeZone; 144 | } 145 | 146 | /// timeZoneFromLocal method returns [TimeZone] in use at an instant in time 147 | /// expressed as milliseconds since January 1, 1970 00:00:00. 148 | TimeZone timeZoneFromLocal(int millisecondsSinceEpoch) { 149 | final t = lookupTimeZone(millisecondsSinceEpoch); 150 | var tz = t.timeZone; 151 | final start = t.start; 152 | final end = t.end; 153 | 154 | if (tz.offset != 0) { 155 | final utc = millisecondsSinceEpoch - tz.offset; 156 | 157 | if (utc < start) { 158 | tz = lookupTimeZone(start - 1).timeZone; 159 | } else if (utc >= end) { 160 | tz = lookupTimeZone(end).timeZone; 161 | } 162 | } 163 | 164 | return tz; 165 | } 166 | 167 | /// This method returns the [TimeZone] to use for times before the first 168 | /// transition time, or when there are no transition times. 169 | /// 170 | /// The reference implementation in localtime.c from 171 | /// http://www.iana.org/time-zones/repository/releases/tzcode2013g.tar.gz 172 | /// implements the following algorithm for these cases: 173 | /// 174 | /// 1. If the first zone is unused by the transitions, use it. 175 | /// 2. Otherwise, if there are transition times, and the first 176 | /// transition is to a zone in daylight time, find the first 177 | /// non-daylight-time zone before and closest to the first transition 178 | /// zone. 179 | /// 3. Otherwise, use the first zone that is not daylight time, if 180 | /// there is one. 181 | /// 4. Otherwise, use the first zone. 182 | /// 183 | TimeZone _firstZone() { 184 | // case 1 185 | if (!_firstZoneIsUsed()) { 186 | return zones.first; 187 | } 188 | 189 | // case 2 190 | if (transitionZone.isNotEmpty && zones[transitionZone.first].isDst) { 191 | for (var zi = transitionZone.first - 1; zi >= 0; zi--) { 192 | final z = zones[zi]; 193 | if (!z.isDst) { 194 | return z; 195 | } 196 | } 197 | } 198 | 199 | // case 3 200 | for (final zi in transitionZone) { 201 | final z = zones[zi]; 202 | if (!z.isDst) { 203 | return z; 204 | } 205 | } 206 | 207 | // case 4 208 | return zones.first; 209 | } 210 | 211 | /// firstZoneUsed returns whether the first zone is used by some transition. 212 | bool _firstZoneIsUsed() { 213 | for (final i in transitionZone) { 214 | if (i == 0) { 215 | return true; 216 | } 217 | } 218 | 219 | return false; 220 | } 221 | 222 | @override 223 | String toString() => name; 224 | 225 | // Override equals and hashCode to support comparing 226 | // Locations created in different isolates. 227 | 228 | @override 229 | bool operator ==(Object other) { 230 | return identical(this, other) || 231 | other is Location && 232 | runtimeType == other.runtimeType && 233 | name == other.name; 234 | } 235 | 236 | @override 237 | int get hashCode { 238 | return name.hashCode; 239 | } 240 | } 241 | 242 | /// A [TimeZone] represents a single time zone such as CEST or CET. 243 | class TimeZone { 244 | // ignore: constant_identifier_names 245 | static const TimeZone UTC = TimeZone(0, isDst: false, abbreviation: 'UTC'); 246 | 247 | /// Milliseconds east of UTC. 248 | final int offset; 249 | 250 | /// Is this [TimeZone] Daylight Savings Time? 251 | final bool isDst; 252 | 253 | /// Abbreviated name, "CET". 254 | final String abbreviation; 255 | 256 | const TimeZone(this.offset, 257 | {required this.isDst, required this.abbreviation}); 258 | 259 | @override 260 | bool operator ==(Object other) { 261 | return identical(this, other) || 262 | other is TimeZone && 263 | offset == other.offset && 264 | isDst == other.isDst && 265 | abbreviation == other.abbreviation; 266 | } 267 | 268 | @override 269 | int get hashCode { 270 | var result = 17; 271 | result = 37 * result + offset.hashCode; 272 | result = 37 * result + isDst.hashCode; 273 | result = 37 * result + abbreviation.hashCode; 274 | return result; 275 | } 276 | 277 | @override 278 | String toString() => '[$abbreviation offset=$offset dst=$isDst]'; 279 | } 280 | 281 | /// A [TzInstant] represents a timezone and an instant in time. 282 | class TzInstant { 283 | final TimeZone timeZone; 284 | final int start; 285 | final int end; 286 | 287 | const TzInstant(this.timeZone, this.start, this.end); 288 | } 289 | -------------------------------------------------------------------------------- /lib/src/location_database.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the timezone project authors. Please see the AUTHORS 2 | // file for details. All rights reserved. Use of this source code is governed 3 | // by a BSD-style license that can be found in the LICENSE file. 4 | 5 | /// Locations database 6 | library timezone.src.location_database; 7 | 8 | import 'package:timezone/src/exceptions.dart'; 9 | import 'package:timezone/src/location.dart'; 10 | 11 | /// LocationDatabase provides interface to find [Location]s by their name. 12 | /// 13 | /// List data = load(); // load database 14 | /// 15 | /// LocationDatabase db = LocationDatabase.fromBytes(data); 16 | /// Location loc = db.get('US/Eastern'); 17 | /// 18 | class LocationDatabase { 19 | /// Mapping between [Location] name and [Location]. 20 | final _locations = {}; 21 | 22 | Map get locations => _locations; 23 | 24 | /// Adds [Location] to the database. 25 | void add(Location location) { 26 | _locations[location.name] = location; 27 | } 28 | 29 | /// Finds [Location] by its name. 30 | Location get(String name) { 31 | if (!isInitialized) { 32 | // Before you can get a location, you need to manually initialize the 33 | // timezone location database by calling initializeDatabase or similar. 34 | throw LocationNotFoundException( 35 | 'Tried to get location before initializing timezone database'); 36 | } 37 | 38 | final loc = _locations[name]; 39 | if (loc == null) { 40 | throw LocationNotFoundException( 41 | 'Location with the name "$name" doesn\'t exist'); 42 | } 43 | return loc; 44 | } 45 | 46 | /// Clears the database of all [Location] entries. 47 | void clear() => _locations.clear(); 48 | 49 | /// Returns whether the database is empty, or has [Location] entries. 50 | @Deprecated("Use 'isInitialized' instead") 51 | bool get isEmpty => isInitialized; 52 | 53 | /// Returns whether the database is empty, or has [Location] entries. 54 | bool get isInitialized => _locations.isNotEmpty; 55 | } 56 | -------------------------------------------------------------------------------- /lib/src/tools.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the timezone project authors. Please see the AUTHORS 2 | // file for details. All rights reserved. Use of this source code is governed 3 | // by a BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:collection'; 6 | 7 | import 'package:timezone/timezone.dart'; 8 | import 'package:timezone/tzdata.dart' as tzfile; 9 | 10 | const commonLocations = [ 11 | 'Africa/Abidjan', 12 | 'Africa/Accra', 13 | 'Africa/Addis_Ababa', 14 | 'Africa/Algiers', 15 | 'Africa/Asmara', 16 | 'Africa/Bamako', 17 | 'Africa/Bangui', 18 | 'Africa/Banjul', 19 | 'Africa/Bissau', 20 | 'Africa/Blantyre', 21 | 'Africa/Brazzaville', 22 | 'Africa/Bujumbura', 23 | 'Africa/Cairo', 24 | 'Africa/Casablanca', 25 | 'Africa/Ceuta', 26 | 'Africa/Conakry', 27 | 'Africa/Dakar', 28 | 'Africa/Dar_es_Salaam', 29 | 'Africa/Djibouti', 30 | 'Africa/Douala', 31 | 'Africa/El_Aaiun', 32 | 'Africa/Freetown', 33 | 'Africa/Gaborone', 34 | 'Africa/Harare', 35 | 'Africa/Johannesburg', 36 | 'Africa/Juba', 37 | 'Africa/Kampala', 38 | 'Africa/Khartoum', 39 | 'Africa/Kigali', 40 | 'Africa/Kinshasa', 41 | 'Africa/Lagos', 42 | 'Africa/Libreville', 43 | 'Africa/Lome', 44 | 'Africa/Luanda', 45 | 'Africa/Lubumbashi', 46 | 'Africa/Lusaka', 47 | 'Africa/Malabo', 48 | 'Africa/Maputo', 49 | 'Africa/Maseru', 50 | 'Africa/Mbabane', 51 | 'Africa/Mogadishu', 52 | 'Africa/Monrovia', 53 | 'Africa/Nairobi', 54 | 'Africa/Ndjamena', 55 | 'Africa/Niamey', 56 | 'Africa/Nouakchott', 57 | 'Africa/Ouagadougou', 58 | 'Africa/Porto-Novo', 59 | 'Africa/Sao_Tome', 60 | 'Africa/Tripoli', 61 | 'Africa/Tunis', 62 | 'Africa/Windhoek', 63 | 'America/Adak', 64 | 'America/Anchorage', 65 | 'America/Anguilla', 66 | 'America/Antigua', 67 | 'America/Araguaina', 68 | 'America/Argentina/Buenos_Aires', 69 | 'America/Argentina/Catamarca', 70 | 'America/Argentina/Cordoba', 71 | 'America/Argentina/Jujuy', 72 | 'America/Argentina/La_Rioja', 73 | 'America/Argentina/Mendoza', 74 | 'America/Argentina/Rio_Gallegos', 75 | 'America/Argentina/Salta', 76 | 'America/Argentina/San_Juan', 77 | 'America/Argentina/San_Luis', 78 | 'America/Argentina/Tucuman', 79 | 'America/Argentina/Ushuaia', 80 | 'America/Aruba', 81 | 'America/Asuncion', 82 | 'America/Atikokan', 83 | 'America/Bahia', 84 | 'America/Bahia_Banderas', 85 | 'America/Barbados', 86 | 'America/Belem', 87 | 'America/Belize', 88 | 'America/Blanc-Sablon', 89 | 'America/Boa_Vista', 90 | 'America/Bogota', 91 | 'America/Boise', 92 | 'America/Cambridge_Bay', 93 | 'America/Campo_Grande', 94 | 'America/Cancun', 95 | 'America/Caracas', 96 | 'America/Cayenne', 97 | 'America/Cayman', 98 | 'America/Chicago', 99 | 'America/Chihuahua', 100 | 'America/Costa_Rica', 101 | 'America/Creston', 102 | 'America/Cuiaba', 103 | 'America/Curacao', 104 | 'America/Danmarkshavn', 105 | 'America/Dawson', 106 | 'America/Dawson_Creek', 107 | 'America/Denver', 108 | 'America/Detroit', 109 | 'America/Dominica', 110 | 'America/Edmonton', 111 | 'America/Eirunepe', 112 | 'America/El_Salvador', 113 | 'America/Fortaleza', 114 | 'America/Glace_Bay', 115 | 'America/Godthab', 116 | 'America/Goose_Bay', 117 | 'America/Grand_Turk', 118 | 'America/Grenada', 119 | 'America/Guadeloupe', 120 | 'America/Guatemala', 121 | 'America/Guayaquil', 122 | 'America/Guyana', 123 | 'America/Halifax', 124 | 'America/Havana', 125 | 'America/Hermosillo', 126 | 'America/Indiana/Indianapolis', 127 | 'America/Indiana/Knox', 128 | 'America/Indiana/Marengo', 129 | 'America/Indiana/Petersburg', 130 | 'America/Indiana/Tell_City', 131 | 'America/Indiana/Vevay', 132 | 'America/Indiana/Vincennes', 133 | 'America/Indiana/Winamac', 134 | 'America/Inuvik', 135 | 'America/Iqaluit', 136 | 'America/Jamaica', 137 | 'America/Juneau', 138 | 'America/Kentucky/Louisville', 139 | 'America/Kentucky/Monticello', 140 | 'America/Kralendijk', 141 | 'America/La_Paz', 142 | 'America/Lima', 143 | 'America/Los_Angeles', 144 | 'America/Lower_Princes', 145 | 'America/Maceio', 146 | 'America/Managua', 147 | 'America/Manaus', 148 | 'America/Marigot', 149 | 'America/Martinique', 150 | 'America/Matamoros', 151 | 'America/Mazatlan', 152 | 'America/Menominee', 153 | 'America/Merida', 154 | 'America/Metlakatla', 155 | 'America/Mexico_City', 156 | 'America/Miquelon', 157 | 'America/Moncton', 158 | 'America/Monterrey', 159 | 'America/Montevideo', 160 | 'America/Montreal', 161 | 'America/Montserrat', 162 | 'America/Nassau', 163 | 'America/New_York', 164 | 'America/Nipigon', 165 | 'America/Nome', 166 | 'America/Noronha', 167 | 'America/North_Dakota/Beulah', 168 | 'America/North_Dakota/Center', 169 | 'America/North_Dakota/New_Salem', 170 | 'America/Ojinaga', 171 | 'America/Panama', 172 | 'America/Pangnirtung', 173 | 'America/Paramaribo', 174 | 'America/Phoenix', 175 | 'America/Port-au-Prince', 176 | 'America/Port_of_Spain', 177 | 'America/Porto_Velho', 178 | 'America/Puerto_Rico', 179 | 'America/Rainy_River', 180 | 'America/Rankin_Inlet', 181 | 'America/Recife', 182 | 'America/Regina', 183 | 'America/Resolute', 184 | 'America/Rio_Branco', 185 | 'America/Santa_Isabel', 186 | 'America/Santarem', 187 | 'America/Santiago', 188 | 'America/Santo_Domingo', 189 | 'America/Sao_Paulo', 190 | 'America/Scoresbysund', 191 | 'America/Sitka', 192 | 'America/St_Barthelemy', 193 | 'America/St_Johns', 194 | 'America/St_Kitts', 195 | 'America/St_Lucia', 196 | 'America/St_Thomas', 197 | 'America/St_Vincent', 198 | 'America/Swift_Current', 199 | 'America/Tegucigalpa', 200 | 'America/Thule', 201 | 'America/Thunder_Bay', 202 | 'America/Tijuana', 203 | 'America/Toronto', 204 | 'America/Tortola', 205 | 'America/Vancouver', 206 | 'America/Whitehorse', 207 | 'America/Winnipeg', 208 | 'America/Yakutat', 209 | 'America/Yellowknife', 210 | 'Antarctica/Casey', 211 | 'Antarctica/Davis', 212 | 'Antarctica/DumontDUrville', 213 | 'Antarctica/Macquarie', 214 | 'Antarctica/Mawson', 215 | 'Antarctica/McMurdo', 216 | 'Antarctica/Palmer', 217 | 'Antarctica/Rothera', 218 | 'Antarctica/Syowa', 219 | 'Antarctica/Troll', 220 | 'Antarctica/Vostok', 221 | 'Arctic/Longyearbyen', 222 | 'Asia/Aden', 223 | 'Asia/Almaty', 224 | 'Asia/Amman', 225 | 'Asia/Anadyr', 226 | 'Asia/Aqtau', 227 | 'Asia/Aqtobe', 228 | 'Asia/Ashgabat', 229 | 'Asia/Baghdad', 230 | 'Asia/Bahrain', 231 | 'Asia/Baku', 232 | 'Asia/Bangkok', 233 | 'Asia/Beirut', 234 | 'Asia/Bishkek', 235 | 'Asia/Brunei', 236 | 'Asia/Chita', 237 | 'Asia/Choibalsan', 238 | 'Asia/Colombo', 239 | 'Asia/Damascus', 240 | 'Asia/Dhaka', 241 | 'Asia/Dili', 242 | 'Asia/Dubai', 243 | 'Asia/Dushanbe', 244 | 'Asia/Gaza', 245 | 'Asia/Hebron', 246 | 'Asia/Ho_Chi_Minh', 247 | 'Asia/Hong_Kong', 248 | 'Asia/Hovd', 249 | 'Asia/Irkutsk', 250 | 'Asia/Jakarta', 251 | 'Asia/Jayapura', 252 | 'Asia/Jerusalem', 253 | 'Asia/Kabul', 254 | 'Asia/Kamchatka', 255 | 'Asia/Karachi', 256 | 'Asia/Kathmandu', 257 | 'Asia/Khandyga', 258 | 'Asia/Kolkata', 259 | 'Asia/Krasnoyarsk', 260 | 'Asia/Kuala_Lumpur', 261 | 'Asia/Kuching', 262 | 'Asia/Kuwait', 263 | 'Asia/Macau', 264 | 'Asia/Magadan', 265 | 'Asia/Makassar', 266 | 'Asia/Manila', 267 | 'Asia/Muscat', 268 | 'Asia/Nicosia', 269 | 'Asia/Novokuznetsk', 270 | 'Asia/Novosibirsk', 271 | 'Asia/Omsk', 272 | 'Asia/Oral', 273 | 'Asia/Phnom_Penh', 274 | 'Asia/Pontianak', 275 | 'Asia/Pyongyang', 276 | 'Asia/Qatar', 277 | 'Asia/Qyzylorda', 278 | 'Asia/Rangoon', 279 | 'Asia/Riyadh', 280 | 'Asia/Sakhalin', 281 | 'Asia/Samarkand', 282 | 'Asia/Seoul', 283 | 'Asia/Shanghai', 284 | 'Asia/Singapore', 285 | 'Asia/Srednekolymsk', 286 | 'Asia/Taipei', 287 | 'Asia/Tashkent', 288 | 'Asia/Tbilisi', 289 | 'Asia/Tehran', 290 | 'Asia/Thimphu', 291 | 'Asia/Tokyo', 292 | 'Asia/Ulaanbaatar', 293 | 'Asia/Urumqi', 294 | 'Asia/Ust-Nera', 295 | 'Asia/Vientiane', 296 | 'Asia/Vladivostok', 297 | 'Asia/Yakutsk', 298 | 'Asia/Yekaterinburg', 299 | 'Asia/Yerevan', 300 | 'Atlantic/Azores', 301 | 'Atlantic/Bermuda', 302 | 'Atlantic/Canary', 303 | 'Atlantic/Cape_Verde', 304 | 'Atlantic/Faroe', 305 | 'Atlantic/Madeira', 306 | 'Atlantic/Reykjavik', 307 | 'Atlantic/South_Georgia', 308 | 'Atlantic/St_Helena', 309 | 'Atlantic/Stanley', 310 | 'Australia/Adelaide', 311 | 'Australia/Brisbane', 312 | 'Australia/Broken_Hill', 313 | 'Australia/Currie', 314 | 'Australia/Darwin', 315 | 'Australia/Eucla', 316 | 'Australia/Hobart', 317 | 'Australia/Lindeman', 318 | 'Australia/Lord_Howe', 319 | 'Australia/Melbourne', 320 | 'Australia/Perth', 321 | 'Australia/Sydney', 322 | 'Canada/Atlantic', 323 | 'Canada/Central', 324 | 'Canada/Eastern', 325 | 'Canada/Mountain', 326 | 'Canada/Newfoundland', 327 | 'Canada/Pacific', 328 | 'Europe/Amsterdam', 329 | 'Europe/Andorra', 330 | 'Europe/Athens', 331 | 'Europe/Belgrade', 332 | 'Europe/Berlin', 333 | 'Europe/Bratislava', 334 | 'Europe/Brussels', 335 | 'Europe/Bucharest', 336 | 'Europe/Budapest', 337 | 'Europe/Busingen', 338 | 'Europe/Chisinau', 339 | 'Europe/Copenhagen', 340 | 'Europe/Dublin', 341 | 'Europe/Gibraltar', 342 | 'Europe/Guernsey', 343 | 'Europe/Helsinki', 344 | 'Europe/Isle_of_Man', 345 | 'Europe/Istanbul', 346 | 'Europe/Jersey', 347 | 'Europe/Kaliningrad', 348 | 'Europe/Kyiv', 349 | 'Europe/Lisbon', 350 | 'Europe/Ljubljana', 351 | 'Europe/London', 352 | 'Europe/Luxembourg', 353 | 'Europe/Madrid', 354 | 'Europe/Malta', 355 | 'Europe/Mariehamn', 356 | 'Europe/Minsk', 357 | 'Europe/Monaco', 358 | 'Europe/Moscow', 359 | 'Europe/Oslo', 360 | 'Europe/Paris', 361 | 'Europe/Podgorica', 362 | 'Europe/Prague', 363 | 'Europe/Riga', 364 | 'Europe/Rome', 365 | 'Europe/Samara', 366 | 'Europe/San_Marino', 367 | 'Europe/Sarajevo', 368 | 'Europe/Simferopol', 369 | 'Europe/Skopje', 370 | 'Europe/Sofia', 371 | 'Europe/Stockholm', 372 | 'Europe/Tallinn', 373 | 'Europe/Tirane', 374 | 'Europe/Uzhgorod', 375 | 'Europe/Vaduz', 376 | 'Europe/Vatican', 377 | 'Europe/Vienna', 378 | 'Europe/Vilnius', 379 | 'Europe/Volgograd', 380 | 'Europe/Warsaw', 381 | 'Europe/Zagreb', 382 | 'Europe/Zaporozhye', 383 | 'Europe/Zurich', 384 | 'GMT', 385 | 'Indian/Antananarivo', 386 | 'Indian/Chagos', 387 | 'Indian/Christmas', 388 | 'Indian/Cocos', 389 | 'Indian/Comoro', 390 | 'Indian/Kerguelen', 391 | 'Indian/Mahe', 392 | 'Indian/Maldives', 393 | 'Indian/Mauritius', 394 | 'Indian/Mayotte', 395 | 'Indian/Reunion', 396 | 'Pacific/Apia', 397 | 'Pacific/Auckland', 398 | 'Pacific/Chatham', 399 | 'Pacific/Chuuk', 400 | 'Pacific/Easter', 401 | 'Pacific/Efate', 402 | 'Pacific/Enderbury', 403 | 'Pacific/Fakaofo', 404 | 'Pacific/Fiji', 405 | 'Pacific/Funafuti', 406 | 'Pacific/Galapagos', 407 | 'Pacific/Gambier', 408 | 'Pacific/Guadalcanal', 409 | 'Pacific/Guam', 410 | 'Pacific/Honolulu', 411 | 'Pacific/Johnston', 412 | 'Pacific/Kiritimati', 413 | 'Pacific/Kosrae', 414 | 'Pacific/Kwajalein', 415 | 'Pacific/Majuro', 416 | 'Pacific/Marquesas', 417 | 'Pacific/Midway', 418 | 'Pacific/Nauru', 419 | 'Pacific/Niue', 420 | 'Pacific/Norfolk', 421 | 'Pacific/Noumea', 422 | 'Pacific/Pago_Pago', 423 | 'Pacific/Palau', 424 | 'Pacific/Pitcairn', 425 | 'Pacific/Pohnpei', 426 | 'Pacific/Port_Moresby', 427 | 'Pacific/Rarotonga', 428 | 'Pacific/Saipan', 429 | 'Pacific/Tahiti', 430 | 'Pacific/Tarawa', 431 | 'Pacific/Tongatapu', 432 | 'Pacific/Wake', 433 | 'Pacific/Wallis', 434 | 'US/Alaska', 435 | 'US/Arizona', 436 | 'US/Central', 437 | 'US/Eastern', 438 | 'US/Hawaii', 439 | 'US/Mountain', 440 | 'US/Pacific', 441 | 'UTC' 442 | ]; 443 | 444 | const int _maxMillisecondsSinceEpoch = 8640000000000000; 445 | const int _maxSecondsSinceEpoch = 8640000000000; 446 | 447 | class FilterReport { 448 | int originalLocationsCount = 0; 449 | int originalTransitionsCount = 0; 450 | int newLocationsCount = 0; 451 | int newTransitionsCount = 0; 452 | } 453 | 454 | class FilteredLocationDatabase { 455 | final LocationDatabase db; 456 | final FilterReport report; 457 | 458 | FilteredLocationDatabase(this.db, this.report); 459 | } 460 | 461 | FilteredLocationDatabase filterTimeZoneData(LocationDatabase db, 462 | {int dateFrom = TZDateTime.minMillisecondsSinceEpoch, 463 | int dateTo = TZDateTime.maxMillisecondsSinceEpoch, 464 | List locations = const []}) { 465 | final report = FilterReport(); 466 | final result = LocationDatabase(); 467 | 468 | final locationsSet = HashSet.from(locations); 469 | 470 | report.originalLocationsCount = db.locations.length; 471 | 472 | for (final l in db.locations.values) { 473 | if (locationsSet.isNotEmpty && !locationsSet.contains(l.name)) { 474 | continue; 475 | } 476 | 477 | final transitionsCount = l.transitionAt.length; 478 | report.originalTransitionsCount += transitionsCount; 479 | 480 | final newTransitionAt = []; 481 | final newTransitionZone = []; 482 | 483 | if (transitionsCount == 0) { 484 | result.add(Location(l.name, newTransitionAt, newTransitionZone, l.zones)); 485 | continue; 486 | } 487 | 488 | var i = 0; 489 | 490 | while (i < transitionsCount && dateFrom > l.transitionAt[i]) { 491 | i++; 492 | } 493 | 494 | if (i < transitionsCount) { 495 | newTransitionAt.add(TZDateTime.minMillisecondsSinceEpoch); 496 | newTransitionZone.add(l.transitionZone[i]); 497 | i++; 498 | report.newTransitionsCount++; 499 | 500 | while (i < transitionsCount && l.transitionAt[i] <= dateTo) { 501 | newTransitionAt.add(l.transitionAt[i]); 502 | newTransitionZone.add(l.transitionZone[i]); 503 | i++; 504 | report.newTransitionsCount++; 505 | } 506 | } else { 507 | newTransitionAt.add(TZDateTime.minMillisecondsSinceEpoch); 508 | newTransitionZone.add(l.transitionZone[i - 1]); 509 | } 510 | 511 | result.add(Location(l.name, newTransitionAt, newTransitionZone, l.zones)); 512 | report.newLocationsCount++; 513 | } 514 | 515 | return FilteredLocationDatabase(result, report); 516 | } 517 | 518 | /// Convert [tzfile.Location] to [Location] 519 | Location tzfileLocationToNativeLocation(tzfile.Location loc) { 520 | // convert to milliseconds 521 | final transitionAt = loc.transitionAt 522 | .map((i) => 523 | (i < -_maxSecondsSinceEpoch) ? -_maxMillisecondsSinceEpoch : i * 1000) 524 | .toList(); 525 | 526 | final zones = []; 527 | 528 | for (final z in loc.zones) { 529 | zones.add(TimeZone(z.offset * 1000, 530 | isDst: z.isDst, abbreviation: loc.abbreviations[z.abbreviationIndex])); 531 | } 532 | 533 | return Location(loc.name, transitionAt, loc.transitionZone, zones); 534 | } 535 | -------------------------------------------------------------------------------- /lib/src/tzdata/zicfile.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the timezone project authors. Please see the AUTHORS 2 | // file for details. All rights reserved. Use of this source code is governed 3 | // by a BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:collection'; 6 | import 'dart:convert' show ascii; 7 | import 'dart:typed_data'; 8 | 9 | /// Time Zone information file magic header "TZif" 10 | const int _ziMagic = 1415211366; 11 | 12 | /// tzfile header structure 13 | class _Header { 14 | /// Header size 15 | static int size = 6 * 4; 16 | 17 | /// The number of UTC/local indicators stored in the file. 18 | final int tzh_ttisgmtcnt; 19 | 20 | /// The number of standard/wall indicators stored in the file. 21 | final int tzh_ttisstdcnt; 22 | 23 | /// The number of leap seconds for which data is stored in the file. 24 | final int tzh_leapcnt; 25 | 26 | /// The number of "transition times" for which data is stored in the file. 27 | final int tzh_timecnt; 28 | 29 | /// The number of "local time types" for which data is stored in the file 30 | /// (must not be zero). 31 | final int tzh_typecnt; 32 | 33 | /// The number of characters of "timezone abbreviation strings" stored in the 34 | /// file. 35 | final int tzh_charcnt; 36 | 37 | _Header(this.tzh_ttisgmtcnt, this.tzh_ttisstdcnt, this.tzh_leapcnt, 38 | this.tzh_timecnt, this.tzh_typecnt, this.tzh_charcnt); 39 | 40 | int dataLength(int longSize) { 41 | final leapBytes = tzh_leapcnt * (longSize + 4); 42 | final timeBytes = tzh_timecnt * (longSize + 1); 43 | final typeBytes = tzh_typecnt * 6; 44 | 45 | return tzh_ttisgmtcnt + 46 | tzh_ttisstdcnt + 47 | leapBytes + 48 | timeBytes + 49 | typeBytes + 50 | tzh_charcnt; 51 | } 52 | 53 | factory _Header.fromBytes(List rawData) { 54 | final data = rawData is Uint8List ? rawData : Uint8List.fromList(rawData); 55 | 56 | final bdata = 57 | data.buffer.asByteData(data.offsetInBytes, data.lengthInBytes); 58 | 59 | final tzh_ttisgmtcnt = bdata.getInt32(0); 60 | final tzh_ttisstdcnt = bdata.getInt32(4); 61 | final tzh_leapcnt = bdata.getInt32(8); 62 | final tzh_timecnt = bdata.getInt32(12); 63 | final tzh_typecnt = bdata.getInt32(16); 64 | final tzh_charcnt = bdata.getInt32(20); 65 | 66 | return _Header(tzh_ttisgmtcnt, tzh_ttisstdcnt, tzh_leapcnt, tzh_timecnt, 67 | tzh_typecnt, tzh_charcnt); 68 | } 69 | } 70 | 71 | /// Read NULL-terminated string 72 | String _readByteString(Uint8List data, int offset) { 73 | for (var i = offset; i < data.length; i++) { 74 | if (data[i] == 0) { 75 | return ascii.decode( 76 | data.buffer.asUint8List(data.offsetInBytes + offset, i - offset)); 77 | } 78 | } 79 | return ascii.decode(data.buffer.asUint8List(data.offsetInBytes + offset)); 80 | } 81 | 82 | /// This exception is thrown when Zone Info data is invalid. 83 | class InvalidZoneInfoDataException implements Exception { 84 | final String msg; 85 | 86 | InvalidZoneInfoDataException(this.msg); 87 | 88 | @override 89 | String toString() => msg; 90 | } 91 | 92 | /// TimeZone data 93 | class TimeZone { 94 | /// Offset in seconds east of UTC. 95 | final int offset; 96 | 97 | /// DST time. 98 | final bool isDst; 99 | 100 | /// Index to abbreviation. 101 | final int abbreviationIndex; 102 | 103 | const TimeZone(this.offset, 104 | {required this.isDst, required this.abbreviationIndex}); 105 | } 106 | 107 | /// Location data 108 | class Location { 109 | /// [Location] name 110 | final String name; 111 | 112 | /// Time in seconds when the transitioning is occured. 113 | final List transitionAt; 114 | 115 | /// Transition zone index. 116 | final List transitionZone; 117 | 118 | /// List of abbreviations. 119 | final List abbreviations; 120 | 121 | /// List of [TimeZone]s. 122 | final List zones; 123 | 124 | /// Time in seconds when the leap seconds should be applied. 125 | final List leapAt; 126 | 127 | /// Amount of leap seconds that should be applied. 128 | final List leapDiff; 129 | 130 | /// Whether transition times associated with local time types are specified as 131 | /// standard time or wall time. 132 | final List isStd; 133 | 134 | /// Whether transition times associated with local time types are specified as 135 | /// UTC or local time. 136 | final List isUtc; 137 | 138 | Location( 139 | this.name, 140 | this.transitionAt, 141 | this.transitionZone, 142 | this.abbreviations, 143 | this.zones, 144 | this.leapAt, 145 | this.leapDiff, 146 | this.isStd, 147 | this.isUtc); 148 | 149 | /// Deserialize [Location] from bytes 150 | factory Location.fromBytes(String name, List rawData) { 151 | final data = rawData is Uint8List ? rawData : Uint8List.fromList(rawData); 152 | 153 | final bdata = 154 | data.buffer.asByteData(data.offsetInBytes, data.lengthInBytes); 155 | 156 | final magic1 = bdata.getUint32(0); 157 | if (magic1 != _ziMagic) { 158 | throw InvalidZoneInfoDataException('Invalid magic header "$magic1"'); 159 | } 160 | final version1 = bdata.getUint8(4); 161 | 162 | var offset = 20; 163 | 164 | switch (version1) { 165 | case 0: 166 | final header = _Header.fromBytes( 167 | Uint8List.view(bdata.buffer, offset, _Header.size)); 168 | 169 | // calculating data offsets 170 | final dataOffset = offset + _Header.size; 171 | final transitionAtOffset = dataOffset; 172 | final transitionZoneOffset = 173 | transitionAtOffset + header.tzh_timecnt * 5; 174 | final abbreviationsOffset = 175 | transitionZoneOffset + header.tzh_typecnt * 6; 176 | final leapOffset = abbreviationsOffset + header.tzh_charcnt; 177 | final stdOrWctOffset = leapOffset + header.tzh_leapcnt * 8; 178 | final utcOrGmtOffset = stdOrWctOffset + header.tzh_ttisstdcnt; 179 | 180 | // read transitions 181 | final transitionAt = []; 182 | final transitionZone = []; 183 | 184 | offset = transitionAtOffset; 185 | 186 | for (var i = 0; i < header.tzh_timecnt; i++) { 187 | transitionAt.add(bdata.getInt32(offset)); 188 | offset += 4; 189 | } 190 | 191 | for (var i = 0; i < header.tzh_timecnt; i++) { 192 | transitionZone.add(bdata.getUint8(offset)); 193 | offset += 1; 194 | } 195 | 196 | // function to read from abbreviation buffer 197 | final abbreviationsData = data.buffer.asUint8List( 198 | data.offsetInBytes + abbreviationsOffset, header.tzh_charcnt); 199 | final abbreviations = []; 200 | final abbreviationsCache = HashMap(); 201 | int readAbbreviation(int offset) { 202 | var result = abbreviationsCache[offset]; 203 | if (result == null) { 204 | result = abbreviations.length; 205 | abbreviationsCache[offset] = result; 206 | abbreviations.add(_readByteString(abbreviationsData, offset)); 207 | } 208 | return result; 209 | } 210 | 211 | // read zones 212 | final zones = []; 213 | offset = transitionZoneOffset; 214 | 215 | for (var i = 0; i < header.tzh_typecnt; i++) { 216 | final tt_gmtoff = bdata.getInt32(offset); 217 | final tt_isdst = bdata.getInt8(offset + 4); 218 | final tt_abbrind = bdata.getUint8(offset + 5); 219 | offset += 6; 220 | 221 | zones.add(TimeZone(tt_gmtoff, 222 | isDst: tt_isdst == 1, 223 | abbreviationIndex: readAbbreviation(tt_abbrind))); 224 | } 225 | 226 | // read leap seconds 227 | final leapAt = []; 228 | final leapDiff = []; 229 | 230 | offset = leapOffset; 231 | for (var i = 0; i < header.tzh_leapcnt; i++) { 232 | leapAt.add(bdata.getInt32(offset)); 233 | leapDiff.add(bdata.getInt32(offset + 4)); 234 | offset += 5; 235 | } 236 | 237 | // read std flags 238 | final isStd = []; 239 | 240 | offset = stdOrWctOffset; 241 | for (var i = 0; i < header.tzh_ttisstdcnt; i++) { 242 | isStd.add(bdata.getUint8(offset)); 243 | offset += 1; 244 | } 245 | 246 | // read utc flags 247 | final isUtc = []; 248 | 249 | offset = utcOrGmtOffset; 250 | for (var i = 0; i < header.tzh_ttisgmtcnt; i++) { 251 | isUtc.add(bdata.getUint8(offset)); 252 | offset += 1; 253 | } 254 | 255 | return Location(name, transitionAt, transitionZone, abbreviations, 256 | zones, leapAt, leapDiff, isStd, isUtc); 257 | 258 | case 50: 259 | case 51: 260 | // skip old version header/data 261 | final header1 = _Header.fromBytes( 262 | Uint8List.view(bdata.buffer, offset, _Header.size)); 263 | offset += _Header.size + header1.dataLength(4); 264 | 265 | final magic2 = bdata.getUint32(offset); 266 | if (magic2 != _ziMagic) { 267 | throw InvalidZoneInfoDataException( 268 | 'Invalid second magic header "$magic2"'); 269 | } 270 | 271 | final version2 = bdata.getUint8(offset + 4); 272 | if (version2 != version1) { 273 | throw InvalidZoneInfoDataException( 274 | 'Second version "$version2" doesn\'t match first version ' 275 | '"$version1"'); 276 | } 277 | 278 | offset += 20; 279 | 280 | final header2 = _Header.fromBytes( 281 | Uint8List.view(bdata.buffer, offset, _Header.size)); 282 | 283 | // calculating data offsets 284 | final dataOffset = offset + _Header.size; 285 | final transitionAtOffset = dataOffset; 286 | final transitionZoneOffset = 287 | transitionAtOffset + header2.tzh_timecnt * 9; 288 | final abbreviationsOffset = 289 | transitionZoneOffset + header2.tzh_typecnt * 6; 290 | final leapOffset = abbreviationsOffset + header2.tzh_charcnt; 291 | final stdOrWctOffset = leapOffset + header2.tzh_leapcnt * 12; 292 | final utcOrGmtOffset = stdOrWctOffset + header2.tzh_ttisstdcnt; 293 | 294 | // read transitions 295 | final transitionAt = []; 296 | final transitionZone = []; 297 | 298 | offset = transitionAtOffset; 299 | 300 | for (var i = 0; i < header2.tzh_timecnt; i++) { 301 | transitionAt.add(bdata.getInt64(offset)); 302 | offset += 8; 303 | } 304 | 305 | for (var i = 0; i < header2.tzh_timecnt; i++) { 306 | transitionZone.add(bdata.getUint8(offset)); 307 | offset += 1; 308 | } 309 | 310 | // function to read from abbreviation buffer 311 | final abbreviationsData = data.buffer.asUint8List( 312 | data.offsetInBytes + abbreviationsOffset, header2.tzh_charcnt); 313 | final abbreviations = []; 314 | final abbreviationsCache = HashMap(); 315 | int readAbbreviation(int offset) { 316 | var result = abbreviationsCache[offset]; 317 | if (result == null) { 318 | result = abbreviations.length; 319 | abbreviationsCache[offset] = result; 320 | abbreviations.add(_readByteString(abbreviationsData, offset)); 321 | } 322 | return result; 323 | } 324 | 325 | // read transition info 326 | final zones = []; 327 | offset = transitionZoneOffset; 328 | 329 | for (var i = 0; i < header2.tzh_typecnt; i++) { 330 | final tt_gmtoff = bdata.getInt32(offset); 331 | final tt_isdst = bdata.getInt8(offset + 4); 332 | final tt_abbrind = bdata.getUint8(offset + 5); 333 | offset += 6; 334 | 335 | zones.add(TimeZone(tt_gmtoff, 336 | isDst: tt_isdst == 1, 337 | abbreviationIndex: readAbbreviation(tt_abbrind))); 338 | } 339 | 340 | // read leap seconds 341 | final leapAt = []; 342 | final leapDiff = []; 343 | 344 | offset = leapOffset; 345 | for (var i = 0; i < header2.tzh_leapcnt; i++) { 346 | leapAt.add(bdata.getInt64(offset)); 347 | leapDiff.add(bdata.getInt32(offset + 8)); 348 | offset += 9; 349 | } 350 | 351 | // read std flags 352 | final isStd = []; 353 | 354 | offset = stdOrWctOffset; 355 | for (var i = 0; i < header2.tzh_ttisstdcnt; i++) { 356 | isStd.add(bdata.getUint8(offset)); 357 | offset += 1; 358 | } 359 | 360 | // read utc flags 361 | final isUtc = []; 362 | 363 | offset = utcOrGmtOffset; 364 | for (var i = 0; i < header2.tzh_ttisgmtcnt; i++) { 365 | isUtc.add(bdata.getUint8(offset)); 366 | offset += 1; 367 | } 368 | 369 | return Location(name, transitionAt, transitionZone, abbreviations, 370 | zones, leapAt, leapDiff, isStd, isUtc); 371 | 372 | default: 373 | throw InvalidZoneInfoDataException('Unknown version: $version1'); 374 | } 375 | } 376 | } 377 | -------------------------------------------------------------------------------- /lib/src/tzdata/zone_tab.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the timezone project authors. Please see the AUTHORS 2 | // file for details. All rights reserved. Use of this source code is governed 3 | // by a BSD-style license that can be found in the LICENSE file. 4 | 5 | /// zone1970.tab file parser 6 | library timezone.src.tzdata.zone_tab; 7 | 8 | /// Latitude and longitude of the zone's principal location 9 | /// in ISO 6709 sign-degrees-minutes-seconds format, 10 | /// either +-DDMM+-DDDMM or +-DDMMSS+-DDDMMSS, 11 | /// first latitude (+ is north), then longitude (+ is east). 12 | final _geoLocationRe = 13 | RegExp(r'^([+\-])(\d{2,3})(\d{2})(\d{2})?([+\-])(\d{2,3})(\d{2})(\d{2})?$'); 14 | 15 | class LocationDescription { 16 | final String name; 17 | final List countryCodes; 18 | final double latitude; 19 | final double longitude; 20 | final String comments; 21 | 22 | LocationDescription(this.name, this.countryCodes, this.latitude, 23 | this.longitude, this.comments); 24 | 25 | factory LocationDescription.fromString(String line) { 26 | final parts = line.split('\t'); 27 | final countryCodes = parts[0].split(','); 28 | final name = parts[2]; 29 | final comments = parts.length > 3 ? parts[3] : ''; 30 | 31 | final match = _geoLocationRe.firstMatch(parts[1])!; 32 | final latSign = match.group(1) == '+' ? 1 : -1; 33 | final latDeg = int.parse(match.group(2)!); 34 | final latMinutes = int.parse(match.group(3)!); 35 | final latSecondsRaw = match.group(4); 36 | final latSeconds = latSecondsRaw != null ? int.parse(latSecondsRaw) : 0; 37 | 38 | final longSign = match.group(5) == '+' ? 1 : -1; 39 | final longDeg = int.parse(match.group(6)!); 40 | final longMinutes = int.parse(match.group(7)!); 41 | final longSecondsRaw = match.group(8); 42 | final longSeconds = longSecondsRaw != null ? int.parse(longSecondsRaw) : 0; 43 | 44 | final latitude = latSign * (latDeg + (latMinutes + (latSeconds / 60)) / 60); 45 | final longitude = 46 | longSign * (longDeg + (longMinutes + (longSeconds / 60)) / 60); 47 | 48 | return LocationDescription( 49 | name, countryCodes, latitude, longitude, comments); 50 | } 51 | } 52 | 53 | class LocationDescriptionDatabase { 54 | final List locations; 55 | 56 | LocationDescriptionDatabase(this.locations); 57 | 58 | factory LocationDescriptionDatabase.fromString(String data) { 59 | final lines = data.replaceAll('\r\n', '\n').split('\n'); 60 | final locations = []; 61 | for (final line in lines) { 62 | if (line.isEmpty || line[0].startsWith('#')) { 63 | continue; 64 | } 65 | locations.add(LocationDescription.fromString(line)); 66 | } 67 | 68 | return LocationDescriptionDatabase(locations); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/src/tzdb.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the timezone project authors. Please see the AUTHORS 2 | // file for details. All rights reserved. Use of this source code is governed 3 | // by a BSD-style license that can be found in the LICENSE file. 4 | 5 | /// TimeZone db file. 6 | library timezone.src.tzdb; 7 | 8 | import 'dart:collection'; 9 | import 'dart:convert' show ascii; 10 | import 'dart:typed_data'; 11 | import 'package:timezone/src/location.dart'; 12 | import 'package:timezone/src/location_database.dart'; 13 | 14 | /// Serialize TimeZone Database 15 | List tzdbSerialize(LocationDatabase db) { 16 | final locationsInBytes = >[]; 17 | var bufferLength = 0; 18 | 19 | for (final l in db.locations.values.toList() 20 | ..sort((l, r) => l.name.compareTo(r.name))) { 21 | List b = _serializeLocation(l); 22 | locationsInBytes.add(b); 23 | bufferLength += 8 + b.length; 24 | bufferLength = _align(bufferLength, 8); 25 | } 26 | 27 | final r = Uint8List(bufferLength); 28 | final rb = r.buffer.asByteData(); 29 | 30 | var offset = 0; 31 | for (final b in locationsInBytes) { 32 | var length = _align(b.length, 8); 33 | rb.setUint32(offset, length); 34 | r.setAll(offset + 8, b); 35 | offset += 8 + length; 36 | } 37 | 38 | return r; 39 | } 40 | 41 | /// Deserialize TimeZone Database 42 | Iterable tzdbDeserialize(List rawData) sync* { 43 | final data = rawData is Uint8List ? rawData : Uint8List.fromList(rawData); 44 | final bdata = data.buffer.asByteData(data.offsetInBytes, data.lengthInBytes); 45 | 46 | var offset = 0; 47 | while (offset < data.length) { 48 | final length = bdata.getUint32(offset); 49 | // u32 _pad; 50 | assert((length % 8) == 0); 51 | offset += 8; 52 | 53 | yield _deserializeLocation( 54 | data.buffer.asUint8List(data.offsetInBytes + offset, length)); 55 | offset += length; 56 | } 57 | } 58 | 59 | Uint8List _serializeLocation(Location location) { 60 | var offset = 0; 61 | 62 | final abbreviations = []; 63 | final abbreviationsIndex = HashMap(); 64 | final zoneAbbreviationOffsets = []; 65 | 66 | // The number of bytes of all abbreviations. 67 | var abbreviationsLength = 0; 68 | for (final z in location.zones) { 69 | final ai = abbreviationsIndex.putIfAbsent(z.abbreviation, () { 70 | final ret = abbreviations.length; 71 | abbreviationsLength += z.abbreviation.length + 1; // abbreviation + '\0' 72 | abbreviations.add(z.abbreviation); 73 | return ret; 74 | }); 75 | 76 | zoneAbbreviationOffsets.add(ai); 77 | } 78 | 79 | final List encName = ascii.encode(location.name); 80 | 81 | final nameOffset = 32; 82 | final nameLength = encName.length; 83 | final abbreviationsOffset = nameOffset + nameLength; 84 | final zonesOffset = _align(abbreviationsOffset + abbreviationsLength, 4); 85 | final zonesLength = location.zones.length; 86 | final transitionsOffset = _align(zonesOffset + zonesLength * 8, 8); 87 | final transitionsLength = location.transitionAt.length; 88 | 89 | final bufferLength = _align(transitionsOffset + transitionsLength * 9, 8); 90 | 91 | final result = Uint8List(bufferLength); 92 | final buffer = ByteData.view(result.buffer); 93 | 94 | // write header 95 | buffer.setUint32(0, nameOffset); 96 | buffer.setUint32(4, nameLength); 97 | buffer.setUint32(8, abbreviationsOffset); 98 | buffer.setUint32(12, abbreviationsLength); 99 | buffer.setUint32(16, zonesOffset); 100 | buffer.setUint32(20, zonesLength); 101 | buffer.setUint32(24, transitionsOffset); 102 | buffer.setUint32(28, transitionsLength); 103 | 104 | // write name 105 | offset = nameOffset; 106 | for (final c in encName) { 107 | buffer.setUint8(offset++, c); 108 | } 109 | 110 | // Write abbreviations. 111 | offset = abbreviationsOffset; 112 | for (final a in abbreviations) { 113 | for (final c in a.codeUnits) { 114 | buffer.setUint8(offset++, c); 115 | } 116 | buffer.setUint8(offset++, 0); 117 | } 118 | 119 | // write zones 120 | offset = zonesOffset; 121 | for (var i = 0; i < location.zones.length; i++) { 122 | final zone = location.zones[i]; 123 | buffer.setInt32(offset, zone.offset ~/ 1000); // convert to sec 124 | buffer.setUint8(offset + 4, zone.isDst ? 1 : 0); 125 | buffer.setUint8(offset + 5, zoneAbbreviationOffsets[i]); 126 | offset += 8; 127 | } 128 | 129 | // write transitions 130 | offset = transitionsOffset; 131 | for (final tAt in location.transitionAt) { 132 | final t = (tAt / 1000).floorToDouble(); 133 | buffer.setFloat64(offset, t.toDouble()); // convert to sec 134 | offset += 8; 135 | } 136 | 137 | for (final tZone in location.transitionZone) { 138 | buffer.setUint8(offset, tZone); 139 | offset += 1; 140 | } 141 | 142 | return result; 143 | } 144 | 145 | Location _deserializeLocation(Uint8List data) { 146 | final bdata = data.buffer.asByteData(data.offsetInBytes, data.lengthInBytes); 147 | var offset = 0; 148 | 149 | // Header 150 | // 151 | // struct { 152 | // u32 nameOffset; 153 | // u32 nameLength; 154 | // u32 abbrsOffset; 155 | // u32 abbrsLength; 156 | // u32 zonesOffset; 157 | // u32 zonesLength; 158 | // u32 transitionsOffset; 159 | // u32 transitionsLength; 160 | // } header; 161 | final nameOffset = bdata.getUint32(0); 162 | final nameLength = bdata.getUint32(4); 163 | final abbreviationsOffset = bdata.getUint32(8); 164 | final abbreviationsLength = bdata.getUint32(12); 165 | final zonesOffset = bdata.getUint32(16); 166 | final zonesLength = bdata.getUint32(20); 167 | final transitionsOffset = bdata.getUint32(24); 168 | final transitionsLength = bdata.getUint32(28); 169 | 170 | final name = ascii.decode( 171 | data.buffer.asUint8List(data.offsetInBytes + nameOffset, nameLength)); 172 | final abbreviations = []; 173 | final zones = []; 174 | final transitionAt = []; 175 | final transitionZone = []; 176 | 177 | // Abbreviations 178 | // 179 | // \0 separated strings 180 | offset = abbreviationsOffset; 181 | final abbreviationsEnd = abbreviationsOffset + abbreviationsLength; 182 | for (var i = abbreviationsOffset; i < abbreviationsEnd; i++) { 183 | if (data[i] == 0) { 184 | final abbreviation = ascii.decode( 185 | data.buffer.asUint8List(data.offsetInBytes + offset, i - offset)); 186 | abbreviations.add(abbreviation); 187 | offset = i + 1; 188 | } 189 | } 190 | 191 | // TimeZones 192 | // 193 | // struct { 194 | // i32 offset; // in seconds 195 | // u8 isDst; 196 | // u8 abbrIndex; 197 | // u8 _pad[2]; 198 | // } zones[zonesLength]; // at zoneOffset 199 | offset = zonesOffset; 200 | assert((offset % 4) == 0); 201 | for (var i = 0; i < zonesLength; i++) { 202 | final zoneOffset = bdata.getInt32(offset) * 1000; // convert to ms 203 | final zoneIsDst = bdata.getUint8(offset + 4); 204 | final zoneAbbreviationIndex = bdata.getUint8(offset + 5); 205 | offset += 8; 206 | zones.add(TimeZone(zoneOffset, 207 | isDst: zoneIsDst == 1, 208 | abbreviation: abbreviations[zoneAbbreviationIndex])); 209 | } 210 | 211 | // Transitions 212 | // 213 | // f64 transitionAt[transitionsLength]; // in seconds 214 | // u8 transitionZone[transitionLength]; // at (transitionsOffset + (transitionsLength * 8)) 215 | offset = transitionsOffset; 216 | assert((offset % 8) == 0); 217 | for (var i = 0; i < transitionsLength; i++) { 218 | transitionAt.add(bdata.getFloat64(offset).toInt() * 1000); // convert to ms 219 | offset += 8; 220 | } 221 | 222 | for (var i = 0; i < transitionsLength; i++) { 223 | transitionZone.add(bdata.getUint8(offset)); 224 | offset += 1; 225 | } 226 | 227 | return Location(name, transitionAt, transitionZone, zones); 228 | } 229 | 230 | int _align(int offset, int boundary) { 231 | final i = offset % boundary; 232 | return i == 0 ? offset : offset + (boundary - i); 233 | } 234 | -------------------------------------------------------------------------------- /lib/standalone.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the timezone project authors. Please see the AUTHORS 2 | // file for details. All rights reserved. Use of this source code is governed 3 | // by a BSD-style license that can be found in the LICENSE file. 4 | 5 | /// TimeZone initialization for standalone environments. 6 | /// 7 | /// ```dart 8 | /// import 'package:timezone/standalone.dart'; 9 | /// 10 | /// initializeTimeZone().then((_) { 11 | /// final detroit = getLocation('America/Detroit'); 12 | /// final now = TZDateTime.now(detroit); 13 | /// }); 14 | /// ``` 15 | library timezone.standalone; 16 | 17 | import 'dart:io' hide BytesBuilder; 18 | import 'dart:isolate'; 19 | import 'dart:typed_data'; 20 | import 'package:path/path.dart' as p; 21 | import 'package:timezone/timezone.dart'; 22 | 23 | export 'package:timezone/timezone.dart' 24 | show 25 | getLocation, 26 | setLocalLocation, 27 | TZDateTime, 28 | Location, 29 | TimeZone, 30 | timeZoneDatabase; 31 | 32 | final String tzDataDefaultPath = p.join('data', tzDataDefaultFilename); 33 | 34 | // Load file 35 | Future> _loadAsBytes(String path) async { 36 | final script = Platform.script; 37 | final scheme = Platform.script.scheme; 38 | 39 | if (scheme.startsWith('http')) { 40 | // TODO: This path is not tested. How would one get to this situation? 41 | return HttpClient() 42 | .getUrl(Uri( 43 | scheme: script.scheme, 44 | host: script.host, 45 | port: script.port, 46 | path: path)) 47 | .then((req) { 48 | return req.close(); 49 | }).then((response) { 50 | // join byte buffers 51 | return response 52 | .fold(BytesBuilder(), (BytesBuilder b, d) => b..add(d)) 53 | .then((builder) { 54 | return builder.takeBytes(); 55 | }); 56 | }); 57 | } else { 58 | var uri = await Isolate.resolvePackageUri( 59 | Uri(scheme: 'package', path: 'timezone/$path')); 60 | return File(p.fromUri(uri)).readAsBytes(); 61 | } 62 | } 63 | 64 | /// Initialize Time Zone database. 65 | /// 66 | /// Throws [TimeZoneInitException] when something is wrong. 67 | /// 68 | /// ```dart 69 | /// import 'package:timezone/standalone.dart'; 70 | /// 71 | /// initializeTimeZone().then(() { 72 | /// final detroit = getLocation('America/Detroit'); 73 | /// final detroitNow = TZDateTime.now(detroit); 74 | /// }); 75 | /// ``` 76 | Future initializeTimeZone([String? path]) { 77 | path ??= tzDataDefaultPath; 78 | return _loadAsBytes(path).then((rawData) { 79 | initializeDatabase(rawData); 80 | }).catchError((dynamic e) { 81 | throw TimeZoneInitException(e.toString()); 82 | }); 83 | } 84 | -------------------------------------------------------------------------------- /lib/timezone.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the timezone project authors. Please see the AUTHORS 2 | // file for details. All rights reserved. Use of this source code is governed 3 | // by a BSD-style license that can be found in the LICENSE file. 4 | 5 | /// # TimeZone library 6 | /// 7 | /// Time zone database and time zone aware DateTime. 8 | library timezone; 9 | 10 | export 'src/date_time.dart'; 11 | export 'src/env.dart'; 12 | export 'src/exceptions.dart'; 13 | export 'src/location.dart'; 14 | export 'src/location_database.dart'; 15 | -------------------------------------------------------------------------------- /lib/tzdata.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the timezone project authors. Please see the AUTHORS 2 | // file for details. All rights reserved. Use of this source code is governed 3 | // by a BSD-style license that can be found in the LICENSE file. 4 | 5 | /// Library to work with [tzdata](http://en.wikipedia.org/wiki/Tz_database) 6 | /// 7 | /// - Zic compiled zone files 8 | /// - zone.tab 9 | /// 10 | /// TODO: move to separate package 11 | library timezone.tzdata; 12 | 13 | export 'src/tzdata/zicfile.dart'; 14 | export 'src/tzdata/zone_tab.dart'; 15 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: timezone 2 | version: 0.10.1 3 | description: Time zone database and time zone aware DateTime. 4 | repository: https://github.com/srawlins/timezone 5 | environment: 6 | sdk: '>=2.19.0 <3.0.0' 7 | dependencies: 8 | http: ^1.2.1 9 | path: ^1.8.0 10 | dev_dependencies: 11 | args: any 12 | file: '>=6.0.0 <8.0.0' 13 | glob: ^2.0.0 14 | lints: ^3.0.0 15 | logging: ^1.2.0 16 | test: ^1.16.0 17 | -------------------------------------------------------------------------------- /test/data/US/Eastern: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/srawlins/timezone/64db18e450c10247e23ee36546865007b01cf135/test/data/US/Eastern -------------------------------------------------------------------------------- /test/data/zone1970.tab: -------------------------------------------------------------------------------- 1 | # comment 2 | AD +4230+00131 Europe/Andorra 3 | AE,OM +2518+05518 Asia/Dubai 4 | AQ -690022+0393524 Antarctica/Syowa Syowa Station, E Ongul I -------------------------------------------------------------------------------- /test/datetime_browser_test.dart: -------------------------------------------------------------------------------- 1 | @TestOn('browser') 2 | library; 3 | 4 | import 'package:test/test.dart'; 5 | import 'package:timezone/browser.dart'; 6 | 7 | Future main() async { 8 | await initializeTimeZone(); 9 | final detroit = getLocation('America/Detroit'); 10 | final la = getLocation('America/Los_Angeles'); 11 | final newYork = getLocation('America/New_York'); 12 | 13 | group('Constructors', () { 14 | test('Default', () { 15 | final t = TZDateTime(la, 2010, 1, 2, 3, 4, 5, 6); 16 | expect(t.toString(), equals('2010-01-02 03:04:05.006-0800')); 17 | }); 18 | 19 | test('from DateTime', () { 20 | final utcTime = DateTime.utc(2010, 1, 2, 3, 4, 5, 6); 21 | final t = TZDateTime.from(utcTime, newYork); 22 | expect(t.toString(), equals('2010-01-01 22:04:05.006-0500')); 23 | }); 24 | 25 | test('from TZDateTime', () { 26 | final laTime = TZDateTime(la, 2010, 1, 2, 3, 4, 5, 6); 27 | final t = TZDateTime.from(laTime, newYork); 28 | expect(t.toString(), equals('2010-01-02 06:04:05.006-0500')); 29 | }); 30 | 31 | test('fromMilliseconds', () { 32 | final t = TZDateTime.fromMillisecondsSinceEpoch(newYork, 1262430245006); 33 | expect(t.toString(), equals('2010-01-02 06:04:05.006-0500')); 34 | }); 35 | 36 | // This started failing, because Chrome is temporarily disallowing 37 | // microsecond timestamps? 38 | test('fromMicroseconds', () { 39 | final t = 40 | TZDateTime.fromMicrosecondsSinceEpoch(newYork, 1262430245006007); 41 | expect(t.toString(), equals('2010-01-02 06:04:05.006007-0500')); 42 | }, skip: true); 43 | 44 | test('utc', () { 45 | final t = TZDateTime.utc(2010, 1, 2, 3, 4, 5, 6); 46 | expect(t.toString(), equals('2010-01-02 03:04:05.006Z')); 47 | }); 48 | }); 49 | 50 | group('Offsets', () { 51 | group('UTC to America/Detroit', () { 52 | group('Simple translations', () { 53 | final u1 = DateTime.utc(1975, 1, 1, 5); 54 | final x1 = TZDateTime.from(u1, detroit); 55 | 56 | test('$u1 => 1975-01-01 00:00:00.000-0500', () { 57 | expect(x1.toString(), equals('1975-01-01 00:00:00.000-0500')); 58 | }); 59 | 60 | final u2 = u1.subtract(const Duration(milliseconds: 1)); 61 | final x2 = TZDateTime.from(u2, detroit); 62 | 63 | test('$u2 => 1974-12-31 23:59:59.999-0500', () { 64 | expect(x2.toString(), equals('1974-12-31 23:59:59.999-0500')); 65 | }); 66 | 67 | final u3 = u1.add(const Duration(milliseconds: 1)); 68 | final x3 = TZDateTime.from(u3, detroit); 69 | 70 | test('$u3 => 1975-01-01 00:00:00.001-0500', () { 71 | expect(x3.toString(), equals('1975-01-01 00:00:00.001-0500')); 72 | }); 73 | }); 74 | 75 | group('EWT/EPT boundaries', () { 76 | final u1 = DateTime.utc(1945, 09, 30, 6); 77 | final x1 = TZDateTime.from(u1, detroit); 78 | 79 | test('$u1 => 1945-09-30 01:00:00.000-0500', () { 80 | expect(x1.toString(), '1945-09-30 01:00:00.000-0500'); 81 | }); 82 | 83 | final u2 = u1.subtract(const Duration(milliseconds: 1)); 84 | final x2 = TZDateTime.from(u2, detroit); 85 | 86 | test('$u2 => 1945-09-30 01:59:59.999-0400', () { 87 | expect(x2.toString(), equals('1945-09-30 01:59:59.999-0400')); 88 | }); 89 | 90 | final u3 = u1.add(const Duration(milliseconds: 1)); 91 | final x3 = TZDateTime.from(u3, detroit); 92 | 93 | test('$u3 => 1945-09-30 01:00:00.001-0500', () { 94 | expect(x3.toString(), equals('1945-09-30 01:00:00.001-0500')); 95 | }); 96 | }); 97 | }); 98 | 99 | group('America/Detroit to UTC', () { 100 | group('EWT/EPT boundaries', () { 101 | final x1 = TZDateTime(detroit, 1945, 9, 30, 1); 102 | final u1 = DateTime.fromMillisecondsSinceEpoch( 103 | x1.millisecondsSinceEpoch, 104 | isUtc: true); 105 | 106 | test('$x1 => 1945-09-30 05:00:00.000Z', () { 107 | expect(u1.toString(), '1945-09-30 05:00:00.000Z'); 108 | }); 109 | 110 | final x2 = x1.subtract(const Duration(milliseconds: 1)); 111 | final u2 = DateTime.fromMillisecondsSinceEpoch( 112 | x2.millisecondsSinceEpoch, 113 | isUtc: true); 114 | 115 | test('$x2 => 1945-09-30 04:59:59.999Z', () { 116 | expect(u2.toString(), equals('1945-09-30 04:59:59.999Z')); 117 | }); 118 | 119 | final x3 = x1.add(const Duration(milliseconds: 1)); 120 | final u3 = DateTime.fromMillisecondsSinceEpoch( 121 | x3.millisecondsSinceEpoch, 122 | isUtc: true); 123 | 124 | test('$x3 => 1945-09-30 05:00:00.001Z', () { 125 | expect(u3.toString(), equals('1945-09-30 05:00:00.001Z')); 126 | }); 127 | 128 | final x4 = x1.add(const Duration(hours: 1)); 129 | final u4 = DateTime.fromMillisecondsSinceEpoch( 130 | x4.millisecondsSinceEpoch, 131 | isUtc: true); 132 | 133 | test('$x4 => 1945-09-30 06:00:00.000Z', () { 134 | expect(u4.toString(), equals('1945-09-30 06:00:00.000Z')); 135 | }); 136 | 137 | final x5 = x2.add(const Duration(hours: 1)); 138 | final u5 = DateTime.fromMillisecondsSinceEpoch( 139 | x5.millisecondsSinceEpoch, 140 | isUtc: true); 141 | 142 | test('$x5 => 1945-09-30 05:59:59.999Z', () { 143 | expect(u5.toString(), equals('1945-09-30 05:59:59.999Z')); 144 | }); 145 | }); 146 | }); 147 | }); 148 | } 149 | -------------------------------------------------------------------------------- /test/datetime_browser_test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | test_datetime_browser 8 | 9 | 10 | 11 | 12 |

13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /test/datetime_test.dart: -------------------------------------------------------------------------------- 1 | @TestOn('vm') 2 | library; 3 | 4 | import 'package:test/test.dart'; 5 | import 'package:timezone/data/latest.dart'; 6 | import 'package:timezone/timezone.dart'; 7 | 8 | Future main() async { 9 | initializeTimeZones(); 10 | final detroit = getLocation('America/Detroit'); 11 | final la = getLocation('America/Los_Angeles'); 12 | final newYork = getLocation('America/New_York'); 13 | 14 | group('Constructors', () { 15 | test('Default', () { 16 | final t = TZDateTime(la, 2010, 1, 2, 3, 4, 5, 6, 7); 17 | expect(t.toString(), equals('2010-01-02 03:04:05.006007-0800')); 18 | }); 19 | 20 | test('Default, only year argument', () { 21 | final t = TZDateTime(la, 2010); 22 | expect(t.toString(), equals('2010-01-01 00:00:00.000-0800')); 23 | }); 24 | 25 | test('from DateTime', () { 26 | final utcTime = DateTime.utc(2010, 1, 2, 3, 4, 5, 6, 7); 27 | final t = TZDateTime.from(utcTime, newYork); 28 | expect(t.toString(), equals('2010-01-01 22:04:05.006007-0500')); 29 | }); 30 | 31 | test('from local DateTime', () { 32 | final localTime = DateTime(2010, 1, 2, 3, 4, 5, 6); 33 | final newYorkTime = TZDateTime.from(localTime, newYork); 34 | // New York time should be 5 hours behind UTC. 35 | expect(newYorkTime.hour, 36 | equals(localTime.toUtc().subtract(Duration(hours: 5)).hour)); 37 | }); 38 | 39 | test('from TZDateTime', () { 40 | final laTime = TZDateTime(la, 2010, 1, 2, 3, 4, 5, 6, 7); 41 | final t = TZDateTime.from(laTime, newYork); 42 | expect(t.toString(), equals('2010-01-02 06:04:05.006007-0500')); 43 | }); 44 | 45 | test('fromMilliseconds', () { 46 | final t = TZDateTime.fromMillisecondsSinceEpoch(newYork, 1262430245006); 47 | expect(t.toString(), equals('2010-01-02 06:04:05.006-0500')); 48 | }); 49 | 50 | test('fromMicroseconds', () { 51 | final t = 52 | TZDateTime.fromMicrosecondsSinceEpoch(newYork, 1262430245006007); 53 | expect(t.toString(), equals('2010-01-02 06:04:05.006007-0500')); 54 | }); 55 | 56 | test('utc', () { 57 | final t = TZDateTime.utc(2010, 1, 2, 3, 4, 5, 6, 7); 58 | expect(t.toString(), equals('2010-01-02 03:04:05.006007Z')); 59 | }); 60 | }); 61 | 62 | group('Offsets', () { 63 | group('UTC to America/Detroit', () { 64 | group('Simple translations', () { 65 | final u1 = DateTime.utc(1975, 1, 1, 5); 66 | final x1 = TZDateTime.from(u1, detroit); 67 | 68 | test('$u1 => 1975-01-01 00:00:00.000-0500', () { 69 | expect(x1.toString(), equals('1975-01-01 00:00:00.000-0500')); 70 | }); 71 | 72 | final u2 = u1.subtract(const Duration(milliseconds: 1)); 73 | final x2 = TZDateTime.from(u2, detroit); 74 | 75 | test('$u2 => 1974-12-31 23:59:59.999-0500', () { 76 | expect(x2.toString(), equals('1974-12-31 23:59:59.999-0500')); 77 | }); 78 | 79 | final u3 = u1.add(const Duration(milliseconds: 1)); 80 | final x3 = TZDateTime.from(u3, detroit); 81 | 82 | test('$u3 => 1975-01-01 00:00:00.001-0500', () { 83 | expect(x3.toString(), equals('1975-01-01 00:00:00.001-0500')); 84 | }); 85 | }); 86 | 87 | group('EWT/EPT boundaries', () { 88 | final u1 = DateTime.utc(1945, 09, 30, 6); 89 | final x1 = TZDateTime.from(u1, detroit); 90 | 91 | test('$u1 => 1945-09-30 01:00:00.000-0500', () { 92 | expect(x1.toString(), '1945-09-30 01:00:00.000-0500'); 93 | }); 94 | 95 | final u2 = u1.subtract(const Duration(milliseconds: 1)); 96 | final x2 = TZDateTime.from(u2, detroit); 97 | 98 | test('$u2 => 1945-09-30 01:59:59.999-0400', () { 99 | expect(x2.toString(), equals('1945-09-30 01:59:59.999-0400')); 100 | }); 101 | 102 | final u3 = u1.add(const Duration(milliseconds: 1)); 103 | final x3 = TZDateTime.from(u3, detroit); 104 | 105 | test('$u3 => 1945-09-30 01:00:00.001-0500', () { 106 | expect(x3.toString(), equals('1945-09-30 01:00:00.001-0500')); 107 | }); 108 | }); 109 | }); 110 | 111 | group('America/Detroit to UTC', () { 112 | group('EWT/EPT boundaries', () { 113 | final x1 = TZDateTime(detroit, 1945, 9, 30, 1); 114 | final u1 = DateTime.fromMillisecondsSinceEpoch( 115 | x1.millisecondsSinceEpoch, 116 | isUtc: true); 117 | 118 | test('$x1 => 1945-09-30 05:00:00.000Z', () { 119 | expect(u1.toString(), '1945-09-30 05:00:00.000Z'); 120 | }); 121 | 122 | final x2 = x1.subtract(const Duration(milliseconds: 1)); 123 | final u2 = DateTime.fromMillisecondsSinceEpoch( 124 | x2.millisecondsSinceEpoch, 125 | isUtc: true); 126 | 127 | test('$x2 => 1945-09-30 04:59:59.999Z', () { 128 | expect(u2.toString(), equals('1945-09-30 04:59:59.999Z')); 129 | }); 130 | 131 | final x3 = x1.add(const Duration(milliseconds: 1)); 132 | final u3 = DateTime.fromMillisecondsSinceEpoch( 133 | x3.millisecondsSinceEpoch, 134 | isUtc: true); 135 | 136 | test('$x3 => 1945-09-30 05:00:00.001Z', () { 137 | expect(u3.toString(), equals('1945-09-30 05:00:00.001Z')); 138 | }); 139 | 140 | final x4 = x1.add(const Duration(hours: 1)); 141 | final u4 = DateTime.fromMillisecondsSinceEpoch( 142 | x4.millisecondsSinceEpoch, 143 | isUtc: true); 144 | 145 | test('$x4 => 1945-09-30 06:00:00.000Z', () { 146 | expect(u4.toString(), equals('1945-09-30 06:00:00.000Z')); 147 | }); 148 | 149 | final x5 = x2.add(const Duration(hours: 1)); 150 | final u5 = DateTime.fromMillisecondsSinceEpoch( 151 | x5.millisecondsSinceEpoch, 152 | isUtc: true); 153 | 154 | test('$x5 => 1945-09-30 05:59:59.999Z', () { 155 | expect(u5.toString(), equals('1945-09-30 05:59:59.999Z')); 156 | }); 157 | }); 158 | }); 159 | 160 | group('America/Detroit DST (negative offset)', () { 161 | // https://www.timeanddate.com/time/change/usa/detroit?year=2023 162 | group('EST/EDT transition', () { 163 | test('2 months before transition', () { 164 | final datetime = TZDateTime(detroit, 2023, 1, 12, 4); 165 | expect(datetime.toString(), '2023-01-12 04:00:00.000-0500'); 166 | }); 167 | 168 | test('1 hour before transition', () { 169 | final datetime = TZDateTime(detroit, 2023, 3, 12, 1); 170 | expect(datetime.toString(), '2023-03-12 01:00:00.000-0500'); 171 | }); 172 | 173 | test('lower transition', () { 174 | final datetime = TZDateTime(detroit, 2023, 3, 12, 2); 175 | expect(datetime.toString(), '2023-03-12 03:00:00.000-0400'); 176 | }); 177 | 178 | test('upper transition', () { 179 | final datetime = TZDateTime(detroit, 2023, 3, 12, 3); 180 | expect(datetime.toString(), '2023-03-12 03:00:00.000-0400'); 181 | }); 182 | 183 | test('1 hour after transition', () { 184 | final datetime = TZDateTime(detroit, 2023, 3, 12, 4); 185 | expect(datetime.toString(), '2023-03-12 04:00:00.000-0400'); 186 | }); 187 | 188 | test('2 months after transition', () { 189 | final datetime = TZDateTime(detroit, 2023, 5, 12, 4); 190 | expect(datetime.toString(), '2023-05-12 04:00:00.000-0400'); 191 | }); 192 | }); 193 | 194 | group('EDT/EST transition', () { 195 | test('2 months before transition', () { 196 | final datetime = TZDateTime(detroit, 2023, 9, 5, 1); 197 | expect(datetime.toString(), '2023-09-05 01:00:00.000-0400'); 198 | }); 199 | 200 | test('1 hour before transition', () { 201 | final datetime = TZDateTime(detroit, 2023, 11, 5); 202 | expect(datetime.toString(), '2023-11-05 00:00:00.000-0400'); 203 | }); 204 | 205 | test('lower transition', () { 206 | final datetime = TZDateTime(detroit, 2023, 11, 5, 1); 207 | expect(datetime.toString(), '2023-11-05 01:00:00.000-0400'); 208 | }); 209 | 210 | test('upper transition', () { 211 | final datetime = TZDateTime(detroit, 2023, 11, 5, 2); 212 | expect(datetime.toString(), '2023-11-05 02:00:00.000-0500'); 213 | }); 214 | 215 | test('1 hour after transition', () { 216 | final datetime = TZDateTime(detroit, 2023, 11, 5, 3); 217 | expect(datetime.toString(), '2023-11-05 03:00:00.000-0500'); 218 | }); 219 | 220 | test('2 months after transition', () { 221 | final datetime = TZDateTime(detroit, 2024, 1, 5, 2); 222 | expect(datetime.toString(), '2024-01-05 02:00:00.000-0500'); 223 | }); 224 | }); 225 | }); 226 | 227 | group('Europe/Berlin DST (positive offset)', () { 228 | // https://www.timeanddate.com/time/change/germany/berlin?year=2023 229 | final berlin = getLocation('Europe/Berlin'); 230 | 231 | group('EST/EDT transition', () { 232 | test('2 months before transition', () { 233 | final datetime = TZDateTime(berlin, 2023, 1, 26, 2); 234 | expect(datetime.toString(), '2023-01-26 02:00:00.000+0100'); 235 | }); 236 | 237 | test('1 hour before transition', () { 238 | final datetime = TZDateTime(berlin, 2023, 3, 26, 1); 239 | expect(datetime.toString(), '2023-03-26 01:00:00.000+0100'); 240 | }); 241 | 242 | test('lower transition', () { 243 | final datetime = TZDateTime(berlin, 2023, 3, 26, 2); 244 | expect(datetime.toString(), '2023-03-26 03:00:00.000+0200'); 245 | }); 246 | 247 | test('upper transition', () { 248 | final datetime = TZDateTime(berlin, 2023, 3, 26, 3); 249 | expect(datetime.toString(), '2023-03-26 03:00:00.000+0200'); 250 | }); 251 | 252 | test('1 hour after transition', () { 253 | final datetime = TZDateTime(berlin, 2023, 3, 26, 4); 254 | expect(datetime.toString(), '2023-03-26 04:00:00.000+0200'); 255 | }); 256 | 257 | test('2 months after transition', () { 258 | final datetime = TZDateTime(berlin, 2023, 5, 26, 3); 259 | expect(datetime.toString(), '2023-05-26 03:00:00.000+0200'); 260 | }); 261 | }); 262 | 263 | group('EDT/EST transition', () { 264 | test('2 months before transition', () { 265 | final datetime = TZDateTime(berlin, 2023, 8, 29, 2); 266 | expect(datetime.toString(), '2023-08-29 02:00:00.000+0200'); 267 | }); 268 | 269 | test('1 hour before transition', () { 270 | final datetime = TZDateTime(berlin, 2023, 10, 29, 1); 271 | expect(datetime.toString(), '2023-10-29 01:00:00.000+0200'); 272 | }); 273 | 274 | test('lower transition', () { 275 | final datetime = TZDateTime(berlin, 2023, 10, 29, 2); 276 | expect(datetime.toString(), '2023-10-29 02:00:00.000+0100'); 277 | }); 278 | 279 | test('upper transition', () { 280 | final datetime = TZDateTime(berlin, 2023, 10, 29, 3); 281 | expect(datetime.toString(), '2023-10-29 03:00:00.000+0100'); 282 | }); 283 | 284 | test('1 hour after transition', () { 285 | final datetime = TZDateTime(berlin, 2023, 10, 29, 4); 286 | expect(datetime.toString(), '2023-10-29 04:00:00.000+0100'); 287 | }); 288 | 289 | test('2 months after transition', () { 290 | final datetime = TZDateTime(berlin, 2024, 1, 29, 3); 291 | expect(datetime.toString(), '2024-01-29 03:00:00.000+0100'); 292 | }); 293 | }); 294 | }); 295 | }); 296 | 297 | group('Timezones', () { 298 | test( 299 | 'getLocation throws $LocationNotFoundException for unrecognized ' 300 | 'timezone', () { 301 | expect(() => getLocation('non-existent-location'), 302 | throwsA(TypeMatcher())); 303 | }); 304 | }); 305 | } 306 | -------------------------------------------------------------------------------- /test/datetime_test_no_database.dart: -------------------------------------------------------------------------------- 1 | @TestOn('vm') 2 | library; 3 | 4 | import 'package:test/test.dart'; 5 | import 'package:timezone/timezone.dart'; 6 | 7 | void main() { 8 | group('Without initializing timezone database', () { 9 | test('Can still construct TZDateTime.utc', () { 10 | TZDateTime.utc(2019); 11 | }); 12 | 13 | test( 14 | 'getLocation throws $LocationNotFoundException with message about the ' 15 | 'database not being initialized', () { 16 | expect( 17 | () => getLocation('America/New_York'), 18 | throwsA(TypeMatcher() 19 | .having((e) => e.msg, 'msg', contains('database')))); 20 | }); 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /test/two_way_test.dart: -------------------------------------------------------------------------------- 1 | @TestOn('vm') 2 | library; 3 | 4 | import 'package:test/test.dart'; 5 | import 'package:timezone/standalone.dart'; 6 | 7 | Future main() async { 8 | const timeStep = 15 * 60 * 1000; // 15 minutes 9 | final startTime = 10 | (DateTime.now().millisecondsSinceEpoch ~/ timeStep) * timeStep; 11 | await initializeTimeZone(); 12 | 13 | test('Europe/London', () { 14 | final location = getLocation('Europe/London'); 15 | for (var i = startTime; i >= 0; i -= timeStep) { 16 | var time1 = DateTime.fromMillisecondsSinceEpoch(i); 17 | 18 | var tzTime1 = TZDateTime(location, time1.year, time1.month, time1.day, 19 | time1.hour, time1.minute); 20 | 21 | DateTime localTime2 = tzTime1.toLocal(); 22 | 23 | var tzTime2 = TZDateTime.from(localTime2, location); 24 | 25 | expect(tzTime1.toString(), equals(tzTime2.toString())); 26 | } 27 | }); 28 | 29 | test('America/Detroit', () { 30 | final location = getLocation('America/Detroit'); 31 | for (var i = startTime; i >= 0; i -= timeStep) { 32 | var time1 = DateTime.fromMillisecondsSinceEpoch(i); 33 | 34 | var tzTime1 = TZDateTime(location, time1.year, time1.month, time1.day, 35 | time1.hour, time1.minute); 36 | 37 | DateTime localTime2 = tzTime1.toLocal(); 38 | 39 | var tzTime2 = TZDateTime.from(localTime2, location); 40 | 41 | expect(tzTime1.toString(), equals(tzTime2.toString())); 42 | } 43 | }); 44 | 45 | test('America/Los_Angeles', () { 46 | final location = getLocation('America/Los_Angeles'); 47 | for (var i = startTime; i >= 0; i -= timeStep) { 48 | var time1 = DateTime.fromMillisecondsSinceEpoch(i); 49 | 50 | var tzTime1 = TZDateTime(location, time1.year, time1.month, time1.day, 51 | time1.hour, time1.minute); 52 | 53 | DateTime localTime2 = tzTime1.toLocal(); 54 | 55 | var tzTime2 = TZDateTime.from(localTime2, location); 56 | 57 | expect(tzTime1.toString(), equals(tzTime2.toString())); 58 | } 59 | }); 60 | 61 | test('America/New_York', () { 62 | final location = getLocation('America/New_York'); 63 | for (var i = startTime; i >= 0; i -= timeStep) { 64 | var time1 = DateTime.fromMillisecondsSinceEpoch(i); 65 | 66 | var tzTime1 = TZDateTime(location, time1.year, time1.month, time1.day, 67 | time1.hour, time1.minute); 68 | 69 | DateTime localTime2 = tzTime1.toLocal(); 70 | 71 | var tzTime2 = TZDateTime.from(localTime2, location); 72 | 73 | expect(tzTime1.toString(), equals(tzTime2.toString())); 74 | } 75 | }); 76 | } 77 | -------------------------------------------------------------------------------- /test/zicfile_test.dart: -------------------------------------------------------------------------------- 1 | @TestOn('vm') 2 | library; 3 | 4 | import 'dart:io'; 5 | import 'dart:isolate'; 6 | 7 | import 'package:path/path.dart' as p; 8 | import 'package:test/test.dart'; 9 | import 'package:timezone/tzdata.dart' as tzdata; 10 | 11 | void main() { 12 | test('Read US/Eastern 2014h tzfile', () async { 13 | var packageUri = Uri(scheme: 'package', path: 'timezone/timezone.dart'); 14 | var packagePath = p.dirname( 15 | p.dirname((await Isolate.resolvePackageUri(packageUri))!.toFilePath())); 16 | var locationDir = p.join(packagePath, 'test'); 17 | var rawData = 18 | await File(p.join(locationDir, 'data/US/Eastern')).readAsBytes(); 19 | final loc = tzdata.Location.fromBytes('US/Eastern', rawData); 20 | 21 | expect(loc.name, equals('US/Eastern')); 22 | expect(loc.abbreviations, equals(['LMT', 'EDT', 'EST', 'EWT', 'EPT'])); 23 | expect(loc.isStd, equals([0, 0, 0, 0, 1])); 24 | expect(loc.isUtc, equals([0, 0, 0, 0, 1])); 25 | expect(loc.leapAt, equals([])); 26 | expect(loc.leapDiff, equals([])); 27 | 28 | expect( 29 | loc.transitionAt, 30 | equals([ 31 | -576460752303423488, 32 | -2717650800, 33 | -1633280400, 34 | -1615140000, 35 | -1601830800, 36 | -1583690400, 37 | -1570381200, 38 | -1551636000, 39 | -1536512400, 40 | -1523210400, 41 | -1504458000, 42 | -1491760800, 43 | -1473008400, 44 | -1459706400, 45 | -1441558800, 46 | -1428256800, 47 | -1410109200, 48 | -1396807200, 49 | -1378659600, 50 | -1365357600, 51 | -1347210000, 52 | -1333908000, 53 | -1315155600, 54 | -1301853600, 55 | -1283706000, 56 | -1270404000, 57 | -1252256400, 58 | -1238954400, 59 | -1220806800, 60 | -1207504800, 61 | -1189357200, 62 | -1176055200, 63 | -1157302800, 64 | -1144605600, 65 | -1125853200, 66 | -1112551200, 67 | -1094403600, 68 | -1081101600, 69 | -1062954000, 70 | -1049652000, 71 | -1031504400, 72 | -1018202400, 73 | -1000054800, 74 | -986752800, 75 | -968000400, 76 | -955303200, 77 | -936550800, 78 | -923248800, 79 | -905101200, 80 | -891799200, 81 | -880218000, 82 | -769395600, 83 | -765396000, 84 | -747248400, 85 | -733946400, 86 | -715798800, 87 | -702496800, 88 | -684349200, 89 | -671047200, 90 | -652899600, 91 | -639597600, 92 | -620845200, 93 | -608148000, 94 | -589395600, 95 | -576093600, 96 | -557946000, 97 | -544644000, 98 | -526496400, 99 | -513194400, 100 | -495046800, 101 | -481744800, 102 | -463597200, 103 | -447271200, 104 | -431542800, 105 | -415821600, 106 | -400093200, 107 | -384372000, 108 | -368643600, 109 | -352922400, 110 | -337194000, 111 | -321472800, 112 | -305744400, 113 | -289418400, 114 | -273690000, 115 | -257968800, 116 | -242240400, 117 | -226519200, 118 | -210790800, 119 | -195069600, 120 | -179341200, 121 | -163620000, 122 | -147891600, 123 | -131565600, 124 | -116442000, 125 | -100116000, 126 | -84387600, 127 | -68666400, 128 | -52938000, 129 | -37216800, 130 | -21488400, 131 | -5767200, 132 | 9961200, 133 | 25682400, 134 | 41410800, 135 | 57736800, 136 | 73465200, 137 | 89186400, 138 | 104914800, 139 | 120636000, 140 | 126687600, 141 | 152085600, 142 | 162370800, 143 | 183535200, 144 | 199263600, 145 | 215589600, 146 | 230713200, 147 | 247039200, 148 | 262767600, 149 | 278488800, 150 | 294217200, 151 | 309938400, 152 | 325666800, 153 | 341388000, 154 | 357116400, 155 | 372837600, 156 | 388566000, 157 | 404892000, 158 | 420015600, 159 | 436341600, 160 | 452070000, 161 | 467791200, 162 | 483519600, 163 | 499240800, 164 | 514969200, 165 | 530690400, 166 | 544604400, 167 | 562140000, 168 | 576054000, 169 | 594194400, 170 | 607503600, 171 | 625644000, 172 | 638953200, 173 | 657093600, 174 | 671007600, 175 | 688543200, 176 | 702457200, 177 | 719992800, 178 | 733906800, 179 | 752047200, 180 | 765356400, 181 | 783496800, 182 | 796806000, 183 | 814946400, 184 | 828860400, 185 | 846396000, 186 | 860310000, 187 | 877845600, 188 | 891759600, 189 | 909295200, 190 | 923209200, 191 | 941349600, 192 | 954658800, 193 | 972799200, 194 | 986108400, 195 | 1004248800, 196 | 1018162800, 197 | 1035698400, 198 | 1049612400, 199 | 1067148000, 200 | 1081062000, 201 | 1099202400, 202 | 1112511600, 203 | 1130652000, 204 | 1143961200, 205 | 1162101600, 206 | 1173596400, 207 | 1194156000, 208 | 1205046000, 209 | 1225605600, 210 | 1236495600, 211 | 1257055200, 212 | 1268550000, 213 | 1289109600, 214 | 1299999600, 215 | 1320559200, 216 | 1331449200, 217 | 1352008800, 218 | 1362898800, 219 | 1383458400, 220 | 1394348400, 221 | 1414908000, 222 | 1425798000, 223 | 1446357600, 224 | 1457852400, 225 | 1478412000, 226 | 1489302000, 227 | 1509861600, 228 | 1520751600, 229 | 1541311200, 230 | 1552201200, 231 | 1572760800, 232 | 1583650800, 233 | 1604210400, 234 | 1615705200, 235 | 1636264800, 236 | 1647154800, 237 | 1667714400, 238 | 1678604400, 239 | 1699164000, 240 | 1710054000, 241 | 1730613600, 242 | 1741503600, 243 | 1762063200, 244 | 1772953200, 245 | 1793512800, 246 | 1805007600, 247 | 1825567200, 248 | 1836457200, 249 | 1857016800, 250 | 1867906800, 251 | 1888466400, 252 | 1899356400, 253 | 1919916000, 254 | 1930806000, 255 | 1951365600, 256 | 1962860400, 257 | 1983420000, 258 | 1994310000, 259 | 2014869600, 260 | 2025759600, 261 | 2046319200, 262 | 2057209200, 263 | 2077768800, 264 | 2088658800, 265 | 2109218400, 266 | 2120108400, 267 | 2140668000 268 | ])); 269 | expect( 270 | loc.transitionZone, 271 | equals([ 272 | 0, 273 | 2, 274 | 1, 275 | 2, 276 | 1, 277 | 2, 278 | 1, 279 | 2, 280 | 1, 281 | 2, 282 | 1, 283 | 2, 284 | 1, 285 | 2, 286 | 1, 287 | 2, 288 | 1, 289 | 2, 290 | 1, 291 | 2, 292 | 1, 293 | 2, 294 | 1, 295 | 2, 296 | 1, 297 | 2, 298 | 1, 299 | 2, 300 | 1, 301 | 2, 302 | 1, 303 | 2, 304 | 1, 305 | 2, 306 | 1, 307 | 2, 308 | 1, 309 | 2, 310 | 1, 311 | 2, 312 | 1, 313 | 2, 314 | 1, 315 | 2, 316 | 1, 317 | 2, 318 | 1, 319 | 2, 320 | 1, 321 | 2, 322 | 3, 323 | 4, 324 | 2, 325 | 1, 326 | 2, 327 | 1, 328 | 2, 329 | 1, 330 | 2, 331 | 1, 332 | 2, 333 | 1, 334 | 2, 335 | 1, 336 | 2, 337 | 1, 338 | 2, 339 | 1, 340 | 2, 341 | 1, 342 | 2, 343 | 1, 344 | 2, 345 | 1, 346 | 2, 347 | 1, 348 | 2, 349 | 1, 350 | 2, 351 | 1, 352 | 2, 353 | 1, 354 | 2, 355 | 1, 356 | 2, 357 | 1, 358 | 2, 359 | 1, 360 | 2, 361 | 1, 362 | 2, 363 | 1, 364 | 2, 365 | 1, 366 | 2, 367 | 1, 368 | 2, 369 | 1, 370 | 2, 371 | 1, 372 | 2, 373 | 1, 374 | 2, 375 | 1, 376 | 2, 377 | 1, 378 | 2, 379 | 1, 380 | 2, 381 | 1, 382 | 2, 383 | 1, 384 | 2, 385 | 1, 386 | 2, 387 | 1, 388 | 2, 389 | 1, 390 | 2, 391 | 1, 392 | 2, 393 | 1, 394 | 2, 395 | 1, 396 | 2, 397 | 1, 398 | 2, 399 | 1, 400 | 2, 401 | 1, 402 | 2, 403 | 1, 404 | 2, 405 | 1, 406 | 2, 407 | 1, 408 | 2, 409 | 1, 410 | 2, 411 | 1, 412 | 2, 413 | 1, 414 | 2, 415 | 1, 416 | 2, 417 | 1, 418 | 2, 419 | 1, 420 | 2, 421 | 1, 422 | 2, 423 | 1, 424 | 2, 425 | 1, 426 | 2, 427 | 1, 428 | 2, 429 | 1, 430 | 2, 431 | 1, 432 | 2, 433 | 1, 434 | 2, 435 | 1, 436 | 2, 437 | 1, 438 | 2, 439 | 1, 440 | 2, 441 | 1, 442 | 2, 443 | 1, 444 | 2, 445 | 1, 446 | 2, 447 | 1, 448 | 2, 449 | 1, 450 | 2, 451 | 1, 452 | 2, 453 | 1, 454 | 2, 455 | 1, 456 | 2, 457 | 1, 458 | 2, 459 | 1, 460 | 2, 461 | 1, 462 | 2, 463 | 1, 464 | 2, 465 | 1, 466 | 2, 467 | 1, 468 | 2, 469 | 1, 470 | 2, 471 | 1, 472 | 2, 473 | 1, 474 | 2, 475 | 1, 476 | 2, 477 | 1, 478 | 2, 479 | 1, 480 | 2, 481 | 1, 482 | 2, 483 | 1, 484 | 2, 485 | 1, 486 | 2, 487 | 1, 488 | 2, 489 | 1, 490 | 2, 491 | 1, 492 | 2, 493 | 1, 494 | 2, 495 | 1, 496 | 2, 497 | 1, 498 | 2, 499 | 1, 500 | 2, 501 | 1, 502 | 2, 503 | 1, 504 | 2, 505 | 1, 506 | 2, 507 | 1, 508 | 2 509 | ])); 510 | }); 511 | } 512 | -------------------------------------------------------------------------------- /test/zone_tab_test.dart: -------------------------------------------------------------------------------- 1 | @TestOn('vm') 2 | library timezone.test.zone_tab_test; 3 | 4 | import 'dart:io'; 5 | import 'dart:isolate'; 6 | 7 | import 'package:path/path.dart' as p; 8 | import 'package:test/test.dart'; 9 | import 'package:timezone/tzdata.dart' as tzdata; 10 | 11 | void main() { 12 | test('Read zone1970.tab file', () async { 13 | var packageUri = Uri(scheme: 'package', path: 'timezone/timezone.dart'); 14 | var packagePath = p.dirname( 15 | p.dirname((await Isolate.resolvePackageUri(packageUri))!.toFilePath())); 16 | var locationDir = p.join(packagePath, 'test'); 17 | var rawData = 18 | await File(p.join(locationDir, 'data/zone1970.tab')).readAsString(); 19 | final db = tzdata.LocationDescriptionDatabase.fromString(rawData); 20 | 21 | expect(db.locations[0].name, equals('Europe/Andorra')); 22 | expect(db.locations[0].countryCodes, equals(['AD'])); 23 | expect(db.locations[0].latitude, equals(42.5)); 24 | expect(db.locations[0].longitude, equals(1.5166666666666666)); 25 | expect(db.locations[0].comments, isEmpty); 26 | 27 | expect(db.locations[1].name, equals('Asia/Dubai')); 28 | expect(db.locations[1].countryCodes, equals(['AE', 'OM'])); 29 | expect(db.locations[1].latitude, equals(25.3)); 30 | expect(db.locations[1].longitude, equals(55.3)); 31 | expect(db.locations[1].comments, isEmpty); 32 | 33 | expect(db.locations[2].name, equals('Antarctica/Syowa')); 34 | expect(db.locations[2].countryCodes, equals(['AQ'])); 35 | expect(db.locations[2].latitude, equals(-69.00611111111111)); 36 | expect(db.locations[2].longitude, equals(39.59)); 37 | expect(db.locations[2].comments, equals('Syowa Station, E Ongul I')); 38 | }); 39 | } 40 | -------------------------------------------------------------------------------- /tool/encode_dart.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'dart:typed_data'; 3 | 4 | import 'package:path/path.dart' as p; 5 | 6 | Future main(List args) async { 7 | final tzDataPath = args[0]; 8 | final dartLibraryPath = args[1]; 9 | final bytes = File(tzDataPath).readAsBytesSync(); 10 | final generatedDartFile = generateDartFile( 11 | name: p.basenameWithoutExtension(tzDataPath), 12 | data: bytesAsString(bytes), 13 | ); 14 | File(dartLibraryPath).writeAsStringSync(generatedDartFile); 15 | } 16 | 17 | String bytesAsString(Uint8List bytes) { 18 | assert(bytes.length.isEven); 19 | return bytes.buffer 20 | .asUint16List() 21 | .map((u) => '\\u${u.toRadixString(16).padLeft(4, '0')}') 22 | .join(); 23 | } 24 | 25 | String generateDartFile({required String name, required String data}) => 26 | '''// This is a generated file. Do not edit. 27 | import 'dart:typed_data'; 28 | 29 | import 'package:timezone/src/env.dart'; 30 | import 'package:timezone/src/exceptions.dart'; 31 | 32 | /// Initialize Time Zone database from $name. 33 | /// 34 | /// Throws [TimeZoneInitException] when something is wrong. 35 | void initializeTimeZones() { 36 | try { 37 | initializeDatabase( 38 | Uint16List.fromList(_embeddedData.codeUnits).buffer.asUint8List()); 39 | } 40 | // ignore: avoid_catches_without_on_clauses 41 | catch (e) { 42 | throw TimeZoneInitException(e.toString()); 43 | } 44 | } 45 | 46 | const _embeddedData = 47 | '$data'; 48 | '''; 49 | -------------------------------------------------------------------------------- /tool/encode_tzf.dart: -------------------------------------------------------------------------------- 1 | /// A tool to create the .tzf databases from a zoneinfo directory. 2 | /// 3 | /// Usage example: 4 | /// 5 | /// ```sh 6 | /// pub run tool/encode_tzf --zoneinfo path/to/zoneinfo 7 | /// ``` 8 | library; 9 | 10 | import 'dart:io'; 11 | import 'package:args/args.dart'; 12 | import 'package:file/file.dart' as pkg_file; 13 | import 'package:glob/glob.dart'; 14 | import 'package:glob/list_local_fs.dart'; 15 | import 'package:logging/logging.dart'; 16 | import 'package:path/path.dart' as p; 17 | 18 | import 'package:timezone/src/tools.dart'; 19 | import 'package:timezone/src/tzdb.dart'; 20 | import 'package:timezone/timezone.dart'; 21 | import 'package:timezone/tzdata.dart' as tzfile; 22 | 23 | Future main(List arguments) async { 24 | // Initialize logger 25 | Logger.root.level = Level.ALL; 26 | Logger.root.onRecord.listen((LogRecord rec) { 27 | print('${rec.level.name}: ${rec.time}: ${rec.message}'); 28 | }); 29 | final log = Logger('main'); 30 | 31 | // Parse CLI arguments 32 | final parser = ArgParser() 33 | ..addOption('output-all', defaultsTo: 'lib/data/latest_all.tzf') 34 | ..addOption('output-common', defaultsTo: 'lib/data/latest.tzf') 35 | ..addOption('output-10y', defaultsTo: 'lib/data/latest_10y.tzf') 36 | ..addOption('zoneinfo'); 37 | final args = parser.parse(arguments); 38 | 39 | final zoneinfoPath = args['zoneinfo'] as String?; 40 | if (zoneinfoPath == null) { 41 | print('Usage:\n${parser.usage}'); 42 | exit(1); 43 | } 44 | 45 | final db = LocationDatabase(); 46 | 47 | log.info('Importing zoneinfo files'); 48 | final files = await Glob('**').list(root: zoneinfoPath).toList(); 49 | for (final f in files) { 50 | if (f is pkg_file.File) { 51 | final name = p.relative(f.path, from: zoneinfoPath).replaceAll('\\', '/'); 52 | log.info('- $name'); 53 | db.add(tzfileLocationToNativeLocation( 54 | tzfile.Location.fromBytes(name, await f.readAsBytes()))); 55 | } 56 | } 57 | 58 | void logReport(FilterReport r) { 59 | log.info(' + locations: ${r.originalLocationsCount} => ' 60 | '${r.newLocationsCount}'); 61 | log.info(' + transitions: ${r.originalTransitionsCount} => ' 62 | '${r.newTransitionsCount}'); 63 | } 64 | 65 | log.info('Building location databases:'); 66 | 67 | log.info('- all locations'); 68 | final allDb = filterTimeZoneData(db); 69 | logReport(allDb.report); 70 | 71 | log.info('- common locations from all locations'); 72 | final commonDb = filterTimeZoneData(allDb.db, locations: commonLocations); 73 | logReport(commonDb.report); 74 | 75 | log.info('- [+- 5 years] from common locations'); 76 | final common_10y_Db = filterTimeZoneData(commonDb.db, 77 | dateFrom: DateTime(DateTime.now().year - 5, 1, 1).millisecondsSinceEpoch, 78 | dateTo: DateTime(DateTime.now().year + 5, 1, 1).millisecondsSinceEpoch, 79 | locations: commonLocations); 80 | logReport(common_10y_Db.report); 81 | 82 | log.info('Serializing location databases'); 83 | Future write(String file, LocationDatabase db) => 84 | File(file).writeAsBytes(tzdbSerialize(db), flush: true); 85 | await write(args['output-all'] as String, allDb.db); 86 | await write(args['output-common'] as String, commonDb.db); 87 | await write(args['output-10y'] as String, common_10y_Db.db); 88 | } 89 | -------------------------------------------------------------------------------- /tool/get.dart: -------------------------------------------------------------------------------- 1 | /// A tool to download and compile TimeZone database 2 | /// 3 | /// Usage example: 4 | /// 5 | /// ```sh 6 | /// dart pub run tool/get 7 | /// dart pub run tool/encode_dart lib/data/latest.{tzf,dart} 8 | /// dart pub run tool/encode_dart lib/data/latest_all.{tzf,dart} 9 | /// dart pub run tool/encode_dart lib/data/latest_10y.{tzf,dart} 10 | /// ``` 11 | library; 12 | 13 | import 'dart:async'; 14 | import 'dart:io'; 15 | import 'package:args/args.dart'; 16 | import 'package:file/file.dart' as pkg_file; 17 | import 'package:glob/glob.dart'; 18 | import 'package:glob/list_local_fs.dart'; 19 | import 'package:logging/logging.dart'; 20 | import 'package:path/path.dart' as p; 21 | 22 | import 'package:timezone/src/tools.dart'; 23 | import 'package:timezone/src/tzdb.dart'; 24 | import 'package:timezone/timezone.dart'; 25 | import 'package:timezone/tzdata.dart' as tzfile; 26 | 27 | final outPath = p.join('lib', 'data'); 28 | 29 | const _zicDataFiles = [ 30 | 'africa', 31 | 'antarctica', 32 | 'asia', 33 | 'australasia', 34 | 'etcetera', 35 | 'europe', 36 | 'northamerica', 37 | 'southamerica', 38 | 'backward' 39 | ]; 40 | 41 | const _repositoryUri = 'https://data.iana.org/time-zones'; 42 | 43 | /// Load [tzfile.Location] from tzfile. 44 | Future loadTzfileLocation(String name, String path) async { 45 | final rawData = await File(path).readAsBytes(); 46 | return tzfile.Location.fromBytes(name, rawData); 47 | } 48 | 49 | /// Download IANA Time Zone database to [dest] directory. 50 | Future downloadTzData(String version, String dest) async { 51 | final outPath = p.join(dest, 'tzdata$version.tar.gz'); 52 | final client = HttpClient(); 53 | try { 54 | var uri = version == 'latest' 55 | ? Uri.parse('$_repositoryUri/tzdata-$version.tar.gz') 56 | : Uri.parse('$_repositoryUri/releases/tzdata$version.tar.gz'); 57 | 58 | final req = await client.getUrl(uri); 59 | final resp = await req.close(); 60 | final out = File(outPath); 61 | final sink = out.openWrite(); 62 | try { 63 | await sink.addStream(resp); 64 | } finally { 65 | await sink.close(); 66 | } 67 | } finally { 68 | client.close(); 69 | } 70 | return outPath; 71 | } 72 | 73 | /// Unpack IANA Time Zone database to [dest] directory. 74 | Future unpackTzData(String archivePath, String dest) async { 75 | final result = 76 | await Process.run('tar', ['--directory=$dest', '-zxf', archivePath]); 77 | if (result.exitCode == 0) { 78 | return true; 79 | } 80 | throw Exception(result.stderr); 81 | } 82 | 83 | /// Compile IANA Time Zone file with zic compiler. 84 | Future zic(String src, String dest) async { 85 | final result = await Process.run('zic', ['-d', dest, src]); 86 | if (result.exitCode == 0) { 87 | return true; 88 | } 89 | throw Exception(result.stderr); 90 | } 91 | 92 | Future main(List arguments) async { 93 | // Initialize logger 94 | Logger.root.level = Level.ALL; 95 | Logger.root.onRecord.listen((LogRecord rec) { 96 | print('${rec.level.name}: ${rec.time}: ${rec.message}'); 97 | }); 98 | final log = Logger('main'); 99 | 100 | // Parse CLI arguments 101 | final parser = ArgParser() 102 | ..addOption('source', abbr: 's', defaultsTo: 'latest'); 103 | final argResults = parser.parse(arguments); 104 | 105 | final source = argResults['source'] as String?; 106 | 107 | if (source == null || source.isEmpty) { 108 | print(parser.usage); 109 | exit(64); 110 | } 111 | 112 | final tzfileLocations = []; 113 | 114 | log.info('Creating temp directory'); 115 | 116 | final tmpDir = await Directory.systemTemp.createTemp('tzdata-'); 117 | log.info('Temp directory created: ${tmpDir.path}'); 118 | try { 119 | log.info('Downloading timezone data'); 120 | final archivePath = await downloadTzData(source /*!*/, tmpDir.path); 121 | 122 | log.info('Unpacking timezone data: $archivePath'); 123 | await unpackTzData(archivePath, tmpDir.path); 124 | 125 | log.info('Creating zic directory'); 126 | final zicDirPath = p.join(tmpDir.path, 'zic'); 127 | await Directory(zicDirPath).create(); 128 | 129 | log.info('Compiling timezone data with zic compiler'); 130 | for (final i in _zicDataFiles) { 131 | log.info('- $i'); 132 | await zic(p.join(tmpDir.path, i), zicDirPath); 133 | } 134 | 135 | log.info('Importing tzfile Locations'); 136 | final files = await Glob('**/*').list(root: zicDirPath).toList(); 137 | for (final f in files) { 138 | if (f is pkg_file.File) { 139 | final name = p.relative(f.path, from: zicDirPath); 140 | log.info('- $name'); 141 | final loc = await loadTzfileLocation(name, f.path); 142 | tzfileLocations.add(loc); 143 | } 144 | } 145 | } finally { 146 | log.info('Cleaning up temporary directories'); 147 | await tmpDir.delete(recursive: true); 148 | } 149 | 150 | log.info('Converting tzfile Locations to native Locations'); 151 | final db = LocationDatabase(); 152 | for (final loc in tzfileLocations) { 153 | db.add(tzfileLocationToNativeLocation(loc)); 154 | } 155 | void logReport(FilterReport r) { 156 | log.info(' + locations: ${r.originalLocationsCount} => ' 157 | '${r.newLocationsCount}'); 158 | log.info(' + transitions: ${r.originalTransitionsCount} => ' 159 | '${r.newTransitionsCount}'); 160 | } 161 | 162 | log.info('Building location databases:'); 163 | 164 | log.info('- all locations'); 165 | final allDb = filterTimeZoneData(db); 166 | logReport(allDb.report); 167 | 168 | log.info('- common locations from all locations'); 169 | final commonDb = filterTimeZoneData(allDb.db, locations: commonLocations); 170 | logReport(commonDb.report); 171 | 172 | log.info('- [+- 5 years] from common locations'); 173 | final common_10y_Db = filterTimeZoneData(commonDb.db, 174 | dateFrom: DateTime(DateTime.now().year - 5, 1, 1).millisecondsSinceEpoch, 175 | dateTo: DateTime(DateTime.now().year + 5, 1, 1).millisecondsSinceEpoch, 176 | locations: commonLocations); 177 | logReport(common_10y_Db.report); 178 | 179 | log.info('Serializing location databases'); 180 | final allOut = File(p.join(outPath, '${source}_all.tzf')); 181 | final commonOut = File(p.join(outPath, '$source.tzf')); 182 | final common_10y_Out = File(p.join(outPath, '${source}_10y.tzf')); 183 | await allOut.writeAsBytes(tzdbSerialize(allDb.db), flush: true); 184 | await commonOut.writeAsBytes(tzdbSerialize(commonDb.db), flush: true); 185 | await common_10y_Out.writeAsBytes(tzdbSerialize(common_10y_Db.db), 186 | flush: true); 187 | 188 | exit(0); 189 | } 190 | -------------------------------------------------------------------------------- /tool/refresh.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | set -e 3 | 4 | [[ $0 != 'tool/refresh.sh' ]] && echo "Must be run as tool/refresh.sh" && exit 5 | 6 | dart pub get 7 | 8 | temp=$(mktemp -d -t tzdata-XXXXXXXXXX) 9 | 10 | pushd $temp > /dev/null 11 | 12 | echo "Fetching latest database..." 13 | curl https://data.iana.org/time-zones/tzdata-latest.tar.gz | tar -zx 14 | 15 | echo "Compiling into zoneinfo files..." 16 | mkdir zoneinfo 17 | make rearguard.zi 18 | zic -d zoneinfo -b fat rearguard.zi 19 | 20 | popd > /dev/null 21 | 22 | mkdir -p lib/data 23 | 24 | # Pass the zoneinfo directory to the encoding script 25 | dart tool/encode_tzf.dart --zoneinfo $temp/zoneinfo 26 | 27 | rm -r $temp 28 | 29 | # Create the source embeddings 30 | for scope in latest latest_all latest_10y; do 31 | echo "Creating embedding: $scope..." 32 | dart tool/encode_dart.dart lib/data/$scope.{tzf,dart} 33 | done 34 | 35 | dart format lib/data 36 | -------------------------------------------------------------------------------- /tool/test_random.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | import 'dart:math'; 4 | import 'package:args/args.dart'; 5 | import 'package:timezone/standalone.dart'; 6 | 7 | final int minEpochTime = DateTime.utc(1890).millisecondsSinceEpoch ~/ 1000; 8 | final int maxEpochTime = DateTime.utc(2020).millisecondsSinceEpoch ~/ 1000; 9 | 10 | Future dateCmd(int time, String tz) { 11 | List dateArgs; 12 | if (Platform.isMacOS) { 13 | dateArgs = ['-r', '$time', '+%Y-%m-%d %H:%M:%S']; 14 | } else if (Platform.isLinux) { 15 | dateArgs = ['-d', '@$time', '+%Y-%m-%d %H:%M:%S']; 16 | } else { 17 | throw UnimplementedError( 18 | 'Tool does not support ${Platform.operatingSystem} yet.'); 19 | } 20 | return Process.run('date', dateArgs, environment: {'TZ': tz}).then((r) { 21 | return r.stdout as String; 22 | }); 23 | } 24 | 25 | /// Run random tests against `date(1)` unix command 26 | void main(List arguments) async { 27 | // Parse CLI arguments 28 | final parser = ArgParser() 29 | ..addOption('iterations', abbr: 'i', defaultsTo: '1000') 30 | ..addOption('seed', abbr: 's', defaultsTo: '0'); 31 | 32 | final argResults = parser.parse(arguments); 33 | 34 | final randomRange = maxEpochTime - minEpochTime; 35 | 36 | final seed = int.parse(argResults['seed'] as String); 37 | var r = Random(seed); 38 | 39 | final iterations = int.parse(argResults['iterations'] as String); 40 | print('Seed: $seed'); 41 | print('Iterations: $iterations'); 42 | 43 | await initializeTimeZone(); 44 | final zoneNames = timeZoneDatabase.locations.keys.map((k) => k).toList(); 45 | final zoneCount = zoneNames.length; 46 | 47 | for (var i = 0; i < iterations; i++) { 48 | final time = r.nextInt(randomRange) + minEpochTime; 49 | final tz = zoneNames[r.nextInt(zoneCount)]; 50 | 51 | var dateOutput = (await dateCmd(time, tz)).trim(); 52 | final tzTime = 53 | TZDateTime.fromMillisecondsSinceEpoch(getLocation(tz), time * 1000); 54 | 55 | if (dateOutput != tzTime.toString().substring(0, 19)) { 56 | print('$i: $tz $time "$tzTime" != $dateOutput'); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tool/zone_dump.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:args/args.dart'; 4 | import 'package:timezone/standalone.dart'; 5 | 6 | /// Print Location details 7 | void main(List arguments) { 8 | // Parse CLI arguments 9 | final parser = ArgParser() 10 | ..addOption('source', abbr: 'd', defaultsTo: tzDataDefaultPath) 11 | ..addOption('location', abbr: 'l'); 12 | 13 | final argResults = parser.parse(arguments); 14 | 15 | final source = argResults['source'] as String?; 16 | final location = argResults['location'] as String?; 17 | 18 | if (source == null || 19 | source.isEmpty || 20 | location == null || 21 | location.isEmpty) { 22 | print(parser.usage); 23 | exit(64); 24 | } 25 | 26 | initializeTimeZone(source).then((_) { 27 | final l = getLocation(location); 28 | 29 | for (var i = 0; i < l.transitionAt.length; i++) { 30 | final t = l.transitionAt[i]; 31 | final z = l.zones[l.transitionZone[i]]; 32 | 33 | final dt = DateTime.fromMillisecondsSinceEpoch(t, isUtc: true); 34 | print('${dt.toIso8601String()} $z'); 35 | } 36 | 37 | exit(0); 38 | }); 39 | } 40 | --------------------------------------------------------------------------------