├── .pubignore ├── .markdownlint.json ├── analysis_options.yaml ├── lib ├── src │ ├── formatters │ │ ├── escaped_token.dart │ │ ├── format_match.dart │ │ └── token.dart │ ├── types.dart │ ├── exception.dart │ ├── localizations │ │ ├── utils │ │ │ ├── abbreviation.dart │ │ │ ├── duration_unit.dart │ │ │ ├── relative_interval.dart │ │ │ └── duration_format.dart │ │ ├── mixins │ │ │ ├── zh_CN │ │ │ │ ├── chinese_numbers.dart │ │ │ │ └── ordinal_number.dart │ │ │ ├── de_DE │ │ │ │ └── units.dart │ │ │ ├── weekday_shortforms.dart │ │ │ ├── ordinal_numbers.dart │ │ │ ├── month_names.dart │ │ │ ├── ru_RU │ │ │ │ ├── relative.dart │ │ │ │ ├── unit_string.dart │ │ │ │ ├── duration.dart │ │ │ │ └── units.dart │ │ │ ├── simple_relative.dart │ │ │ ├── english_like_ordinal.dart │ │ │ ├── simple_units.dart │ │ │ ├── mn_MN │ │ │ │ ├── duration.dart │ │ │ │ └── units.dart │ │ │ ├── mn_Mong_MN │ │ │ │ ├── duration.dart │ │ │ │ └── units.dart │ │ │ ├── simple_duration.dart │ │ │ ├── simple_range.dart │ │ │ └── complex_calendar.dart │ │ ├── all.dart │ │ ├── ru_RU.dart │ │ ├── mn_MN.dart │ │ ├── ko_KR.dart │ │ ├── ar_PS.dart │ │ ├── ja_JP.dart │ │ └── en_US.dart │ ├── time_range │ │ ├── pageable_range.dart │ │ ├── custom.dart │ │ ├── year.dart │ │ ├── month.dart │ │ ├── week.dart │ │ ├── day.dart │ │ └── hour.dart │ ├── extensions.dart │ ├── extensions │ │ ├── constructor.dart │ │ ├── duration.dart │ │ ├── clamped_setters.dart │ │ ├── unit_comparison.dart │ │ ├── start_of.dart │ │ ├── end_of.dart │ │ ├── benefits.dart │ │ └── weekday_finder.dart │ └── calendar.dart └── moment_dart.dart ├── .github ├── ISSUE_TEMPLATE │ ├── enhancement.md │ └── feature_request.md └── workflows │ └── test.yml ├── .gitignore ├── .vscode └── launch.json ├── pubspec.yaml ├── test ├── global_localization_test.dart ├── localizations │ ├── lcid_test.dart │ ├── mn │ │ └── word_gender_test.dart │ ├── find_by_locale_test.dart │ └── find_by_language_test.dart ├── extensions │ ├── clamped_setters_test.dart │ └── relative_finder_test.dart └── localizations_old_test.dart ├── LICENSE ├── CONTRIBUTE.md ├── .all-contributorsrc ├── example └── moment_dart_example.dart └── README.md /.pubignore: -------------------------------------------------------------------------------- 1 | test 2 | .vscode 3 | .github 4 | .all-contributorsrc 5 | -------------------------------------------------------------------------------- /.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "MD013": false, 3 | "MD041": false 4 | } 5 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:lints/recommended.yaml 2 | 3 | linter: 4 | rules: 5 | analyzer: 6 | exclude: 7 | -------------------------------------------------------------------------------- /lib/src/formatters/escaped_token.dart: -------------------------------------------------------------------------------- 1 | class EscapedToken { 2 | final String text; 3 | 4 | const EscapedToken(this.text); 5 | } 6 | -------------------------------------------------------------------------------- /lib/moment_dart.dart: -------------------------------------------------------------------------------- 1 | export './src/moment.dart'; 2 | export './src/localization.dart'; 3 | export './src/localizations/all.dart'; 4 | export './src/formatters/token.dart'; 5 | -------------------------------------------------------------------------------- /lib/src/types.dart: -------------------------------------------------------------------------------- 1 | import 'package:moment_dart/src/formatters/token.dart'; 2 | 3 | typedef FormatSet = Map; 4 | typedef FormatSetOptional = Map; 5 | -------------------------------------------------------------------------------- /lib/src/exception.dart: -------------------------------------------------------------------------------- 1 | class MomentException implements Exception { 2 | final String message; 3 | 4 | const MomentException(this.message); 5 | 6 | @override 7 | String toString() { 8 | return "[Moment Dart] Exception: $message"; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/enhancement.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Enhancement 3 | about: Improve existing feature for the best 4 | title: '' 5 | labels: improvement 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## What can be improved? 11 | 12 | 13 | ## Ideas on implementing the enhancement? 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Files and directories created by pub. 2 | .dart_tool/ 3 | .packages 4 | 5 | # Conventional directory for build outputs. 6 | build/ 7 | 8 | # Omit committing pubspec.lock for library packages; see 9 | # https://dart.dev/guides/libraries/private-files#pubspeclock. 10 | pubspec.lock 11 | 12 | test/playground.dart 13 | -------------------------------------------------------------------------------- /lib/src/localizations/utils/abbreviation.dart: -------------------------------------------------------------------------------- 1 | enum Abbreviation { 2 | /// Full form. 3 | /// 4 | /// e.g., "3 hours", "7 minutes" 5 | none, 6 | 7 | /// Semi-abbreviated form. 8 | /// 9 | /// e.g., "3 hr", "7 min" 10 | semi, 11 | 12 | /// Abbreviated form. 13 | /// 14 | /// e.g., "3h", "7m" 15 | full, 16 | } 17 | -------------------------------------------------------------------------------- /lib/src/time_range/pageable_range.dart: -------------------------------------------------------------------------------- 1 | import 'package:moment_dart/moment_dart.dart'; 2 | import 'package:moment_dart/src/time_range.dart'; 3 | 4 | mixin PageableRange on TimeRange { 5 | /// Returns a new instance, preserves the timezone 6 | T get next; 7 | 8 | /// Returns a new instance, preserves the timezone 9 | T get last; 10 | } 11 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "moment_dart", 9 | "request": "launch", 10 | "type": "dart" 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /lib/src/formatters/format_match.dart: -------------------------------------------------------------------------------- 1 | import 'package:moment_dart/src/formatters/token.dart'; 2 | 3 | class FormatMatch { 4 | final int startIndex; 5 | final FormatterToken token; 6 | 7 | int get endIndex => token.name.length + startIndex; 8 | 9 | const FormatMatch({ 10 | required this.startIndex, 11 | required this.token, 12 | }); 13 | 14 | @override 15 | String toString() => token.name; 16 | } 17 | -------------------------------------------------------------------------------- /lib/src/localizations/mixins/zh_CN/chinese_numbers.dart: -------------------------------------------------------------------------------- 1 | import 'package:moment_dart/moment_dart.dart'; 2 | 3 | /// This mixin provides 4 | /// 5 | /// * [chineseNumbers] - final property with chinese numbers 6 | mixin ChineseNumbers on MomentLocalization { 7 | final Map chineseNumbers = { 8 | 1: "一", 9 | 2: "二", 10 | 3: "三", 11 | 4: "四", 12 | 5: "五", 13 | 6: "六", 14 | 7: "七", 15 | 8: "八", 16 | 9: "九", 17 | 10: "十", 18 | 11: "十一", 19 | 12: "十二", 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: moment_dart 2 | description: Multi-purpose immutable DateTime subclass. Supports multiple localizations to easily convert DateTime and Duration into human-readable format 3 | version: 5.3.0+1 4 | homepage: https://github.com/sadespresso/moment_dart 5 | repository: https://github.com/sadespresso/moment_dart 6 | issue_tracker: https://github.com/sadespresso/moment_dart/issues 7 | funding: 8 | - https://buymeacoffee.com/sadespresso 9 | documentation: https://dev.gege.mn/moment_dart_docs 10 | 11 | environment: 12 | sdk: ">=2.19.4 <4.0.0" 13 | 14 | dev_dependencies: 15 | lints: ^5.1.1 16 | test: ^1.23.1 17 | -------------------------------------------------------------------------------- /lib/src/extensions.dart: -------------------------------------------------------------------------------- 1 | export 'package:moment_dart/src/extensions/benefits.dart'; 2 | 3 | export 'package:moment_dart/src/localizations/utils/duration_unit.dart'; 4 | 5 | export 'package:moment_dart/src/extensions/start_of.dart'; 6 | 7 | export 'package:moment_dart/src/extensions/end_of.dart'; 8 | 9 | export 'package:moment_dart/src/extensions/unit_comparison.dart'; 10 | 11 | export 'package:moment_dart/src/extensions/weekday_finder.dart'; 12 | 13 | export 'package:moment_dart/src/extensions/relative_finder.dart'; 14 | 15 | export 'package:moment_dart/src/extensions/constructor.dart'; 16 | 17 | export 'package:moment_dart/src/extensions/clamped_setters.dart'; 18 | -------------------------------------------------------------------------------- /test/global_localization_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:moment_dart/moment_dart.dart'; 2 | import 'package:test/expect.dart'; 3 | import 'package:test/scaffolding.dart'; 4 | 5 | void main() { 6 | test('Test if global localization is changing', () { 7 | expect(Moment.now().localization.locale, "en_US"); 8 | Moment.setGlobalLocalization(MomentLocalizations.fr()); 9 | expect(Moment.now().localization.locale, "fr_FR"); 10 | Moment.setGlobalLocalization(MomentLocalizations.jaJp()); 11 | expect(Moment.now().localization.locale, "ja_JP"); 12 | Moment.setGlobalLocalization(MomentLocalizations.arPs()); 13 | expect(Moment.now().localization.locale, "ar_PS"); 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: feature 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | 6 | name: Dart 7 | 8 | on: 9 | push: 10 | pull_request: 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - uses: dart-lang/setup-dart@9a04e6d73cca37bd455e0608d7e5092f881fd603 20 | 21 | - name: Install dependencies 22 | run: dart pub get 23 | 24 | - name: Analyze project source 25 | run: dart analyze 26 | 27 | - name: Run tests 28 | run: dart test 29 | -------------------------------------------------------------------------------- /lib/src/localizations/mixins/de_DE/units.dart: -------------------------------------------------------------------------------- 1 | import 'package:moment_dart/src/localizations/mixins/simple_units.dart'; 2 | import 'package:moment_dart/src/localizations/utils/abbreviation.dart'; 3 | 4 | class UnitStringDeDe extends UnitString { 5 | final String full; 6 | final String mid; 7 | final String short; 8 | 9 | final String? standalone; 10 | 11 | const UnitStringDeDe(this.full, this.mid, this.short, {this.standalone}); 12 | 13 | @override 14 | String get(Abbreviation form, bool dropPrefixOrSuffix) { 15 | switch (form) { 16 | case Abbreviation.none: 17 | return dropPrefixOrSuffix ? (standalone ?? full) : full; 18 | case Abbreviation.semi: 19 | return mid; 20 | case Abbreviation.full: 21 | return short; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/src/localizations/mixins/weekday_shortforms.dart: -------------------------------------------------------------------------------- 1 | import 'package:moment_dart/moment_dart.dart'; 2 | import 'package:moment_dart/src/types.dart'; 3 | 4 | /// This mixin provides: 5 | /// 6 | /// * `formattersForWeekdayShortForms` - getter for set of formatters related to weekday short forms 7 | /// 8 | /// These fields can be optionally implemented: 9 | /// * [weekdayNameMin] 10 | mixin WeekdayShortForms on MomentLocalization { 11 | Map get weekdayNameShort; 12 | 13 | Map? get weekdayNameMin => null; 14 | 15 | FormatSet get formattersForWeekdayShortForms => { 16 | FormatterToken.ddd: (DateTime dateTime) => 17 | weekdayNameShort[dateTime.weekday]!, 18 | if (weekdayNameMin != null) 19 | FormatterToken.dd: (DateTime dateTime) => 20 | weekdayNameMin![dateTime.weekday]!, 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /test/localizations/lcid_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:moment_dart/moment_dart.dart'; 2 | import 'package:test/expect.dart'; 3 | import 'package:test/scaffolding.dart'; 4 | 5 | void main() { 6 | test("Locale ids for each localization", () { 7 | expect(LocalizationArPs().locale, "ar_PS"); 8 | expect(LocalizationMnMn().locale, "mn_MN"); 9 | expect(LocalizationMnMongMn().locale, "mn_Mong_MN"); 10 | expect(LocalizationMnQaaqMn().locale, "mn_Qaaq_MN"); 11 | expect(LocalizationKoKr().locale, "ko_KR"); 12 | expect(LocalizationJaJp().locale, "ja_JP"); 13 | expect(LocalizationDeDe().locale, "de_DE"); 14 | expect(LocalizationEsEs().locale, "es_ES"); 15 | expect(LocalizationFrFr().locale, "fr_FR"); 16 | expect(LocalizationTrTr().locale, "tr_TR"); 17 | expect(LocalizationPtPt().locale, "pt_PT"); 18 | expect(LocalizationItIt().locale, "it_IT"); 19 | expect(LocalizationZhCn().locale, "zh_CN"); 20 | expect(LocalizationRuRu().locale, "ru_RU"); 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /lib/src/time_range/custom.dart: -------------------------------------------------------------------------------- 1 | import 'package:moment_dart/src/extensions.dart'; 2 | import 'package:moment_dart/src/time_range.dart'; 3 | 4 | class CustomTimeRange extends TimeRange { 5 | @override 6 | 7 | /// Returns if [from] is in UTC timezone. 8 | /// 9 | /// Does NOT check it [to] is in UTC timezone. 10 | bool get isUtc => from.isUtc; 11 | 12 | @override 13 | final DateTime from; 14 | @override 15 | final DateTime to; 16 | 17 | @override 18 | DurationUnit? get unit => null; 19 | 20 | /// The timezone is assumed by the [from] passed in here. 21 | /// 22 | /// [CustomTimeRange] does NOT ensure that [from] and [to] have the same timezone. 23 | CustomTimeRange(this.from, this.to) 24 | : assert(from <= to, "[from] must be before or equal to [to]"), 25 | assert(from.isUtc == to.isUtc, 26 | "[from] and [to] must have the same timezone"); 27 | 28 | @override 29 | CustomTimeRange toUtc() => 30 | isUtc ? this : CustomTimeRange(from.toUtc(), to.toUtc()); 31 | } 32 | -------------------------------------------------------------------------------- /lib/src/localizations/mixins/ordinal_numbers.dart: -------------------------------------------------------------------------------- 1 | import 'package:moment_dart/moment_dart.dart'; 2 | 3 | /// This mixin provides: 4 | /// 5 | /// * [formattersWithOrdinal] - getter for set of formatters related to ordinals 6 | mixin Ordinal on MomentLocalization { 7 | String ordinalNumber(int n); 8 | 9 | /// Overrides: 10 | /// 11 | /// `Mo`, `Qo`, `Do`, `DDDo`, `d_o`, and `wo` 12 | Map get formattersWithOrdinal => { 13 | FormatterToken.Mo: (DateTime dateTime) => ordinalNumber(dateTime.month), 14 | FormatterToken.Qo: (DateTime dateTime) => 15 | ordinalNumber(dateTime.quarter), 16 | FormatterToken.Do: (DateTime dateTime) => ordinalNumber(dateTime.day), 17 | FormatterToken.DDDo: (DateTime dateTime) => 18 | ordinalNumber(dateTime.dayOfYear), 19 | FormatterToken.d_o: (DateTime dateTime) => 20 | ordinalNumber(dateTime.weekday), 21 | FormatterToken.wo: (DateTime dateTime) => ordinalNumber(dateTime.week) 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /lib/src/localizations/mixins/month_names.dart: -------------------------------------------------------------------------------- 1 | import 'package:moment_dart/moment_dart.dart'; 2 | 3 | /// This mixin provides: 4 | /// 5 | /// * [formattersForMonthNames] - getter for set of formatters related to month names 6 | mixin MonthNames on MomentLocalization { 7 | /// Month name from month number (1-12) (January, Febuary, ..., December) 8 | Map get monthNames; 9 | 10 | /// Short month name from month (1-12) (Jan, Feb, ..., Dec) 11 | Map get monthNamesShort; 12 | 13 | /// Min month name from month (1-12) (01, 02, ..., 12) 14 | Map? get monthNamesMin => null; 15 | 16 | /// Overrides: 17 | /// 18 | /// `MMM` and `MMMM` 19 | Map get formattersForMonthNames => { 20 | if (monthNamesMin != null) 21 | FormatterToken.MM: (dateTime) => monthNamesMin![dateTime.month]!, 22 | FormatterToken.MMM: (dateTime) => monthNamesShort[dateTime.month]!, 23 | FormatterToken.MMMM: (dateTime) => monthNames[dateTime.month]!, 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /lib/src/localizations/mixins/ru_RU/relative.dart: -------------------------------------------------------------------------------- 1 | import 'package:moment_dart/moment_dart.dart'; 2 | import 'package:moment_dart/src/localizations/mixins/ru_RU/units.dart'; 3 | 4 | mixin RuRuRelative on RuRuUnits { 5 | @override 6 | String relative( 7 | Duration duration, { 8 | bool dropPrefixOrSuffix = false, 9 | Abbreviation form = Abbreviation.none, 10 | }) { 11 | final bool past = duration.isNegative; 12 | 13 | duration = duration.abs(); 14 | 15 | DurationInterval interval = MomentLocalization.relativeThreshold(duration); 16 | 17 | String value = (units[interval]?.get(form, dropPrefixOrSuffix, 18 | DurationUnit.relativeDuration(duration, interval.unit)) ?? 19 | "¯\\_(ツ)_/¯"); 20 | 21 | if (!interval.singular) { 22 | value = value.replaceAll( 23 | srDelta, 24 | DurationUnit.relativeDuration(duration, interval.unit).toString(), 25 | ); 26 | } 27 | 28 | if (dropPrefixOrSuffix) return value; 29 | 30 | return past ? relativePast(value) : relativeFuture(value); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Batmend Ganbaatar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /lib/src/localizations/mixins/simple_relative.dart: -------------------------------------------------------------------------------- 1 | import 'package:moment_dart/src/extensions.dart'; 2 | import 'package:moment_dart/src/localization.dart'; 3 | import 'package:moment_dart/src/localizations/mixins/simple_units.dart'; 4 | 5 | /// Depends on [SimpleUnits] 6 | /// 7 | /// This mixin **overrides** [MomentLocalization.relative] 8 | mixin SimpleRelative on SimpleUnits { 9 | @override 10 | String relative( 11 | Duration duration, { 12 | bool dropPrefixOrSuffix = false, 13 | Abbreviation form = Abbreviation.none, 14 | }) { 15 | final bool past = duration.isNegative; 16 | 17 | duration = duration.abs(); 18 | 19 | DurationInterval interval = MomentLocalization.relativeThreshold(duration); 20 | 21 | String value = 22 | (units[interval]?.get(form, dropPrefixOrSuffix) ?? "¯\\_(ツ)_/¯"); 23 | 24 | if (!interval.singular) { 25 | value = value.replaceAll( 26 | srDelta, 27 | DurationUnit.relativeDuration(duration, interval.unit).toString(), 28 | ); 29 | } 30 | 31 | if (dropPrefixOrSuffix) return value; 32 | 33 | return past ? relativePast(value) : relativeFuture(value); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/src/localizations/mixins/zh_CN/ordinal_number.dart: -------------------------------------------------------------------------------- 1 | import 'package:moment_dart/moment_dart.dart'; 2 | 3 | import 'chinese_numbers.dart'; 4 | 5 | /// This mixin provides 6 | /// 7 | /// * [formattersWithZnCnOrdinal] - getter for set of formatters related to Chinese ordinal numbers 8 | mixin ZnCnOrdinal on ChineseNumbers { 9 | String ordinalNumberDay(int n) => "$n日"; 10 | String ordinalNumberWeek(int n) => "$n周"; 11 | String ordinalNumberMonth(int n) => "$n月"; 12 | 13 | /// Overrides: 14 | /// 15 | /// `Mo`, `Qo`, `Do`, `DDDo`, `d_o`, and `wo` 16 | Map get formattersWithZnCnOrdinal => { 17 | FormatterToken.Mo: (DateTime dateTime) => 18 | ordinalNumberMonth(dateTime.month), 19 | FormatterToken.Qo: (DateTime dateTime) => 20 | "${chineseNumbers[dateTime.quarter]}季度", 21 | FormatterToken.Do: (DateTime dateTime) => 22 | ordinalNumberDay(dateTime.day), 23 | FormatterToken.DDDo: (DateTime dateTime) => 24 | ordinalNumberDay(dateTime.dayOfYear), 25 | FormatterToken.d_o: (DateTime dateTime) => 26 | ordinalNumberDay(dateTime.weekday), 27 | FormatterToken.wo: (DateTime dateTime) => 28 | ordinalNumberWeek(dateTime.week) 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /CONTRIBUTE.md: -------------------------------------------------------------------------------- 1 | # Contributing to moment_dart 2 | 3 | 1. Fork the repository. 4 | 2. Create a new branch from the `master` branch. 5 | 3. Make your changes. 6 | * Make sure your code doesn't make the analyzer unhappy. 7 | * If you are adding a new feature, please write tests to ensure that it 8 | works correctly. 9 | * Run the tests to make sure they are passing. 10 | 4. Make a pull request to `master` branch. 11 | * In the body of the pull request, describe your changes and why you made them. 12 | * If your changes should close an issue, please 13 | [mention](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue) 14 | the issue. 15 | 5. Wait for your pull request to be reviewed and merged. 16 | 17 | Once your pull request is accepted, you will be considered a contributor to the 18 | project. The project maintainer is responsible for adding you as a contributor, 19 | but you also have the option to add yourself to the contributors list 20 | (automatically added upon merge of your PR). You can refer to the 21 | [All Contributors bot usage documentation](https://allcontributors.org/docs/en/bot/usage) 22 | for instructions. 23 | 24 | Please note that this project follows the [all-contributors](https://allcontributors.org/) 25 | specification. Thank you for your valuable contribution! 💖 26 | -------------------------------------------------------------------------------- /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "moment_dart", 3 | "projectOwner": "sadespresso", 4 | "repoType": "github", 5 | "repoHost": "https://github.com", 6 | "files": [ 7 | "README.md" 8 | ], 9 | "imageSize": 80, 10 | "contributorsPerLine": 7, 11 | "contributors": [ 12 | { 13 | "login": "sadespresso", 14 | "name": "Батмэнд Ганбаатар", 15 | "avatar_url": "https://avatars.githubusercontent.com/u/51638223?v=4", 16 | "profile": "https://github.com/sadespresso", 17 | "contributions": [ 18 | "code", 19 | "maintenance", 20 | "translation", 21 | "doc" 22 | ] 23 | }, 24 | { 25 | "login": "Ultrate", 26 | "name": "Ultrate", 27 | "avatar_url": "https://avatars.githubusercontent.com/u/124692023?v=4", 28 | "profile": "https://github.com/Ultrate", 29 | "contributions": [ 30 | "code", 31 | "translation" 32 | ] 33 | }, 34 | { 35 | "login": "muhammadyusuf-kurbonov", 36 | "name": "Muhammadyusuf", 37 | "avatar_url": "https://avatars.githubusercontent.com/u/45625440?v=4", 38 | "profile": "https://www.linkedin.com/in/muhammadyusuf-kurbonov-2b6ba8235/", 39 | "contributions": [ 40 | "translation", 41 | "code" 42 | ] 43 | } 44 | ], 45 | "commitConvention": "angular", 46 | "commitType": "docs" 47 | } 48 | -------------------------------------------------------------------------------- /lib/src/extensions/constructor.dart: -------------------------------------------------------------------------------- 1 | extension DateTimeConstructors on DateTime { 2 | static DateTime nowWithTimezone(bool isUtc) { 3 | if (isUtc) { 4 | return DateTime.now().toUtc(); 5 | } 6 | 7 | return DateTime.now(); 8 | } 9 | 10 | static DateTime withTimezone( 11 | bool isUtc, 12 | int year, [ 13 | int month = 1, 14 | int day = 1, 15 | int hour = 0, 16 | int minute = 0, 17 | int second = 0, 18 | int millisecond = 0, 19 | int microsecond = 0, 20 | ]) { 21 | if (isUtc) { 22 | return DateTime.utc( 23 | year, 24 | month, 25 | day, 26 | hour, 27 | minute, 28 | second, 29 | millisecond, 30 | microsecond, 31 | ); 32 | } 33 | return DateTime( 34 | year, 35 | month, 36 | day, 37 | hour, 38 | minute, 39 | second, 40 | millisecond, 41 | microsecond, 42 | ); 43 | } 44 | 45 | static DateTime dateWithTimezone( 46 | int year, [ 47 | int month = 1, 48 | int day = 1, 49 | bool isUtc = false, 50 | ]) { 51 | if (isUtc) { 52 | return DateTime.utc( 53 | year, 54 | month, 55 | day, 56 | 0, 57 | 0, 58 | 0, 59 | 0, 60 | 0, 61 | ); 62 | } 63 | return DateTime( 64 | year, 65 | month, 66 | day, 67 | 0, 68 | 0, 69 | 0, 70 | 0, 71 | 0, 72 | ); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /lib/src/time_range/year.dart: -------------------------------------------------------------------------------- 1 | import 'package:moment_dart/src/extensions.dart'; 2 | import 'package:moment_dart/src/time_range.dart'; 3 | 4 | class YearTimeRange extends TimeRange with PageableRange { 5 | @override 6 | final bool isUtc; 7 | 8 | final int year; 9 | 10 | factory YearTimeRange( 11 | int year, { 12 | bool isUtc = false, 13 | }) { 14 | final DateTime dateTime = DateTimeConstructors.withTimezone(isUtc, year); 15 | return YearTimeRange._internal(dateTime.year, isUtc: dateTime.isUtc); 16 | } 17 | 18 | const YearTimeRange._internal( 19 | this.year, { 20 | this.isUtc = false, 21 | }); 22 | 23 | /// Will preserve the timezone of [dateTime] 24 | /// 25 | /// Please note that [month], [day], [hour], [minute], [second], [millisecond], [microsecond] 26 | /// will be ignored. 27 | factory YearTimeRange.fromDateTime(DateTime dateTime) => 28 | YearTimeRange(dateTime.year, isUtc: dateTime.isUtc); 29 | 30 | @override 31 | DateTime get from => DateTimeConstructors.withTimezone(isUtc, year); 32 | 33 | @override 34 | DateTime get to => from.endOfYear(); 35 | 36 | @override 37 | DurationUnit get unit => DurationUnit.year; 38 | 39 | @override 40 | YearTimeRange toUtc() => isUtc ? this : YearTimeRange(year, isUtc: true); 41 | 42 | @override 43 | YearTimeRange get next => YearTimeRange(year + 1, isUtc: isUtc); 44 | 45 | @override 46 | YearTimeRange get last => YearTimeRange(year - 1, isUtc: isUtc); 47 | } 48 | -------------------------------------------------------------------------------- /lib/src/calendar.dart: -------------------------------------------------------------------------------- 1 | class CalenderLocalizationData { 2 | /// Relative days on calendar. 3 | /// 4 | /// For example, -1 is Yestderday, 0 is Today, 1 is Tomorrow 5 | /// 6 | /// Some language have words for 7 | /// day before yesterday (-2: ereyesterday) 8 | /// or day after tomorrow (2: overmorrow) 9 | final Map relativeDayNames; 10 | 11 | /// Time keywords. If value is absent or null for [CalendarKeyword], Moment will assume the language doesn't require the keyword. 12 | final CalenderLocalizationKeywords keywords; 13 | 14 | const CalenderLocalizationData({ 15 | required this.relativeDayNames, 16 | required this.keywords, 17 | }); 18 | } 19 | 20 | typedef CalendarKeywordLastWeekdayString = String Function(String weekday); 21 | String defaultCalendarKeywordLastString(String weekday) => weekday; 22 | 23 | typedef CalendarKeywordNextWeekdayString = String Function(String weekday); 24 | String defaultCalendarKeywordNextString(String weekday) => weekday; 25 | 26 | typedef CalendarKeywordDateAtTimeString = String Function( 27 | String date, String time); 28 | String defaultCalendarKeywordDateAtTimeString(String date, String time) => 29 | "$date $time"; 30 | 31 | class CalenderLocalizationKeywords { 32 | final CalendarKeywordLastWeekdayString lastWeekday; 33 | 34 | final CalendarKeywordNextWeekdayString nextWeekday; 35 | 36 | final CalendarKeywordDateAtTimeString at; 37 | 38 | const CalenderLocalizationKeywords({ 39 | this.lastWeekday = defaultCalendarKeywordLastString, 40 | this.nextWeekday = defaultCalendarKeywordNextString, 41 | this.at = defaultCalendarKeywordDateAtTimeString, 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /lib/src/extensions/duration.dart: -------------------------------------------------------------------------------- 1 | import 'package:moment_dart/moment_dart.dart'; 2 | 3 | extension DurationExtra on Duration { 4 | static const int daysPerWeek = 7; 5 | 6 | /// Only returns whole weeks. Here, a week equals [daysPerWeek] days 7 | int get inWeeks => inDays ~/ daysPerWeek; 8 | 9 | static const int daysPerMonth = 30; 10 | 11 | /// Precise number for nerds 12 | static const double daysPerMonthPrecise = 30.4368499; 13 | 14 | /// Only returns whole months. Here, a month equals [daysPerMonth] days 15 | int get inMonths => inDays ~/ daysPerMonth; 16 | 17 | static const int daysPerYear = 365; 18 | 19 | /// Precise number for nerds 20 | static const double daysPerYearPrecise = 365.242199; 21 | 22 | /// Only returns whole years 23 | int get inYears => inDays ~/ daysPerYear; 24 | 25 | static const int weeksPerYear = 52; 26 | 27 | /// Precise number for nerds 28 | static const double weeksPerYearPrecise = 52.177457; 29 | 30 | static const int monthsPerYear = 12; 31 | 32 | String toDurationString({ 33 | MomentLocalization? localization, 34 | bool round = true, 35 | bool omitZeros = true, 36 | bool includeWeeks = false, 37 | Abbreviation form = Abbreviation.none, 38 | String? delimiter, 39 | DurationFormat format = DurationFormat.auto, 40 | bool dropPrefixOrSuffix = false, 41 | }) { 42 | return (localization ?? Moment.defaultLocalization).duration( 43 | this, 44 | round: round, 45 | omitZeros: omitZeros, 46 | includeWeeks: includeWeeks, 47 | form: form, 48 | delimiter: delimiter, 49 | format: format, 50 | dropPrefixOrSuffix: dropPrefixOrSuffix, 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/src/time_range/month.dart: -------------------------------------------------------------------------------- 1 | import 'package:moment_dart/src/extensions.dart'; 2 | import 'package:moment_dart/src/time_range.dart'; 3 | 4 | class MonthTimeRange extends TimeRange with PageableRange { 5 | @override 6 | final bool isUtc; 7 | 8 | final int year; 9 | final int month; 10 | 11 | factory MonthTimeRange( 12 | int year, 13 | int month, { 14 | bool isUtc = false, 15 | }) { 16 | final DateTime date = DateTimeConstructors.withTimezone(isUtc, year, month); 17 | return MonthTimeRange._internal(date.year, date.month, isUtc: date.isUtc); 18 | } 19 | 20 | const MonthTimeRange._internal( 21 | this.year, 22 | this.month, { 23 | this.isUtc = false, 24 | }) : assert(month > 0 && month <= 12); 25 | 26 | /// Will preserve the timezone of [dateTime] 27 | /// 28 | /// Please note that [day], [hour], [minute], [second], [millisecond], [microsecond] 29 | /// will be ignored. 30 | factory MonthTimeRange.fromDateTime(DateTime dateTime) => MonthTimeRange( 31 | dateTime.year, 32 | dateTime.month, 33 | isUtc: dateTime.isUtc, 34 | ); 35 | 36 | @override 37 | DateTime get from => DateTimeConstructors.withTimezone(isUtc, year, month); 38 | 39 | @override 40 | DateTime get to => from.endOfMonth(); 41 | 42 | @override 43 | DurationUnit get unit => DurationUnit.month; 44 | 45 | @override 46 | MonthTimeRange toUtc() => 47 | isUtc ? this : MonthTimeRange(year, month, isUtc: true); 48 | 49 | @override 50 | MonthTimeRange get next => MonthTimeRange(year, month + 1, isUtc: isUtc); 51 | 52 | @override 53 | MonthTimeRange get last => MonthTimeRange(year, month - 1, isUtc: isUtc); 54 | } 55 | -------------------------------------------------------------------------------- /lib/src/localizations/mixins/ru_RU/unit_string.dart: -------------------------------------------------------------------------------- 1 | import 'package:moment_dart/moment_dart.dart'; 2 | import 'package:moment_dart/src/localizations/mixins/ru_RU/units.dart'; 3 | 4 | class RussianSingularUnitString extends CountDependentUnitString { 5 | final String imenitelniyPadej; 6 | final String vinitelniyPadej; 7 | final String short; 8 | 9 | const RussianSingularUnitString( 10 | this.imenitelniyPadej, 11 | this.vinitelniyPadej, 12 | this.short, 13 | ); 14 | 15 | @override 16 | String get(Abbreviation form, bool dropPrefixOrSuffix, [int count = 1]) { 17 | if (form != Abbreviation.none) { 18 | return short; 19 | } 20 | 21 | if (dropPrefixOrSuffix) { 22 | return imenitelniyPadej; 23 | } 24 | 25 | return vinitelniyPadej; 26 | } 27 | } 28 | 29 | class RussianPluralUnitString extends CountDependentUnitString { 30 | final String singular; 31 | final String few; 32 | final String many; 33 | final String short; 34 | 35 | RussianPluralUnitString( 36 | {required this.singular, 37 | required this.few, 38 | required this.many, 39 | required this.short}); 40 | 41 | @override 42 | String get(Abbreviation form, bool dropPrefixOrSuffix, [int count = 1]) { 43 | if (form != Abbreviation.none) { 44 | return short; 45 | } 46 | // Rules for Russian pluralization: 47 | // 1. If number ends with 1 but not 11: singular 48 | // 2. If number ends with 2-4 but not 12-14: few 49 | // 3. Otherwise: many 50 | if ((count % 10) == 1 && (count % 100) != 11) { 51 | return singular; 52 | } else if ([2, 3, 4].contains(count % 10) && 53 | ![12, 13, 14].contains(count % 100)) { 54 | return few; 55 | } else { 56 | return many; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /lib/src/time_range/week.dart: -------------------------------------------------------------------------------- 1 | import 'package:moment_dart/src/extensions.dart'; 2 | import 'package:moment_dart/src/time_range/custom.dart'; 3 | import 'package:moment_dart/src/time_range/pageable_range.dart'; 4 | 5 | class LocalWeekTimeRange extends CustomTimeRange 6 | implements PageableRange { 7 | final int? weekStart; 8 | 9 | /// from `.startOfLocalWeek` to `.endOfLocalWeek`, inclusive 10 | LocalWeekTimeRange(DateTime dateTime, [this.weekStart]) 11 | : super( 12 | dateTime.startOfLocalWeek(weekStart), 13 | dateTime.endOfLocalWeek(weekStart), 14 | ); 15 | 16 | @override 17 | DurationUnit get unit => DurationUnit.week; 18 | 19 | @override 20 | CustomTimeRange toUtc() => throw UnsupportedError( 21 | "Local week time range cannot be converted to UTC"); 22 | 23 | @override 24 | LocalWeekTimeRange get next => 25 | LocalWeekTimeRange(from.startOfNextLocalWeek(weekStart)); 26 | 27 | @override 28 | LocalWeekTimeRange get last => 29 | LocalWeekTimeRange(from.startOfLastLocalWeek(weekStart)); 30 | } 31 | 32 | class IsoWeekTimeRange extends CustomTimeRange 33 | with PageableRange { 34 | IsoWeekTimeRange(DateTime dateTime) 35 | : super(dateTime.startOfLocalWeek(1), dateTime.endOfLocalWeek(1)); 36 | 37 | int get weekYear => from.weekYear; 38 | int get week => from.week; 39 | 40 | @override 41 | DurationUnit get unit => DurationUnit.week; 42 | 43 | @override 44 | CustomTimeRange toUtc() => 45 | throw UnsupportedError("ISO week time range cannot be converted to UTC"); 46 | 47 | @override 48 | IsoWeekTimeRange get next => IsoWeekTimeRange(from.nextMonday()); 49 | 50 | @override 51 | IsoWeekTimeRange get last => IsoWeekTimeRange(from.lastMonday()); 52 | } 53 | -------------------------------------------------------------------------------- /lib/src/time_range/day.dart: -------------------------------------------------------------------------------- 1 | import 'package:moment_dart/src/extensions.dart'; 2 | import 'package:moment_dart/src/time_range.dart'; 3 | 4 | class DayTimeRange extends TimeRange with PageableRange { 5 | @override 6 | final bool isUtc; 7 | 8 | final int year; 9 | final int month; 10 | final int day; 11 | 12 | factory DayTimeRange( 13 | int year, 14 | int month, 15 | int day, { 16 | bool isUtc = false, 17 | }) { 18 | final DateTime dateTime = DateTimeConstructors.withTimezone( 19 | isUtc, 20 | year, 21 | month, 22 | day, 23 | ); 24 | 25 | return DayTimeRange._internal( 26 | dateTime.year, 27 | dateTime.month, 28 | dateTime.day, 29 | isUtc: dateTime.isUtc, 30 | ); 31 | } 32 | 33 | const DayTimeRange._internal( 34 | this.year, 35 | this.month, 36 | this.day, { 37 | this.isUtc = false, 38 | }) : assert(month > 0 && month <= 12 && day > 0 && day <= 31); 39 | 40 | /// Will preserve the timezone of [dateTime] 41 | /// 42 | /// Please note that [hour], [minute], [second], [millisecond], [microsecond] 43 | /// will be ignored. 44 | factory DayTimeRange.fromDateTime(DateTime dateTime) => DayTimeRange( 45 | dateTime.year, 46 | dateTime.month, 47 | dateTime.day, 48 | isUtc: dateTime.isUtc, 49 | ); 50 | 51 | @override 52 | DateTime get from => 53 | DateTimeConstructors.withTimezone(isUtc, year, month, day); 54 | 55 | @override 56 | DateTime get to => from.endOfDay(); 57 | 58 | @override 59 | DurationUnit get unit => DurationUnit.day; 60 | 61 | @override 62 | DayTimeRange toUtc() => 63 | isUtc ? this : DayTimeRange(year, month, day, isUtc: true); 64 | 65 | @override 66 | DayTimeRange get next => DayTimeRange(year, month, day + 1, isUtc: isUtc); 67 | 68 | @override 69 | DayTimeRange get last => DayTimeRange(year, month, day - 1, isUtc: isUtc); 70 | } 71 | -------------------------------------------------------------------------------- /lib/src/localizations/mixins/english_like_ordinal.dart: -------------------------------------------------------------------------------- 1 | import 'package:moment_dart/moment_dart.dart'; 2 | 3 | /// This mixin provides: 4 | /// 5 | /// * [formattersWithOrdinal] - getter for set of formatters related to English-like ordinals 6 | /// * [ordinalNumber] - function returns *ordinal number string* for an `int` 7 | mixin EnglishLikeOrdinal on MomentLocalization { 8 | /// Ordinal suffixes in order: 9 | /// 10 | /// 1, 2, 3, other 11 | /// 12 | /// For example: ["st", "nd", "rd", "th"] 13 | List get ordinalSuffixes; 14 | 15 | /// Returns ordinal number string. 16 | /// 17 | /// An example in English (US): 18 | /// 19 | /// * 1st 20 | /// * 12th 21 | /// * 2602nd 22 | /// * 303rd 23 | /// * 1000th 24 | String ordinalNumber(int n) { 25 | final int lastTwoDigit = n % 100; 26 | 27 | if (!(lastTwoDigit > 10 && lastTwoDigit < 14)) { 28 | final int lastDigit = n % 10; 29 | 30 | switch (lastDigit) { 31 | case 1: 32 | return "$n${ordinalSuffixes[1]}"; 33 | case 2: 34 | return "$n${ordinalSuffixes[2]}"; 35 | case 3: 36 | return "$n${ordinalSuffixes[3]}"; 37 | default: 38 | break; 39 | } 40 | } 41 | 42 | return "$n${ordinalSuffixes[0]}"; 43 | } 44 | 45 | /// Overrides: 46 | /// 47 | /// `Mo`, `Qo`, `Do`, `DDDo`, `d_o`, and `wo` 48 | Map get formattersWithOrdinal => { 49 | FormatterToken.Mo: (DateTime dateTime) => ordinalNumber(dateTime.month), 50 | FormatterToken.Qo: (DateTime dateTime) => 51 | ordinalNumber(dateTime.quarter), 52 | FormatterToken.Do: (DateTime dateTime) => ordinalNumber(dateTime.day), 53 | FormatterToken.DDDo: (DateTime dateTime) => 54 | ordinalNumber(dateTime.dayOfYear), 55 | FormatterToken.d_o: (DateTime dateTime) => 56 | ordinalNumber(dateTime.weekday), 57 | FormatterToken.wo: (DateTime dateTime) => ordinalNumber(dateTime.week) 58 | }; 59 | } 60 | -------------------------------------------------------------------------------- /example/moment_dart_example.dart: -------------------------------------------------------------------------------- 1 | import "package:moment_dart/moment_dart.dart"; 2 | 3 | void main() { 4 | final MomentLocalization localization = MomentLocalizations.enUS(); 5 | 6 | /// If localization is omitted, it defaults to English (United States) 7 | final Moment moment = 8 | Moment.now(localization: localization) - Duration(days: 1); 9 | final Moment epoch = 10 | Moment(DateTime.fromMicrosecondsSinceEpoch(0, isUtc: true)); 11 | final Moment epochPlusFiveDays = epoch + Duration(days: 5); 12 | final Moment epochPlusAYear = epoch + Duration(days: 365); 13 | 14 | localization.relative(const Duration(seconds: 2)); // "in a few seconds" 15 | epochPlusFiveDays.from(epoch, dropPrefixOrSuffix: true); // "5 days" 16 | epochPlusFiveDays.from(epoch); // "in 5 days" 17 | epoch.calendar( 18 | reference: epochPlusFiveDays, omitHours: true); // "Last Thursday" 19 | epochPlusFiveDays.calendar(reference: epoch, omitHours: true); // "Tuesday" 20 | epochPlusAYear.from(epoch); //"in a year" 21 | epochPlusAYear.calendar(reference: epoch); // "01/01/1971 at 12:00AM" 22 | 23 | /// Returns relative string, such as `Yesterday`, `Last Sunday`, or default date format concatenated with default hour format. 24 | /// 25 | /// You can omit the hours using [omitHours] argument. 26 | /// 27 | /// [reference] is `Moment.now()` by default. 28 | moment.calendar(); 29 | 30 | /// Equivalent to `moment.from(Moment.now())` 31 | /// 32 | /// Example when using [LocalizationEnUs]: 33 | /// 34 | /// If [this] is yesterday, will result `"a day ago"` 35 | /// 36 | /// If [this] is tomorrow, will result `"in a day"` 37 | moment.fromNow(); 38 | 39 | /// Unmatched strings will be left as is. 40 | moment.format("YYYY년 MMMM Do dddd A hh:mm"); 41 | 42 | /// String encapsulated in square brackets will be escaped. 43 | moment.format("[YEAR:] YYYY, [MONTH:] MMMM, Do dddd A hh:mm"); 44 | 45 | print(Moment(DateTime(1971, 2, 14), localization: LocalizationEnUs()) 46 | .format("[YEAR:] YYYY, [MONTH:] MMMM, Do dddd A hh:mm")); 47 | } 48 | -------------------------------------------------------------------------------- /test/localizations/mn/word_gender_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:moment_dart/moment_dart.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | void main() { 5 | test("Mongolain Number word gender test", () { 6 | // Powers of 10 7 | expect(LocalizationMnMongMn.isFeminine(1), true); 8 | expect(LocalizationMnMongMn.isFeminine(10), false); 9 | expect(LocalizationMnMongMn.isFeminine(100), false); 10 | expect(LocalizationMnMongMn.isFeminine(1000), false); 11 | expect(LocalizationMnMongMn.isFeminine(10000), false); 12 | 13 | // Units 14 | expect(LocalizationMnMongMn.isFeminine(2), false); 15 | expect(LocalizationMnMongMn.isFeminine(3), false); 16 | expect(LocalizationMnMongMn.isFeminine(4), true); 17 | expect(LocalizationMnMongMn.isFeminine(5), false); 18 | expect(LocalizationMnMongMn.isFeminine(6), false); 19 | expect(LocalizationMnMongMn.isFeminine(7), false); 20 | expect(LocalizationMnMongMn.isFeminine(8), false); 21 | expect(LocalizationMnMongMn.isFeminine(9), true); 22 | expect(LocalizationMnMongMn.isFeminine(10), false); 23 | expect(LocalizationMnMongMn.isFeminine(123452), false); 24 | expect(LocalizationMnMongMn.isFeminine(123453), false); 25 | expect(LocalizationMnMongMn.isFeminine(123454), true); 26 | expect(LocalizationMnMongMn.isFeminine(123455), false); 27 | expect(LocalizationMnMongMn.isFeminine(123456), false); 28 | expect(LocalizationMnMongMn.isFeminine(123457), false); 29 | expect(LocalizationMnMongMn.isFeminine(123458), false); 30 | expect(LocalizationMnMongMn.isFeminine(123459), true); 31 | expect(LocalizationMnMongMn.isFeminine(1234510), false); 32 | 33 | // Tens 34 | expect(LocalizationMnMongMn.isFeminine(123410), false); 35 | expect(LocalizationMnMongMn.isFeminine(123420), false); 36 | expect(LocalizationMnMongMn.isFeminine(123430), false); 37 | expect(LocalizationMnMongMn.isFeminine(123440), true); 38 | expect(LocalizationMnMongMn.isFeminine(123450), false); 39 | expect(LocalizationMnMongMn.isFeminine(123460), false); 40 | expect(LocalizationMnMongMn.isFeminine(123470), false); 41 | expect(LocalizationMnMongMn.isFeminine(123480), false); 42 | expect(LocalizationMnMongMn.isFeminine(123490), true); 43 | }); 44 | } 45 | -------------------------------------------------------------------------------- /lib/src/localizations/mixins/simple_units.dart: -------------------------------------------------------------------------------- 1 | import 'package:moment_dart/moment_dart.dart'; 2 | 3 | /// [units] is used by following mixins: `simple_relative`, and `simple_duration`. 4 | /// 5 | /// 6 | /// This mixin provides: 7 | /// 8 | /// * [srDelta] - string getter; 9 | /// * [units] - map of unit translations that use [srDelta] as unit placeholder 10 | /// * [relativePast] - A function suffixes/prefixes given duration 11 | /// * [relativeFuture] - A function suffixes/prefixes given duration 12 | mixin SimpleUnits on MomentLocalization { 13 | /// This is used as a placeholder for unit in plural expressions 14 | String get srDelta => "%"; 15 | 16 | Map get units; 17 | 18 | String relativePast(String unit); 19 | String relativeFuture(String unit); 20 | } 21 | 22 | abstract class UnitString { 23 | const UnitString(); 24 | 25 | String get(Abbreviation form, bool dropPrefixOrSuffix); 26 | 27 | factory UnitString.withForm(String full, String mid, String short) => 28 | UnitStringWithForm._default(full, mid, short); 29 | 30 | factory UnitString.duo(String full, String shorter) => 31 | UnitStringWithForm._default(full, shorter, shorter); 32 | 33 | /// Use same text for all forms. 34 | factory UnitString.single(String value) => UnitStringSingle._default(value); 35 | } 36 | 37 | /// Unit string in full, mid, short forms. 38 | /// 39 | /// An example: 40 | /// * full: "16 minutes" 41 | /// * mid: "16 mins" 42 | /// * short: "16m" 43 | class UnitStringWithForm extends UnitString { 44 | final String full; 45 | final String mid; 46 | final String short; 47 | 48 | const UnitStringWithForm._default( 49 | this.full, 50 | this.mid, 51 | this.short, 52 | ) : super(); 53 | 54 | @override 55 | String get(Abbreviation form, bool dropPrefixOrSuffix) { 56 | switch (form) { 57 | case Abbreviation.none: 58 | return full; 59 | case Abbreviation.semi: 60 | return mid; 61 | case Abbreviation.full: 62 | return short; 63 | } 64 | } 65 | } 66 | 67 | class UnitStringSingle extends UnitString { 68 | final String value; 69 | 70 | const UnitStringSingle._default(this.value) : super(); 71 | 72 | @override 73 | String get(Abbreviation form, bool dropPrefixOrSuffix) => value; 74 | } 75 | -------------------------------------------------------------------------------- /lib/src/time_range/hour.dart: -------------------------------------------------------------------------------- 1 | import 'package:moment_dart/src/extensions.dart'; 2 | import 'package:moment_dart/src/time_range.dart'; 3 | 4 | class HourTimeRange extends TimeRange with PageableRange { 5 | @override 6 | final bool isUtc; 7 | 8 | final int year; 9 | final int month; 10 | final int day; 11 | final int hour; 12 | 13 | /// Please note that [minute], [second], [millisecond], [microsecond] 14 | /// will be ignored. 15 | factory HourTimeRange( 16 | int year, 17 | int month, 18 | int day, 19 | int hour, { 20 | bool isUtc = false, 21 | }) { 22 | final DateTime dateTime = DateTimeConstructors.withTimezone( 23 | isUtc, 24 | year, 25 | month, 26 | day, 27 | hour, 28 | ); 29 | 30 | return HourTimeRange._internal( 31 | dateTime.year, 32 | dateTime.month, 33 | dateTime.day, 34 | dateTime.hour, 35 | isUtc: dateTime.isUtc, 36 | ); 37 | } 38 | 39 | /// Please note that [minute], [second], [millisecond], [microsecond] 40 | /// will be ignored. 41 | const HourTimeRange._internal( 42 | this.year, 43 | this.month, 44 | this.day, 45 | this.hour, { 46 | this.isUtc = false, 47 | }) : assert(month > 0 && 48 | month <= 12 && 49 | day > 0 && 50 | day <= 31 && 51 | hour >= 0 && 52 | hour < 24); 53 | 54 | /// Will preserve the timezone of [dateTime] 55 | factory HourTimeRange.fromDateTime(DateTime dateTime) => HourTimeRange( 56 | dateTime.year, 57 | dateTime.month, 58 | dateTime.day, 59 | dateTime.hour, 60 | isUtc: dateTime.isUtc, 61 | ); 62 | 63 | @override 64 | DateTime get from => 65 | DateTimeConstructors.withTimezone(isUtc, year, month, day, hour); 66 | 67 | @override 68 | DateTime get to => from.endOfHour(); 69 | 70 | @override 71 | DurationUnit get unit => DurationUnit.hour; 72 | 73 | @override 74 | HourTimeRange toUtc() => 75 | isUtc ? this : HourTimeRange(year, month, day, hour, isUtc: true); 76 | 77 | @override 78 | HourTimeRange get next => HourTimeRange( 79 | year, 80 | month, 81 | day, 82 | hour + 1, 83 | isUtc: isUtc, 84 | ); 85 | 86 | @override 87 | HourTimeRange get last => HourTimeRange( 88 | year, 89 | month, 90 | day, 91 | hour - 1, 92 | isUtc: isUtc, 93 | ); 94 | } 95 | -------------------------------------------------------------------------------- /lib/src/localizations/mixins/mn_MN/duration.dart: -------------------------------------------------------------------------------- 1 | import 'package:moment_dart/moment_dart.dart'; 2 | import 'package:moment_dart/src/localizations/mixins/mn_MN/units.dart'; 3 | 4 | mixin MnMnDuration on MnMnUnits { 5 | /// If overriden, must implement for all [Abbreviation]s 6 | Map get durationDelimiter => { 7 | Abbreviation.none: " ", 8 | Abbreviation.semi: " ", 9 | Abbreviation.full: " ", 10 | }; 11 | 12 | @override 13 | String duration( 14 | Duration duration, { 15 | bool round = true, 16 | bool omitZeros = true, 17 | bool includeWeeks = false, 18 | Abbreviation form = Abbreviation.none, 19 | String? delimiter, 20 | DurationFormat format = DurationFormat.auto, 21 | bool dropPrefixOrSuffix = false, 22 | }) { 23 | final bool past = duration.isNegative; 24 | 25 | Duration left = duration.abs(); 26 | 27 | if (format.isAuto) { 28 | format = DurationFormat.resolveAuto(duration, includeWeeks); 29 | } 30 | 31 | final List result = []; 32 | 33 | for (int i = 0; i < format.length; i++) { 34 | final bool last = i == format.length - 1; 35 | 36 | final DurationUnit unit = format.units[i]; 37 | 38 | final int unitValue = last 39 | ? (_roundOrTruncate(left.inMicroseconds / unit.microseconds, round)) 40 | : (left.inMicroseconds ~/ unit.microseconds); 41 | 42 | final DurationInterval interval = 43 | DurationInterval.findByUnit(unitValue, unit); 44 | 45 | final String unitString = getUnit(interval, form, 46 | dropPrefixOrSuffix: !last || dropPrefixOrSuffix); 47 | 48 | if (!(omitZeros && unitValue == 0)) { 49 | result.add(unitString.replaceAll(srDelta, unitValue.toString())); 50 | } 51 | 52 | left -= Duration(microseconds: unit.microseconds * unitValue); 53 | } 54 | 55 | late final String value; 56 | 57 | if (result.isEmpty) { 58 | final String unitString = getUnit(DurationInterval.lessThanASecond, form, 59 | dropPrefixOrSuffix: dropPrefixOrSuffix); 60 | 61 | value = unitString; 62 | } else { 63 | value = result.join(delimiter ?? durationDelimiter[form]!); 64 | } 65 | 66 | if (dropPrefixOrSuffix) return value; 67 | 68 | return past ? relativePast(value) : relativeFuture(value); 69 | } 70 | 71 | int _roundOrTruncate(num x, bool round) { 72 | return round ? x.round() : x.toInt(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /lib/src/localizations/mixins/mn_Mong_MN/duration.dart: -------------------------------------------------------------------------------- 1 | import 'package:moment_dart/moment_dart.dart'; 2 | import 'package:moment_dart/src/localizations/mixins/mn_Mong_MN/units.dart'; 3 | 4 | mixin MnMongMnDuration on MnMongMnUnits { 5 | /// If overriden, must implement for all [Abbreviation]s 6 | Map get durationDelimiter => { 7 | Abbreviation.none: " ", 8 | Abbreviation.semi: " ", 9 | Abbreviation.full: " ", 10 | }; 11 | 12 | @override 13 | String duration( 14 | Duration duration, { 15 | bool round = true, 16 | bool omitZeros = true, 17 | bool includeWeeks = false, 18 | Abbreviation form = Abbreviation.none, 19 | String? delimiter, 20 | DurationFormat format = DurationFormat.auto, 21 | bool dropPrefixOrSuffix = false, 22 | }) { 23 | final bool past = duration.isNegative; 24 | 25 | Duration left = duration.abs(); 26 | 27 | if (format.isAuto) { 28 | format = DurationFormat.resolveAuto(duration, includeWeeks); 29 | } 30 | 31 | final List result = []; 32 | 33 | for (int i = 0; i < format.length; i++) { 34 | final bool last = i == format.length - 1; 35 | 36 | final DurationUnit unit = format.units[i]; 37 | 38 | final int unitValue = last 39 | ? (_roundOrTruncate(left.inMicroseconds / unit.microseconds, round)) 40 | : (left.inMicroseconds ~/ unit.microseconds); 41 | 42 | final DurationInterval interval = 43 | DurationInterval.findByUnit(unitValue, unit); 44 | 45 | final String unitString = getUnit(interval, form, 46 | dropPrefixOrSuffix: !last || dropPrefixOrSuffix); 47 | 48 | if (!(omitZeros && unitValue == 0)) { 49 | result.add(unitString.replaceAll(srDelta, unitValue.toString())); 50 | } 51 | 52 | left -= Duration(microseconds: unit.microseconds * unitValue); 53 | } 54 | 55 | late final String value; 56 | 57 | if (result.isEmpty) { 58 | final String unitString = getUnit(DurationInterval.lessThanASecond, form, 59 | dropPrefixOrSuffix: dropPrefixOrSuffix); 60 | 61 | value = unitString; 62 | } else { 63 | value = result.join(delimiter ?? durationDelimiter[form]!); 64 | } 65 | 66 | if (dropPrefixOrSuffix) return value; 67 | 68 | return past ? relativePast(value) : relativeFuture(value); 69 | } 70 | 71 | int _roundOrTruncate(num x, bool round) { 72 | return round ? x.round() : x.toInt(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /lib/src/localizations/mixins/ru_RU/duration.dart: -------------------------------------------------------------------------------- 1 | import 'package:moment_dart/moment_dart.dart'; 2 | import 'package:moment_dart/src/localizations/mixins/ru_RU/units.dart'; 3 | 4 | mixin RuRuDuration on RuRuUnits { 5 | /// If overriden, must implement for all [Abbreviation]s 6 | Map get durationDelimiter => { 7 | Abbreviation.none: " и ", 8 | Abbreviation.semi: " ", 9 | Abbreviation.full: " ", 10 | }; 11 | 12 | @override 13 | String duration( 14 | Duration duration, { 15 | bool round = true, 16 | bool omitZeros = true, 17 | bool includeWeeks = false, 18 | Abbreviation form = Abbreviation.none, 19 | String? delimiter, 20 | DurationFormat format = DurationFormat.auto, 21 | bool dropPrefixOrSuffix = false, 22 | }) { 23 | final bool past = duration.isNegative; 24 | 25 | Duration left = duration.abs(); 26 | 27 | if (format.isAuto) { 28 | format = DurationFormat.resolveAuto(duration, includeWeeks); 29 | } 30 | 31 | final List result = []; 32 | 33 | for (int i = 0; i < format.length; i++) { 34 | final bool last = i == format.length - 1; 35 | 36 | final DurationUnit unit = format.units[i]; 37 | 38 | final int unitValue = last 39 | ? (_roundOrTruncate(left.inMicroseconds / unit.microseconds, round)) 40 | : (left.inMicroseconds ~/ unit.microseconds); 41 | 42 | final DurationInterval interval = 43 | DurationInterval.findByUnit(unitValue, unit); 44 | 45 | final String unitString = 46 | units[interval]?.get(form, dropPrefixOrSuffix, unitValue) ?? 47 | "¯\\_(ツ)_/¯"; 48 | 49 | if (!(omitZeros && unitValue == 0)) { 50 | result.add(unitString.replaceAll(srDelta, unitValue.toString())); 51 | } 52 | 53 | left -= Duration(microseconds: unit.microseconds * unitValue); 54 | } 55 | 56 | late final String value; 57 | 58 | if (result.isEmpty) { 59 | final String unitString = units[DurationInterval.lessThanASecond] 60 | ?.get(form, dropPrefixOrSuffix) ?? 61 | "¯\\_(ツ)_/¯"; 62 | 63 | value = unitString; 64 | } else { 65 | value = result.join(delimiter ?? durationDelimiter[form]!); 66 | } 67 | 68 | if (dropPrefixOrSuffix) return value; 69 | 70 | return past ? relativePast(value) : relativeFuture(value); 71 | } 72 | 73 | int _roundOrTruncate(num x, bool round) { 74 | return round ? x.round() : x.toInt(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /lib/src/localizations/utils/duration_unit.dart: -------------------------------------------------------------------------------- 1 | import 'package:moment_dart/src/extensions.dart'; 2 | 3 | /// Does not take account for timezone 4 | enum DurationUnit { 5 | /// A microsecond equals a millionth of a second. 6 | /// 7 | /// Smallest unit we have in [Duration] 8 | microsecond(0, 1), 9 | 10 | /// A second equals [Duration.microsecondsPerMillisecond] microseconds 11 | millisecond(1, Duration.microsecondsPerMillisecond), 12 | 13 | /// A second equals [Duration.millisecondsPerSecond] milliseconds 14 | second(2, Duration.microsecondsPerSecond), 15 | 16 | /// An hour equals [Duration.secondsPerMinute] minutes 17 | /// 18 | /// No leap seconds 19 | minute(3, Duration.microsecondsPerMinute), 20 | 21 | /// An hour equals [Duration.minutesPerHour] minutes 22 | hour(4, Duration.microsecondsPerHour), 23 | 24 | /// A day equals [Duration.hoursPerDay] hours 25 | /// 26 | /// Timezone is NOT taken into account 27 | day(5, Duration.microsecondsPerDay), 28 | 29 | /// A week equals [DurationExtra.daysPerWeek] days 30 | /// 31 | /// Timezone is NOT taken into account 32 | week(6, DurationExtra.daysPerWeek * Duration.microsecondsPerDay), 33 | 34 | /// A month equals [DurationExtra.daysPerMonth] days 35 | /// 36 | /// Timezone is NOT taken into account 37 | month(7, DurationExtra.daysPerMonth * Duration.microsecondsPerDay), 38 | 39 | /// A year equals [DurationExtra.daysPerYear] days 40 | /// 41 | /// Timezone is NOT taken into account 42 | year(8, DurationExtra.daysPerYear * Duration.microsecondsPerDay); 43 | 44 | final int value; 45 | final int microseconds; 46 | 47 | const DurationUnit(this.value, this.microseconds); 48 | 49 | /// Returns **rounded** unit in specified [unit] 50 | static int relativeDuration(Duration duration, DurationUnit unit) { 51 | switch (unit) { 52 | case DurationUnit.microsecond: 53 | return duration.inMicroseconds; 54 | case DurationUnit.millisecond: 55 | return (duration.inMicroseconds / Duration.microsecondsPerMillisecond) 56 | .round(); 57 | case DurationUnit.second: 58 | return (duration.inMilliseconds / Duration.millisecondsPerSecond) 59 | .round(); 60 | case DurationUnit.minute: 61 | return (duration.inSeconds / Duration.secondsPerMinute).round(); 62 | case DurationUnit.hour: 63 | return (duration.inMinutes / Duration.minutesPerHour).round(); 64 | case DurationUnit.day: 65 | return (duration.inHours / Duration.hoursPerDay).round(); 66 | case DurationUnit.week: 67 | return (duration.inHours / Duration.hoursPerDay / 7).round(); 68 | case DurationUnit.month: 69 | return (duration.inDays / DurationExtra.daysPerMonth).round(); 70 | case DurationUnit.year: 71 | return (duration.inDays / DurationExtra.daysPerYear).round(); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /lib/src/localizations/utils/relative_interval.dart: -------------------------------------------------------------------------------- 1 | import 'package:moment_dart/src/localizations/utils/duration_unit.dart'; 2 | 3 | enum DurationInterval { 4 | //"(in) a few seconds (ago)" 5 | lessThanASecond(0, unit: DurationUnit.second, singular: false), 6 | //"(in) a second (ago)" 7 | aSecond(1, unit: DurationUnit.second, singular: true), 8 | //"(in) X seconds (ago)" 9 | seconds(2, unit: DurationUnit.second, singular: false), 10 | //"(in) a minute (ago)" 11 | aMinute(3, unit: DurationUnit.minute, singular: true), 12 | //"(in) X minutes (ago)" 13 | minutes(4, unit: DurationUnit.minute, singular: false), 14 | //"(in) an hour (ago)" 15 | anHour(5, unit: DurationUnit.hour, singular: true), 16 | //"(in) X hours (ago)" 17 | hours(6, unit: DurationUnit.hour, singular: false), 18 | //"(in) a day (ago)" 19 | aDay(7, unit: DurationUnit.day, singular: true), 20 | //"(in) X days (ago)" 21 | days(8, unit: DurationUnit.day, singular: false), 22 | //"(in) a day (ago)" 23 | aWeek(9, unit: DurationUnit.week, singular: true), 24 | //"(in) X days (ago)" 25 | weeks(10, unit: DurationUnit.week, singular: false), 26 | //"(in) a month (ago)" 27 | aMonth(11, unit: DurationUnit.month, singular: true), 28 | //"(in) X months (ago)" 29 | months(12, unit: DurationUnit.month, singular: false), 30 | //"(in) a year (ago)" 31 | aYear(13, unit: DurationUnit.year, singular: true), 32 | //"(in) X years (ago)" 33 | years(14, unit: DurationUnit.year, singular: false); 34 | 35 | final int value; 36 | final DurationUnit unit; 37 | final bool singular; 38 | 39 | const DurationInterval( 40 | this.value, { 41 | required this.unit, 42 | this.singular = false, 43 | }); 44 | 45 | /// Returns [DurationInterval] based on value and unit. 46 | /// 47 | /// [DurationUnit.microsecond] and [DurationUnit.millisecond] will return 48 | static DurationInterval findByUnit(int unitValue, DurationUnit unit) { 49 | final bool singular = unitValue == 1; 50 | 51 | switch (unit) { 52 | case DurationUnit.microsecond: 53 | case DurationUnit.millisecond: 54 | return DurationInterval.lessThanASecond; 55 | case DurationUnit.second: 56 | return singular ? DurationInterval.aSecond : DurationInterval.seconds; 57 | case DurationUnit.minute: 58 | return singular ? DurationInterval.aMinute : DurationInterval.minutes; 59 | case DurationUnit.hour: 60 | return singular ? DurationInterval.anHour : DurationInterval.hours; 61 | case DurationUnit.day: 62 | return singular ? DurationInterval.aDay : DurationInterval.days; 63 | case DurationUnit.week: 64 | return singular ? DurationInterval.aWeek : DurationInterval.weeks; 65 | case DurationUnit.month: 66 | return singular ? DurationInterval.aMonth : DurationInterval.months; 67 | case DurationUnit.year: 68 | return singular ? DurationInterval.aYear : DurationInterval.years; 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /test/localizations/find_by_locale_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:moment_dart/src/localizations/all.dart'; 2 | import 'package:test/expect.dart'; 3 | import 'package:test/scaffolding.dart'; 4 | 5 | void main() { 6 | test("localization test", () { 7 | expect( 8 | MomentLocalizations.byLocale("mn_MN"), 9 | TypeMatcher(), 10 | ); 11 | expect( 12 | MomentLocalizations.byLocale("mn"), 13 | TypeMatcher(), 14 | ); 15 | 16 | expect( 17 | MomentLocalizations.byLocale("mn_Mong_MN"), 18 | TypeMatcher(), 19 | ); 20 | 21 | expect( 22 | MomentLocalizations.byLocale("mn_Qaaq_MN"), 23 | TypeMatcher(), 24 | ); 25 | 26 | expect( 27 | MomentLocalizations.byLocale("ko_KR"), 28 | TypeMatcher(), 29 | ); 30 | 31 | expect( 32 | MomentLocalizations.byLocale("ja_JP"), 33 | TypeMatcher(), 34 | ); 35 | 36 | expect( 37 | MomentLocalizations.byLocale("ar_PS"), 38 | TypeMatcher(), 39 | ); 40 | 41 | expect( 42 | MomentLocalizations.byLocale("de_DE"), 43 | TypeMatcher(), 44 | ); 45 | expect( 46 | MomentLocalizations.byLocale("de"), 47 | TypeMatcher(), 48 | ); 49 | 50 | expect( 51 | MomentLocalizations.byLocale("es_ES"), 52 | TypeMatcher(), 53 | ); 54 | expect( 55 | MomentLocalizations.byLocale("es"), 56 | TypeMatcher(), 57 | ); 58 | 59 | expect( 60 | MomentLocalizations.byLocale("fr_FR"), 61 | TypeMatcher(), 62 | ); 63 | expect( 64 | MomentLocalizations.byLocale("fr"), 65 | TypeMatcher(), 66 | ); 67 | 68 | expect( 69 | MomentLocalizations.byLocale("tr_TR"), 70 | TypeMatcher(), 71 | ); 72 | expect( 73 | MomentLocalizations.byLocale("tr"), 74 | TypeMatcher(), 75 | ); 76 | 77 | expect( 78 | MomentLocalizations.byLocale("pt_PT"), 79 | TypeMatcher(), 80 | ); 81 | expect( 82 | MomentLocalizations.byLocale("pt"), 83 | TypeMatcher(), 84 | ); 85 | 86 | expect( 87 | MomentLocalizations.byLocale("it_IT"), 88 | TypeMatcher(), 89 | ); 90 | expect( 91 | MomentLocalizations.byLocale("it"), 92 | TypeMatcher(), 93 | ); 94 | expect( 95 | MomentLocalizations.byLocale("zh_CN"), 96 | TypeMatcher(), 97 | ); 98 | 99 | expect( 100 | MomentLocalizations.byLocale("ru_RU"), 101 | TypeMatcher(), 102 | ); 103 | expect( 104 | MomentLocalizations.byLocale("ru"), 105 | TypeMatcher(), 106 | ); 107 | }); 108 | } 109 | -------------------------------------------------------------------------------- /lib/src/localizations/utils/duration_format.dart: -------------------------------------------------------------------------------- 1 | import 'package:moment_dart/src/extensions/benefits.dart'; 2 | import 'package:moment_dart/src/localizations/utils/duration_unit.dart'; 3 | 4 | /// [units] must be in descending order 5 | /// 6 | /// ___Anything more precise than [DurationUnit.second] is not yet implemented___ 7 | class DurationFormat { 8 | final List units; 9 | 10 | /// [units] should be provi ded in descending order 11 | /// 12 | /// If not, result produced with [this] format is unexpected 13 | const DurationFormat(this.units); 14 | 15 | int get length => units.length; 16 | 17 | /// Returns [units.last], will throw [StateError] if [units] is empty 18 | DurationUnit get last => units.last; 19 | bool get isAuto => length == 0; 20 | 21 | /// Returns most suitable [DurationFormat] for [duration] 22 | /// 23 | /// Set [includeWeeks] to true if you need *week* as a unit 24 | static DurationFormat resolveAuto( 25 | Duration duration, [ 26 | bool includeWeeks = false, 27 | ]) { 28 | // I really don't want to deal with negative numbers 29 | duration = duration.abs(); 30 | 31 | if (duration.inYears >= 1) { 32 | return DurationFormat.ym; 33 | } else if (duration.inMonths >= 1) { 34 | return DurationFormat.md; 35 | } else if (includeWeeks && duration.inWeeks >= 1) { 36 | return DurationFormat.wd; 37 | } else if (duration.inDays >= 1) { 38 | return DurationFormat.dh; 39 | } else if (duration.inHours >= 1) { 40 | return DurationFormat.hm; 41 | } else if (duration.inMinutes >= 1) { 42 | return DurationFormat.ms; 43 | } else { 44 | return DurationFormat.s; 45 | } 46 | } 47 | 48 | /// Tries to find the most optimal format for the duration 49 | static const DurationFormat auto = DurationFormat([]); 50 | 51 | // year month day hour minute second 52 | static const DurationFormat all = DurationFormat([ 53 | DurationUnit.year, 54 | DurationUnit.month, 55 | DurationUnit.day, 56 | DurationUnit.hour, 57 | DurationUnit.minute, 58 | DurationUnit.second, 59 | ]); 60 | 61 | /// **X** year(s) **y** months 62 | static const DurationFormat ym = 63 | DurationFormat([DurationUnit.year, DurationUnit.month]); 64 | 65 | /// **X** month(s) **y** day(s) 66 | static const DurationFormat md = 67 | DurationFormat([DurationUnit.month, DurationUnit.day]); 68 | 69 | /// **X** week(s) **y** day(s) 70 | static const DurationFormat wd = 71 | DurationFormat([DurationUnit.week, DurationUnit.day]); 72 | 73 | /// **X** day(s) **y** hour(s) 74 | static const DurationFormat dh = 75 | DurationFormat([DurationUnit.day, DurationUnit.hour]); 76 | 77 | /// **X** minute(s) **y** second(s) 78 | static const DurationFormat hm = 79 | DurationFormat([DurationUnit.hour, DurationUnit.minute]); 80 | 81 | /// **X** minute(s) **y** second(s) 82 | static const DurationFormat ms = 83 | DurationFormat([DurationUnit.minute, DurationUnit.second]); 84 | 85 | /// **X** second(s) 86 | static const DurationFormat s = DurationFormat([DurationUnit.second]); 87 | } 88 | -------------------------------------------------------------------------------- /lib/src/extensions/clamped_setters.dart: -------------------------------------------------------------------------------- 1 | import 'package:moment_dart/moment_dart.dart'; 2 | 3 | extension ClampedSetters on DateTime { 4 | int get lastDayOfMonth => isLeapYear 5 | ? Moment.daysInMonthsInLeapYear[month] 6 | : Moment.daysInMonths[month]; 7 | 8 | /// Prevents [second] overflows resolving into the next minute by clamping the 9 | /// second between 0 and 59 10 | DateTime setClampedSecond(int second) { 11 | return copyWith(second: second.clamp(0, 59)); 12 | } 13 | 14 | /// Prevents [minute] overflows resolving into the next hour by clamping the 15 | /// minute between 0 and 59 16 | DateTime setClampedMinute(int minute) { 17 | return copyWith(minute: minute.clamp(0, 59)); 18 | } 19 | 20 | /// Prevents [hour] overflows resolving into the next day by clamping the hour 21 | /// between 0 and 23 22 | DateTime setClampedHour(int hour) { 23 | return copyWith(hour: hour.clamp(0, 23)); 24 | } 25 | 26 | /// Prevents [day] overflows resolving into the next month by clamping the day 27 | /// to between 1 and the last day of the month 28 | /// 29 | /// For example, if the current month is February, and the day is 30, 30 | /// the day will be clamped to 28. 31 | /// 32 | /// ```dart 33 | /// final example = DateTime(2022, DateTime.february, 1); 34 | /// print(example.setClampedDay(30)); // 2022-02-28 00:00:00.000 35 | /// ``` 36 | DateTime setClampedDay(int day) { 37 | return copyWith(day: day.clamp(1, lastDayOfMonth)); 38 | } 39 | 40 | /// Prevents [month] overflows resolving into the next year by clamping the 41 | /// month between January (1) and December (12). This also clamps the [day] 42 | /// between 1 and last day of the month if necessary. 43 | /// 44 | /// For example, if the current month is December, and the month is February, 45 | /// the [day] will be clamped between 1 and 28 46 | /// 47 | /// ```dart 48 | /// final example1 = DateTime(2022, DateTime.december, 31); 49 | /// print(example1.setClampedMonth(DateTime.february)); // 2022-02-28 00:00:00.000 50 | /// 51 | /// final example2 = DateTime(2004, DateTime.february, 29); 52 | /// print(example2.setClampedMonth(DateTime.december)); // 2004-12-29 00:00:00.000 53 | /// ``` 54 | DateTime setClampedMonth(int month) { 55 | final clampedMonth = 56 | month.toInt().clamp(DateTime.january, DateTime.december); 57 | final clampedDay = day.clamp( 58 | 1, 59 | isLeapYear 60 | ? Moment.daysInMonthsInLeapYear[clampedMonth] 61 | : Moment.daysInMonths[clampedMonth]); 62 | 63 | return copyWith( 64 | month: clampedMonth, 65 | day: clampedDay, 66 | ); 67 | } 68 | } 69 | 70 | extension ClampedSettersPlus on Moment { 71 | Moment setClampedSecond(int second) => 72 | copyWith(dateTime: forcedSuperType.setClampedSecond(second)); 73 | 74 | Moment setClampedMinute(int minute) => 75 | copyWith(dateTime: forcedSuperType.setClampedMinute(minute)); 76 | 77 | Moment setClampedHour(int hour) => 78 | copyWith(dateTime: forcedSuperType.setClampedHour(hour)); 79 | 80 | Moment setClampedDay(int day) => 81 | copyWith(dateTime: forcedSuperType.setClampedDay(day)); 82 | 83 | Moment setClampedMonth(int month) => copyWith( 84 | dateTime: forcedSuperType.setClampedMonth(month), 85 | ); 86 | } 87 | -------------------------------------------------------------------------------- /lib/src/localizations/mixins/simple_duration.dart: -------------------------------------------------------------------------------- 1 | import 'package:moment_dart/moment_dart.dart'; 2 | import 'package:moment_dart/src/localizations/mixins/simple_units.dart'; 3 | 4 | /// Depends on [SimpleUnits] 5 | /// 6 | /// This mixin provides: 7 | /// 8 | /// * [durationDelimiter] - getter for delimeters of each [Abbreviation] 9 | /// 10 | /// And **overrides** [MomentLocalization.duration] 11 | mixin SimpleDuration on SimpleUnits { 12 | /// If overriden, must implement for all [Abbreviation]s 13 | Map get durationDelimiter => { 14 | Abbreviation.none: " ", 15 | Abbreviation.semi: " ", 16 | Abbreviation.full: " ", 17 | }; 18 | 19 | /// "and" keyword. 20 | /// 21 | /// Used to connect last two elements of duration list items. Will be appended after [durationDelimiter] For example: 22 | /// 23 | /// When null --> 24 | /// 2 minutes 3 seconds 25 | /// 26 | /// When equal to 'and' --> 27 | /// 2 minutes _and_ 3 seconds 28 | String? get delimeterConnector => null; 29 | 30 | @override 31 | String duration( 32 | Duration duration, { 33 | bool round = true, 34 | bool omitZeros = true, 35 | bool includeWeeks = false, 36 | Abbreviation form = Abbreviation.none, 37 | String? delimiter, 38 | DurationFormat format = DurationFormat.auto, 39 | bool dropPrefixOrSuffix = false, 40 | }) { 41 | final bool past = duration.isNegative; 42 | 43 | Duration left = duration.abs(); 44 | 45 | if (format.isAuto) { 46 | format = DurationFormat.resolveAuto(duration, includeWeeks); 47 | } 48 | 49 | final List result = []; 50 | 51 | for (int i = 0; i < format.length; i++) { 52 | final bool last = i == format.length - 1; 53 | 54 | final DurationUnit unit = format.units[i]; 55 | 56 | final int unitValue = last 57 | ? (_roundOrTruncate(left.inMicroseconds / unit.microseconds, round)) 58 | : (left.inMicroseconds ~/ unit.microseconds); 59 | 60 | final DurationInterval interval = 61 | DurationInterval.findByUnit(unitValue, unit); 62 | 63 | final UnitString? unitString = units[interval]; 64 | 65 | if (unitString == null) { 66 | throw MomentException( 67 | "UnitString implementation is missing for $interval in localization $locale"); 68 | } 69 | 70 | if (!(omitZeros && unitValue == 0)) { 71 | result.add(unitString 72 | .get(form, dropPrefixOrSuffix) 73 | .replaceAll(srDelta, unitValue.toString())); 74 | } 75 | 76 | left -= Duration(microseconds: unit.microseconds * unitValue); 77 | } 78 | 79 | late final String value; 80 | 81 | if (result.isEmpty) { 82 | final UnitString? unitString = units[DurationInterval.lessThanASecond]; 83 | 84 | if (unitString == null) { 85 | throw MomentException( 86 | "UnitString implementation is missing for ${DurationInterval.lessThanASecond} in localization $locale"); 87 | } 88 | 89 | value = unitString.get(form, dropPrefixOrSuffix); 90 | } else { 91 | value = result.join(delimiter ?? durationDelimiter[form]!); 92 | } 93 | 94 | if (dropPrefixOrSuffix) return value; 95 | 96 | return past ? relativePast(value) : relativeFuture(value); 97 | } 98 | 99 | int _roundOrTruncate(num x, bool round) { 100 | return round ? x.round() : x.toInt(); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /lib/src/localizations/mixins/ru_RU/units.dart: -------------------------------------------------------------------------------- 1 | import 'package:moment_dart/moment_dart.dart'; 2 | import 'package:moment_dart/src/localizations/mixins/ru_RU/unit_string.dart'; 3 | import 'package:moment_dart/src/localizations/mixins/simple_units.dart'; 4 | 5 | abstract class CountDependentUnitString extends UnitString { 6 | const CountDependentUnitString(); 7 | 8 | @override 9 | String get(Abbreviation form, bool dropPrefixOrSuffix, [int count = 1]); 10 | } 11 | 12 | mixin RuRuUnits on SimpleUnits { 13 | @override 14 | String relativePast(String unit) { 15 | return "$unit назад"; 16 | } 17 | 18 | @override 19 | String relativeFuture(String unit) { 20 | return "через $unit"; 21 | } 22 | 23 | @override 24 | Map get units => { 25 | DurationInterval.lessThanASecond: RussianSingularUnitString( 26 | "несколько секунд", 27 | "несколько секунд", 28 | "неск. сек", 29 | ), 30 | DurationInterval.aSecond: RussianSingularUnitString( 31 | "секунда", 32 | "секунду", 33 | "сек", 34 | ), 35 | DurationInterval.seconds: RussianPluralUnitString( 36 | singular: "$srDelta секунда", 37 | few: "$srDelta секунды", 38 | many: "$srDelta секунд", 39 | short: "$srDeltaсек", 40 | ), 41 | DurationInterval.aMinute: RussianSingularUnitString( 42 | "1 минута", 43 | "минуту", 44 | "мин", 45 | ), 46 | DurationInterval.minutes: RussianPluralUnitString( 47 | singular: "$srDelta минута", 48 | few: "$srDelta минуты", 49 | many: "$srDelta минут", 50 | short: "$srDeltaмин", 51 | ), 52 | DurationInterval.anHour: RussianSingularUnitString("час", "час", "ч"), 53 | DurationInterval.hours: RussianPluralUnitString( 54 | singular: "$srDelta час", 55 | few: "$srDelta часа", 56 | many: "$srDelta часов", 57 | short: "$srDeltaч", 58 | ), 59 | DurationInterval.aDay: RussianSingularUnitString("день", "день", "дн"), 60 | DurationInterval.days: RussianPluralUnitString( 61 | singular: "$srDelta день", 62 | few: "$srDelta дня", 63 | many: "$srDelta дней", 64 | short: "$srDeltaдн", 65 | ), 66 | DurationInterval.aWeek: RussianSingularUnitString( 67 | "1 неделя", 68 | "неделю", 69 | "1 нед", 70 | ), 71 | DurationInterval.weeks: RussianPluralUnitString( 72 | singular: "$srDelta неделя", 73 | few: "$srDelta недели", 74 | many: "$srDelta недель", 75 | short: "$srDeltaнед", 76 | ), 77 | DurationInterval.aMonth: RussianSingularUnitString( 78 | "месяц", 79 | "месяц", 80 | "1мес", 81 | ), 82 | DurationInterval.months: RussianPluralUnitString( 83 | singular: "$srDelta месяц", 84 | few: "$srDelta месяца", 85 | many: "$srDelta месяцев", 86 | short: "$srDeltaмес", 87 | ), 88 | DurationInterval.aYear: RussianSingularUnitString( 89 | "год", 90 | "год", 91 | "1г", 92 | ), 93 | DurationInterval.years: RussianPluralUnitString( 94 | singular: "$srDelta год", 95 | few: "$srDelta года", 96 | many: "$srDelta лет", 97 | short: "$srDeltaг.", 98 | ), 99 | }; 100 | } 101 | -------------------------------------------------------------------------------- /lib/src/localizations/mixins/mn_Mong_MN/units.dart: -------------------------------------------------------------------------------- 1 | import 'package:moment_dart/src/localizations/mixins/simple_units.dart'; 2 | import 'package:moment_dart/src/localizations/utils/duration_unit.dart'; 3 | import 'package:moment_dart/src/localizations/utils/relative_interval.dart'; 4 | import 'package:moment_dart/src/localizations/utils/abbreviation.dart'; 5 | 6 | /// This mixin provides: 7 | /// 8 | /// * [srDelta] - string getter; 9 | /// * [units] - map of unit translations that use [srDelta] as unit placeholder 10 | /// * [relativePast] - A function suffixes/prefixes given duration 11 | /// * [relativeFuture] - A function suffixes/prefixes given duration 12 | mixin MnMongMnUnits on SimpleUnits { 13 | /// This is used as a placeholder for unit in plural expressions 14 | @override 15 | String get srDelta => "%"; 16 | 17 | String getUnit( 18 | DurationInterval interval, 19 | Abbreviation form, { 20 | bool dropPrefixOrSuffix = false, 21 | }) { 22 | final String result = 23 | units[interval]?.get(form, dropPrefixOrSuffix) ?? "¯\\_(ツ)_/¯"; 24 | 25 | if (dropPrefixOrSuffix) return result; 26 | 27 | final useFeminineSuffix = interval != DurationInterval.lessThanASecond && 28 | (interval.unit == DurationUnit.minute || 29 | interval.unit == DurationUnit.second || 30 | interval.unit == DurationUnit.day); 31 | 32 | late final String suffix; // Language specific stuff 33 | 34 | if (form != Abbreviation.none) { 35 | suffix = ""; 36 | } else if (interval.unit == DurationUnit.month) { 37 | suffix = " ᠶᠢᠨ"; 38 | } else { 39 | suffix = (useFeminineSuffix ? " ᠦᠨ" : " ᠤᠨ"); 40 | } 41 | 42 | return result + suffix; 43 | } 44 | 45 | @override 46 | String relativePast(String unit) => "$unit ᠡᠮᠦᠨ᠎ᠡ"; 47 | @override 48 | String relativeFuture(String unit) => "$unit ᠳᠠᠷᠠᠭ᠎ᠠ"; 49 | 50 | @override 51 | Map get units => { 52 | DurationInterval.lessThanASecond: UnitString.withForm( 53 | "ᠬᠡᠳᠦᠨ ᠬᠣᠷᠤᠮ", 54 | "ᠬᠣᠷᠤᠮ", 55 | "ᠬᠣᠷᠤᠮ", 56 | ), 57 | DurationInterval.aSecond: UnitString.withForm( 58 | "1 ᠰᠧᠻᠦ᠋ᠨ᠍ᠳ᠋", 59 | "1 ᠰᠧᠻ᠊᠍", 60 | "1ᠰ", 61 | ), 62 | DurationInterval.seconds: UnitString.withForm( 63 | "$srDelta ᠰᠧᠻᠦ᠋ᠨ᠍ᠳ᠋", 64 | "$srDelta ᠰᠧᠻ᠍᠊᠍", 65 | "$srDeltaᠰ", 66 | ), 67 | DurationInterval.aMinute: UnitString.withForm( 68 | "1 ᠮᠢᠨᠦᠢᠲ", 69 | "1 ᠮᠢᠨ᠊᠍", 70 | "1ᠮ", 71 | ), 72 | DurationInterval.minutes: UnitString.withForm( 73 | "$srDelta ᠮᠢᠨᠦᠢᠲ", 74 | "$srDelta ᠮᠢᠨ᠊᠍", 75 | "$srDeltaᠮ", 76 | ), 77 | DurationInterval.anHour: UnitString.withForm( 78 | "1 ᠴᠠᠭ", 79 | "1 ᠴᠠᠭ", 80 | "1ᠴ", 81 | ), 82 | DurationInterval.hours: UnitString.withForm( 83 | "$srDelta ᠴᠠᠭ", 84 | "$srDelta ᠴᠠᠭ", 85 | "$srDeltaᠴ", 86 | ), 87 | DurationInterval.aDay: UnitString.withForm( 88 | "1 ᠡᠳᠦᠷ", 89 | "1 ᠡᠳᠦᠷ", 90 | "1ᠥ", 91 | ), 92 | DurationInterval.days: UnitString.withForm( 93 | "$srDelta ᠡᠳᠦᠷ", 94 | "$srDelta ᠡᠳᠦᠷ", 95 | "$srDeltaᠥ", 96 | ), 97 | DurationInterval.aWeek: UnitString.withForm( 98 | "1 ᠳᠣᠯᠤᠭ᠎ᠠ ᠬᠣᠨᠤᠭ", 99 | "1 ᠳᠣᠯᠤᠭ᠎ᠠ ᠬᠣᠨ᠍᠊᠍", 100 | "1ᠳᠣ.ᠬᠣᠨ᠍᠊᠍", 101 | ), 102 | DurationInterval.weeks: UnitString.withForm( 103 | "$srDelta ᠳᠣᠯᠤᠭ᠎ᠠ ᠬᠣᠨᠤᠭ", 104 | "$srDelta ᠳᠣᠯᠤᠭ᠎ᠠ ᠬᠣᠨ᠍᠊᠍", 105 | "$srDeltaᠳᠣ.ᠬᠣᠨ᠍᠊᠍", 106 | ), 107 | DurationInterval.aMonth: UnitString.withForm( 108 | "1 ᠰᠠᠷ᠎ᠠ", 109 | "1 ᠰᠠ᠊᠍", 110 | "1ᠰᠠ᠊᠍", 111 | ), 112 | DurationInterval.months: UnitString.withForm( 113 | "$srDelta ᠰᠠᠷ᠎ᠠ", 114 | "$srDelta ᠰᠠ᠊᠍", 115 | "$srDeltaᠰᠠ᠊᠍", 116 | ), 117 | DurationInterval.aYear: UnitString.withForm( 118 | "1 ᠵᠢᠯ", 119 | "1 ᠵᠢᠯ", 120 | "1ᠵᠢᠯ", 121 | ), 122 | DurationInterval.years: UnitString.withForm( 123 | "$srDelta ᠵᠢᠯ", 124 | "$srDelta ᠵᠢᠯ", 125 | "$srDeltaᠵᠢᠯ", 126 | ), 127 | }; 128 | } 129 | -------------------------------------------------------------------------------- /lib/src/localizations/mixins/simple_range.dart: -------------------------------------------------------------------------------- 1 | import 'package:moment_dart/src/localization.dart'; 2 | import 'package:moment_dart/src/moment.dart'; 3 | 4 | class SimpleRangeData { 5 | final String thisWeek; 6 | 7 | final String Function(YearTimeRange yearTimeRange, 8 | {DateTime? anchor, bool useRelative}) year; 9 | 10 | final String Function(MonthTimeRange monthTimeRange, 11 | {DateTime? anchor, bool useRelative}) month; 12 | 13 | /// When end date equals [Moment.maxValue] 14 | /// 15 | /// For example, in en_US, 16 | /// (formattedDate) => "After $formattedDate" 17 | final String Function(String formattedDate) allAfter; 18 | 19 | /// When start date equals [Moment.minValue] 20 | /// 21 | /// For example, in en_US, 22 | /// (formattedDate) => "Before $formattedDate" 23 | final String Function(String formattedDate) allBefore; 24 | 25 | /// When range [from] is equal to [Moment.minValue] and 26 | /// [to] is equal to [Moment.maxValue] 27 | /// 28 | /// For example, in en_US, 29 | /// "All time" 30 | final String customRangeAllTime; 31 | 32 | /// Delimiter for custom range 33 | /// 34 | /// For example, when set to " - ", 35 | /// output will look like "2021-01-01 - 2021-01-31" 36 | /// 37 | /// Defaults to " - " 38 | final String customRangeDelimiter; 39 | 40 | /// Reverses order of `from` and `to` 41 | /// 42 | /// Defaults to [false] 43 | final bool reverseRangeDates; 44 | 45 | const SimpleRangeData({ 46 | required this.thisWeek, 47 | required this.year, 48 | required this.month, 49 | required this.allAfter, 50 | required this.allBefore, 51 | required this.customRangeAllTime, 52 | this.customRangeDelimiter = " - ", 53 | this.reverseRangeDates = false, 54 | }); 55 | } 56 | 57 | mixin SimpleRange on MomentLocalization { 58 | SimpleRangeData get simpleRangeData; 59 | 60 | @override 61 | String range(TimeRange range, {DateTime? anchor, bool useRelative = true}) { 62 | anchor ??= Moment.now(); 63 | 64 | if (range is HourTimeRange || 65 | range is DayTimeRange || 66 | range is IsoWeekTimeRange) { 67 | return super.range(range, anchor: anchor, useRelative: useRelative); 68 | } else if (range is LocalWeekTimeRange) { 69 | if (useRelative && LocalWeekTimeRange(anchor) == range) { 70 | return simpleRangeData.thisWeek; 71 | } 72 | 73 | return super.range(range, anchor: anchor, useRelative: useRelative); 74 | } else if (range is MonthTimeRange) { 75 | return simpleRangeData.month(range, 76 | anchor: anchor, useRelative: useRelative); 77 | } else if (range is YearTimeRange) { 78 | return simpleRangeData.year(range, 79 | anchor: anchor, useRelative: useRelative); 80 | } 81 | 82 | return _customRange(range, this); 83 | } 84 | 85 | static String _customRange( 86 | TimeRange custom, 87 | SimpleRange localization, { 88 | DateTime? anchor, 89 | }) { 90 | if (custom.to == Moment.maxValue || custom.to == Moment.maxValueUtc) { 91 | if (custom.from == Moment.minValue || custom.from == Moment.minValueUtc) { 92 | return localization.simpleRangeData.customRangeAllTime; 93 | } 94 | 95 | final String formattedDate = custom.from 96 | .toMoment(localization: localization) 97 | .calendar(omitHours: custom.from.isMidnight, reference: anchor); 98 | 99 | return localization.simpleRangeData.allAfter(formattedDate); 100 | } 101 | 102 | if (custom.from == Moment.minValue || custom.from == Moment.minValueUtc) { 103 | final String formattedDate = custom.to 104 | .toMoment(localization: localization) 105 | .calendar(omitHours: custom.to.isMidnight, reference: anchor); 106 | 107 | return localization.simpleRangeData.allBefore(formattedDate); 108 | } 109 | 110 | final bool omitHours = custom.from.isMidnight && custom.to.isMidnight; 111 | 112 | final String from = custom.from 113 | .toMoment(localization: localization) 114 | .calendar(omitHours: omitHours, reference: anchor); 115 | final String to = custom.to 116 | .toMoment(localization: localization) 117 | .calendar(omitHours: omitHours, reference: anchor); 118 | 119 | final List parts = [from, to]; 120 | 121 | return localization.simpleRangeData.reverseRangeDates 122 | ? parts.reversed.join(localization.simpleRangeData.customRangeDelimiter) 123 | : parts.join(localization.simpleRangeData.customRangeDelimiter); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /lib/src/localizations/mixins/complex_calendar.dart: -------------------------------------------------------------------------------- 1 | import 'package:moment_dart/src/localization.dart'; 2 | import 'package:moment_dart/src/moment.dart'; 3 | 4 | /// This mixin provides: 5 | /// 6 | /// * [complexCalendarData] - getter for custom data 7 | /// 8 | /// And **overrides** [MomentLocalization.calendar] 9 | mixin ComplexCalendar on MomentLocalization { 10 | @override 11 | CalenderLocalizationData? get calendarData => null; 12 | ComplexCalenderLocalizationData get complexCalendarData; 13 | 14 | /// Calendar string 15 | @override 16 | String calendar( 17 | Moment moment, { 18 | DateTime? reference, 19 | String? customFormat, 20 | bool omitHours = false, 21 | bool omitHoursIfDistant = true, 22 | }) { 23 | reference ??= Moment.nowWithTimezone(moment.isUtc); 24 | 25 | late final String dateString; 26 | 27 | final int deltaDays = moment.differenceInDays(reference); 28 | final String? deltaDaysName = 29 | complexCalendarData.relativeDayNames[deltaDays]; 30 | 31 | if (deltaDaysName != null) { 32 | dateString = deltaDaysName; 33 | } else { 34 | /// If it occured before [reference] 35 | if (moment < reference) { 36 | final DateTime weekBefore = reference.subtract(const Duration(days: 7)); 37 | 38 | if (moment.isBefore(weekBefore)) { 39 | if (omitHoursIfDistant) { 40 | omitHours = true; 41 | } 42 | 43 | dateString = moment.format( 44 | customFormat ?? MomentLocalization.localizationDefaultDateFormat, 45 | ); 46 | } else { 47 | dateString = complexCalendarData.keywords.lastWeekday( 48 | moment, 49 | reference: reference, 50 | ); 51 | } 52 | } 53 | 54 | /// If it occured after [reference] 55 | else { 56 | final DateTime weekAfter = reference.add(const Duration(days: 7)); 57 | 58 | /// If it's this or next week (relative to the reference) 59 | if (moment.isBefore(weekAfter)) { 60 | dateString = complexCalendarData.keywords.nextWeekday( 61 | moment, 62 | reference: reference, 63 | ); 64 | } else { 65 | if (omitHoursIfDistant) { 66 | omitHours = true; 67 | } 68 | 69 | dateString = moment.format( 70 | customFormat ?? MomentLocalization.localizationDefaultDateFormat, 71 | ); 72 | } 73 | } 74 | } 75 | 76 | if (customFormat != null) return dateString; 77 | 78 | if (omitHours) { 79 | return dateString; 80 | } 81 | 82 | return complexCalendarData.keywords.at( 83 | moment, 84 | dateString, 85 | calendarTime(moment), 86 | reference: reference, 87 | ); 88 | } 89 | } 90 | 91 | class ComplexCalenderLocalizationData { 92 | /// Relative days on calendar. 93 | /// 94 | /// For example, -1 is Yestderday, 0 is Today, 1 is Tomorrow 95 | /// 96 | /// Some language have words for 97 | /// day before yesterday (-2) 98 | /// or day after tomorrow (2) 99 | final Map relativeDayNames; 100 | 101 | /// Time keywords. If value is absent or null for [CalendarKeyword], Moment will assume the language doesn't require the keyword. 102 | final ComplexCalenderLocalizationKeywords keywords; 103 | 104 | const ComplexCalenderLocalizationData({ 105 | required this.relativeDayNames, 106 | required this.keywords, 107 | }); 108 | } 109 | 110 | typedef CalendarKeywordLastWeekdayString = String Function( 111 | DateTime dateTime, { 112 | DateTime? reference, 113 | }); 114 | 115 | typedef CalendarKeywordNextWeekdayString = String Function( 116 | DateTime dateTime, { 117 | DateTime? reference, 118 | }); 119 | 120 | typedef CalendarKeywordDateAtTimeString = String Function( 121 | DateTime dateTime, 122 | String dateString, 123 | String timeString, { 124 | DateTime? reference, 125 | }); 126 | 127 | class ComplexCalenderLocalizationKeywords { 128 | final CalendarKeywordLastWeekdayString lastWeekday; 129 | 130 | final CalendarKeywordNextWeekdayString nextWeekday; 131 | 132 | /// Defaults to 133 | /// 134 | /// ```dart 135 | /// "$dateString $timeString" 136 | /// ``` 137 | final CalendarKeywordDateAtTimeString at; 138 | 139 | const ComplexCalenderLocalizationKeywords({ 140 | required this.lastWeekday, 141 | required this.nextWeekday, 142 | this.at = ComplexCalenderLocalizationKeywords._defaultAt, 143 | }); 144 | 145 | static String _defaultAt(dateTime, dateString, timeString, {reference}) => 146 | "$dateString $timeString"; 147 | } 148 | -------------------------------------------------------------------------------- /lib/src/localizations/mixins/mn_MN/units.dart: -------------------------------------------------------------------------------- 1 | import 'package:moment_dart/src/localizations/mixins/simple_units.dart'; 2 | import 'package:moment_dart/src/localizations/utils/duration_unit.dart'; 3 | import 'package:moment_dart/src/localizations/utils/relative_interval.dart'; 4 | import 'package:moment_dart/src/localizations/utils/abbreviation.dart'; 5 | 6 | /// This mixin provides: 7 | /// 8 | /// * [srDelta] - string getter; 9 | /// * [units] - map of unit translations that use [srDelta] as unit placeholder 10 | /// * [relativePast] - A function suffixes/prefixes given duration 11 | /// * [relativeFuture] - A function suffixes/prefixes given duration 12 | mixin MnMnUnits on SimpleUnits { 13 | /// This is used as a placeholder for unit in plural expressions 14 | @override 15 | String get srDelta => "%"; 16 | 17 | String getUnit(DurationInterval interval, Abbreviation form, 18 | {bool dropPrefixOrSuffix = false}) { 19 | if (!dropPrefixOrSuffix) { 20 | final bool useMasculineSuffix = 21 | interval == DurationInterval.lessThanASecond || 22 | interval.unit == DurationUnit.minute || 23 | interval.unit == DurationUnit.month; 24 | 25 | final String suffix = 26 | form != Abbreviation.none ? "" : (useMasculineSuffix ? "ын" : "ийн"); 27 | 28 | return unitsWithSuffixConsidered[interval]! 29 | .get(form, dropPrefixOrSuffix) + 30 | suffix; 31 | } 32 | 33 | return units[interval]?.get(form, dropPrefixOrSuffix) ?? "¯\\_(ツ)_/¯"; 34 | } 35 | 36 | @override 37 | String relativePast(String unit) => "$unit өмнө"; 38 | @override 39 | String relativeFuture(String unit) => "$unit дараа"; 40 | 41 | @override 42 | Map get units => { 43 | DurationInterval.lessThanASecond: UnitString.withForm( 44 | "хэдэн хором", 45 | "хором", 46 | "хором", 47 | ), 48 | DurationInterval.aSecond: UnitString.withForm( 49 | "1 секунд", 50 | "1 сек", 51 | "1с", 52 | ), 53 | DurationInterval.seconds: UnitString.withForm( 54 | "$srDelta секунд", 55 | "$srDelta сек", 56 | "$srDeltaс", 57 | ), 58 | DurationInterval.aMinute: UnitString.withForm( 59 | "1 минут", 60 | "1 мин", 61 | "1м", 62 | ), 63 | DurationInterval.minutes: UnitString.withForm( 64 | "$srDelta минут", 65 | "$srDelta мин", 66 | "$srDeltaм", 67 | ), 68 | DurationInterval.anHour: UnitString.withForm( 69 | "1 цаг", 70 | "1 цаг", 71 | "1ц", 72 | ), 73 | DurationInterval.hours: UnitString.withForm( 74 | "$srDelta цаг", 75 | "$srDelta цаг", 76 | "$srDeltaц", 77 | ), 78 | DurationInterval.aDay: UnitString.withForm( 79 | "1 өдөр", 80 | "1 өдөр", 81 | "1ө", 82 | ), 83 | DurationInterval.days: UnitString.withForm( 84 | "$srDelta өдөр", 85 | "$srDelta өдөр", 86 | "$srDeltaө", 87 | ), 88 | DurationInterval.aWeek: UnitString.withForm( 89 | "1 долоо хоног", 90 | "1 долоо хон", 91 | "1дол.хон", 92 | ), 93 | DurationInterval.weeks: UnitString.withForm( 94 | "$srDelta долоо хоног", 95 | "$srDelta долоо хон", 96 | "$srDeltaдол.хон", 97 | ), 98 | DurationInterval.aMonth: UnitString.withForm( 99 | "1 сар", 100 | "1 сар", 101 | "1сар", 102 | ), 103 | DurationInterval.months: UnitString.withForm( 104 | "$srDelta сар", 105 | "$srDelta сар", 106 | "$srDeltaсар", 107 | ), 108 | DurationInterval.aYear: UnitString.withForm( 109 | "1 жил", 110 | "1 жил", 111 | "1жил", 112 | ), 113 | DurationInterval.years: UnitString.withForm( 114 | "$srDelta жил", 115 | "$srDelta жил", 116 | "$srDeltaжил", 117 | ), 118 | }; 119 | 120 | Map get unitsWithSuffixConsidered => { 121 | ...units, 122 | DurationInterval.lessThanASecond: UnitString.withForm( 123 | "хэдэн хорм", 124 | "хорм", 125 | "хорм", 126 | ), 127 | DurationInterval.aDay: UnitString.withForm( 128 | "1 өдр", 129 | "1 өдөр", 130 | "1ө", 131 | ), 132 | DurationInterval.days: UnitString.withForm( 133 | "$srDelta өдр", 134 | "$srDelta өдөр", 135 | "$srDeltaө", 136 | ), 137 | }; 138 | } 139 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # moment_dart 2 | 3 | [![pub package](https://img.shields.io/pub/v/moment_dart.svg)](https://pub.dartlang.org/packages/moment_dart) 4 | [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT) 5 | [![All Contributors](https://img.shields.io/github/all-contributors/sadespresso/moment_dart?color=ae00ff)](#contributors) 6 | 7 | > Inspired by moment.js 8 | 9 | ## References 10 | 11 | ### [📖 Read documentation](https://moment-dart.dev.gege.mn) 12 | 13 | ### [🆕 Migrate to 5.x.x](https://moment-dart.dev.gege.mn/upgrading/) 14 | 15 | ## Usage 16 | 17 | ### Creating instance 18 | 19 | There are multiple ways to create an instance. 20 | 21 | ```dart 22 | // From DateTime 23 | final a = DateTime.now().toMoment(); 24 | 25 | final b = Moment(DateTime(...)); 26 | 27 | // This instance won't be affected by global localization! 28 | final c = Moment(b, localization: MomentLocalizations.koKr()); 29 | ``` 30 | 31 | ### Setting global localization 32 | 33 | By default, the global localization is `en_US`. 34 | 35 | ```dart 36 | Moment.setGlobalLocalization(MomentLocalizations.fr()); 37 | ``` 38 | 39 | ### Relative Duration 40 | 41 | Relative durations are rather inprecise. See [precise duration](#precise-duration) 42 | for accuracy and flexibility. 43 | 44 | Read about thresholds, and more on [docs](https://moment-dart.dev.gege.mn/duration) 45 | 46 | ```dart 47 | final yesterday = Moment.now() - const Duration(days: 1); 48 | yesterday.fromNow(); // a day ago 49 | 50 | final add5sec = Moment.now().add(const Duration(seconds: 5)); 51 | add5sec.fromNow(dropPrefixOrSuffix: true); // a few seconds 52 | ``` 53 | 54 | ### Precise duration 55 | 56 | You can create localized human-readable duration text with 57 | abbreviation, highly-customizable units. 58 | 59 | See more on [docs](https://moment-dart.dev.gege.mn/duration/precise). 60 | (delimiter, custom units, abbreviation...) 61 | 62 | On `Duration` objects: 63 | 64 | ```dart 65 | Duration(days: 67, hours: 3, minutes: 2).toDurationString( 66 | localization: MomentLocalizations.de(), 67 | form: Abbreviation.semi, 68 | ); // in 2 Mo. 7 Tg. 69 | ``` 70 | 71 | For `Moment`: 72 | 73 | ```dart 74 | final epoch = Moment.fromMillisecondsSinceEpoch(0); 75 | final now = DateTime(2023, DateTime.july, 14).toMoment(); 76 | 77 | print(epoch.fromPrecise(now)); // 53 years 7 months ago 78 | ``` 79 | 80 | ### Calendar 81 | 82 | ```dart 83 | final Moment now = Moment.now(); 84 | 85 | print(now.calendar()); // Today at 03:00 PM 86 | ``` 87 | 88 | ### Other helpful methods 89 | 90 | `moment_dart` provides numerous convenience methods. Works on both `Moment` and `DateTime`. 91 | 92 | See all available options in the [docs](https://moment-dart.dev.gege.mn/extension/) 93 | 94 | ```dart 95 | final now = DateTime.now(); // July 19 2023 03:12 PM (UTC+8) 96 | 97 | // ISO week, ISO week year 98 | now.week; // 48 99 | now.weekYear; // 2023 100 | 101 | now.isLeapYear; // false 102 | 103 | // Date creates new `DateTime` with hours, minutes, etc.. omitted 104 | now.date; // July 19 2023 00:00 AM 105 | 106 | now.clone(); // July 19 2023 03:12 PM 107 | 108 | now.nextTuesday(); // July 25 2023 03:12 PM 109 | 110 | // and many more! 111 | ``` 112 | 113 | ## Contributing 114 | 115 | Contributions of any kind are welcome! Please refer to [CONTRIBUTE.md](CONTRIBUTE.md) 116 | 117 | ## Contributors 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 |
Батмэнд Ганбаатар
Батмэнд Ганбаатар

💻 🚧 🌍 📖
Ultrate
Ultrate

💻 🌍
Muhammadyusuf
Muhammadyusuf

🌍 💻
131 | 132 | 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /test/extensions/clamped_setters_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:moment_dart/moment_dart.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | void main() { 5 | group('ClampedSetters on DateTime', () { 6 | test('setClampedSecond clamps seconds between 0 and 59', () { 7 | final date = DateTime(2024, 1, 1, 12, 0, 0); 8 | 9 | expect(date.setClampedSecond(-1).second, 0); 10 | expect(date.setClampedSecond(0).second, 0); 11 | expect(date.setClampedSecond(30).second, 30); 12 | expect(date.setClampedSecond(59).second, 59); 13 | expect(date.setClampedSecond(60).second, 59); 14 | }); 15 | 16 | test('setClampedMinute clamps minutes between 0 and 59', () { 17 | final date = DateTime(2024, 1, 1, 12, 0, 0); 18 | 19 | expect(date.setClampedMinute(-1).minute, 0); 20 | expect(date.setClampedMinute(0).minute, 0); 21 | expect(date.setClampedMinute(30).minute, 30); 22 | expect(date.setClampedMinute(59).minute, 59); 23 | expect(date.setClampedMinute(60).minute, 59); 24 | }); 25 | 26 | test('setClampedHour clamps hours between 0 and 23', () { 27 | final date = DateTime(2024, 1, 1, 12, 0, 0); 28 | 29 | expect(date.setClampedHour(-1).hour, 0); 30 | expect(date.setClampedHour(0).hour, 0); 31 | expect(date.setClampedHour(12).hour, 12); 32 | expect(date.setClampedHour(23).hour, 23); 33 | expect(date.setClampedHour(24).hour, 23); 34 | }); 35 | 36 | test('setClampedDay clamps days based on month length', () { 37 | // Test February in non-leap year 38 | final feb2023 = DateTime(2023, 2, 1); 39 | expect(feb2023.setClampedDay(0).day, 1); 40 | expect(feb2023.setClampedDay(1).day, 1); 41 | expect(feb2023.setClampedDay(15).day, 15); 42 | expect(feb2023.setClampedDay(28).day, 28); 43 | expect(feb2023.setClampedDay(29).day, 28); 44 | 45 | // Test February in leap year 46 | final feb2024 = DateTime(2024, 2, 1); 47 | expect(feb2024.setClampedDay(29).day, 29); 48 | expect(feb2024.setClampedDay(30).day, 29); 49 | 50 | // Test month with 31 days 51 | final jan2024 = DateTime(2024, 1, 1); 52 | expect(jan2024.setClampedDay(31).day, 31); 53 | expect(jan2024.setClampedDay(32).day, 31); 54 | }); 55 | 56 | test( 57 | 'setClampedMonth clamps months between 1 and 12 and adjusts day if needed', 58 | () { 59 | final date = DateTime(2024, 1, 31); 60 | 61 | expect(date.setClampedMonth(0).month, 1); 62 | expect(date.setClampedMonth(1).month, 1); 63 | expect(date.setClampedMonth(6).month, 6); 64 | expect(date.setClampedMonth(12).month, 12); 65 | expect(date.setClampedMonth(13).month, 12); 66 | 67 | // Test day adjustment when changing to month with fewer days 68 | expect(date.setClampedMonth(2).day, 29); // 2024 is leap year 69 | expect(DateTime(2023, 1, 31).setClampedMonth(2).day, 70 | 28); // 2023 is not leap year 71 | }); 72 | }); 73 | 74 | group('ClampedSettersPlus on Moment', () { 75 | test('setClampedSecond clamps seconds between 0 and 59', () { 76 | final moment = Moment(DateTime(2024, 1, 1, 12, 0, 0)); 77 | 78 | expect(moment.setClampedSecond(-1).second, 0); 79 | expect(moment.setClampedSecond(0).second, 0); 80 | expect(moment.setClampedSecond(30).second, 30); 81 | expect(moment.setClampedSecond(59).second, 59); 82 | expect(moment.setClampedSecond(60).second, 59); 83 | }); 84 | 85 | test('setClampedMinute clamps minutes between 0 and 59', () { 86 | final moment = Moment(DateTime(2024, 1, 1, 12, 0, 0)); 87 | 88 | expect(moment.setClampedMinute(-1).minute, 0); 89 | expect(moment.setClampedMinute(0).minute, 0); 90 | expect(moment.setClampedMinute(30).minute, 30); 91 | expect(moment.setClampedMinute(59).minute, 59); 92 | expect(moment.setClampedMinute(60).minute, 59); 93 | }); 94 | 95 | test('setClampedHour clamps hours between 0 and 23', () { 96 | final moment = Moment(DateTime(2024, 1, 1, 12, 0, 0)); 97 | 98 | expect(moment.setClampedHour(-1).hour, 0); 99 | expect(moment.setClampedHour(0).hour, 0); 100 | expect(moment.setClampedHour(12).hour, 12); 101 | expect(moment.setClampedHour(23).hour, 23); 102 | expect(moment.setClampedHour(24).hour, 23); 103 | }); 104 | 105 | test('setClampedDay clamps days based on month length', () { 106 | // Test February in non-leap year 107 | final feb2023 = Moment(DateTime(2023, 2, 1)); 108 | expect(feb2023.setClampedDay(0).day, 1); 109 | expect(feb2023.setClampedDay(1).day, 1); 110 | expect(feb2023.setClampedDay(15).day, 15); 111 | expect(feb2023.setClampedDay(28).day, 28); 112 | expect(feb2023.setClampedDay(29).day, 28); 113 | 114 | // Test February in leap year 115 | final feb2024 = Moment(DateTime(2024, 2, 1)); 116 | expect(feb2024.setClampedDay(29).day, 29); 117 | expect(feb2024.setClampedDay(30).day, 29); 118 | 119 | // Test month with 31 days 120 | final jan2024 = Moment(DateTime(2024, 1, 1)); 121 | expect(jan2024.setClampedDay(31).day, 31); 122 | expect(jan2024.setClampedDay(32).day, 31); 123 | }); 124 | 125 | test( 126 | 'setClampedMonth clamps months between 1 and 12 and adjusts day if needed', 127 | () { 128 | final moment = Moment(DateTime(2024, 1, 31)); 129 | 130 | expect(moment.setClampedMonth(0).month, 1); 131 | expect(moment.setClampedMonth(1).month, 1); 132 | expect(moment.setClampedMonth(6).month, 6); 133 | expect(moment.setClampedMonth(12).month, 12); 134 | expect(moment.setClampedMonth(13).month, 12); 135 | 136 | // Test day adjustment when changing to month with fewer days 137 | expect(moment.setClampedMonth(2).day, 29); // 2024 is leap year 138 | expect(Moment(DateTime(2023, 1, 31)).setClampedMonth(2).day, 139 | 28); // 2023 is not leap year 140 | }); 141 | }); 142 | } 143 | -------------------------------------------------------------------------------- /lib/src/extensions/unit_comparison.dart: -------------------------------------------------------------------------------- 1 | import 'package:moment_dart/moment_dart.dart'; 2 | 3 | extension UnitComparision on DateTime { 4 | /// [unit]: 0-7 5 | /// 6 | /// year, month, day, hour, minute, second, millisecond, microsecond 7 | bool _isAtSameUnitAs( 8 | DateTime other, 9 | DurationUnit unit, { 10 | bool enforceUTC = false, 11 | }) { 12 | if (enforceUTC) { 13 | return toUtc()._isAtSameUnitAs(other.toUtc(), unit, enforceUTC: false); 14 | } 15 | 16 | if (other.year != year) return false; 17 | if (unit == DurationUnit.year) return true; 18 | 19 | if (other.month != month) return false; 20 | if (unit == DurationUnit.month) return true; 21 | 22 | if (other.day != day) return false; 23 | if (unit == DurationUnit.day) return true; 24 | 25 | if (other.hour != hour) return false; 26 | if (unit == DurationUnit.hour) return true; 27 | 28 | if (other.minute != minute) return false; 29 | if (unit == DurationUnit.minute) return true; 30 | 31 | if (other.second != second) return false; 32 | if (unit == DurationUnit.second) return true; 33 | 34 | if (other.millisecond != millisecond) return false; 35 | if (unit == DurationUnit.millisecond) return true; 36 | 37 | return other.microsecond == microsecond; 38 | } 39 | 40 | /// Returns if two dates are in same year. 41 | /// 42 | /// Assumes both [this] and [other] has same timezone 43 | /// 44 | /// To convert both to `UTC` before comparison, set [enforceUTC] to `true` 45 | bool isAtSameYearAs( 46 | DateTime other, { 47 | bool enforceUTC = false, 48 | }) => 49 | _isAtSameUnitAs( 50 | other, 51 | DurationUnit.year, 52 | enforceUTC: enforceUTC, 53 | ); 54 | 55 | /// Returns if two dates are in same month, year. 56 | /// 57 | /// Assumes both [this] and [other] has same timezone 58 | /// 59 | /// To convert both to `UTC` before comparison, set [enforceUTC] to `true` 60 | bool isAtSameMonthAs( 61 | DateTime other, { 62 | bool enforceUTC = false, 63 | }) => 64 | _isAtSameUnitAs( 65 | other, 66 | DurationUnit.month, 67 | enforceUTC: enforceUTC, 68 | ); 69 | 70 | /// Returns if two dates are in same day, month, year. 71 | /// 72 | /// Assumes both [this] and [other] has same timezone 73 | /// 74 | /// To convert both to `UTC` before comparison, set [enforceUTC] to `true` 75 | bool isAtSameDayAs( 76 | DateTime other, { 77 | bool enforceUTC = false, 78 | }) => 79 | _isAtSameUnitAs( 80 | other, 81 | DurationUnit.day, 82 | enforceUTC: enforceUTC, 83 | ); 84 | 85 | /// Returns if two dates are in same hour, day, month, year. 86 | /// 87 | /// Assumes both [this] and [other] has same timezone 88 | /// 89 | /// To convert both to `UTC` before comparison, set [enforceUTC] to `true` 90 | bool isAtSameHourAs( 91 | DateTime other, { 92 | bool enforceUTC = false, 93 | }) => 94 | _isAtSameUnitAs( 95 | other, 96 | DurationUnit.hour, 97 | enforceUTC: enforceUTC, 98 | ); 99 | 100 | /// Returns if two dates are in same minute, hour, day, month, year. 101 | /// 102 | /// Assumes both [this] and [other] has same timezone 103 | /// 104 | /// To convert both to `UTC` before comparison, set [enforceUTC] to `true` 105 | bool isAtSameMinuteAs( 106 | DateTime other, { 107 | bool enforceUTC = false, 108 | }) => 109 | _isAtSameUnitAs( 110 | other, 111 | DurationUnit.minute, 112 | enforceUTC: enforceUTC, 113 | ); 114 | 115 | /// Returns if two dates are in same second, minute, hour, day, month, year. 116 | /// 117 | /// Assumes both [this] and [other] has same timezone 118 | /// 119 | /// To convert both to `UTC` before comparison, set [enforceUTC] to `true` 120 | bool isAtSameSecondAs( 121 | DateTime other, { 122 | bool enforceUTC = false, 123 | }) => 124 | _isAtSameUnitAs( 125 | other, 126 | DurationUnit.second, 127 | enforceUTC: enforceUTC, 128 | ); 129 | 130 | /// Returns if two dates are in same millisecond, second, minute, hour, day, month, year. 131 | /// 132 | /// Assumes both [this] and [other] has same timezone 133 | /// 134 | /// To convert both to `UTC` before comparison, set [enforceUTC] to `true` 135 | bool isAtSameMillisecondAs( 136 | DateTime other, { 137 | bool enforceUTC = false, 138 | }) => 139 | _isAtSameUnitAs( 140 | other, 141 | DurationUnit.millisecond, 142 | enforceUTC: enforceUTC, 143 | ); 144 | 145 | /// Returns if two dates are in same microsecond, millisecond, second, minute, hour, day, month, year. 146 | /// 147 | /// Assumes both [this] and [other] has same timezone 148 | /// 149 | /// To convert both to `UTC` before comparison, set [enforceUTC] to `true` 150 | bool isAtSameMicrosecondAs( 151 | DateTime other, { 152 | bool enforceUTC = false, 153 | }) => 154 | _isAtSameUnitAs( 155 | other, 156 | DurationUnit.microsecond, 157 | enforceUTC: enforceUTC, 158 | ); 159 | 160 | /// Returns whether [this] and [other] is in same **local** week. Local week is determined by [weekStart], defaults to [DateTime.monday] 161 | /// 162 | /// Assumes both [this] and [other] is in local timezone. 163 | bool isSameLocalWeekAs( 164 | DateTime other, [ 165 | int? weekStart, 166 | ]) { 167 | weekStart ??= Moment.defaultLocalization.weekStart; 168 | 169 | final DateTime startOfWeek = startOfLocalWeek(weekStart); 170 | if (other < startOfWeek) return false; 171 | 172 | final DateTime endOfWeek = endOfLocalWeek(weekStart); 173 | 174 | return other <= endOfWeek; 175 | } 176 | } 177 | 178 | extension UnitComparisonMoment on Moment { 179 | /// Returns whether [this] and [other] is in same **local** week. Local week is determined by [localization.weekStart], defaults to [DateTime.monday] 180 | /// 181 | /// Assumes both [this] and [other] is in local timezone. 182 | bool isSameLocalWeekAs(DateTime other) => 183 | forcedSuperType.isSameLocalWeekAs(other, localization.weekStart); 184 | } 185 | -------------------------------------------------------------------------------- /lib/src/localizations/all.dart: -------------------------------------------------------------------------------- 1 | import '../localization.dart'; 2 | 3 | import 'ar_PS.dart'; 4 | export 'ar_PS.dart'; 5 | 6 | import 'it_IT.dart'; 7 | export 'it_IT.dart'; 8 | 9 | import 'en_US.dart'; 10 | export 'en_US.dart'; 11 | 12 | import 'mn_MN.dart'; 13 | export 'mn_MN.dart'; 14 | 15 | import 'mn_Mong_MN.dart'; 16 | export 'mn_Mong_MN.dart'; 17 | 18 | import 'ko_KR.dart'; 19 | export 'ko_KR.dart'; 20 | 21 | import 'ja_JP.dart'; 22 | export 'ja_JP.dart'; 23 | 24 | import 'de_DE.dart'; 25 | export 'de_DE.dart'; 26 | 27 | import 'es_ES.dart'; 28 | export 'es_ES.dart'; 29 | 30 | import 'fr_FR.dart'; 31 | export 'fr_FR.dart'; 32 | 33 | import 'tr_TR.dart'; 34 | export 'tr_TR.dart'; 35 | 36 | import 'ru_RU.dart'; 37 | export 'ru_RU.dart'; 38 | 39 | import 'pt_PT.dart'; 40 | export 'pt_PT.dart'; 41 | 42 | import 'zh_CN.dart'; 43 | export 'zh_CN.dart'; 44 | 45 | typedef LocalizationFn = MomentLocalization Function(); 46 | 47 | abstract class MomentLocalizations { 48 | MomentLocalizations._(); 49 | 50 | static Map locales = { 51 | "en_US": () => LocalizationEnUs(), 52 | "mn_MN": () => LocalizationMnMn(), 53 | "mn_Mong_MN": () => LocalizationMnMongMn(), 54 | "mn_Qaaq_MN": () => LocalizationMnQaaqMn(), 55 | "ar_PS": () => LocalizationArPs(), 56 | "ko_KR": () => LocalizationKoKr(), 57 | "ja_JP": () => LocalizationJaJp(), 58 | "de_DE": () => LocalizationDeDe(), 59 | "es_ES": () => LocalizationEsEs(), 60 | "fr_FR": () => LocalizationFrFr(), 61 | "pt_PT": () => LocalizationPtPt(), 62 | "tr_TR": () => LocalizationTrTr(), 63 | "ru_RU": () => LocalizationRuRu(), 64 | "it_IT": () => LocalizationItIt(), 65 | "zh_CN": () => LocalizationZhCn(), 66 | }; 67 | 68 | /// Retreives locale 69 | /// 70 | /// Tries to find locales matching the criteria 71 | /// 72 | /// If `strict == false`, it tries to find closest 73 | /// possible match by omitting scriptCode. 74 | /// If still no luck, tries again with omitting countryCode 75 | static MomentLocalization? byLanguage( 76 | String languageCode, { 77 | String? countryCode, 78 | String? scriptCode, 79 | bool strict = false, 80 | }) => 81 | _byLanguage( 82 | languageCode, 83 | countryCode: countryCode, 84 | scriptCode: scriptCode, 85 | strict: strict, 86 | ); 87 | static MomentLocalization? _byLanguage( 88 | String languageCode, { 89 | String? countryCode, 90 | String? scriptCode, 91 | bool strict = false, 92 | }) { 93 | String prefix = languageCode; 94 | 95 | if (scriptCode != null) prefix += "_$scriptCode"; 96 | if (countryCode != null) prefix += "_$countryCode"; 97 | 98 | try { 99 | final String key = 100 | locales.keys.firstWhere((element) => element.startsWith(prefix)); 101 | 102 | return locales[key]!(); 103 | } on StateError { 104 | if (!strict) { 105 | if (countryCode != null) { 106 | // Omitting country code to try locales with matching language code and script code 107 | return _byLanguage(languageCode, scriptCode: scriptCode); 108 | } 109 | 110 | if (scriptCode != null) { 111 | // Omitting script code to try locales with matching language code and script code 112 | return _byLanguage(languageCode); 113 | } 114 | 115 | // If both are null, we've got nothing to omit to widen our result. 116 | } 117 | 118 | return null; 119 | } 120 | } 121 | 122 | static final RegExp _soleLanguageCode = RegExp(r'^[a-z]{2}$'); 123 | 124 | /// Retreives locale by code. 125 | /// 126 | /// e.g.: 127 | /// * "en_US" // English (United States) 128 | /// * "es-ES" // Spanish (Spain) 129 | /// * "es" // Spanish (Spain) - Here, language code is same as country code, so Moment will return "es_ES". 130 | /// * "en" // **Throws error** since [MomentLocalization] with such locale doesn't exist 131 | /// 132 | /// Note that you can use dash or underscore interchangeably. **But underscore is preferred** 133 | static MomentLocalization? byLocale(String locale) => _byLocale(locale); 134 | 135 | static MomentLocalization? _byLocale( 136 | String locale, [ 137 | bool tryAddCountryCode = true, 138 | ]) { 139 | final String localeFormatted = locale.replaceAll('-', '_'); 140 | 141 | if (locales[localeFormatted] == null) { 142 | if (tryAddCountryCode && _soleLanguageCode.hasMatch(locale)) { 143 | return _byLocale("${locale}_${locale.toUpperCase()}", false); 144 | } 145 | return null; 146 | } 147 | 148 | return locales[localeFormatted]!(); 149 | } 150 | 151 | /// English (United States) 152 | static LocalizationEnUs enUS() => LocalizationEnUs(); 153 | 154 | /// Mongolian (Cyrillic) 155 | static LocalizationMnMn mn() => LocalizationMnMn(); 156 | 157 | /// Mongolian (Traditional Mongolian Script with Roman Numerals) 158 | static LocalizationMnMongMn mnMong() => LocalizationMnMongMn(); 159 | 160 | /// Mongolian (Traditional Mongolian Script with Traditional Numerals) 161 | static LocalizationMnQaaqMn mnMongtn() => LocalizationMnQaaqMn(); 162 | 163 | /// Arabic (Palestine) 164 | static LocalizationArPs arPs() => LocalizationArPs(); 165 | 166 | /// Korean (South Korea) 167 | static LocalizationKoKr koKr() => LocalizationKoKr(); 168 | 169 | /// Japanese (Japan) 170 | static LocalizationJaJp jaJp() => LocalizationJaJp(); 171 | 172 | /// German (Germany - Standard) 173 | static LocalizationDeDe de() => LocalizationDeDe(); 174 | 175 | /// Spanish (Spain) 176 | static LocalizationEsEs es() => LocalizationEsEs(); 177 | 178 | /// French (France) 179 | static LocalizationFrFr fr() => LocalizationFrFr(); 180 | 181 | /// Turkish (Turkey) 182 | static LocalizationTrTr tr() => LocalizationTrTr(); 183 | 184 | /// Russian (Russia) 185 | static LocalizationRuRu ru() => LocalizationRuRu(); 186 | 187 | /// Portuguese (Portuguese Republic) 188 | static LocalizationPtPt pt() => LocalizationPtPt(); 189 | 190 | /// Italian (Italy) 191 | static LocalizationItIt it() => LocalizationItIt(); 192 | 193 | /// Simplified Chinese (China) 194 | static LocalizationZhCn zhCn() => LocalizationZhCn(); 195 | } 196 | -------------------------------------------------------------------------------- /lib/src/localizations/ru_RU.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: file_names 2 | 3 | import 'package:moment_dart/moment_dart.dart'; 4 | import 'package:moment_dart/src/localizations/mixins/month_names.dart'; 5 | import 'package:moment_dart/src/localizations/mixins/ordinal_numbers.dart'; 6 | import 'package:moment_dart/src/localizations/mixins/ru_RU/duration.dart'; 7 | import 'package:moment_dart/src/localizations/mixins/ru_RU/relative.dart'; 8 | import 'package:moment_dart/src/localizations/mixins/ru_RU/units.dart'; 9 | import 'package:moment_dart/src/localizations/mixins/simple_range.dart'; 10 | import 'package:moment_dart/src/localizations/mixins/simple_units.dart'; 11 | import 'package:moment_dart/src/localizations/mixins/weekday_shortforms.dart'; 12 | import 'package:moment_dart/src/types.dart'; 13 | 14 | /// Language: Russian (Russia) 15 | /// Country: Russia 16 | class LocalizationRuRu extends MomentLocalization 17 | with 18 | MonthNames, 19 | Ordinal, 20 | SimpleUnits, 21 | RuRuUnits, 22 | RuRuRelative, 23 | RuRuDuration, 24 | SimpleRange, 25 | WeekdayShortForms { 26 | static LocalizationRuRu? _instance; 27 | 28 | LocalizationRuRu._internal() : super(); 29 | 30 | factory LocalizationRuRu() { 31 | _instance ??= LocalizationRuRu._internal(); 32 | return _instance!; 33 | } 34 | 35 | @override 36 | String get endonym => "Русский"; 37 | 38 | @override 39 | String get languageCode => "ru"; 40 | 41 | @override 42 | String get countryCode => "RU"; 43 | 44 | @override 45 | String get languageNameInEnglish => "Russian (Russia)"; 46 | 47 | @override 48 | Map get monthNames => { 49 | 1: "Январь", 50 | 2: "Февраль", 51 | 3: "Март", 52 | 4: "Апрель", 53 | 5: "Май", 54 | 6: "Июнь", 55 | 7: "Июль", 56 | 8: "Август", 57 | 9: "Сентябрь", 58 | 10: "Октябрь", 59 | 11: "Ноябрь", 60 | 12: "Декабрь", 61 | }; 62 | 63 | @override 64 | Map get monthNamesShort => monthNames.map( 65 | (key, value) => MapEntry( 66 | key, 67 | value.substring(0, 3), 68 | ), 69 | ); 70 | 71 | @override 72 | Map get weekdayName => { 73 | DateTime.monday: "Понедельник", 74 | DateTime.tuesday: "Вторник", 75 | DateTime.wednesday: "Среда", 76 | DateTime.thursday: "Четверг", 77 | DateTime.friday: "Пятница", 78 | DateTime.saturday: "Суббота", 79 | DateTime.sunday: "Воскресенье", 80 | }; 81 | 82 | @override 83 | Map get weekdayNameShort => { 84 | DateTime.monday: "Пон", 85 | DateTime.tuesday: "Вт", 86 | DateTime.wednesday: "Ср", 87 | DateTime.thursday: "Чт", 88 | DateTime.friday: "Пт", 89 | DateTime.saturday: "Сб", 90 | DateTime.sunday: "Вс", 91 | }; 92 | 93 | @override 94 | Map get weekdayNameMin => weekdayNameShort; 95 | 96 | @override 97 | FormatSetOptional overrideFormatters() { 98 | return { 99 | // From [EnglishLikeOrdinal] mixin 100 | ...formattersWithOrdinal, 101 | // From [MonthNames] mixin 102 | ...formattersForMonthNames, 103 | // From [WeekdayShortForms] mixin 104 | ...formattersForWeekdayShortForms, 105 | // Localization aware formats 106 | FormatterToken.L: (dateTime) => reformat(dateTime, "DD.MM.YYYY"), 107 | FormatterToken.l: (dateTime) => reformat(dateTime, "D.M.YYYY"), 108 | FormatterToken.LL: (dateTime) => reformat(dateTime, "D MMMM YYYY"), 109 | FormatterToken.ll: (dateTime) => reformat(dateTime, "D MMM YYYY"), 110 | FormatterToken.LLL: (dateTime) => reformat(dateTime, "D MMMM YYYY HH:mm"), 111 | FormatterToken.lll: (dateTime) => reformat(dateTime, "D MMM YYYY H:mm"), 112 | FormatterToken.LLLL: (dateTime) => 113 | reformat(dateTime, "dddd, D MMMM YYYY HH:mm"), 114 | FormatterToken.llll: (dateTime) => 115 | reformat(dateTime, "ddd, D MMM YYYY H:mm"), 116 | FormatterToken.LT: (dateTime) => reformat(dateTime, "HH:mm"), 117 | FormatterToken.LTS: (dateTime) => reformat(dateTime, "HH:mm:ss"), 118 | }; 119 | } 120 | 121 | @override 122 | String ordinalNumber(int n) { 123 | return "$n-й"; 124 | } 125 | 126 | @override 127 | CalenderLocalizationData get calendarData => calenderLocalizationDataRuRu; 128 | 129 | static String last(String weekday) { 130 | switch (weekday) { 131 | case "Понедельник": 132 | case "Вторник": 133 | case "Четверг": 134 | return "Прошлый ${weekday.toLowerCase()}"; 135 | case "Среда": 136 | case "Пятница": 137 | case "Суббота": 138 | return "Прошлая ${weekday.toLowerCase()}"; 139 | case "Воскресенье": 140 | return "Прошлое ${weekday.toLowerCase()}"; 141 | default: 142 | return "Прошлый ${weekday.toLowerCase()}"; 143 | } 144 | } 145 | 146 | static String at(String date, String time) => "$date в $time"; 147 | 148 | static const CalenderLocalizationData calenderLocalizationDataRuRu = 149 | CalenderLocalizationData( 150 | relativeDayNames: { 151 | -2: "Позавчера", 152 | -1: "Вчера", 153 | 0: "Сегодня", 154 | 1: "Завтра", 155 | 2: "Послезавтра", 156 | }, 157 | keywords: CalenderLocalizationKeywords( 158 | at: at, 159 | lastWeekday: last, 160 | ), 161 | ); 162 | 163 | @override 164 | int get weekStart => DateTime.monday; 165 | 166 | @override 167 | SimpleRangeData get simpleRangeData => SimpleRangeData( 168 | thisWeek: "Эта неделя", 169 | year: (range, {anchor, useRelative = true}) { 170 | anchor ??= Moment.now(); 171 | 172 | if (useRelative && range.year == anchor.year) { 173 | return "В этом году"; 174 | } 175 | 176 | return "${range.year} год"; 177 | }, 178 | month: (range, {anchor, useRelative = true}) { 179 | anchor ??= Moment.now(); 180 | 181 | if (useRelative && anchor.year == range.year) { 182 | if (anchor.month == range.month) { 183 | return "В этом месяце"; 184 | } 185 | 186 | return monthNames[range.month]!; 187 | } 188 | 189 | return "${monthNames[range.month]!} ${range.year}"; 190 | }, 191 | allAfter: (formattedDate) => "После $formattedDate", 192 | allBefore: (formattedDate) => "До $formattedDate", 193 | customRangeAllTime: "За все время", 194 | ); 195 | } 196 | -------------------------------------------------------------------------------- /lib/src/localizations/mn_MN.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: file_names 2 | // Author: Batmend Ganbaatar (https://github.com/sadespresso) 3 | 4 | import 'package:moment_dart/moment_dart.dart'; 5 | import 'package:moment_dart/src/localizations/mixins/ordinal_numbers.dart'; 6 | import 'package:moment_dart/src/localizations/mixins/simple_duration.dart'; 7 | import 'package:moment_dart/src/localizations/mixins/simple_range.dart'; 8 | import 'package:moment_dart/src/localizations/mixins/simple_units.dart'; 9 | import 'package:moment_dart/src/localizations/mixins/mn_MN/units.dart'; 10 | import 'package:moment_dart/src/types.dart'; 11 | 12 | /// Language: Mongolian (Cyrillic) 13 | /// Country: Mongolia 14 | class LocalizationMnMn extends MomentLocalization 15 | with Ordinal, SimpleUnits, MnMnUnits, SimpleDuration, SimpleRange { 16 | static LocalizationMnMn? _instance; 17 | 18 | LocalizationMnMn._internal() : super(); 19 | 20 | factory LocalizationMnMn() { 21 | _instance ??= LocalizationMnMn._internal(); 22 | 23 | return _instance!; 24 | } 25 | 26 | @override 27 | String get endonym => "Монгол хэл"; 28 | 29 | @override 30 | String get languageCode => "mn"; 31 | 32 | @override 33 | String get countryCode => "MN"; 34 | 35 | @override 36 | String get languageNameInEnglish => "Mongolian"; 37 | 38 | /// Please note that Mongolian language string is not in it's base form. A suffix has been added to work with `relativePast`, `relativeFuture`. 39 | @override 40 | String relative( 41 | Duration duration, { 42 | bool dropPrefixOrSuffix = false, 43 | Abbreviation form = Abbreviation.none, 44 | }) { 45 | final bool past = duration.isNegative; 46 | 47 | duration = duration.abs(); 48 | 49 | DurationInterval interval = MomentLocalization.relativeThreshold(duration); 50 | 51 | String value = getUnit( 52 | interval, 53 | form, 54 | dropPrefixOrSuffix: dropPrefixOrSuffix, 55 | ); 56 | 57 | if (!interval.singular) { 58 | value = value.replaceAll( 59 | srDelta, 60 | DurationUnit.relativeDuration(duration, interval.unit).toString(), 61 | ); 62 | } 63 | 64 | if (dropPrefixOrSuffix) return value; 65 | 66 | return past ? relativePast(value) : relativeFuture(value); 67 | } 68 | 69 | String monthName(int i) => "${ordinalNumber(i)} сар"; 70 | 71 | @override 72 | FormatSetOptional overrideFormatters() { 73 | return { 74 | // From [Ordinal] mixin 75 | ...formattersWithOrdinal, 76 | // Localization aware formats 77 | FormatterToken.L: (dateTime) => reformat(dateTime, "YYYY/MM/DD"), 78 | FormatterToken.l: (dateTime) => reformat(dateTime, "YYYY/M/D"), 79 | FormatterToken.LL: (dateTime) => reformat(dateTime, "YYYY оны MMMMын D"), 80 | FormatterToken.ll: (dateTime) => reformat(dateTime, "YYYY оны MMMын D"), 81 | FormatterToken.LLL: (dateTime) => 82 | reformat(dateTime, "YYYY оны MMMMын D, HH:mm"), 83 | FormatterToken.lll: (dateTime) => 84 | reformat(dateTime, "YYYY оны MMMын D, H:mm"), 85 | FormatterToken.LLLL: (dateTime) => 86 | reformat(dateTime, "dddd, YYYY оны MMMMын D, HH:mm"), 87 | FormatterToken.llll: (dateTime) => 88 | reformat(dateTime, "ddd, YYYY оны MMMын D, H:mm"), 89 | FormatterToken.LT: (dateTime) => reformat(dateTime, "HH:mm"), 90 | FormatterToken.LTS: (dateTime) => reformat(dateTime, "HH:mm:ss"), 91 | // Customs 92 | FormatterToken.A: (dateTime) => dateTime.hour < 12 ? "Ү.Ө" : "Ү.Х", 93 | FormatterToken.a: (dateTime) => dateTime.hour < 12 ? "ү.ө" : "ү.х", 94 | FormatterToken.MMM: (dateTime) => "${dateTime.month} сар", 95 | FormatterToken.MMMM: (dateTime) => monthName(dateTime.month), 96 | // Wanted to include the "гараг" in full form 97 | FormatterToken.dd: (dateTime) => 98 | weekdayName[dateTime.weekday]!.substring(0, 3), 99 | FormatterToken.ddd: (dateTime) => weekdayName[dateTime.weekday]!, 100 | FormatterToken.dddd: (dateTime) => 101 | "${weekdayName[dateTime.weekday]} гараг", 102 | // Era 103 | FormatterToken.NN: (dateTime) => dateTime.year < 1 ? "НТӨ" : "НТ", 104 | FormatterToken.NNNN: (dateTime) => 105 | dateTime.year < 1 ? "Нийтийн тооллын өмнөх" : "Нийтийн тоолол", 106 | FormatterToken.NNNNN: (dateTime) => dateTime.year < 1 ? "НТӨ" : "НТ", 107 | }; 108 | } 109 | 110 | /// Tibet weekday names are here, because it is majorly used in Mongolia 111 | @override 112 | Map get weekdayName => { 113 | DateTime.monday: "Даваа", 114 | DateTime.tuesday: "Мягмар", 115 | DateTime.wednesday: "Лхагва", 116 | DateTime.thursday: "Пүрэв", 117 | DateTime.friday: "Баасан", 118 | DateTime.saturday: "Бямба", 119 | DateTime.sunday: "Ням", 120 | }; 121 | 122 | @override 123 | String ordinalNumber(int n) { 124 | return "$n ${(LocalizationMnMongMn.isFeminine(n) ? "дүгээр" : "дугаар")}"; 125 | } 126 | 127 | @override 128 | CalenderLocalizationData get calendarData => calenderLocalizationDataMn; 129 | 130 | static String last(String weekday) => "Өнгөрсөн $weekday"; 131 | 132 | static const CalenderLocalizationData calenderLocalizationDataMn = 133 | CalenderLocalizationData( 134 | relativeDayNames: { 135 | -2: "Уржигдар", 136 | -1: "Өчигдөр", 137 | 0: "Өнөөдөр", 138 | 1: "Маргааш", 139 | 2: "Нөгөөдөр", 140 | }, 141 | keywords: CalenderLocalizationKeywords( 142 | lastWeekday: last, 143 | ), 144 | ); 145 | 146 | @override 147 | SimpleRangeData get simpleRangeData => SimpleRangeData( 148 | thisWeek: "Энэ долоо хоног", 149 | year: (range, {DateTime? anchor, bool useRelative = true}) { 150 | anchor ??= Moment.now(); 151 | 152 | if (useRelative && range.year == anchor.year) { 153 | return "Энэ жил"; 154 | } 155 | 156 | return "${range.year} он"; 157 | }, 158 | month: (range, {DateTime? anchor, bool useRelative = true}) { 159 | anchor ??= Moment.now(); 160 | 161 | if (useRelative && anchor.year == range.year) { 162 | if (anchor.month == range.month) { 163 | return "Энэ сар"; 164 | } 165 | 166 | return monthName(range.month); 167 | } 168 | 169 | return "${range.year} оны ${monthName(range.month)}"; 170 | }, 171 | allAfter: (formattedDate) => "$formattedDate-с хойш", 172 | allBefore: (formattedDate) => "$formattedDate-с өмнө", 173 | customRangeAllTime: "Бүх цаг үе", 174 | ); 175 | } 176 | -------------------------------------------------------------------------------- /lib/src/extensions/start_of.dart: -------------------------------------------------------------------------------- 1 | import 'package:moment_dart/src/exception.dart'; 2 | import 'package:moment_dart/src/moment.dart'; 3 | 4 | extension StartOfUnit on DateTime { 5 | /// Returned object will have same timezone as [this] 6 | /// 7 | /// Notes: 8 | /// 9 | /// * instance.startOf(DurationUnit.week) will throw [MomentException] 10 | /// 11 | /// * instance.startOf(DurationUnit.microsecond) will return clone of that instance 12 | DateTime startOf(DurationUnit unit) { 13 | switch (unit) { 14 | case DurationUnit.microsecond: 15 | return clone(); 16 | case DurationUnit.week: 17 | throw MomentException( 18 | "endOf(DurationUnit.week) is not supported on DateTime. You can use Moment(...).startOf(DurationUnit.week)"); 19 | case DurationUnit.millisecond: 20 | return DateTimeConstructors.withTimezone( 21 | isUtc, 22 | year, 23 | month, 24 | day, 25 | hour, 26 | minute, 27 | second, 28 | millisecond, 29 | ); 30 | case DurationUnit.second: 31 | return DateTimeConstructors.withTimezone( 32 | isUtc, 33 | year, 34 | month, 35 | day, 36 | hour, 37 | minute, 38 | second, 39 | ); 40 | case DurationUnit.minute: 41 | return DateTimeConstructors.withTimezone( 42 | isUtc, 43 | year, 44 | month, 45 | day, 46 | hour, 47 | minute, 48 | ); 49 | case DurationUnit.hour: 50 | return DateTimeConstructors.withTimezone( 51 | isUtc, 52 | year, 53 | month, 54 | day, 55 | hour, 56 | ); 57 | case DurationUnit.day: 58 | return DateTimeConstructors.withTimezone( 59 | isUtc, 60 | year, 61 | month, 62 | day, 63 | ); 64 | case DurationUnit.month: 65 | return DateTimeConstructors.withTimezone( 66 | isUtc, 67 | year, 68 | month, 69 | ); 70 | case DurationUnit.year: 71 | return DateTimeConstructors.withTimezone( 72 | isUtc, 73 | year, 74 | ); 75 | } 76 | } 77 | 78 | /// Returns start of the millisecond 79 | /// 80 | /// Returned object will have same timezone as [this] 81 | DateTime startOfMillisecond() => startOf(DurationUnit.millisecond); 82 | 83 | /// Returns start of the second 84 | /// 85 | /// Returned object will have same timezone as [this] 86 | DateTime startOfSecond() => startOf(DurationUnit.second); 87 | 88 | /// Returns start of the minute 89 | /// 90 | /// Returned object will have same timezone as [this] 91 | DateTime startOfMinute() => startOf(DurationUnit.minute); 92 | 93 | /// Returns start of the hour 94 | /// 95 | /// Returned object will have same timezone as [this] 96 | DateTime startOfHour() => startOf(DurationUnit.hour); 97 | 98 | /// Returns start of the day 99 | /// 100 | /// Returned object will have same timezone as [this] 101 | DateTime startOfDay() => startOf(DurationUnit.day); 102 | 103 | /// Returns start of the week based on [weekStart]. If it's null, it uses [Moment.defaultLocalization.weekStart] 104 | /// 105 | /// Returned object will have same timezone as [this] 106 | DateTime startOfLocalWeek([int? weekStart]) { 107 | weekStart ??= Moment.defaultLocalization.weekStart; 108 | 109 | final int delta = (weekday - weekStart + 7) % 7; 110 | 111 | return subtract(Duration(days: delta)).startOfDay(); 112 | } 113 | 114 | /// Returns start of the ISO week (always Monday) 115 | /// 116 | /// Returned object will have same timezone as [this] 117 | DateTime startOfIsoWeek() => startOfLocalWeek(DateTime.monday); 118 | 119 | /// Returns start of the month 120 | /// 121 | /// Returned object will have same timezone as [this] 122 | DateTime startOfMonth() => startOf(DurationUnit.month); 123 | 124 | /// Returns start of the year 125 | /// 126 | /// Returned object will have same timezone as [this] 127 | DateTime startOfYear() => startOf(DurationUnit.year); 128 | } 129 | 130 | extension StartOfUnitMoment on Moment { 131 | /// Returned object will have same timezone as [this] 132 | /// 133 | /// instance.startOf(DurationUnit.microsecond) will return clone of that instance 134 | Moment startOf(DurationUnit unit) { 135 | switch (unit) { 136 | case DurationUnit.week: 137 | return startOfLocalWeek(); 138 | 139 | case DurationUnit.microsecond: 140 | case DurationUnit.millisecond: 141 | case DurationUnit.second: 142 | case DurationUnit.minute: 143 | case DurationUnit.hour: 144 | case DurationUnit.day: 145 | case DurationUnit.month: 146 | case DurationUnit.year: 147 | return forcedSuperType 148 | .startOf(unit) 149 | .toMoment(localization: setLocalization); 150 | } 151 | } 152 | 153 | /// Returns start of the millisecond 154 | /// 155 | /// Returned object will have same timezone as [this] 156 | Moment startOfMillisecond() => startOf(DurationUnit.millisecond); 157 | 158 | /// Returns start of the second 159 | /// 160 | /// Returned object will have same timezone as [this] 161 | Moment startOfSecond() => startOf(DurationUnit.second); 162 | 163 | /// Returns start of the minute 164 | /// 165 | /// Returned object will have same timezone as [this] 166 | Moment startOfMinute() => startOf(DurationUnit.minute); 167 | 168 | /// Returns start of the hour 169 | /// 170 | /// Returned object will have same timezone as [this] 171 | Moment startOfHour() => startOf(DurationUnit.hour); 172 | 173 | /// Returns start of the day 174 | /// 175 | /// Returned object will have same timezone as [this] 176 | Moment startOfDay() => startOf(DurationUnit.day); 177 | 178 | /// Returns start of the week based on [localization.weekStart]. You can override this with [weekStart] 179 | /// 180 | /// Returned object will have same timezone as [this] 181 | Moment startOfLocalWeek([int? weekStart]) { 182 | return forcedSuperType 183 | .startOfLocalWeek(weekStart ?? localization.weekStart) 184 | .toMoment(localization: setLocalization); 185 | } 186 | 187 | /// Returns start of the ISO week (always Monday) 188 | /// 189 | /// Returned object will have same timezone as [this] 190 | Moment startOfIsoWeek() => startOfLocalWeek(DateTime.monday); 191 | 192 | /// Returns start of the month 193 | /// 194 | /// Returned object will have same timezone as [this] 195 | Moment startOfMonth() => startOf(DurationUnit.month); 196 | 197 | /// Returns start of the year 198 | /// 199 | /// Returned object will have same timezone as [this] 200 | Moment startOfYear() => startOf(DurationUnit.year); 201 | } 202 | -------------------------------------------------------------------------------- /lib/src/localizations/ko_KR.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: file_names 2 | // Author: Batmend Ganbaatar (https://github.com/sadespresso) 3 | 4 | import 'package:moment_dart/moment_dart.dart'; 5 | import 'package:moment_dart/src/localizations/mixins/simple_duration.dart'; 6 | import 'package:moment_dart/src/localizations/mixins/simple_range.dart'; 7 | import 'package:moment_dart/src/localizations/mixins/simple_relative.dart'; 8 | import 'package:moment_dart/src/localizations/mixins/simple_units.dart'; 9 | import 'package:moment_dart/src/types.dart'; 10 | 11 | /// Language: Korean 12 | /// Country: Korea, Republic of 13 | class LocalizationKoKr extends MomentLocalization 14 | with SimpleUnits, SimpleRelative, SimpleDuration, SimpleRange { 15 | static LocalizationKoKr? _instance; 16 | 17 | LocalizationKoKr._internal() : super(); 18 | 19 | factory LocalizationKoKr() { 20 | _instance ??= LocalizationKoKr._internal(); 21 | 22 | return _instance!; 23 | } 24 | 25 | @override 26 | String get endonym => "한국어"; 27 | 28 | @override 29 | String get languageCode => "ko"; 30 | 31 | @override 32 | String get countryCode => "KR"; 33 | 34 | @override 35 | String get languageNameInEnglish => "Korean"; 36 | 37 | @override 38 | String relativePast(String unit) => "$unit 전"; 39 | @override 40 | String relativeFuture(String unit) => "$unit 후"; 41 | 42 | // 평일 이름 43 | @override 44 | Map get weekdayName => { 45 | DateTime.monday: "월요일", 46 | DateTime.tuesday: "화요일", 47 | DateTime.wednesday: "수요일", 48 | DateTime.thursday: "목요일", 49 | DateTime.friday: "금요일", 50 | DateTime.saturday: "토요일", 51 | DateTime.sunday: "일요일", 52 | }; 53 | 54 | Map get weekdayNameShort => { 55 | DateTime.monday: "월", 56 | DateTime.tuesday: "화", 57 | DateTime.wednesday: "수", 58 | DateTime.thursday: "목", 59 | DateTime.friday: "금", 60 | DateTime.saturday: "토", 61 | DateTime.sunday: "일", 62 | }; 63 | 64 | String monthName(int i) => "$i월"; 65 | 66 | @override 67 | FormatSetOptional overrideFormatters() { 68 | return { 69 | // Localization aware formats 70 | FormatterToken.L: (dateTime) => reformat(dateTime, "YYYY.MM.DD."), 71 | FormatterToken.l: (dateTime) => reformat(dateTime, "YYYY.M.D."), 72 | FormatterToken.LL: (dateTime) => reformat(dateTime, "YYYY년 MMMM D일"), 73 | FormatterToken.ll: (dateTime) => reformat(dateTime, "YYYY년 MMM D일"), 74 | FormatterToken.LLL: (dateTime) => 75 | reformat(dateTime, "YYYY년 MMMM D일 A hh:mm"), 76 | FormatterToken.lll: (dateTime) => 77 | reformat(dateTime, "YYYY년 MMM D일 A h:mm"), 78 | FormatterToken.LLLL: (dateTime) => 79 | reformat(dateTime, "YYYY년 MMMM D일 dddd A hh:mm"), 80 | FormatterToken.llll: (dateTime) => 81 | reformat(dateTime, "YYYY년 MMM D일 ddd A h:mm"), 82 | FormatterToken.LT: (dateTime) => reformat(dateTime, "A h:mm"), 83 | FormatterToken.LTS: (dateTime) => reformat(dateTime, "A h:mm:ss"), 84 | // Missings 85 | FormatterToken.A: (dateTime) => dateTime.hour < 12 ? "오전" : "오후", 86 | FormatterToken.a: (dateTime) => dateTime.hour < 12 ? "오전" : "오후", 87 | // Custom Ordinals 88 | FormatterToken.Mo: (dateTime) => "${dateTime.month}월", 89 | FormatterToken.Qo: (dateTime) => "${dateTime.quarter}분기", 90 | FormatterToken.Do: (dateTime) => "${dateTime.day}일", 91 | FormatterToken.DDDo: (dateTime) => "${dateTime.dayOfYear}일", 92 | FormatterToken.d_o: (dateTime) => "${dateTime.weekday}일", 93 | FormatterToken.wo: (dateTime) => "${dateTime.week}주째", 94 | // Weekday 95 | FormatterToken.dd: (dateTime) => weekdayNameShort[dateTime.weekday]!, 96 | // Controversial :)) 97 | FormatterToken.ddd: (dateTime) => weekdayNameShort[dateTime.weekday]!, 98 | FormatterToken.dddd: (dateTime) => weekdayName[dateTime.weekday]!, 99 | // Month names 100 | FormatterToken.MMM: (dateTime) => monthName(dateTime.month), 101 | FormatterToken.MMMM: (dateTime) => monthName(dateTime.month), 102 | }; 103 | } 104 | 105 | @override 106 | CalenderLocalizationData get calendarData => calenderLocalizationDataKo; 107 | 108 | static String last(String weekday) => "지난 $weekday"; 109 | 110 | static const CalenderLocalizationData calenderLocalizationDataKo = 111 | CalenderLocalizationData( 112 | relativeDayNames: { 113 | -2: "그저께", 114 | -1: "어제", 115 | 0: "오늘", 116 | 1: "내일", 117 | 2: "모레", 118 | }, 119 | keywords: CalenderLocalizationKeywords( 120 | lastWeekday: last, 121 | ), 122 | ); 123 | 124 | @override 125 | int get weekStart => DateTime.sunday; 126 | 127 | // Korean hangul is compact enough 128 | @override 129 | Map get units => { 130 | DurationInterval.lessThanASecond: UnitString.single("몇 초"), 131 | DurationInterval.aSecond: UnitString.single("1초"), 132 | DurationInterval.seconds: UnitString.single("$srDelta초"), 133 | DurationInterval.aMinute: UnitString.single("1분"), 134 | DurationInterval.minutes: UnitString.single("$srDelta분"), 135 | DurationInterval.anHour: UnitString.single("1시간"), 136 | DurationInterval.hours: UnitString.single("$srDelta시간"), 137 | DurationInterval.aDay: UnitString.single("1일"), 138 | DurationInterval.days: UnitString.single("$srDelta일"), 139 | DurationInterval.aWeek: UnitString.single("1주"), 140 | DurationInterval.weeks: UnitString.single("$srDelta주"), 141 | DurationInterval.aMonth: UnitString.single("1개월"), 142 | DurationInterval.months: UnitString.single("$srDelta개월"), 143 | DurationInterval.aYear: UnitString.single("1년"), 144 | DurationInterval.years: UnitString.single("$srDelta년"), 145 | }; 146 | 147 | @override 148 | SimpleRangeData get simpleRangeData => SimpleRangeData( 149 | thisWeek: "이번 주", 150 | year: (range, {DateTime? anchor, bool useRelative = true}) { 151 | anchor ??= Moment.now(); 152 | 153 | if (useRelative && range.year == anchor.year) { 154 | return "올해"; 155 | } 156 | 157 | return "${range.year}년"; 158 | }, 159 | month: (range, {DateTime? anchor, bool useRelative = true}) { 160 | anchor ??= Moment.now(); 161 | 162 | if (useRelative && anchor.year == range.year) { 163 | if (anchor.month == range.month) { 164 | return "이번 달"; 165 | } 166 | 167 | return monthName(range.month); 168 | } 169 | 170 | return "${range.year}년 ${monthName(range.month)}"; 171 | }, 172 | allAfter: (formattedDate) => "$formattedDate 이후", 173 | allBefore: (formattedDate) => "$formattedDate 이전", 174 | customRangeAllTime: "전체 시간", 175 | ); 176 | } 177 | -------------------------------------------------------------------------------- /test/localizations_old_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:moment_dart/moment_dart.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | void main() { 5 | test('Mongolian', () { 6 | MomentLocalization localization = LocalizationMnMn(); 7 | final moment = Moment.now(localization: localization) - Duration(days: 1); 8 | final epoch = Moment(DateTime.fromMicrosecondsSinceEpoch(0, isUtc: true), 9 | localization: localization); 10 | final epochPlusFiveDays = epoch + Duration(days: 5); 11 | final epochPlusAYear = epoch + Duration(days: 365); 12 | 13 | expect(moment.lastMonday().weekday, 1); 14 | expect(localization.relative(const Duration(seconds: 2)), 15 | "хэдэн хормын дараа"); 16 | expect(localization.weekdayName[epoch.weekday], "Пүрэв"); 17 | expect(epochPlusFiveDays.from(epoch, dropPrefixOrSuffix: true), "5 өдөр"); 18 | expect(epochPlusFiveDays.from(epoch), "5 өдрийн дараа"); 19 | expect(epoch.calendar(reference: epochPlusFiveDays, omitHours: true), 20 | "Өнгөрсөн Пүрэв"); 21 | expect(epochPlusFiveDays.calendar(reference: epoch, omitHours: true), 22 | "Мягмар"); 23 | expect(epochPlusAYear.from(epoch), "1 жилийн дараа"); 24 | expect(epochPlusAYear.calendar(reference: epoch), "1971/1/1"); 25 | 26 | expect(epochPlusFiveDays.format("L LT"), "1970/01/06 00:00"); 27 | }); 28 | 29 | test('Arabic', () { 30 | MomentLocalization localization = LocalizationArPs(); 31 | final moment = Moment.now(localization: localization) - Duration(days: 1); 32 | final epoch = Moment(DateTime.fromMicrosecondsSinceEpoch(0, isUtc: true), 33 | localization: localization); 34 | final epochPlusFiveDays = epoch + Duration(days: 5); 35 | final epochPlusAYear = epoch + Duration(days: 365); 36 | 37 | expect(moment.lastMonday().weekday, 1); 38 | expect(localization.relative(const Duration(seconds: 2)), 39 | "خلال بضع ثوانٍ"); // Corrected tanween 40 | expect(localization.weekdayName[epoch.weekday], "الخميس"); 41 | expect(epochPlusFiveDays.from(epoch, dropPrefixOrSuffix: true), "5 أيام"); 42 | expect(epochPlusFiveDays.from(epoch), "خلال 5 أيام"); // Using 'خلال' 43 | expect(epoch.calendar(reference: epochPlusFiveDays, omitHours: true), 44 | "الخميس الماضي"); // Should now be handled by gender func 45 | expect(epochPlusFiveDays.calendar(reference: epoch, omitHours: true), 46 | "الثلاثاء"); 47 | expect(epochPlusAYear.from(epoch), "خلال عام"); // Using 'خلال' 48 | // Explicitly format as 'L' (DD/MM/YYYY) instead of relying on calendar() default 49 | expect(epochPlusAYear.format('L'), "01/01/1971"); // FIX: Use format('L') 50 | expect(epochPlusFiveDays.format("L LT"), "06/01/1970 00:00"); // Using HH:mm 51 | }); 52 | 53 | test('English', () { 54 | MomentLocalization localization = LocalizationEnUs(); 55 | final moment = Moment.now(localization: localization) - Duration(days: 1); 56 | final epoch = Moment(DateTime.fromMicrosecondsSinceEpoch(0, isUtc: true), 57 | localization: localization); 58 | final epochPlusFiveDays = epoch + Duration(days: 5); 59 | final epochPlusAYear = epoch + Duration(days: 365); 60 | 61 | expect(moment.lastMonday().weekday, 1); 62 | expect( 63 | localization.relative(const Duration(seconds: 2)), "in a few seconds"); 64 | expect(localization.weekdayName[epoch.weekday], "Thursday"); 65 | expect(epochPlusFiveDays.from(epoch, dropPrefixOrSuffix: true), "5 days"); 66 | expect(epochPlusFiveDays.from(epoch), "in 5 days"); 67 | expect(epoch.calendar(reference: epochPlusFiveDays, omitHours: true), 68 | "Last Thursday"); 69 | expect(epochPlusFiveDays.calendar(reference: epoch, omitHours: true), 70 | "Tuesday"); 71 | expect(epochPlusAYear.from(epoch), "in a year"); 72 | expect(epochPlusAYear.calendar(reference: epoch), "1/1/1971"); 73 | 74 | expect(epochPlusFiveDays.format("L LT"), "01/06/1970 12:00 AM"); 75 | }); 76 | 77 | test('Traditional Mongolian', () { 78 | MomentLocalization localization = LocalizationMnMongMn(); 79 | 80 | final moment = Moment.now(localization: localization) - Duration(days: 1); 81 | final epoch = Moment(DateTime.fromMicrosecondsSinceEpoch(0, isUtc: true), 82 | localization: localization); 83 | final epochPlusFiveDays = epoch + Duration(days: 5); 84 | final epochPlusAYear = epoch + Duration(days: 365); 85 | 86 | expect(moment.lastMonday().weekday, 1); 87 | expect(localization.relative(const Duration(seconds: 2)), 88 | "ᠬᠡᠳᠦᠨ ᠬᠣᠷᠤᠮ ᠤᠨ ᠳᠠᠷᠠᠭ᠎ᠠ"); 89 | expect(localization.weekdayName[epoch.weekday], "ᠭᠠᠳᠠᠰᠤ"); 90 | expect(epochPlusFiveDays.from(epoch, dropPrefixOrSuffix: true), "5 ᠡᠳᠦᠷ"); 91 | expect(epochPlusFiveDays.from(epoch), "5 ᠡᠳᠦᠷ ᠦᠨ ᠳᠠᠷᠠᠭ᠎ᠠ"); 92 | expect(epoch.calendar(reference: epochPlusFiveDays, omitHours: true), 93 | "ᠥᠩᠭᠡᠷᠡᠭᠰᠡᠨ ᠭᠠᠳᠠᠰᠤ"); 94 | expect(epochPlusFiveDays.calendar(reference: epoch, omitHours: true), 95 | "ᠤᠯᠠᠭᠠᠨ"); 96 | expect(epochPlusAYear.from(epoch), "1 ᠵᠢᠯ ᠤᠨ ᠳᠠᠷᠠᠭ᠎ᠠ"); 97 | expect(epochPlusAYear.calendar(reference: epoch), "1971/1/1"); 98 | 99 | expect(epochPlusFiveDays.format("L LT"), "1970/01/06 00:00"); 100 | }); 101 | 102 | test('Korean', () { 103 | MomentLocalization localization = LocalizationKoKr(); 104 | final moment = Moment.now(localization: localization) - Duration(days: 1); 105 | final epoch = Moment(DateTime.fromMicrosecondsSinceEpoch(0, isUtc: true), 106 | localization: localization); 107 | final epochPlusFiveDays = epoch + Duration(days: 5); 108 | final epochPlusAYear = epoch + Duration(days: 365); 109 | 110 | expect(moment.lastMonday().weekday, 1); 111 | expect(localization.relative(const Duration(seconds: 2)), "몇 초 후"); 112 | expect(localization.weekdayName[epoch.weekday], "목요일"); 113 | expect(epochPlusFiveDays.from(epoch, dropPrefixOrSuffix: true), "5일"); 114 | expect(epochPlusFiveDays.from(epoch), "5일 후"); 115 | expect(epoch.calendar(reference: epochPlusFiveDays, omitHours: true), 116 | "지난 목요일"); 117 | expect( 118 | epochPlusFiveDays.calendar(reference: epoch, omitHours: true), "화요일"); 119 | expect(epochPlusAYear.from(epoch), "1년 후"); 120 | expect(epochPlusAYear.calendar(reference: epoch), "1971.1.1."); 121 | 122 | expect(epochPlusFiveDays.format("L LT"), "1970.01.06. 오전 12:00"); 123 | }); 124 | 125 | test("Text escaping test", () { 126 | MomentLocalization localization = LocalizationEnUs(); 127 | final epoch = Moment(DateTime.fromMicrosecondsSinceEpoch(0, isUtc: true), 128 | localization: localization); 129 | 130 | expect(epoch.format(r"[hellooo]"), "hellooo"); 131 | expect(epoch.format(r"YYYY [escaped] YYYY"), "1970 escaped 1970"); 132 | expect(epoch.format(r"L [L] LT [LT]"), "01/01/1970 L 12:00 AM LT"); 133 | expect(epoch.format(r"l [L] LT [LT]"), "1/1/1970 L 12:00 AM LT"); 134 | expect(epoch.format(r"YYYY [YYYY] MMMM [MMMM] Do [Do] LT [A]"), 135 | "1970 YYYY January MMMM 1st Do 12:00 AM A"); 136 | }); 137 | } 138 | -------------------------------------------------------------------------------- /lib/src/extensions/end_of.dart: -------------------------------------------------------------------------------- 1 | import 'package:moment_dart/src/exception.dart'; 2 | import 'package:moment_dart/src/moment.dart'; 3 | 4 | extension EndOfUnit on DateTime { 5 | /// Returned object will have same timezone as [this] 6 | /// 7 | /// Notes: 8 | /// 9 | /// * instance.startOf(DurationUnit.week) will throw [MomentException] 10 | /// 11 | /// * instance.startOf(DurationUnit.microsecond) will return clone of that instance 12 | DateTime endOf(DurationUnit unit) { 13 | switch (unit) { 14 | case DurationUnit.microsecond: 15 | return clone(); 16 | case DurationUnit.week: 17 | throw MomentException( 18 | "endOf(DurationUnit.week) is not supported on DateTime object. You can use it on Moment objects"); 19 | case DurationUnit.millisecond: 20 | return DateTimeConstructors.withTimezone( 21 | isUtc, 22 | year, 23 | month, 24 | day, 25 | hour, 26 | minute, 27 | second, 28 | millisecond, 29 | 999, 30 | ); 31 | case DurationUnit.second: 32 | return DateTimeConstructors.withTimezone( 33 | isUtc, 34 | year, 35 | month, 36 | day, 37 | hour, 38 | minute, 39 | second, 40 | 999, 41 | 999, 42 | ); 43 | case DurationUnit.minute: 44 | return DateTimeConstructors.withTimezone( 45 | isUtc, 46 | year, 47 | month, 48 | day, 49 | hour, 50 | minute, 51 | 59, 52 | 999, 53 | 999, 54 | ); 55 | case DurationUnit.hour: 56 | return DateTimeConstructors.withTimezone( 57 | isUtc, 58 | year, 59 | month, 60 | day, 61 | hour, 62 | 59, 63 | 59, 64 | 999, 65 | 999, 66 | ); 67 | case DurationUnit.day: 68 | return DateTimeConstructors.withTimezone( 69 | isUtc, 70 | year, 71 | month, 72 | day, 73 | 23, 74 | 59, 75 | 59, 76 | 999, 77 | 999, 78 | ); 79 | case DurationUnit.month: 80 | return DateTime( 81 | year, 82 | month, 83 | isLeapYear 84 | ? Moment.daysInMonthsInLeapYear[month] 85 | : Moment.daysInMonths[month], 86 | 23, 87 | 59, 88 | 59, 89 | 999, 90 | 999, 91 | ); 92 | case DurationUnit.year: 93 | return DateTimeConstructors.withTimezone( 94 | isUtc, 95 | year, 96 | 12, 97 | 31, 98 | 23, 99 | 59, 100 | 59, 101 | 999, 102 | 999, 103 | ); 104 | } 105 | } 106 | 107 | /// Returns end of the millisecond 108 | /// 109 | /// Returned object will have same timezone as [this] 110 | DateTime endOfMillisecond() => endOf(DurationUnit.millisecond); 111 | 112 | /// Returns end of the second 113 | /// 114 | /// Returned object will have same timezone as [this] 115 | DateTime endOfSecond() => endOf(DurationUnit.second); 116 | 117 | /// Returns end of the minute 118 | /// 119 | /// Returned object will have same timezone as [this] 120 | DateTime endOfMinute() => endOf(DurationUnit.minute); 121 | 122 | /// Returns end of the hour 123 | /// 124 | /// Returned object will have same timezone as [this] 125 | DateTime endOfHour() => endOf(DurationUnit.hour); 126 | 127 | /// Returns end of the day 128 | /// 129 | /// Returned object will have same timezone as [this] 130 | DateTime endOfDay() => endOf(DurationUnit.day); 131 | 132 | /// Returns start of the week based on [weekStart]. If it's null, it uses [Moment.defaultLocalization.weekStart] 133 | /// 134 | /// Returned object will have same timezone as [this] 135 | DateTime endOfLocalWeek([int? weekStart]) { 136 | weekStart ??= Moment.defaultLocalization.weekStart; 137 | 138 | final int delta = (weekStart + 6 - weekday) % 7; 139 | 140 | return add(Duration(days: delta)).endOfDay(); 141 | } 142 | 143 | /// Returns end of the ISO week (always Sunday) 144 | /// 145 | /// Returned object will have same timezone as [this] 146 | DateTime endOfIsoWeek() => endOfLocalWeek(DateTime.monday); 147 | 148 | /// Returns end of the month 149 | /// 150 | /// Returned object will have same timezone as [this] 151 | DateTime endOfMonth() => endOf(DurationUnit.month); 152 | 153 | /// Returns end of the year 154 | /// 155 | /// Returned object will have same timezone as [this] 156 | DateTime endOfYear() => endOf(DurationUnit.year); 157 | } 158 | 159 | extension EndOfUnitMoment on Moment { 160 | /// Returned object will have same timezone as [this] 161 | Moment endOf(DurationUnit unit) { 162 | switch (unit) { 163 | case DurationUnit.week: 164 | return endOfLocalWeek(); 165 | 166 | case DurationUnit.microsecond: 167 | case DurationUnit.millisecond: 168 | case DurationUnit.second: 169 | case DurationUnit.minute: 170 | case DurationUnit.hour: 171 | case DurationUnit.day: 172 | case DurationUnit.month: 173 | case DurationUnit.year: 174 | return forcedSuperType 175 | .endOf(unit) 176 | .toMoment(localization: setLocalization); 177 | } 178 | } 179 | 180 | /// Returns end of the millisecond 181 | /// 182 | /// Returned object will have same timezone as [this] 183 | Moment endOfMillisecond() => endOf(DurationUnit.millisecond); 184 | 185 | /// Returns end of the second 186 | /// 187 | /// Returned object will have same timezone as [this] 188 | Moment endOfSecond() => endOf(DurationUnit.second); 189 | 190 | /// Returns end of the minute 191 | /// 192 | /// Returned object will have same timezone as [this] 193 | Moment endOfMinute() => endOf(DurationUnit.minute); 194 | 195 | /// Returns end of the hour 196 | /// 197 | /// Returned object will have same timezone as [this] 198 | Moment endOfHour() => endOf(DurationUnit.hour); 199 | 200 | /// Returns end of the day 201 | /// 202 | /// Returned object will have same timezone as [this] 203 | Moment endOfDay() => endOf(DurationUnit.day); 204 | 205 | /// Returns end of the week based on [localization.weekStart]. You can override this with [weekStart] 206 | /// 207 | /// Returned object will have same timezone as [this] 208 | Moment endOfLocalWeek([int? weekStart]) { 209 | return forcedSuperType 210 | .endOfLocalWeek(weekStart ?? localization.weekStart) 211 | .toMoment(localization: setLocalization); 212 | } 213 | 214 | /// Returns start of the ISO week (always Monday) 215 | /// 216 | /// Returned object will have same timezone as [this] 217 | Moment endOfIsoWeek() => endOfLocalWeek(DateTime.monday); 218 | 219 | /// Returns end of the month 220 | /// 221 | /// Returned object will have same timezone as [this] 222 | Moment endOfMonth() => endOf(DurationUnit.month); 223 | 224 | /// Returns end of the year 225 | /// 226 | /// Returned object will have same timezone as [this] 227 | Moment endOfYear() => endOf(DurationUnit.year); 228 | } 229 | -------------------------------------------------------------------------------- /test/localizations/find_by_language_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:moment_dart/moment_dart.dart'; 2 | import 'package:test/expect.dart'; 3 | import 'package:test/scaffolding.dart'; 4 | 5 | void main() { 6 | test("MomentLocalizations.byLanguage test", () { 7 | expect( 8 | MomentLocalizations.byLanguage('mn'), 9 | TypeMatcher(), 10 | ); 11 | expect( 12 | MomentLocalizations.byLanguage('mn', scriptCode: 'Mong'), 13 | TypeMatcher(), 14 | ); 15 | expect( 16 | MomentLocalizations.byLanguage('mn', scriptCode: 'Qaaq'), 17 | TypeMatcher(), 18 | ); 19 | expect( 20 | MomentLocalizations.byLanguage('ar'), 21 | TypeMatcher(), 22 | ); 23 | expect( 24 | MomentLocalizations.byLanguage('ko'), 25 | TypeMatcher(), 26 | ); 27 | expect( 28 | MomentLocalizations.byLanguage('ja'), 29 | TypeMatcher(), 30 | ); 31 | expect( 32 | MomentLocalizations.byLanguage('de'), 33 | TypeMatcher(), 34 | ); 35 | expect( 36 | MomentLocalizations.byLanguage('es'), 37 | TypeMatcher(), 38 | ); 39 | expect( 40 | MomentLocalizations.byLanguage('fr'), 41 | TypeMatcher(), 42 | ); 43 | expect( 44 | MomentLocalizations.byLanguage('tr'), 45 | TypeMatcher(), 46 | ); 47 | expect( 48 | MomentLocalizations.byLanguage('pt'), 49 | TypeMatcher(), 50 | ); 51 | expect( 52 | MomentLocalizations.byLanguage('it'), 53 | TypeMatcher(), 54 | ); 55 | expect( 56 | MomentLocalizations.byLanguage('en'), 57 | TypeMatcher(), 58 | ); 59 | expect( 60 | MomentLocalizations.byLanguage('zh'), 61 | TypeMatcher(), 62 | ); 63 | expect( 64 | MomentLocalizations.byLanguage('ru'), 65 | TypeMatcher(), 66 | ); 67 | }); 68 | 69 | test("MomentLocalizations.byLanguage strict test", () { 70 | expect( 71 | MomentLocalizations.byLanguage('mn', strict: true), 72 | TypeMatcher(), 73 | ); 74 | expect( 75 | MomentLocalizations.byLanguage('mn', countryCode: 'MN', strict: true), 76 | TypeMatcher(), 77 | ); 78 | 79 | expect( 80 | MomentLocalizations.byLanguage( 81 | 'mn', 82 | scriptCode: 'Mong', 83 | countryCode: 'MN', 84 | strict: true, 85 | ), 86 | TypeMatcher(), 87 | ); 88 | expect( 89 | MomentLocalizations.byLanguage( 90 | 'mn', 91 | scriptCode: 'Mong', 92 | countryCode: 'CN', 93 | strict: true, 94 | ), 95 | null, 96 | ); 97 | 98 | expect( 99 | MomentLocalizations.byLanguage( 100 | 'mn', 101 | scriptCode: 'Qaaq', 102 | countryCode: 'MN', 103 | strict: true, 104 | ), 105 | TypeMatcher(), 106 | ); 107 | expect( 108 | MomentLocalizations.byLanguage( 109 | 'mn', 110 | scriptCode: 'Qaaq', 111 | strict: true, 112 | ), 113 | TypeMatcher(), 114 | ); 115 | 116 | expect( 117 | MomentLocalizations.byLanguage( 118 | 'ar', 119 | strict: true, 120 | ), 121 | TypeMatcher(), 122 | ); 123 | expect( 124 | MomentLocalizations.byLanguage( 125 | 'ar', 126 | countryCode: 'PS', 127 | strict: true, 128 | ), 129 | TypeMatcher(), 130 | ); 131 | 132 | expect( 133 | MomentLocalizations.byLanguage( 134 | 'ko', 135 | strict: true, 136 | ), 137 | TypeMatcher(), 138 | ); 139 | expect( 140 | MomentLocalizations.byLanguage( 141 | 'ko', 142 | countryCode: 'KR', 143 | strict: true, 144 | ), 145 | TypeMatcher(), 146 | ); 147 | 148 | expect( 149 | MomentLocalizations.byLanguage( 150 | 'ja', 151 | strict: true, 152 | ), 153 | TypeMatcher(), 154 | ); 155 | expect( 156 | MomentLocalizations.byLanguage( 157 | 'ja', 158 | countryCode: "JP", 159 | strict: true, 160 | ), 161 | TypeMatcher(), 162 | ); 163 | 164 | expect( 165 | MomentLocalizations.byLanguage( 166 | 'de', 167 | countryCode: 'DE', 168 | strict: true, 169 | ), 170 | TypeMatcher(), 171 | ); 172 | expect( 173 | MomentLocalizations.byLanguage( 174 | 'de', 175 | strict: true, 176 | ), 177 | TypeMatcher(), 178 | ); 179 | 180 | expect( 181 | MomentLocalizations.byLanguage( 182 | 'es', 183 | strict: true, 184 | ), 185 | TypeMatcher(), 186 | ); 187 | expect( 188 | MomentLocalizations.byLanguage( 189 | 'es', 190 | countryCode: 'ES', 191 | strict: true, 192 | ), 193 | TypeMatcher(), 194 | ); 195 | 196 | expect( 197 | MomentLocalizations.byLanguage( 198 | 'fr', 199 | strict: true, 200 | ), 201 | TypeMatcher(), 202 | ); 203 | expect( 204 | MomentLocalizations.byLanguage( 205 | 'fr', 206 | countryCode: 'FR', 207 | strict: true, 208 | ), 209 | TypeMatcher(), 210 | ); 211 | 212 | expect( 213 | MomentLocalizations.byLanguage( 214 | 'it', 215 | strict: true, 216 | ), 217 | TypeMatcher(), 218 | ); 219 | expect( 220 | MomentLocalizations.byLanguage( 221 | 'it', 222 | countryCode: 'IT', 223 | strict: true, 224 | ), 225 | TypeMatcher(), 226 | ); 227 | expect( 228 | MomentLocalizations.byLanguage( 229 | 'en', 230 | strict: true, 231 | ), 232 | TypeMatcher(), 233 | ); 234 | expect( 235 | MomentLocalizations.byLanguage( 236 | 'en', 237 | countryCode: 'US', 238 | strict: true, 239 | ), 240 | TypeMatcher(), 241 | ); 242 | expect( 243 | MomentLocalizations.byLanguage( 244 | 'zh', 245 | countryCode: 'CN', 246 | strict: true, 247 | ), 248 | TypeMatcher(), 249 | ); 250 | expect( 251 | MomentLocalizations.byLanguage( 252 | 'zh', 253 | strict: true, 254 | ), 255 | TypeMatcher(), 256 | ); 257 | 258 | expect( 259 | MomentLocalizations.byLanguage( 260 | 'ru', 261 | strict: true, 262 | ), 263 | TypeMatcher(), 264 | ); 265 | expect( 266 | MomentLocalizations.byLanguage( 267 | 'ru', 268 | countryCode: 'RU', 269 | strict: true, 270 | ), 271 | TypeMatcher(), 272 | ); 273 | }); 274 | } 275 | -------------------------------------------------------------------------------- /lib/src/localizations/ar_PS.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: file_names 2 | // Author: Ultrate (https://github.com/Ultrate) 3 | 4 | import 'package:moment_dart/moment_dart.dart'; 5 | import 'package:moment_dart/src/localizations/mixins/month_names.dart'; 6 | import 'package:moment_dart/src/localizations/mixins/ordinal_numbers.dart'; 7 | import 'package:moment_dart/src/localizations/mixins/simple_duration.dart'; 8 | import 'package:moment_dart/src/localizations/mixins/simple_range.dart'; 9 | import 'package:moment_dart/src/localizations/mixins/simple_relative.dart'; 10 | import 'package:moment_dart/src/localizations/mixins/simple_units.dart'; 11 | import 'package:moment_dart/src/localizations/mixins/weekday_shortforms.dart'; 12 | import 'package:moment_dart/src/types.dart'; 13 | 14 | /// Language: Arabic 15 | /// Country: Palestine 16 | class LocalizationArPs extends MomentLocalization 17 | with 18 | Ordinal, 19 | MonthNames, 20 | SimpleUnits, 21 | SimpleRelative, 22 | SimpleDuration, 23 | SimpleRange, 24 | WeekdayShortForms { 25 | static LocalizationArPs? _instance; 26 | 27 | LocalizationArPs._internal() : super(); 28 | 29 | factory LocalizationArPs() { 30 | _instance ??= LocalizationArPs._internal(); 31 | return _instance!; 32 | } 33 | 34 | @override 35 | String get endonym => "العربية"; 36 | 37 | @override 38 | String get languageCode => "ar"; 39 | 40 | @override 41 | String get countryCode => "PS"; 42 | 43 | @override 44 | String get languageNameInEnglish => "Arabic"; 45 | 46 | @override 47 | String relativePast(String unit) => "منذ $unit"; 48 | @override 49 | String relativeFuture(String unit) => "خلال $unit"; 50 | 51 | @override 52 | Map get monthNames => { 53 | 1: "يناير", 54 | 2: "فبراير", 55 | 3: "مارس", 56 | 4: "أبريل", 57 | 5: "ماي", 58 | 6: "يونيو", 59 | 7: "يوليوز", 60 | 8: "غشت", 61 | 9: "شتنبر", 62 | 10: "أكتوبر", 63 | 11: "نونبر", 64 | 12: "دجنبر", 65 | }; 66 | 67 | @override 68 | Map get monthNamesShort => { 69 | 1: "ينا", 70 | 2: "فبر", 71 | 3: "مار", 72 | 4: "أبر", 73 | 5: "ماي", 74 | 6: "يون", 75 | 7: "يول", 76 | 8: "غشت", 77 | 9: "شتن", 78 | 10: "أكت", 79 | 11: "نون", 80 | 12: "دجن", 81 | }; 82 | 83 | @override 84 | Map get weekdayName => { 85 | DateTime.monday: "الاثنين", 86 | DateTime.tuesday: "الثلاثاء", 87 | DateTime.wednesday: "الأربعاء", 88 | DateTime.thursday: "الخميس", 89 | DateTime.friday: "الجمعة", 90 | DateTime.saturday: "السبت", 91 | DateTime.sunday: "الأحد", 92 | }; 93 | 94 | @override 95 | Map get weekdayNameShort => { 96 | DateTime.monday: "إثن", 97 | DateTime.tuesday: "ثلا", 98 | DateTime.wednesday: "أرب", 99 | DateTime.thursday: "خمي", 100 | DateTime.friday: "جمعة", 101 | DateTime.saturday: "سبت", 102 | DateTime.sunday: "أحد", 103 | }; 104 | 105 | @override 106 | FormatSetOptional overrideFormatters() { 107 | String getShortMonth(DateTime dt) => monthNamesShort[dt.month]!; 108 | String getShortWeekday(DateTime dt) => weekdayNameShort[dt.weekday]!; 109 | 110 | return { 111 | ...formattersWithOrdinal, 112 | ...formattersForMonthNames, 113 | ...formattersForWeekdayShortForms, 114 | FormatterToken.L: (dateTime) => reformat(dateTime, "DD/MM/YYYY"), 115 | FormatterToken.l: (dateTime) => reformat(dateTime, "D/M/YYYY"), 116 | FormatterToken.LL: (dateTime) => reformat(dateTime, "D MMMM YYYY"), 117 | FormatterToken.ll: (dateTime) => 118 | "${dateTime.day} ${getShortMonth(dateTime)} ${dateTime.year}", 119 | FormatterToken.LLL: (dateTime) => reformat(dateTime, "D MMMM YYYY HH:mm"), 120 | FormatterToken.lll: (dateTime) => 121 | "${dateTime.day} ${getShortMonth(dateTime)} ${dateTime.year} ${reformat(dateTime, 'HH:mm')}", 122 | FormatterToken.LLLL: (dateTime) => 123 | "${weekdayName[dateTime.weekday]}، ${reformat(dateTime, 'D MMMM YYYY HH:mm')}", 124 | FormatterToken.llll: (dateTime) => 125 | "${getShortWeekday(dateTime)}، ${dateTime.day} ${getShortMonth(dateTime)} ${dateTime.year} ${reformat(dateTime, 'HH:mm')}", 126 | FormatterToken.LT: (dateTime) => reformat(dateTime, "HH:mm"), 127 | FormatterToken.LTS: (dateTime) => reformat(dateTime, "HH:mm:ss"), 128 | }; 129 | } 130 | 131 | @override 132 | CalenderLocalizationData get calendarData => _calendarLocalizationDataAr; 133 | 134 | static String _lastWeekdayGenderAware(String weekday) { 135 | if (weekday.endsWith('ة')) { 136 | return "$weekday الماضية"; 137 | } 138 | return "$weekday الماضي"; 139 | } 140 | 141 | static String _at(String date, String time) => "$date الساعة $time"; 142 | 143 | static final CalenderLocalizationData _calendarLocalizationDataAr = 144 | CalenderLocalizationData( 145 | relativeDayNames: { 146 | -1: "أمس", 147 | 0: "اليوم", 148 | 1: "غداً", 149 | }, 150 | keywords: CalenderLocalizationKeywords( 151 | at: _at, 152 | lastWeekday: _lastWeekdayGenderAware, 153 | ), 154 | ); 155 | 156 | @override 157 | int get weekStart => DateTime.saturday; 158 | 159 | @override 160 | Map get units => { 161 | DurationInterval.lessThanASecond: UnitString.single("بضع ثوانٍ"), 162 | DurationInterval.aSecond: UnitString.single("ثانية"), 163 | DurationInterval.seconds: UnitString.single("$srDelta ثوانٍ"), 164 | DurationInterval.aMinute: UnitString.single("دقيقة"), 165 | DurationInterval.minutes: UnitString.single("$srDelta دقائق"), 166 | DurationInterval.anHour: UnitString.single("ساعة"), 167 | DurationInterval.hours: UnitString.single("$srDelta ساعات"), 168 | DurationInterval.aDay: UnitString.single("يوم"), 169 | DurationInterval.days: UnitString.single("$srDelta أيام"), 170 | DurationInterval.aWeek: UnitString.single("أسبوع"), 171 | DurationInterval.weeks: UnitString.single("$srDelta أسابيع"), 172 | DurationInterval.aMonth: UnitString.single("شهر"), 173 | DurationInterval.months: UnitString.single("$srDelta أشهر"), 174 | DurationInterval.aYear: UnitString.single("عام"), 175 | DurationInterval.years: UnitString.single("$srDelta أعوام"), 176 | }; 177 | 178 | @override 179 | SimpleRangeData get simpleRangeData => SimpleRangeData( 180 | thisWeek: "هذا الأسبوع", 181 | year: (range, {DateTime? anchor, bool useRelative = true}) { 182 | anchor ??= Moment.now(); 183 | if (useRelative && range.year == anchor.year) { 184 | return "هذا العام"; 185 | } 186 | return "عام ${range.year}"; 187 | }, 188 | month: (range, {DateTime? anchor, bool useRelative = true}) { 189 | anchor ??= Moment.now(); 190 | if (useRelative && anchor.year == range.year) { 191 | if (anchor.month == range.month) { 192 | return "هذا الشهر"; 193 | } 194 | return monthNames[range.month]!; 195 | } 196 | return "${monthNames[range.month]!} ${range.year}"; 197 | }, 198 | allAfter: (formattedDate) => "بعد $formattedDate", 199 | allBefore: (formattedDate) => "قبل $formattedDate", 200 | customRangeAllTime: "كل الوقت", 201 | ); 202 | 203 | @override 204 | String ordinalNumber(int n) => n.toString(); 205 | } 206 | -------------------------------------------------------------------------------- /lib/src/extensions/benefits.dart: -------------------------------------------------------------------------------- 1 | import 'package:moment_dart/moment_dart.dart'; 2 | 3 | export 'duration.dart'; 4 | 5 | extension MomentBenefits on DateTime { 6 | bool get isFuture => isFutureAnchored(); 7 | bool get isPast => isPastAnchored(); 8 | 9 | bool isFutureAnchored([DateTime? anchor]) => 10 | isAfter(anchor ?? DateTime.now()); 11 | bool isPastAnchored([DateTime? anchor]) => isBefore(anchor ?? DateTime.now()); 12 | 13 | /// Returns if [year] is leap year. 14 | /// 15 | /// More about leap years: [Leap Year](https://en.wikipedia.org/wiki/Leap_year) 16 | bool get isLeapYear { 17 | if (year % 400 == 0) return true; 18 | if (year % 100 == 0) return false; 19 | return year % 4 == 0; 20 | } 21 | 22 | /// Returns hour in 12-hour format 23 | /// 24 | /// Relevant: [isAm] or [isPm] 25 | int get hour12 { 26 | if (hour == 0 || hour == 12) return 12; 27 | 28 | return hour % 12; 29 | } 30 | 31 | /// Returns whether the [hour] is before noon (ante meridiem) in the current timezone 32 | bool get isAm => hour < 12; 33 | 34 | /// Returns whether the [hour] is after noon (post meridiem) in the current timezone 35 | bool get isPm => hour >= 12; 36 | 37 | /// Returns quarter of the year. 38 | /// 39 | /// Jan,Feb,Mar is Q1 40 | /// 41 | /// Apr,May,Jun is Q2 42 | /// 43 | /// Jul,Aug,Sep is Q3 44 | /// 45 | /// Oct,Nov,Dec is Q4 46 | int get quarter => (month - 1) ~/ 3 + 1; 47 | 48 | int get _isoWeekRaw => (10 + dayOfYear - weekday) ~/ 7; 49 | 50 | bool get _isoWeekInNextYear => 51 | DateTime(year, 1, 1).weekday != DateTime.thursday && 52 | DateTime(year, 12, 31).weekday != DateTime.thursday; 53 | 54 | /// Returns [ISO week](https://en.wikipedia.org/wiki/ISO_week_date) number of the year 55 | /// 56 | /// [1, 2, 3, ..., 52, 53] 57 | int get week { 58 | final int w = _isoWeekRaw; 59 | 60 | // Last year may have 52 or 53 weeks, we shall check 61 | // 62 | // Dec 28 is always in the last week 63 | if (w == 0) { 64 | return DateTime(year - 1, 12, 28).week; 65 | } 66 | 67 | // It might actually be [Week 1] in the next year 68 | if (w == 53 && _isoWeekInNextYear) { 69 | return 1; 70 | } 71 | 72 | return w; 73 | } 74 | 75 | /// Returns year according to [ISO week](https://en.wikipedia.org/wiki/ISO_week_date) number of the year 76 | int get weekYear { 77 | final int w = _isoWeekRaw; 78 | 79 | if (w == 0) return year - 1; 80 | 81 | if (w == 53 && _isoWeekInNextYear) { 82 | return year + 1; 83 | } 84 | 85 | return year; 86 | } 87 | 88 | /// Returns ordinal day of the year in the current timezone 89 | /// 90 | /// [1,2,3,...,365,366] 91 | int get dayOfYear { 92 | const List dayCount = [ 93 | 0, 94 | 0, 95 | 31, 96 | 59, 97 | 90, 98 | 120, 99 | 151, 100 | 181, 101 | 212, 102 | 243, 103 | 273, 104 | 304, 105 | 334 106 | ]; 107 | 108 | final int dayOfTheYear = dayCount[month] + day; 109 | 110 | if (isLeapYear && month > 2) { 111 | return dayOfTheYear + 1; 112 | } 113 | 114 | return dayOfTheYear; 115 | } 116 | 117 | /// Difference calculated after omitting hour, minute, ..., microsecond 118 | /// 119 | /// Does not take timezones of [this] and [other]! 120 | /// 121 | /// Uses DateTime.difference(), therefore behaves same. 122 | /// So, be careful with UTC and local timezones. 123 | /// 124 | /// ------- 125 | /// 126 | /// If [other] occured after [this], result is negative 127 | /// 128 | /// ```dart 129 | /// today.differenceInDays(tomorrow); // -1 130 | /// tomorrow.differenceInDays(today); // 1 131 | /// 132 | /// // 0 means [this] and [other] occured at the same day. 133 | /// ``` 134 | int differenceInDays(DateTime other) { 135 | return date.difference(other.date).inDays; 136 | } 137 | 138 | /// Equivalent to `add(other)` 139 | operator +(Duration other) => add(other); 140 | 141 | /// Equivalent to `subtract(other)` 142 | operator -(Duration other) => subtract(other); 143 | 144 | /// Equivalent to `isAfter(other)` 145 | operator >(DateTime other) => isAfter(other); 146 | 147 | /// Equivalent to `isBefore(other)` 148 | operator <(DateTime other) => isBefore(other); 149 | 150 | /// Equivalent to `isAfter(other) || isAtSameMomentAs(other)` 151 | operator >=(DateTime other) => isAfter(other) || isAtSameMomentAs(other); 152 | 153 | /// Equivalent to `isBefore(other) || isAtSameMomentAs(other)` 154 | operator <=(DateTime other) => isBefore(other) || isAtSameMomentAs(other); 155 | 156 | /// Returns timezone: 157 | /// 158 | /// -06:00 => GMT-6 159 | /// 160 | /// +13:00 => GMT+13 161 | /// 162 | /// You can disable [seperateWithColon]. 163 | /// 164 | /// -0730 => GMT-7:30 165 | /// 166 | /// +1300 => GMT+13 167 | String timeZoneFormatted([bool seperateWithColon = true]) { 168 | final int inMinutes = timeZoneOffset.abs().inMinutes; 169 | 170 | final int hours = inMinutes ~/ 60; 171 | final int minutes = inMinutes - (hours * 60); 172 | 173 | return (timeZoneOffset.isNegative ? "-" : "+") + 174 | hours.toString().padLeft(2, '0') + 175 | (seperateWithColon ? ":" : "") + 176 | minutes.toString().padLeft(2, '0'); 177 | } 178 | 179 | /// Returns new `DateTime`, preserves timezone 180 | DateTime clone() { 181 | return DateTimeConstructors.withTimezone( 182 | isUtc, 183 | year, 184 | month, 185 | day, 186 | hour, 187 | minute, 188 | second, 189 | millisecond, 190 | microsecond, 191 | ); 192 | } 193 | 194 | /// Returns new `DateTime` with the date at 00:00:00 AM, preserves timezone 195 | DateTime get date => DateTimeConstructors.dateWithTimezone( 196 | year, 197 | month, 198 | day, 199 | isUtc, 200 | ); 201 | 202 | /// Whether it rquals [date] 203 | bool get isMidnight => date == this; 204 | 205 | Moment toMoment({MomentLocalization? localization}) => 206 | Moment(this, localization: localization); 207 | 208 | String format({ 209 | String payload = MomentLocalization.localizationDefaultDateFormat, 210 | bool forceLocal = false, 211 | MomentLocalization? localization, 212 | }) { 213 | if (this is Moment) { 214 | return localization == null 215 | ? (this as Moment).format(payload, forceLocal) 216 | : (this as Moment) 217 | .copyWith(localization: localization) 218 | .format(payload, forceLocal); 219 | } 220 | 221 | return toMoment(localization: localization).format(payload, forceLocal); 222 | } 223 | 224 | /// Returns [CustomTimeRange] from [this] to [other] 225 | /// 226 | /// If [this] is after [other], it will be swapped. 227 | CustomTimeRange rangeTo(DateTime other) { 228 | if (this < other) return CustomTimeRange(this, other); 229 | 230 | return CustomTimeRange(other, this); 231 | } 232 | 233 | /// Returns [CustomTimeRange] from [this] to [other] 234 | /// 235 | /// If [this] is after [other], it will be swapped. 236 | CustomTimeRange rangeToMax() => CustomTimeRange(this, Moment.maxValue); 237 | } 238 | 239 | extension MomentBenefitsPlus on Moment { 240 | /// Returns [this] as [DateTime] (the super type) 241 | DateTime get forcedSuperType => this; 242 | 243 | Moment clone() { 244 | return Moment( 245 | forcedSuperType.clone(), 246 | localization: setLocalization, 247 | ); 248 | } 249 | 250 | Moment get date { 251 | return Moment( 252 | forcedSuperType.date, 253 | localization: setLocalization, 254 | ); 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /lib/src/extensions/weekday_finder.dart: -------------------------------------------------------------------------------- 1 | import 'package:moment_dart/src/moment.dart'; 2 | 3 | extension WeekdayFinder on DateTime { 4 | /// Returns new [DateTime] instance of nearest `n`th weekday in the future 5 | /// 6 | /// If `n`th day is today, will return `7 days in the future`. 7 | DateTime nextWeekday(int weekday) { 8 | assert(weekday > -1 && weekday < 8, 9 | "[moment_dart] Weekday must be in range `0<=n<=7`"); 10 | 11 | final int requiredDelta = (weekday - this.weekday) % 7; 12 | 13 | return this + Duration(days: requiredDelta == 0 ? 7 : requiredDelta); 14 | } 15 | 16 | /// Returns new [DateTime] instance of nearest Monday in the Future 17 | /// 18 | /// If [this] is Monday, will return `7 days in the future` 19 | DateTime nextMonday() => nextWeekday(DateTime.monday); 20 | 21 | /// Returns new [DateTime] instance of nearest Tuesday in the Future 22 | /// 23 | /// If [this] is Tuesday, will return `7 days in the future` 24 | DateTime nextTuesday() => nextWeekday(DateTime.tuesday); 25 | 26 | /// Returns new [DateTime] instance of nearest Wednesday in the Future 27 | /// 28 | /// If [this] is Wednesday, will return `7 days in the future` 29 | DateTime nextWednesday() => nextWeekday(DateTime.wednesday); 30 | 31 | /// Returns new [DateTime] instance of nearest Thursday in the Future 32 | /// 33 | /// If [this] is Thursday, will return `7 days in the future` 34 | DateTime nextThursday() => nextWeekday(DateTime.thursday); 35 | 36 | /// Returns new [DateTime] instance of nearest Friday in the Future 37 | /// 38 | /// If [this] is Friday, will return `7 days in the future` 39 | DateTime nextFriday() => nextWeekday(DateTime.friday); 40 | 41 | /// Returns new [DateTime] instance of nearest Saturday in the Future 42 | /// 43 | /// If [this] is Saturday, will return `7 days in the future` 44 | DateTime nextSaturday() => nextWeekday(DateTime.saturday); 45 | 46 | /// Returns new [DateTime] instance of nearest Sunday in the Future 47 | /// 48 | /// If [this] is Sunday, will return `7 days in the future` 49 | DateTime nextSunday() => nextWeekday(DateTime.sunday); 50 | 51 | /// Returns new [DateTime] instance of last `n`th weekday 52 | /// 53 | /// If today is the `n`th day, will return `7 days in the past` 54 | DateTime lastWeekday(int weekday) { 55 | assert(weekday > -1 && weekday < 8, 56 | "[Moment Dart] Weekday must be in range `0<=n<=7`"); 57 | 58 | final int requiredDelta = (this.weekday - weekday) % 7; 59 | 60 | return this - Duration(days: requiredDelta == 0 ? 7 : requiredDelta); 61 | } 62 | 63 | /// Returns new [DateTime] instance of nearest Monday in the past 64 | /// 65 | /// If [this] is Monday, will return `7 days in the past` 66 | DateTime lastMonday() => lastWeekday(DateTime.monday); 67 | 68 | /// Returns new [DateTime] instance of nearest Tuesday in the past 69 | /// 70 | /// If [this] is Tuesday, will return `7 days in the past` 71 | DateTime lastTuesday() => lastWeekday(DateTime.tuesday); 72 | 73 | /// Returns new [DateTime] instance of nearest Wednesday in the past 74 | /// 75 | /// If [this] is Wednesday, will return `7 days in the past` 76 | DateTime lastWednesday() => lastWeekday(DateTime.wednesday); 77 | 78 | /// Returns new [DateTime] instance of nearest Thursday in the past 79 | /// 80 | /// If [this] is Thursday, will return `7 days in the past` 81 | DateTime lastThursday() => lastWeekday(DateTime.thursday); 82 | 83 | /// Returns new [DateTime] instance of nearest Friday in the past 84 | /// 85 | /// If [this] is Friday, will return `7 days in the past` 86 | DateTime lastFriday() => lastWeekday(DateTime.friday); 87 | 88 | /// Returns new [DateTime] instance of nearest Saturday in the past 89 | /// 90 | /// If [this] is Saturday, will return `7 days in the past` 91 | DateTime lastSaturday() => lastWeekday(DateTime.saturday); 92 | 93 | /// Returns new [DateTime] instance of nearest Sunday in the past 94 | /// 95 | /// If [this] is Sunday, will return `7 days in the past` 96 | DateTime lastSunday() => lastWeekday(DateTime.sunday); 97 | } 98 | 99 | extension WeekdayFinderMoment on Moment { 100 | /// Returns new [Moment] instance of nearest `n`th weekday in the future 101 | /// 102 | /// If `n`th day is today, will return `7 days in the future`. 103 | Moment nextWeekday(int weekday) => forcedSuperType 104 | .nextWeekday(weekday) 105 | .toMoment(localization: setLocalization); 106 | 107 | /// Returns new [Moment] instance of nearest Monday in the Future 108 | /// 109 | /// If [this] is Monday, will return `7 days in the future` 110 | Moment nextMonday() => nextWeekday(DateTime.monday); 111 | 112 | /// Returns new [Moment] instance of nearest Tuesday in the Future 113 | /// 114 | /// If [this] is Tuesday, will return `7 days in the future` 115 | Moment nextTuesday() => nextWeekday(DateTime.tuesday); 116 | 117 | /// Returns new [Moment] instance of nearest Wednesday in the Future 118 | /// 119 | /// If [this] is Wednesday, will return `7 days in the future` 120 | Moment nextWednesday() => nextWeekday(DateTime.wednesday); 121 | 122 | /// Returns new [Moment] instance of nearest Thursday in the Future 123 | /// 124 | /// If [this] is Thursday, will return `7 days in the future` 125 | Moment nextThursday() => nextWeekday(DateTime.thursday); 126 | 127 | /// Returns new [Moment] instance of nearest Friday in the Future 128 | /// 129 | /// If [this] is Friday, will return `7 days in the future` 130 | Moment nextFriday() => nextWeekday(DateTime.friday); 131 | 132 | /// Returns new [Moment] instance of nearest Saturday in the Future 133 | /// 134 | /// If [this] is Saturday, will return `7 days in the future` 135 | Moment nextSaturday() => nextWeekday(DateTime.saturday); 136 | 137 | /// Returns new [Moment] instance of nearest Sunday in the Future 138 | /// 139 | /// If [this] is Sunday, will return `7 days in the future` 140 | Moment nextSunday() => nextWeekday(DateTime.sunday); 141 | 142 | /// Returns new [Moment] instance of last `n`th weekday 143 | /// 144 | /// If today is the `n`th day, will return `7 days in the past` 145 | Moment lastWeekday(int weekday) => forcedSuperType 146 | .lastWeekday(weekday) 147 | .toMoment(localization: setLocalization); 148 | 149 | /// Returns new [Moment] instance of nearest Monday in the past 150 | /// 151 | /// If [this] is Monday, will return `7 days in the past` 152 | Moment lastMonday() => lastWeekday(DateTime.monday); 153 | 154 | /// Returns new [Moment] instance of nearest Tuesday in the past 155 | /// 156 | /// If [this] is Tuesday, will return `7 days in the past` 157 | Moment lastTuesday() => lastWeekday(DateTime.tuesday); 158 | 159 | /// Returns new [Moment] instance of nearest Wednesday in the past 160 | /// 161 | /// If [this] is Wednesday, will return `7 days in the past` 162 | Moment lastWednesday() => lastWeekday(DateTime.wednesday); 163 | 164 | /// Returns new [Moment] instance of nearest Thursday in the past 165 | /// 166 | /// If [this] is Thursday, will return `7 days in the past` 167 | Moment lastThursday() => lastWeekday(DateTime.thursday); 168 | 169 | /// Returns new [Moment] instance of nearest Friday in the past 170 | /// 171 | /// If [this] is Friday, will return `7 days in the past` 172 | Moment lastFriday() => lastWeekday(DateTime.friday); 173 | 174 | /// Returns new [Moment] instance of nearest Saturday in the past 175 | /// 176 | /// If [this] is Saturday, will return `7 days in the past` 177 | Moment lastSaturday() => lastWeekday(DateTime.saturday); 178 | 179 | /// Returns new [Moment] instance of nearest Sunday in the past 180 | /// 181 | /// If [this] is Sunday, will return `7 days in the past` 182 | Moment lastSunday() => lastWeekday(DateTime.sunday); 183 | } 184 | -------------------------------------------------------------------------------- /test/extensions/relative_finder_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:moment_dart/moment_dart.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | void main() { 5 | // Sunday as the start of the week 6 | Moment.setGlobalLocalization(LocalizationEnUs()); 7 | 8 | group('YearFinder', () { 9 | test('startOfNextYear', () { 10 | final date = DateTime(2022); 11 | final nextYear = date.startOfNextYear(); 12 | expect(nextYear.year, 2023); 13 | expect(nextYear.month, 1); 14 | expect(nextYear.day, 1); 15 | }); 16 | 17 | test('startOfLastYear', () { 18 | final date = DateTime(2022); 19 | final lastYear = date.startOfLastYear(); 20 | expect(lastYear.year, 2021); 21 | expect(lastYear.month, 1); 22 | expect(lastYear.day, 1); 23 | }); 24 | 25 | test('endOfNextYear', () { 26 | final date = DateTime(2022); 27 | final endNextYear = date.endOfNextYear(); 28 | expect(endNextYear.year, 2023); 29 | expect(endNextYear.month, 12); 30 | expect(endNextYear.day, 31); 31 | }); 32 | 33 | test('endOfLastYear', () { 34 | final date = DateTime(2022); 35 | final endLastYear = date.endOfLastYear(); 36 | expect(endLastYear.year, 2021); 37 | expect(endLastYear.month, 12); 38 | expect(endLastYear.day, 31); 39 | }); 40 | test('endOfNextYear', () { 41 | final date = DateTime(2022); 42 | final endLastYear = date.endOfNextYear(); 43 | expect(endLastYear.year, 2023); 44 | expect(endLastYear.month, 12); 45 | expect(endLastYear.day, 31); 46 | }); 47 | }); 48 | 49 | group('MonthFinder', () { 50 | test('startOfNextMonth', () { 51 | final date = DateTime(2022, 5); 52 | final nextMonth = date.startOfNextMonth(); 53 | expect(nextMonth.year, 2022); 54 | expect(nextMonth.month, 6); 55 | expect(nextMonth.day, 1); 56 | }); 57 | test('startOfLastMonth', () { 58 | final date = DateTime(2022, 5); 59 | final lastMonth = date.startOfLastMonth(); 60 | expect(lastMonth.year, 2022); 61 | expect(lastMonth.month, 4); 62 | expect(lastMonth.day, 1); 63 | }); 64 | test('endOfNextMonth', () { 65 | final date = DateTime(2022, 5); 66 | final nextMonth = date.endOfNextMonth(); 67 | expect(nextMonth.year, 2022); 68 | expect(nextMonth.month, 6); 69 | expect(nextMonth.day, 30); 70 | }); 71 | test('endOfLastMonth', () { 72 | final date = DateTime(2022, 5); 73 | final lastMonth = date.endOfLastMonth(); 74 | expect(lastMonth.year, 2022); 75 | expect(lastMonth.month, 4); 76 | expect(lastMonth.day, 30); 77 | }); 78 | }); 79 | group('LocalWeekFinder', () { 80 | test('startOfNextWeek', () { 81 | final date = DateTime(2022, 5, 5); 82 | final nextWeek = date.startOfNextLocalWeek(); 83 | expect(nextWeek.year, 2022); 84 | expect(nextWeek.month, 5); 85 | expect(nextWeek.day, 8); 86 | }); 87 | test('startOfLastWeek', () { 88 | final date = DateTime(2022, 5, 5); 89 | final lastWeek = date.startOfLastLocalWeek(); 90 | expect(lastWeek.year, 2022); 91 | expect(lastWeek.month, 4); 92 | expect(lastWeek.day, 24); 93 | }); 94 | test('endOfNextWeek', () { 95 | final date = DateTime(2022, 5, 5); 96 | final nextWeek = date.endOfNextLocalWeek(); 97 | expect(nextWeek.year, 2022); 98 | expect(nextWeek.month, 5); 99 | expect(nextWeek.day, 14); 100 | }); 101 | test('endOfLastWeek', () { 102 | final date = DateTime(2022, 5, 5); 103 | final lastWeek = date.endOfLastLocalWeek(); 104 | expect(lastWeek.year, 2022); 105 | expect(lastWeek.month, 4); 106 | expect(lastWeek.day, 30); 107 | }); 108 | }); 109 | group("IsoWeekFinder", () { 110 | test('startOfNextIsoWeek', () { 111 | final date = DateTime(2022, 5, 5); 112 | final nextWeek = date.startOfNextIsoWeek(); 113 | expect(nextWeek.year, 2022); 114 | expect(nextWeek.month, 5); 115 | expect(nextWeek.day, 9); 116 | }); 117 | test('startOfLastIsoWeek', () { 118 | final date = DateTime(2022, 5, 5); 119 | final lastWeek = date.startOfLastIsoWeek(); 120 | expect(lastWeek.year, 2022); 121 | expect(lastWeek.month, 4); 122 | expect(lastWeek.day, 25); 123 | }); 124 | test('endOfNextIsoWeek', () { 125 | final date = DateTime(2022, 5, 5); 126 | final nextWeek = date.endOfNextIsoWeek(); 127 | expect(nextWeek.year, 2022); 128 | expect(nextWeek.month, 5); 129 | expect(nextWeek.day, 15); 130 | }); 131 | test('endOfLastIsoWeek', () { 132 | final date = DateTime(2022, 5, 5); 133 | final lastWeek = date.endOfLastIsoWeek(); 134 | expect(lastWeek.year, 2022); 135 | expect(lastWeek.month, 5); 136 | expect(lastWeek.day, 1); 137 | }); 138 | }); 139 | group("DayFinder", () { 140 | test('startOfNextDay', () { 141 | final date = DateTime(2022, 5, 5); 142 | final nextDay = date.startOfNextDay(); 143 | expect(nextDay.year, 2022); 144 | expect(nextDay.month, 5); 145 | expect(nextDay.day, 6); 146 | }); 147 | test('startOfLastDay', () { 148 | final date = DateTime(2022, 5, 5); 149 | final lastDay = date.startOfLastDay(); 150 | expect(lastDay.year, 2022); 151 | expect(lastDay.month, 5); 152 | expect(lastDay.day, 4); 153 | }); 154 | test('endOfNextDay', () { 155 | final date = DateTime(2022, 5, 5); 156 | final nextDay = date.endOfNextDay(); 157 | expect(nextDay.year, 2022); 158 | expect(nextDay.month, 5); 159 | expect(nextDay.day, 6); 160 | }); 161 | test('endOfLastDay', () { 162 | final date = DateTime(2022, 5, 5); 163 | final lastDay = date.endOfLastDay(); 164 | expect(lastDay.year, 2022); 165 | expect(lastDay.month, 5); 166 | expect(lastDay.day, 4); 167 | }); 168 | }); 169 | group("HourFinder", () { 170 | test('startOfNextHour', () { 171 | final date = DateTime(2022, 5, 5, 5); 172 | final nextHour = date.startOfNextHour(); 173 | expect(nextHour.year, 2022); 174 | expect(nextHour.month, 5); 175 | expect(nextHour.day, 5); 176 | expect(nextHour.hour, 6); 177 | expect(nextHour.minute, 0); 178 | expect(nextHour.second, 0); 179 | expect(nextHour.millisecond, 0); 180 | expect(nextHour.microsecond, 0); 181 | }); 182 | test('startOfLastHour', () { 183 | final date = DateTime(2022, 5, 5, 5); 184 | final lastHour = date.startOfLastHour(); 185 | expect(lastHour.year, 2022); 186 | expect(lastHour.month, 5); 187 | expect(lastHour.day, 5); 188 | expect(lastHour.hour, 4); 189 | expect(lastHour.minute, 0); 190 | expect(lastHour.second, 0); 191 | expect(lastHour.millisecond, 0); 192 | expect(lastHour.microsecond, 0); 193 | }); 194 | test('endOfNextHour', () { 195 | final date = DateTime(2022, 5, 5, 5); 196 | final nextHour = date.endOfNextHour(); 197 | expect(nextHour.year, 2022); 198 | expect(nextHour.month, 5); 199 | expect(nextHour.day, 5); 200 | expect(nextHour.hour, 6); 201 | expect(nextHour.minute, 59); 202 | expect(nextHour.second, 59); 203 | expect(nextHour.millisecond, 999); 204 | expect(nextHour.microsecond, 999); 205 | }); 206 | test('endOfLastHour', () { 207 | final date = DateTime(2022, 5, 5, 5); 208 | final lastHour = date.endOfLastHour(); 209 | expect(lastHour.year, 2022); 210 | expect(lastHour.month, 5); 211 | expect(lastHour.day, 5); 212 | expect(lastHour.hour, 4); 213 | expect(lastHour.minute, 59); 214 | expect(lastHour.second, 59); 215 | expect(lastHour.millisecond, 999); 216 | expect(lastHour.microsecond, 999); 217 | }); 218 | }); 219 | } 220 | -------------------------------------------------------------------------------- /lib/src/localizations/ja_JP.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: file_names 2 | // Author: Batmend Ganbaatar (https://github.com/sadespresso) 3 | 4 | import 'package:moment_dart/moment_dart.dart'; 5 | import 'package:moment_dart/src/localizations/mixins/complex_calendar.dart'; 6 | import 'package:moment_dart/src/localizations/mixins/simple_duration.dart'; 7 | import 'package:moment_dart/src/localizations/mixins/simple_range.dart'; 8 | import 'package:moment_dart/src/localizations/mixins/simple_relative.dart'; 9 | import 'package:moment_dart/src/localizations/mixins/simple_units.dart'; 10 | import 'package:moment_dart/src/types.dart'; 11 | 12 | /// Language: Japanese 13 | /// Country: Japan 14 | class LocalizationJaJp extends MomentLocalization 15 | with 16 | SimpleUnits, 17 | SimpleRelative, 18 | SimpleDuration, 19 | ComplexCalendar, 20 | SimpleRange { 21 | static LocalizationJaJp? _instance; 22 | 23 | LocalizationJaJp._internal() : super(); 24 | 25 | factory LocalizationJaJp() { 26 | _instance ??= LocalizationJaJp._internal(); 27 | 28 | return _instance!; 29 | } 30 | 31 | @override 32 | String get endonym => "日本語"; 33 | 34 | @override 35 | String get languageCode => "ja"; 36 | 37 | @override 38 | String get countryCode => "JP"; 39 | 40 | @override 41 | String get languageNameInEnglish => "Japanese"; 42 | 43 | @override 44 | String relativePast(String unit) => "$unit前"; 45 | @override 46 | String relativeFuture(String unit) => "$unit後"; 47 | 48 | // 평日 이름 49 | @override 50 | Map get weekdayName => { 51 | DateTime.monday: "月曜日", 52 | DateTime.tuesday: "火曜日", 53 | DateTime.wednesday: "水曜日", 54 | DateTime.thursday: "木曜日", 55 | DateTime.friday: "金曜日", 56 | DateTime.saturday: "土曜日", 57 | DateTime.sunday: "日曜日", 58 | }; 59 | 60 | Map get weekdayNameShort => { 61 | DateTime.monday: "月", 62 | DateTime.tuesday: "火", 63 | DateTime.wednesday: "水", 64 | DateTime.thursday: "木", 65 | DateTime.friday: "金", 66 | DateTime.saturday: "土", 67 | DateTime.sunday: "日", 68 | }; 69 | 70 | String monthName(int i) => "$i月"; 71 | 72 | @override 73 | Map get durationDelimiter => { 74 | Abbreviation.none: "", 75 | Abbreviation.semi: "", 76 | Abbreviation.full: "", 77 | }; 78 | 79 | @override 80 | FormatSetOptional overrideFormatters() { 81 | return { 82 | // Localization aware formats 83 | FormatterToken.L: (dateTime) => reformat(dateTime, "YYYY/MM/DD"), 84 | FormatterToken.l: (dateTime) => reformat(dateTime, "YYYY/MM/DD"), 85 | FormatterToken.LL: (dateTime) => reformat(dateTime, "YYYY年M月D日"), 86 | FormatterToken.ll: (dateTime) => reformat(dateTime, "YYYY年M月D日"), 87 | FormatterToken.LLL: (dateTime) => reformat(dateTime, "YYYY年M月D日 HH:mm"), 88 | FormatterToken.lll: (dateTime) => reformat(dateTime, "YYYY年M月D日 HH:mm"), 89 | FormatterToken.LLLL: (dateTime) => 90 | reformat(dateTime, "YYYY年M月D日 dddd HH:mm"), 91 | FormatterToken.llll: (dateTime) => 92 | reformat(dateTime, "YYYY年M月D日(ddd) HH:mm"), 93 | FormatterToken.LT: (dateTime) => reformat(dateTime, "HH:mm"), 94 | FormatterToken.LTS: (dateTime) => reformat(dateTime, "HH:mm:ss"), 95 | // Missings 96 | FormatterToken.A: (dateTime) => dateTime.hour < 12 ? "午前" : "午後", 97 | FormatterToken.a: (dateTime) => dateTime.hour < 12 ? "午前" : "午後", 98 | // Custom Ordinals 99 | FormatterToken.Mo: (dateTime) => "${dateTime.month}月", 100 | FormatterToken.Qo: (dateTime) => "${dateTime.quarter}분기", 101 | FormatterToken.Do: (dateTime) => "${dateTime.day}日", 102 | FormatterToken.DDDo: (dateTime) => "${dateTime.dayOfYear}日", 103 | FormatterToken.d_o: (dateTime) => "${dateTime.weekday}日", 104 | FormatterToken.wo: (dateTime) => "${dateTime.week}주째", 105 | // Weekday 106 | FormatterToken.dd: (dateTime) => weekdayNameShort[dateTime.weekday]!, 107 | // Controversial :)) 108 | FormatterToken.ddd: (dateTime) => weekdayNameShort[dateTime.weekday]!, 109 | FormatterToken.dddd: (dateTime) => weekdayName[dateTime.weekday]!, 110 | // Month names 111 | FormatterToken.MMM: (dateTime) => monthName(dateTime.month), 112 | FormatterToken.MMMM: (dateTime) => monthName(dateTime.month), 113 | }; 114 | } 115 | 116 | @override 117 | int get weekStart => DateTime.sunday; 118 | 119 | // Korean hangul is compact enough 120 | @override 121 | Map get units => { 122 | DurationInterval.lessThanASecond: UnitString.single("数秒"), 123 | DurationInterval.aSecond: UnitString.single("1秒"), 124 | DurationInterval.seconds: UnitString.single("$srDelta秒"), 125 | DurationInterval.aMinute: UnitString.single("1分"), 126 | DurationInterval.minutes: UnitString.single("$srDelta分"), 127 | DurationInterval.anHour: UnitString.single("1時間"), 128 | DurationInterval.hours: UnitString.single("$srDelta時間"), 129 | DurationInterval.aDay: UnitString.single("1日"), 130 | DurationInterval.days: UnitString.single("$srDelta日"), 131 | DurationInterval.aWeek: UnitString.single("1週間"), 132 | DurationInterval.weeks: UnitString.single("$srDelta週間"), 133 | DurationInterval.aMonth: UnitString.single("1ヶ月"), 134 | DurationInterval.months: UnitString.single("$srDeltaヶ月"), 135 | DurationInterval.aYear: UnitString.single("1年"), 136 | DurationInterval.years: UnitString.single("$srDelta年"), 137 | }; 138 | 139 | @override 140 | ComplexCalenderLocalizationData get complexCalendarData => 141 | ComplexCalenderLocalizationData( 142 | relativeDayNames: { 143 | -1: "昨日", 144 | 0: "今日", 145 | 1: "明日", 146 | }, 147 | keywords: ComplexCalenderLocalizationKeywords( 148 | lastWeekday: (dateTime, {reference}) { 149 | final String dddd = reformat(dateTime, "dddd"); 150 | final bool sameWeek = dateTime.isSameLocalWeekAs( 151 | reference ?? 152 | DateTimeConstructors.nowWithTimezone(dateTime.isUtc), 153 | weekStart); 154 | 155 | if (!sameWeek) { 156 | return "先週$dddd"; 157 | } 158 | 159 | return dddd; 160 | }, 161 | nextWeekday: (dateTime, {reference}) { 162 | final String dddd = reformat(dateTime, "dddd"); 163 | final bool sameWeek = dateTime.isSameLocalWeekAs( 164 | reference ?? 165 | DateTimeConstructors.nowWithTimezone(dateTime.isUtc), 166 | weekStart); 167 | 168 | if (!sameWeek) { 169 | return "来週$dddd"; 170 | } 171 | 172 | return dddd; 173 | }, 174 | ), 175 | ); 176 | 177 | @override 178 | SimpleRangeData get simpleRangeData => SimpleRangeData( 179 | thisWeek: "今週", 180 | year: (range, {DateTime? anchor, bool useRelative = true}) { 181 | anchor ??= Moment.now(); 182 | 183 | if (useRelative && range.year == anchor.year) { 184 | return "今年"; 185 | } 186 | 187 | return "${range.year}年"; 188 | }, 189 | month: (range, {DateTime? anchor, bool useRelative = true}) { 190 | anchor ??= Moment.now(); 191 | 192 | if (useRelative && anchor.year == range.year) { 193 | if (anchor.month == range.month) { 194 | return "今月"; 195 | } 196 | 197 | return monthName(range.month); 198 | } 199 | return "${range.year}年${monthName(range.month)}"; 200 | }, 201 | allAfter: (formattedDate) => "$formattedDate以降", 202 | allBefore: (formattedDate) => "$formattedDate以前", 203 | customRangeAllTime: '全ての時間', 204 | ); 205 | } 206 | -------------------------------------------------------------------------------- /lib/src/localizations/en_US.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: file_names 2 | // Author: Batmend Ganbaatar (https://github.com/sadespresso) 3 | 4 | import 'package:moment_dart/moment_dart.dart'; 5 | import 'package:moment_dart/src/localizations/mixins/english_like_ordinal.dart'; 6 | import 'package:moment_dart/src/localizations/mixins/month_names.dart'; 7 | import 'package:moment_dart/src/localizations/mixins/simple_duration.dart'; 8 | import 'package:moment_dart/src/localizations/mixins/simple_range.dart'; 9 | import 'package:moment_dart/src/localizations/mixins/simple_relative.dart'; 10 | import 'package:moment_dart/src/localizations/mixins/simple_units.dart'; 11 | import 'package:moment_dart/src/types.dart'; 12 | 13 | /// Language: English (US) 14 | /// Country: United States of America 15 | class LocalizationEnUs extends MomentLocalization 16 | with 17 | MonthNames, 18 | EnglishLikeOrdinal, 19 | SimpleUnits, 20 | SimpleRelative, 21 | SimpleDuration, 22 | SimpleRange { 23 | static LocalizationEnUs? _instance; 24 | 25 | LocalizationEnUs._internal() : super(); 26 | 27 | factory LocalizationEnUs() { 28 | _instance ??= LocalizationEnUs._internal(); 29 | 30 | return _instance!; 31 | } 32 | 33 | @override 34 | String get endonym => "English"; 35 | 36 | @override 37 | String get languageCode => "en"; 38 | 39 | @override 40 | String get countryCode => "US"; 41 | 42 | @override 43 | String get languageNameInEnglish => "English (United States)"; 44 | 45 | @override 46 | String relativePast(String unit) => "$unit ago"; 47 | @override 48 | String relativeFuture(String unit) => "in $unit"; 49 | 50 | @override 51 | Map get monthNames => { 52 | 1: "January", 53 | 2: "February", 54 | 3: "March", 55 | 4: "April", 56 | 5: "May", 57 | 6: "June", 58 | 7: "July", 59 | 8: "August", 60 | 9: "September", 61 | 10: "October", 62 | 11: "November", 63 | 12: "December", 64 | }; 65 | 66 | @override 67 | Map get monthNamesShort => monthNames.map( 68 | (key, value) => MapEntry( 69 | key, 70 | value.substring(0, 3), 71 | ), 72 | ); 73 | 74 | @override 75 | Map get weekdayName => { 76 | DateTime.monday: "Monday", 77 | DateTime.tuesday: "Tuesday", 78 | DateTime.wednesday: "Wednesday", 79 | DateTime.thursday: "Thursday", 80 | DateTime.friday: "Friday", 81 | DateTime.saturday: "Saturday", 82 | DateTime.sunday: "Sunday", 83 | }; 84 | 85 | @override 86 | FormatSetOptional overrideFormatters() { 87 | return { 88 | // From [EnglishLikeOrdinal] mixin 89 | ...formattersWithOrdinal, 90 | // From [MonthNames] mixin 91 | ...formattersForMonthNames, 92 | // Localization aware formats 93 | FormatterToken.L: (dateTime) => reformat(dateTime, "MM/DD/YYYY"), 94 | FormatterToken.l: (dateTime) => reformat(dateTime, "M/D/YYYY"), 95 | FormatterToken.LL: (dateTime) => reformat(dateTime, "MMMM D YYYY"), 96 | FormatterToken.ll: (dateTime) => reformat(dateTime, "MMM D YYYY"), 97 | FormatterToken.LLL: (dateTime) => 98 | reformat(dateTime, "MMMM D YYYY hh:mm A"), 99 | FormatterToken.lll: (dateTime) => reformat(dateTime, "MMM D YYYY h:mm A"), 100 | FormatterToken.LLLL: (dateTime) => 101 | reformat(dateTime, "dddd, MMMM D YYYY hh:mm A"), 102 | FormatterToken.llll: (dateTime) => 103 | reformat(dateTime, "ddd, MMM D YYYY h:mm A"), 104 | FormatterToken.LT: (dateTime) => reformat(dateTime, "h:mm A"), 105 | FormatterToken.LTS: (dateTime) => reformat(dateTime, "h:mm:ss A"), 106 | }; 107 | } 108 | 109 | @override 110 | List get ordinalSuffixes => ["th", "st", "nd", "rd"]; 111 | 112 | @override 113 | CalenderLocalizationData get calendarData => calenderLocalizationDataEnUs; 114 | 115 | static String last(String weekday) => "Last $weekday"; 116 | static String at(String date, String time) => "$date at $time"; 117 | 118 | static const CalenderLocalizationData calenderLocalizationDataEnUs = 119 | CalenderLocalizationData( 120 | relativeDayNames: { 121 | -1: "Yesterday", 122 | 0: "Today", 123 | 1: "Tomorrow", 124 | }, 125 | keywords: CalenderLocalizationKeywords( 126 | at: at, 127 | lastWeekday: last, 128 | ), 129 | ); 130 | 131 | @override 132 | int get weekStart => DateTime.sunday; 133 | 134 | @override 135 | Map get units => { 136 | DurationInterval.lessThanASecond: UnitString.withForm( 137 | "a few seconds", 138 | "a few sec", 139 | "a sec", 140 | ), 141 | DurationInterval.aSecond: UnitString.withForm( 142 | "a second", 143 | "1 sec", 144 | "1s", 145 | ), 146 | DurationInterval.seconds: UnitString.withForm( 147 | "$srDelta seconds", 148 | "$srDelta sec", 149 | "${srDelta}s", 150 | ), 151 | DurationInterval.aMinute: UnitString.withForm( 152 | "a minute", 153 | "1 min", 154 | "1m", 155 | ), 156 | DurationInterval.minutes: UnitString.withForm( 157 | "$srDelta minutes", 158 | "$srDelta min", 159 | "${srDelta}m", 160 | ), 161 | DurationInterval.anHour: UnitString.withForm( 162 | "an hour", 163 | "1 hr", 164 | "1h", 165 | ), 166 | DurationInterval.hours: UnitString.withForm( 167 | "$srDelta hours", 168 | "$srDelta hr", 169 | "${srDelta}h", 170 | ), 171 | DurationInterval.aDay: UnitString.withForm( 172 | "a day", 173 | "1 day", 174 | "1d", 175 | ), 176 | DurationInterval.days: UnitString.withForm( 177 | "$srDelta days", 178 | "$srDelta day", 179 | "${srDelta}d", 180 | ), 181 | DurationInterval.aWeek: UnitString.withForm( 182 | "a week", 183 | "1 wk", 184 | "1w", 185 | ), 186 | DurationInterval.weeks: UnitString.withForm( 187 | "$srDelta weeks", 188 | "$srDelta wk", 189 | "${srDelta}w", 190 | ), 191 | DurationInterval.aMonth: UnitString.withForm( 192 | "a month", 193 | "1 mo", 194 | "1mo", 195 | ), 196 | DurationInterval.months: UnitString.withForm( 197 | "$srDelta months", 198 | "$srDelta mo", 199 | "${srDelta}mo", 200 | ), 201 | DurationInterval.aYear: UnitString.withForm( 202 | "a year", 203 | "1 yr", 204 | "1y", 205 | ), 206 | DurationInterval.years: UnitString.withForm( 207 | "$srDelta years", 208 | "$srDelta yr", 209 | "${srDelta}y", 210 | ), 211 | }; 212 | 213 | @override 214 | SimpleRangeData get simpleRangeData => SimpleRangeData( 215 | thisWeek: "This week", 216 | year: (range, {DateTime? anchor, bool useRelative = true}) { 217 | anchor ??= Moment.now(); 218 | 219 | if (useRelative && range.year == anchor.year) { 220 | return "This year"; 221 | } 222 | 223 | return "Year ${range.year}"; 224 | }, 225 | month: (range, {DateTime? anchor, bool useRelative = true}) { 226 | anchor ??= Moment.now(); 227 | 228 | if (useRelative && anchor.year == range.year) { 229 | if (anchor.month == range.month) { 230 | return "This month"; 231 | } 232 | 233 | return monthNames[range.month]!; 234 | } 235 | 236 | return "${monthNames[range.month]!} ${range.year}"; 237 | }, 238 | allAfter: (formattedDate) => "After $formattedDate", 239 | allBefore: (formattedDate) => "Before $formattedDate", 240 | customRangeAllTime: "All time", 241 | ); 242 | } 243 | -------------------------------------------------------------------------------- /lib/src/formatters/token.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: constant_identifier_names 2 | 3 | import 'package:moment_dart/src/localization.dart'; 4 | 5 | typedef FormatterFn = String Function( 6 | DateTime, FormatterToken, MomentLocalization); 7 | typedef FormatterTokenFn = String Function(DateTime); 8 | 9 | enum FormatterToken { 10 | /// [Month] 11 | /// 12 | /// 1 2 ... 11 12 13 | M, 14 | 15 | /// [Month] 16 | /// 17 | /// 1st 2nd ... 11th 12th 18 | /// 19 | /// **Not part of default formatters**. Therefore, must be implemented in the localization 20 | Mo, 21 | 22 | /// [Month] 23 | /// 24 | /// 01 02 ... 11 12 25 | MM, 26 | 27 | /// [Month] 28 | /// 29 | /// Jan Feb ... Nov Dec 30 | /// 31 | /// **Not part of default formatters**. Therefore, must be implemented in the localization 32 | MMM, 33 | 34 | /// [Month] 35 | /// 36 | /// January February ... November December 37 | /// 38 | /// **Not part of default formatters**. Therefore, must be implemented in the localization 39 | MMMM, 40 | 41 | /// [Quarter] 42 | /// 43 | /// 1 2 3 4 44 | Q, 45 | 46 | /// [Quarter] 47 | /// 48 | /// 1st 2nd 3rd 4th 49 | /// 50 | /// **Not part of default formatters**. Therefore, must be implemented in the localization 51 | Qo, 52 | 53 | /// [Day of Month] 54 | /// 55 | /// 1 2 ... 30 31 56 | D, 57 | 58 | /// [Day of Month] 59 | /// 60 | /// 1st 2nd ... 30th 31st 61 | /// 62 | /// **Not part of default formatters**. Therefore, must be implemented in the localization 63 | Do, 64 | 65 | /// [Day of Month] 66 | /// 67 | /// 01 02 ... 30 31 68 | DD, 69 | 70 | /// [Day of Year] 71 | /// 72 | /// 1 2 ... 364 365 73 | DDD, 74 | 75 | /// [Day of Year] 76 | /// 77 | /// 1st 2nd ... 364th 365th 78 | /// 79 | /// **Not part of default formatters**. Therefore, must be implemented in the localization 80 | DDDo, 81 | 82 | /// [Day of Year] 83 | /// 84 | /// 001 002 ... 364 365 85 | DDDD, 86 | 87 | /// [Day of Week] 88 | /// 89 | /// 1 2 ...6 7 90 | /// 91 | /// Moment.js uses `0-6`. However, we'll be using `1-7` to be in accordance with [DateTime] 92 | d, 93 | 94 | /// [Day of Week] 95 | /// 96 | /// 1st 2nd ... 6th 7th 97 | /// 98 | /// Moment.js uses `0-6`. However, we'll be using `1-7` to be in accordance with [DateTime] 99 | /// 100 | /// Please note that `do` is dart language keyword 101 | /// 102 | /// **Not part of default formatters**. Therefore, must be implemented in the localization 103 | d_o, 104 | 105 | /// [Day of Week] 106 | /// 107 | /// Mo Tu ... Sa Su 108 | /// 109 | /// **Not part of default formatters**. Therefore, must be implemented in the localization 110 | dd, 111 | 112 | /// [Day of Week] 113 | /// 114 | /// Mon Tue ... Sat Sun 115 | /// 116 | /// **Not part of default formatters**. Therefore, must be implemented in the localization 117 | ddd, 118 | 119 | /// [Day of Week] 120 | /// 121 | /// Monday ... Saturday Sunday 122 | /// 123 | /// **Not part of default formatters**. Therefore, must be implemented in the localization 124 | dddd, 125 | 126 | /// [Day of Week (ISO)] 127 | /// 128 | /// 1 2 ... 6 7 129 | /// 130 | /// Please note that [DateTime] uses 1-7 as weekday (in accordance with ISO), therefore, we'll be following the path. 131 | e, 132 | 133 | /// [Week of Year (ISO)] 134 | /// 135 | /// 1 2 ... 52 53 136 | w, 137 | 138 | /// [Week of Year (ISO)] 139 | /// 140 | /// 1st 2nd ... 52nd 53rd 141 | /// 142 | /// **Not part of default formatters**. Therefore, must be implemented in the localization 143 | wo, 144 | 145 | /// [Week of Year (ISO)] 146 | /// 147 | /// 01 02 ... 52 53 148 | ww, 149 | 150 | /// [Year] 151 | /// 152 | /// 1968 1969 70 71 ... 29 30 2031 2032 153 | /// 154 | /// If [year] doesn't belong in range (1970, 2030), displays full YYYY 155 | YY, 156 | 157 | /// [Year] 158 | /// 159 | /// 1970 1971 ... 2029 2030 160 | /// 161 | /// [DateTime] complies ISO 8601 standard, therefore Moment.js's YYYYYY, Y are redundant 162 | YYYY, 163 | 164 | /// [Era Year] 165 | /// 166 | /// 1 2 ... 2020 ... 167 | /// 168 | /// **Unimplemented by default** 169 | y, 170 | 171 | /// [Era] 172 | /// 173 | /// BC AD 174 | /// 175 | /// Note: Abbr era name 176 | NN, 177 | 178 | /// [Era] 179 | /// 180 | /// Before Christ, Anno Domini 181 | /// 182 | /// Note: Full era name 183 | NNNN, 184 | 185 | /// [Era] 186 | /// 187 | /// BC AD 188 | /// 189 | /// Note: Narrow era name 190 | NNNNN, 191 | 192 | /// [Week Year] 193 | /// 194 | /// 70 71 ... 29 30 195 | gg, 196 | 197 | /// [Week Year] 198 | /// 199 | /// 1970 1971 ... 2029 2030 200 | gggg, 201 | 202 | /// [AM/PM] 203 | /// 204 | /// AM PM (uppercase) 205 | /// 206 | /// **Not part of default formatters**. Therefore, must be implemented in the localization 207 | A, 208 | 209 | /// [AM/PM] 210 | /// 211 | /// am pm (lowercase) 212 | /// 213 | /// **Not part of default formatters**. Therefore, must be implemented in the localization 214 | a, 215 | 216 | /// [Hour] 217 | /// 218 | /// 0 1 ... 22 23 219 | H, 220 | 221 | /// [Hour] 222 | /// 223 | /// 00 01 ... 22 23 224 | HH, 225 | 226 | /// [Hour] 227 | /// 228 | /// 1 2 ... 11 12 229 | h, 230 | 231 | /// [Hour] 232 | /// 233 | /// 01 02 ... 11 12 234 | hh, 235 | 236 | /// [Hour] 237 | /// 238 | /// 1 2 ... 23 24 239 | k, 240 | 241 | /// [Hour] 242 | /// 243 | /// 01 02 ... 23 24 244 | kk, 245 | 246 | /// [Minute] 247 | /// 248 | /// 0 1 ... 58 59 249 | m, 250 | 251 | /// [Minute] 252 | /// 253 | /// 00 01 ... 58 59 254 | mm, 255 | 256 | /// [Second] 257 | /// 258 | /// 0 1 ... 58 59 259 | s, 260 | 261 | /// [Second] 262 | /// 263 | /// 00 01 ... 58 59 264 | ss, 265 | 266 | /// [Fractional Second] 267 | /// 268 | /// 0 1 ... 8 9 269 | S, 270 | 271 | /// [Fractional Second] 272 | /// 273 | /// 00 01 ... 98 99 274 | SS, 275 | 276 | /// [Fractional Second (Millisecond)] 277 | /// 278 | /// 000 001 ... 998 999 279 | SSS, 280 | 281 | /// [Fractional Second] 282 | /// 283 | /// 0000 0001 ... 9998,9999 284 | SSSS, 285 | 286 | /// [Fractional Second] 287 | /// 288 | /// 00000 00001 ... 99998,99999 289 | SSSSS, 290 | 291 | /// [Fractional Second (Microsecond)] 292 | /// 293 | /// 000000 000001 ... 999998,999999 294 | SSSSSS, 295 | 296 | /// [Timezone] 297 | /// 298 | /// -07:00 -06:00 ... +06:00 +07:00 299 | Z, 300 | 301 | /// [Timezone] 302 | /// 303 | /// -0700 -0600 ... +0600 +0700 304 | ZZ, 305 | 306 | /// [Timezone Name] 307 | /// 308 | /// Returns [DateTime.timeZoneName], result may not be consistent across platforms 309 | ZZZ, 310 | 311 | /// [Unix timestamp (seconds)] 312 | X, 313 | 314 | /// [Unix millisecond timestamp] 315 | x, 316 | 317 | /// [Unix microsecond timestamp] 318 | xx, 319 | 320 | /// Date (in local format) 321 | /// 322 | /// (no zero padding) 323 | /// 324 | /// 9/4/1986 325 | l, 326 | 327 | /// Date (in local format) 328 | /// 329 | /// (zero-padded) 330 | /// 331 | /// 09/04/1986 332 | L, 333 | 334 | /// Month name, day of month, year 335 | /// 336 | /// (no zero padding) 337 | /// 338 | /// Sep 4 1986 339 | ll, 340 | 341 | /// Month name, day of month, year 342 | /// 343 | /// (zero-padded) 344 | /// 345 | /// September 04 1986 346 | LL, 347 | 348 | /// Month name, day of month, year, time 349 | /// 350 | /// (no zero padding) 351 | /// 352 | /// Sep 4 1986 8:30 PM 353 | lll, 354 | 355 | /// Month name, day of month, year, time 356 | /// 357 | /// (zero-padded) 358 | /// 359 | /// September 04 1986 08:30 PM 360 | LLL, 361 | 362 | /// Day of week, month name, day of month, year, time 363 | /// 364 | /// (no zero padding) 365 | /// 366 | /// Thu, Sep 4 1986 8:30 PM 367 | llll, 368 | 369 | /// Day of week, month name, day of month, year, time 370 | /// 371 | /// (zero-padded) 372 | /// 373 | /// Thursday, September 04 1986 08:30 PM 374 | LLLL, 375 | 376 | /// Time (without seconds) 377 | /// 378 | /// 8:30 PM 379 | LT, 380 | 381 | /// Time (with seconds) 382 | /// 383 | /// 8:30:00 PM 384 | LTS, 385 | } 386 | --------------------------------------------------------------------------------