这是indexloc提供的服务,不要输入任何密码
Skip to content

Location agnostic time zone parsing #30

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,20 @@ jobs:
- name: Run tests
run: melos run test

- name: Run integration tests
- name: Run petstore integration tests
run: |
cd integration_test/petstore/petstore_test
dart pub get
dart test --concurrency=1
dart test --concurrency=1

- name: Run music streaming integration tests
run: |
cd integration_test/music_streaming/music_streaming_test
dart pub get
dart test --concurrency=1

- name: Run gov integration tests
run: |
cd integration_test/gov/gov_test
dart pub get
dart test --concurrency=1
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"dart.flutterSdkPath": ".fvm/versions/3.32.0",
"cSpell.words": [
"Pubspec"
"Pubspec",
"tonik"
]
}
27 changes: 6 additions & 21 deletions docs/data_types.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ This document provides information about how Tonik is mapping data types in Open

| OAS Type | OAS Format | Dart Type | Dart Package | Comment |
|----------|------------|-----------|--------------|---------|
| `string` | `date-time` | `DateTime` | `dart:core` | See [Timezone-Aware DateTime Parsing](#timezone-aware-datetime-parsing) |
| `string` | `date-time` | `DateTime` / `OffsetDateTime` | `dart:core` / `tonik_util` | See [Timezone-Aware DateTime Parsing](#timezone-aware-datetime-parsing) |
| `string` | `date` | `Date` | `tonik_util` | RFC3339 date format (YYYY-MM-DD) |
| `string` | `decimal`, `currency`, `money`, `number` | `BigDecimal` | `big_decimal` | High-precision decimal numbers |
| `string` | `uri`, `url` | `Uri` | `dart:core` | URI/URL parsing and validation |
Expand All @@ -22,27 +22,12 @@ This document provides information about how Tonik is mapping data types in Open

### Timezone-Aware DateTime Parsing

Tonik provides intelligent timezone-aware parsing for `date-time` format strings. The parsing behavior depends on the timezone information present in the input:
Tonik provides timezone-aware parsing for `date-time` format strings using the `OffsetDateTime` class. The parsing behavior depends on the timezone information present in the input:

> **⚠️ Important:** Before using timezone-aware parsing features, you must initialize the timezone database by calling `tz.initializeTimeZones()` from the `timezone` package. This is typically done in your application's setup code.

All generated code will always expose Dart `DateTime` objects. However, standard Dart `DateTime` objects do not preserve timezone information, which is why Tonik uses `TZDateTime` internally during parsing to maintain timezone location data. During parsing, Tonik selects the most appropriate type to represent the date and time value:
All generated code will always expose Dart `DateTime` objects in the genreated code. However, internally Tonik uses `OffsetDateTime.parse()` to provide consistent timezone handling. The `OffsetDateTime` class extends Dart's `DateTime` interface while preserving timezone offset information:

| Input Format | Return Type | Example | Description |
|--------------|-------------|---------|-------------|
| UTC (with Z) | `DateTime` (UTC) | `2023-12-25T15:30:45Z` | Standard Dart DateTime in UTC |
| Local (no timezone) | `DateTime` (local) | `2023-12-25T15:30:45` | Standard Dart DateTime in local timezone |
| Timezone offset | `TZDateTime` | `2023-12-25T15:30:45+05:00` | Timezone-aware DateTime with proper location |



#### Timezone Location Selection

For strings with timezone offsets (e.g., `+05:00`), Tonik intelligently selects the best matching timezone location:

1. **Prefers common locations** from the timezone package's curated list of 535+ well-known timezones
2. **Accounts for DST changes** by checking the offset at the specific timestamp
3. **Avoids deprecated locations** (e.g., `US/Eastern` → `America/New_York`)
4. **Attempts fixed offset locations** (`Etc/GMT±N`) for standard hour offsets when no timezone match is found
5. **Falls back to UTC** for non-standard offsets or when `Etc/GMT±N` locations are unavailable

| UTC (with Z) | `OffsetDateTime` (UTC) | `2023-12-25T15:30:45Z` | OffsetDateTime with zero offset (UTC) |
| Local (no timezone) | `OffsetDateTime` (system timezone) | `2023-12-25T15:30:45` | OffsetDateTime with system timezone offset |
| Timezone offset | `OffsetDateTime` (specified offset) | `2023-12-25T15:30:45+05:00` | OffsetDateTime with the specified offset |
3 changes: 3 additions & 0 deletions integration_test/gov/gov_test/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,6 @@ dev_dependencies:
test: ^1.25.15
very_good_analysis: ^9.0.0

dependency_overrides:
tonik_util:
path: ../../../packages/tonik_util
23 changes: 3 additions & 20 deletions integration_test/music_streaming/music_streaming_test/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -137,14 +137,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.3"
http:
dependency: transitive
description:
name: http
sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b"
url: "https://pub.dev"
source: hosted
version: "1.4.0"
http_multi_server:
dependency: transitive
description:
Expand Down Expand Up @@ -392,21 +384,12 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.6.11"
timezone:
dependency: transitive
description:
name: timezone
sha256: dd14a3b83cfd7cb19e7888f1cbc20f258b8d71b54c06f79ac585f14093a287d1
url: "https://pub.dev"
source: hosted
version: "0.10.1"
tonik_util:
dependency: "direct main"
description:
name: tonik_util
sha256: "4b86da571a6a3ce18d89bc0e0a489aa1eebf8d43a8a883d85a69a870df3c69e8"
url: "https://pub.dev"
source: hosted
path: "../../../packages/tonik_util"
relative: true
source: path
version: "0.0.7"
typed_data:
dependency: transitive
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,7 @@ dependencies:
dev_dependencies:
test: ^1.25.15
very_good_analysis: ^9.0.0

dependency_overrides:
tonik_util:
path: ../../../packages/tonik_util
3 changes: 3 additions & 0 deletions integration_test/petstore/petstore_test/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,6 @@ dev_dependencies:
test: ^1.25.15
very_good_analysis: ^9.0.0

dependency_overrides:
tonik_util:
path: ../../../packages/tonik_util
24 changes: 23 additions & 1 deletion integration_test/setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,36 @@ if [[ $(echo "$JAVA_VERSION" | cut -d. -f1) -lt 11 ]]; then
exit 1
fi

# Generate API code
# Function to add dependency overrides to generated packages
add_dependency_overrides() {
local pubspec_file="$1"

if [ -f "$pubspec_file" ]; then
echo "Adding dependency overrides to $pubspec_file"

# Add dependency_overrides section if it doesn't exist
if ! grep -q "dependency_overrides:" "$pubspec_file"; then
echo "" >> "$pubspec_file"
echo "dependency_overrides:" >> "$pubspec_file"
echo " tonik_util:" >> "$pubspec_file"
echo " path: ../../../packages/tonik_util" >> "$pubspec_file"
fi
else
echo "Warning: $pubspec_file not found"
fi
}

# Generate API code with automatic dependency overrides for local tonik_util
dart run ../packages/tonik/bin/tonik.dart -p petstore_api -s petstore/openapi.yaml -o petstore --log-level verbose
add_dependency_overrides "petstore/petstore_api/pubspec.yaml"
cd petstore/petstore_api && dart pub get && cd ../..

dart run ../packages/tonik/bin/tonik.dart -p music_streaming_api -s music_streaming/openapi.yaml -o music_streaming --log-level verbose
add_dependency_overrides "music_streaming/music_streaming_api/pubspec.yaml"
cd music_streaming/music_streaming_api && dart pub get && cd ../..

dart run ../packages/tonik/bin/tonik.dart -p gov_api -s gov/openapi.yaml -o gov --log-level verbose
add_dependency_overrides "gov/gov_api/pubspec.yaml"
cd gov/gov_api && dart pub get && cd ../..

# Download Imposter JAR only if it doesn't exist
Expand Down
24 changes: 12 additions & 12 deletions packages/tonik_core/test/model/path_parameter_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,16 @@ void main() {

final resolved = param.resolve(name: 'newName');

expect(resolved.name, equals('newName'));
expect(resolved.rawName, equals('originalRawName'));
expect(resolved.description, equals('description'));
expect(resolved.name, 'newName');
expect(resolved.rawName, 'originalRawName');
expect(resolved.description, 'description');
expect(resolved.isRequired, isTrue);
expect(resolved.isDeprecated, isFalse);
expect(resolved.allowEmptyValue, isFalse);
expect(resolved.explode, isFalse);
expect(resolved.model, equals(model));
expect(resolved.encoding, equals(PathParameterEncoding.simple));
expect(resolved.context, equals(context));
expect(resolved.model, model);
expect(resolved.encoding, PathParameterEncoding.simple);
expect(resolved.context, context);
});

test('resolve preserves original name when no new name provided', () {
Expand All @@ -53,7 +53,7 @@ void main() {

final resolved = param.resolve();

expect(resolved.name, equals('originalName'));
expect(resolved.name, 'originalName');
});

test('PathParameterAlias.resolve resolves with alias name', () {
Expand Down Expand Up @@ -81,8 +81,8 @@ void main() {

final resolved = alias.resolve();

expect(resolved.name, equals('aliasName'));
expect(resolved.rawName, equals('originalRawName'));
expect(resolved.name, 'aliasName');
expect(resolved.rawName, 'originalRawName');
});

test(
Expand Down Expand Up @@ -112,7 +112,7 @@ void main() {

final resolved = alias.resolve(name: 'overrideName');

expect(resolved.name, equals('overrideName'));
expect(resolved.name, 'overrideName');
},
);

Expand Down Expand Up @@ -147,8 +147,8 @@ void main() {

final resolved = secondAlias.resolve();

expect(resolved.name, equals('secondAliasName'));
expect(resolved.rawName, equals('originalRawName'));
expect(resolved.name, 'secondAliasName');
expect(resolved.rawName, 'originalRawName');
});
});
}
Loading