Skip to content

Commit

Permalink
Add ECMA402 datetime format to package:intl4x (#680)
Browse files Browse the repository at this point in the history
* Adding Datetime format

* Add tests

* Rename Datetime -> DateTime

* Add readme check

* Add changelog entry

* Fix tests

* Revert name change

* Switch to stable health workflow

* Allow number in package names

* Add checkmark

* Fix merge problems

* Rev pubspec

* Add platforms to pubspec

* Renaming options

* Fixes

* Export Options from all formatters

* fix readme example
  • Loading branch information
mosuem authored Jul 24, 2023
1 parent 5bf6c61 commit b963401
Show file tree
Hide file tree
Showing 22 changed files with 678 additions and 76 deletions.
4 changes: 4 additions & 0 deletions pkgs/intl4x/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.5.0

- Add `DateTime` formatting.

## 0.4.0

- Add a `Locale` class.
Expand Down
14 changes: 8 additions & 6 deletions pkgs/intl4x/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ via our [issue tracker](https://github.com/dart-lang/i18n/issues)).

| | Number format | List format | Date format | Collation | Display names |
|---|:---:|:---:|:---:|:---:|:---:|
| **ECMA402 (web)** | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | :heavy_check_mark: |
| **ECMA402 (web)** | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| **ICU4X (web/native)** | | | | | |

## Implementation and Goals
Expand All @@ -36,10 +36,12 @@ import 'package:intl4x/ecma_policy.dart';
import 'package:intl4x/intl4x.dart';
import 'package:intl4x/number_format.dart';
final numberFormat = Intl(
ecmaPolicy: const AlwaysEcma(),
defaultLocale: Locale(language: 'en', country: 'US'),
).numberFormat(NumberFormatOptions.percent());
void main() {
final numberFormat = Intl(
ecmaPolicy: const AlwaysEcma(),
locale: const Locale(language: 'en', region: 'US'),
).numberFormat(NumberFormatOptions.percent());
print(numberFormat.format(0.5)); // prints 50%
print(numberFormat.format(0.5)); // prints 50%
}
```
1 change: 1 addition & 0 deletions pkgs/intl4x/lib/collation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@

export 'src/collation/collation.dart';
export 'src/collation/collation_options.dart';
export 'src/options.dart';
7 changes: 7 additions & 0 deletions pkgs/intl4x/lib/datetime_format.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

export 'src/datetime_format/datetime_format.dart';
export 'src/datetime_format/datetime_format_options.dart';
export 'src/options.dart';
1 change: 1 addition & 0 deletions pkgs/intl4x/lib/display_names.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@

export 'src/display_names/display_names.dart';
export 'src/display_names/display_names_options.dart';
export 'src/options.dart';
10 changes: 9 additions & 1 deletion pkgs/intl4x/lib/intl4x.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import 'display_names.dart';
import 'number_format.dart';
import 'src/collation/collation_impl.dart';
import 'src/data.dart';
import 'src/datetime_format/datetime_format.dart';
import 'src/datetime_format/datetime_format_impl.dart';
import 'src/datetime_format/datetime_format_options.dart';
import 'src/display_names/display_names_impl.dart';
import 'src/ecma/ecma_policy.dart';
import 'src/ecma/ecma_stub.dart' if (dart.library.js) 'src/ecma/ecma_web.dart';
Expand All @@ -16,7 +19,6 @@ import 'src/list_format/list_format_impl.dart';
import 'src/list_format/list_format_options.dart';
import 'src/locale.dart';
import 'src/number_format/number_format_impl.dart';
import 'src/options.dart';

export 'src/locale.dart';

Expand Down Expand Up @@ -66,6 +68,12 @@ class Intl {
DisplayNamesImpl.build(locale, localeMatcher, ecmaPolicy),
);

DateTimeFormat datetimeFormat([DateTimeFormatOptions? options]) =>
DateTimeFormat(
options ?? const DateTimeFormatOptions(),
DateTimeFormatImpl.build(locale, localeMatcher, ecmaPolicy),
);

/// Construct an [Intl] instance providing the current [locale] and the
/// [ecmaPolicy] defining which locales should fall back to the browser
/// provided functions.
Expand Down
1 change: 1 addition & 0 deletions pkgs/intl4x/lib/list_format.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@

export 'src/list_format/list_format.dart';
export 'src/list_format/list_format_options.dart';
export 'src/options.dart';
1 change: 1 addition & 0 deletions pkgs/intl4x/lib/number_format.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@

export 'src/number_format/number_format.dart';
export 'src/number_format/number_format_options.dart';
export 'src/options.dart';
35 changes: 35 additions & 0 deletions pkgs/intl4x/lib/src/datetime_format/datetime_format.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import '../test_checker.dart';
import 'datetime_format_impl.dart';
import 'datetime_format_options.dart';

/// `DateTime` formatting, for example:
///
/// ```dart
/// final date = DateTime.utc(2021, 12, 17, 4, 0, 42);
/// Intl(locale: const Locale(language: 'fr'))
/// .datetimeFormat(const DateTimeFormatOptions(
/// hour: TimeRepresentation.numeric,
/// hourCycle: HourCycle.h12,
/// dayPeriod: DayPeriod.narrow,
/// timeZone: 'UTC',
/// ))
/// .format(date); // Output: '4 mat.'
/// ```
class DateTimeFormat {
final DateTimeFormatOptions _options;
final DateTimeFormatImpl impl;

DateTimeFormat(this._options, this.impl);

String format(DateTime datetime) {
if (isInTest) {
return '$datetime//${impl.locale}';
} else {
return impl.formatImpl(datetime, _options);
}
}
}
19 changes: 19 additions & 0 deletions pkgs/intl4x/lib/src/datetime_format/datetime_format_4x.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import '../locale.dart';
import 'datetime_format_impl.dart';
import 'datetime_format_options.dart';

DateTimeFormatImpl getDateTimeFormatter4X(Locale locale) =>
DateTimeFormat4X(locale);

class DateTimeFormat4X extends DateTimeFormatImpl {
DateTimeFormat4X(super.locale);

@override
String formatImpl(DateTime datetime, DateTimeFormatOptions options) {
throw UnimplementedError('Insert diplomat bindings here');
}
}
160 changes: 160 additions & 0 deletions pkgs/intl4x/lib/src/datetime_format/datetime_format_ecma.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'package:js/js.dart';
import 'package:js/js_util.dart';

import '../locale.dart';
import '../options.dart';
import 'datetime_format_impl.dart';
import 'datetime_format_options.dart';

DateTimeFormatImpl? getDateTimeFormatterECMA(
Locale locale,
LocaleMatcher localeMatcher,
) =>
_DateTimeFormatECMA.tryToBuild(locale, localeMatcher);

@JS('Intl.DateTimeFormat')
class _DateTimeFormatJS {
external factory _DateTimeFormatJS([List<String> locale, Object options]);
external String format(Object num);
}

@JS('Intl.DateTimeFormat.supportedLocalesOf')
external List<String> _supportedLocalesOfJS(
List<String> listOfLocales, [
Object options,
]);

@JS('Date')
class DateJS {
external factory DateJS(
int year,
int monthIndex,
int day,
int hours,
int minutes,
int seconds,
int milliseconds,
);

external factory DateJS.fromTimeStamp(int timeStamp);
}

@JS('Date.UTC')
// ignore: non_constant_identifier_names
external int UTC(
int year,
int monthIndex,
int day,
int hours,
int minutes,
int seconds,
int milliseconds,
);

class _DateTimeFormatECMA extends DateTimeFormatImpl {
_DateTimeFormatECMA(super.locale);

static DateTimeFormatImpl? tryToBuild(
Locale locale,
LocaleMatcher localeMatcher,
) {
final supportedLocales = supportedLocalesOf(localeMatcher, locale);
return supportedLocales.isNotEmpty
? _DateTimeFormatECMA(supportedLocales.first)
: null; //TODO: Add support to force return an instance instead of null.
}

static List<Locale> supportedLocalesOf(
LocaleMatcher localeMatcher,
Locale locale,
) {
final o = newObject<Object>();
setProperty(o, 'localeMatcher', localeMatcher.jsName);
return List.from(_supportedLocalesOfJS([locale.toLanguageTag()], o))
.whereType<String>()
.map(Locale.parse)
.toList();
}

@override
String formatImpl(DateTime datetime, DateTimeFormatOptions options) {
final datetimeFormatJS = _DateTimeFormatJS(
[locale.toLanguageTag()],
options.toJsOptions(),
);
return datetimeFormatJS.format(datetime.toJs());
}
}

extension on DateTime {
DateJS toJs() {
if (isUtc) {
return DateJS.fromTimeStamp(
UTC(year, month - 1, day, hour, minute, second, millisecond));
} else {
return DateJS(year, month - 1, day, hour, minute, second, millisecond);
}
}
}

extension on DateTimeFormatOptions {
Object toJsOptions() {
final o = newObject<Object>();
setProperty(o, 'localeMatcher', localeMatcher.jsName);
if (dateFormatStyle != null) {
setProperty(o, 'dateStyle', dateFormatStyle!.name);
}
if (timeFormatStyle != null) {
setProperty(o, 'timeStyle', timeFormatStyle!.name);
}
if (calendar != null) setProperty(o, 'calendar', calendar!.jsName);
if (dayPeriod != null) setProperty(o, 'dayPeriod', dayPeriod!.name);
if (numberingSystem != null) {
setProperty(o, 'numberingSystem', numberingSystem!.name);
}
if (timeZone != null) setProperty(o, 'timeZone', timeZone!);
if (clockstyle != null) {
setProperty(o, 'hour12', clockstyle!.is12Hour);
if (clockstyle!.startAtZero != null) {
setProperty(o, 'hourCycle', clockstyle!.hourStyleJsString());
}
}
if (weekday != null) setProperty(o, 'weekday', weekday!.name);
if (era != null) setProperty(o, 'era', era!.name);
if (year != null) setProperty(o, 'year', year!.jsName);
if (month != null) setProperty(o, 'month', month!.jsName);
if (day != null) setProperty(o, 'day', day!.jsName);
if (hour != null) setProperty(o, 'hour', hour!.jsName);
if (minute != null) setProperty(o, 'minute', minute!.jsName);
if (second != null) setProperty(o, 'second', second!.jsName);
if (fractionalSecondDigits != null) {
setProperty(o, 'fractionalSecondDigits', fractionalSecondDigits!);
}
if (timeZoneName != null) {
setProperty(o, 'timeZoneName', timeZoneName!.name);
}
setProperty(o, 'formatMatcher', formatMatcher.jsName);
return o;
}
}

extension on ClockStyle {
String hourStyleJsString() {
// The four possible values are h11, h12, h23, h24.
final firstDigit = is12Hour ? 1 : 2;

final subtrahend = startAtZero! ? 1 : 0;
final secondDigit = firstDigit * 2 - subtrahend;

/// The cases are
/// * firstDigit == 1 && subtrahend == 1 --> h11
/// * firstDigit == 1 && subtrahend == 0 --> h12
/// * firstDigit == 2 && subtrahend == 1 --> h23
/// * firstDigit == 2 && subtrahend == 0 --> h24
return 'h$firstDigit$secondDigit';
}
}
35 changes: 35 additions & 0 deletions pkgs/intl4x/lib/src/datetime_format/datetime_format_impl.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import '../ecma/ecma_policy.dart';
import '../locale.dart';
import '../options.dart';
import '../utils.dart';
import 'datetime_format_4x.dart';
import 'datetime_format_options.dart';
import 'datetime_format_stub.dart'
if (dart.library.js) 'datetime_format_ecma.dart';

/// This is an intermediate to defer to the actual implementations of
/// datetime formatting.
abstract class DateTimeFormatImpl {
final Locale locale;

DateTimeFormatImpl(this.locale);

String formatImpl(DateTime datetime, DateTimeFormatOptions options);

factory DateTimeFormatImpl.build(
Locale locale,
LocaleMatcher localeMatcher,
EcmaPolicy ecmaPolicy,
) =>
buildFormatter(
locale,
localeMatcher,
ecmaPolicy,
getDateTimeFormatterECMA,
getDateTimeFormatter4X,
);
}
Loading

0 comments on commit b963401

Please sign in to comment.