diff --git a/CHANGELOG.md b/CHANGELOG.md index ebeb585..63ecd16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 1.4.2 + +- Added the `isLeapYear` extension to the `DateTime` and `int` objects. +- Added the `FixedSizeList` class. See the readme and examples for details. + ## 1.4.1 - Added a `List` equality extension, `equals`. diff --git a/README.md b/README.md index 8f033fb..703e520 100644 --- a/README.md +++ b/README.md @@ -258,6 +258,18 @@ The following operations are now available on a `DateTime` object: final DateTime tomorrow = DateTime(0).tomorrow; // 2024-10-08 00:00:00.000 ``` +#### Leap Years + +- `isLeapYear`: returns a `bool` corresponding to whether a given year is a leap + year. This can also be used directly on an `int`. + + ```dart + print(DateTime(2024).isLeapYear); // true + print(DateTime(2025).isLeapYear); // false + print(2024.isLeapYear); // true + print(2025.isLeapYear); // false + ``` + ### JWT Parsing These extensions enhance the `String` class with JWT-specific functionalities, diff --git a/example/lib/main.dart b/example/lib/main.dart index 7c994ee..334196a 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -19,6 +19,11 @@ void main() { print("Yesterday: $yesterday"); print("Tomorrow: $tomorrow"); + print(DateTime(2024).isLeapYear); // true + print(DateTime(2025).isLeapYear); // false + print(2024.isLeapYear); // true + print(2025.isLeapYear); // false + // * Strings const String? nullString = null; const String emptyString = " "; @@ -70,12 +75,45 @@ void main() { // * Dynamic debug printing // Debug print the `Person` object before returning the name - final String alice = const Person(id: 0, name: "Alice").printValue().name; + final String alice = + const Person(id: 0, name: "Alice").printValue().name; print(alice); // Output: 'Alice' // Debug print the `Person` object with a label before returning the name - final String bob = const Person(id: 1, name: "Bob").printValue("Person").name; + final String bob = + const Person(id: 1, name: "Bob").printValue("Person").name; print(bob); // Output: 'Bob' + + // * Fixed-size lists + // Create a FixedSizeList with a capacity of 3 strings. + final recentLogs = FixedSizeList(3); + + // Output: Initial recentLogs: [] + print("Initial recentLogs: ${recentLogs.items}"); + + // Add some log messages. + recentLogs.add("Request received at 10:00 AM"); + print("recentLogs after first add: ${recentLogs.items}"); + // Output: recentLogs after first add: [Request received at 10:00 AM] + recentLogs.add("Processing request..."); + print("recentLogs after second add: ${recentLogs.items}"); + // Output: recentLogs after second add: [Request received at 10:00 AM, Processing request...] + recentLogs.add("Request completed at 10:05 AM"); + print("recentLogs after third add: ${recentLogs.items}"); + // Output: recentLogs after third add: [Request received at 10:00 AM, Processing request..., Request completed at 10:05 AM] + + // Add one more log message, which will cause the oldest one to be removed. + recentLogs.add("Sending response..."); + print("recentLogs after fourth add: ${recentLogs.items}"); + // Output: recentLogs after fourth add: [Processing request..., Request completed at 10:05 AM, Sending response...] + + // Try to modify the list through the 'items' getter (will throw an error). + try { + // This will cause an UnsupportedError because 'items' returns an unmodifiable list. + recentLogs.items.add("This will fail"); + } catch (e) { + print("Error trying to modify items: $e"); + } } class Person { diff --git a/lib/arcane_helper_utils.dart b/lib/arcane_helper_utils.dart index b71c2ba..057d8eb 100644 --- a/lib/arcane_helper_utils.dart +++ b/lib/arcane_helper_utils.dart @@ -1,5 +1,6 @@ library arcane_helper_utils; +export "package:arcane_helper_utils/src/classes/fixed_size_list.dart"; export "package:arcane_helper_utils/src/extensions/date_time.dart"; export "package:arcane_helper_utils/src/extensions/dynamic.dart"; export "package:arcane_helper_utils/src/extensions/jwt.dart"; diff --git a/lib/src/classes/fixed_size_list.dart b/lib/src/classes/fixed_size_list.dart new file mode 100644 index 0000000..b17f546 --- /dev/null +++ b/lib/src/classes/fixed_size_list.dart @@ -0,0 +1,29 @@ +/// Represents a `List` with a fixed capacity. +/// +/// When a new element is added and the list reaches its maximum capacity, +/// the oldest element in the list is automatically removed to make space. +class FixedSizeList { + final int _capacity; + final List _list; + + /// Creates a [FixedSizeList] with the specified [capacity]. + /// + /// The initial list will be empty. + FixedSizeList(this._capacity) : _list = []; + + /// Adds a new [element] to the list. + /// + /// If the list is already at its maximum [capacity], the oldest element + /// will be removed before the new [element] is added. + void add(String element) { + _list.add(element); + if (_list.length > _capacity) { + _list.removeAt(0); + } + } + + /// Returns an unmodifiable view of the current items in the list. + /// + /// This prevents external modification of the internal list. + List get items => List.unmodifiable(_list); +} diff --git a/lib/src/extensions/date_time.dart b/lib/src/extensions/date_time.dart index 34527a3..eb3c591 100644 --- a/lib/src/extensions/date_time.dart +++ b/lib/src/extensions/date_time.dart @@ -1,5 +1,3 @@ -import "package:week_number/iso.dart"; - /// An extension on `DateTime` to get the start and end of various time periods. extension StartAndEndOfPeriod on DateTime { /// Returns a `DateTime` object representing the start of the hour. @@ -141,3 +139,17 @@ extension YesterdayAndTomorrow on DateTime { DateTime get tomorrow => DateTime.now().add(const Duration(days: 1)).startOfDay; } + +extension IsLeapYear on DateTime { + /// Returns `true` if the year is a leap year, otherwise returns `false`. + bool get isLeapYear => year.isLeapYear; +} + +extension IsIntLeapYear on int { + /// Returns `true` if the given value would be considered a leap year, + /// otherwise returns `false`. + bool get isLeapYear => + !this.isNegative && + this > 0 && + ((this * 1073750999) & 3221352463) <= 126976; +} diff --git a/pubspec.yaml b/pubspec.yaml index 92d1c43..1bb34c7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: arcane_helper_utils description: Provides a variety of helpful utilities and extensions for Flutter and Dart. -version: 1.4.1 +version: 1.4.2 repository: https://github.com/hanskokx/arcane_helper_utils issue_tracker: https://github.com/hanskokx/arcane_helper_utils/issues diff --git a/test/classes/fixed_size_list_test.dart b/test/classes/fixed_size_list_test.dart new file mode 100644 index 0000000..f980d38 --- /dev/null +++ b/test/classes/fixed_size_list_test.dart @@ -0,0 +1,75 @@ +import "package:arcane_helper_utils/arcane_helper_utils.dart"; +import "package:test/test.dart"; + +void main() { + group("FixedSizeList", () { + test("initial list is empty", () { + final list = FixedSizeList(5); + expect(list.items, isEmpty); + }); + + test("adding elements below capacity", () { + final list = FixedSizeList(3); + list.add("one"); + expect(list.items, ["one"]); + list.add("two"); + expect(list.items, ["one", "two"]); + }); + + test("adding elements up to capacity", () { + final list = FixedSizeList(2); + list.add("a"); + list.add("b"); + expect(list.items, ["a", "b"]); + }); + + test("adding elements beyond capacity removes the oldest", () { + final list = FixedSizeList(2); + list.add("first"); + list.add("second"); + expect(list.items, ["first", "second"]); + list.add("third"); + expect(list.items, ["second", "third"]); + list.add("fourth"); + expect(list.items, ["third", "fourth"]); + }); + + test("capacity of zero results in an empty list that stays empty", () { + final list = FixedSizeList(0); + list.add("anything"); + expect(list.items, isEmpty); + list.add("something else"); + expect(list.items, isEmpty); + }); + + test("items getter returns an unmodifiable list", () { + final list = FixedSizeList(2); + list.add("alpha"); + list.add("beta"); + final items = list.items; + expect(() => items.add("gamma"), throwsUnsupportedError); + + // Ensure original list is not modified + expect(list.items, ["alpha", "beta"]); + }); + + test("adding multiple elements beyond capacity", () { + final list = FixedSizeList(1); + list.add("one"); + list.add("two"); + list.add("three"); + list.add("four"); + expect(list.items, ["four"]); + }); + + test("adding the same element multiple times", () { + final list = FixedSizeList(3); + list.add("same"); + list.add("same"); + list.add("same"); + expect(list.items, ["same", "same", "same"]); + list.add("different"); + expect(list.items, ["same", "same", "different"]); + }); + }); +} diff --git a/test/extensions/date_time_test.dart b/test/extensions/date_time_test.dart index 6a8354d..d7b928a 100644 --- a/test/extensions/date_time_test.dart +++ b/test/extensions/date_time_test.dart @@ -96,5 +96,16 @@ void main() { DateTime(now.year, now.month, now.day).add(const Duration(days: 1)); expect(DateTime.now().tomorrow, expected); }); + + test("leap year calculations work as expected", () { + expect(DateTime(0).isLeapYear, false); + expect(DateTime(2024).isLeapYear, true); + expect(DateTime(2025).isLeapYear, false); + + expect((-1).isLeapYear, false); + expect(0.isLeapYear, false); + expect(2024.isLeapYear, true); + expect(2025.isLeapYear, false); + }); }); }