From 92627b971ba6885b40b0dc99d311a8427d4c8584 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 21 Apr 2026 04:05:23 +0000 Subject: [PATCH 1/2] feat(stdlib): add datetime module bindings Adds F# bindings for the Python datetime module, covering: - timedelta (duration with days/seconds/microseconds, total_seconds()) - date (year/month/day, isoformat, strftime, weekday, fromisoformat, replace) - time (hour/minute/second/microsecond, isoformat, strftime, fromisoformat) - datetime (full date+time, now, utcnow, fromisoformat, strptime, combine, timestamp, astimezone) - timezone (fixed-offset tzinfo, utc singleton) Each class has a separate static factory type (timedeltaStatic, dateStatic, timeStatic, datetimeStatic, timezoneStatic) for constructors and class methods. Int arguments use int($1) Emit wrappers to handle Fable's Int32 boxing. Adds 30 tests covering all bound classes and their main operations. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Fable.Python.fsproj | 1 + src/stdlib/Datetime.fs | 293 ++++++++++++++++++++++++++++++++++ test/Fable.Python.Test.fsproj | 1 + test/TestDatetime.fs | 234 +++++++++++++++++++++++++++ 4 files changed, 529 insertions(+) create mode 100644 src/stdlib/Datetime.fs create mode 100644 test/TestDatetime.fs diff --git a/src/Fable.Python.fsproj b/src/Fable.Python.fsproj index 4920133..3dff498 100644 --- a/src/Fable.Python.fsproj +++ b/src/Fable.Python.fsproj @@ -25,6 +25,7 @@ + diff --git a/src/stdlib/Datetime.fs b/src/stdlib/Datetime.fs new file mode 100644 index 0000000..90dc928 --- /dev/null +++ b/src/stdlib/Datetime.fs @@ -0,0 +1,293 @@ +/// Type bindings for Python datetime module: https://docs.python.org/3/library/datetime.html +module Fable.Python.Datetime + +open Fable.Core + +// fsharplint:disable MemberNames,InterfaceNames + +// ============================================================================ +// timedelta +// ============================================================================ + +/// A duration expressing the difference between two date, time, or datetime instances. +/// See https://docs.python.org/3/library/datetime.html#datetime.timedelta +[] +type timedelta = + /// Number of full days (may be negative) + abstract days: int + /// Remaining seconds after full days have been removed; 0 <= seconds < 86400 + abstract seconds: int + /// Remaining microseconds; 0 <= microseconds < 1000000 + abstract microseconds: int + /// Return the total duration represented in fractional seconds + /// See https://docs.python.org/3/library/datetime.html#datetime.timedelta.total_seconds + abstract total_seconds: unit -> float + +/// Static factory for timedelta instances +[] +type timedeltaStatic = + /// Create a timedelta; all arguments default to 0, may be floats, and may be negative. + /// See https://docs.python.org/3/library/datetime.html#datetime.timedelta + [] + [] + abstract Create: + ?days: float * + ?seconds: float * + ?microseconds: float * + ?milliseconds: float * + ?minutes: float * + ?hours: float * + ?weeks: float -> + timedelta + +/// Factory for creating timedelta values +[] +let timedelta: timedeltaStatic = nativeOnly + +// ============================================================================ +// date +// ============================================================================ + +/// A naive date (year, month, day) with no time or timezone component. +/// See https://docs.python.org/3/library/datetime.html#datetime.date +[] +type date = + /// Year in range [MINYEAR, MAXYEAR] + abstract year: int + /// Month in range [1, 12] + abstract month: int + /// Day in range [1, number of days in the month and year] + abstract day: int + /// Return a string in ISO 8601 format, e.g. "2026-04-21" + /// See https://docs.python.org/3/library/datetime.html#datetime.date.isoformat + abstract isoformat: unit -> string + /// Return a string representing the date, formatted with format + /// See https://docs.python.org/3/library/datetime.html#datetime.date.strftime + abstract strftime: format: string -> string + /// Return the day of the week as an integer; Monday is 0 and Sunday is 6 + /// See https://docs.python.org/3/library/datetime.html#datetime.date.weekday + abstract weekday: unit -> int + /// Return the day of the week as an integer; Monday is 1 and Sunday is 7 + /// See https://docs.python.org/3/library/datetime.html#datetime.date.isoweekday + abstract isoweekday: unit -> int + /// Return the proleptic Gregorian ordinal of the date; January 1 of year 1 has ordinal 1 + abstract toordinal: unit -> int + /// Return a date with the given fields replaced + /// See https://docs.python.org/3/library/datetime.html#datetime.date.replace + [] + abstract replace: year: int * month: int * day: int -> date + +/// Static factory for date instances +[] +type dateStatic = + /// Create a date for the given year, month and day + /// See https://docs.python.org/3/library/datetime.html#datetime.date + [] + abstract Create: year: int * month: int * day: int -> date + /// Return the current local date + /// See https://docs.python.org/3/library/datetime.html#datetime.date.today + abstract today: unit -> date + /// Return the local date corresponding to a POSIX timestamp + /// See https://docs.python.org/3/library/datetime.html#datetime.date.fromtimestamp + abstract fromtimestamp: timestamp: float -> date + /// Return the date corresponding to the proleptic Gregorian ordinal + abstract fromordinal: ordinal: int -> date + /// Return a date from a string in any valid ISO 8601 format + /// See https://docs.python.org/3/library/datetime.html#datetime.date.fromisoformat + abstract fromisoformat: date_string: string -> date + /// The earliest representable date + abstract min: date + /// The latest representable date + abstract max: date + +/// Factory for creating date values +[] +let date: dateStatic = nativeOnly + +// ============================================================================ +// time +// ============================================================================ + +/// A naive or aware time of day (hour, minute, second, microsecond, tzinfo). +/// See https://docs.python.org/3/library/datetime.html#datetime.time +[] +type time = + /// Hour in range [0, 23] + abstract hour: int + /// Minute in range [0, 59] + abstract minute: int + /// Second in range [0, 59] + abstract second: int + /// Microsecond in range [0, 999999] + abstract microsecond: int + /// Fold value (0 or 1) for disambiguating wall-clock times that repeat during DST transitions + abstract fold: int + /// Return a string in ISO 8601 format, e.g. "14:30:00" + /// See https://docs.python.org/3/library/datetime.html#datetime.time.isoformat + abstract isoformat: unit -> string + /// Return a string representing the time, formatted with format + /// See https://docs.python.org/3/library/datetime.html#datetime.time.strftime + abstract strftime: format: string -> string + /// Return the UTC offset as a timedelta for aware times; None for naive times + abstract utcoffset: unit -> timedelta option + /// Return the timezone abbreviation string for aware times; None for naive times + abstract tzname: unit -> string option + +/// Static factory for time instances +[] +type timeStatic = + /// Create a time-of-day value; all arguments default to 0 + /// See https://docs.python.org/3/library/datetime.html#datetime.time + [] + [] + abstract Create: + ?hour: int * + ?minute: int * + ?second: int * + ?microsecond: int -> + time + /// Return a time from a string in ISO 8601 format + /// See https://docs.python.org/3/library/datetime.html#datetime.time.fromisoformat + abstract fromisoformat: time_string: string -> time + /// The earliest representable time, time(0, 0, 0, 0) + abstract min: time + /// The latest representable time, time(23, 59, 59, 999999) + abstract max: time + +/// Factory for creating time-of-day values +[] +let time: timeStatic = nativeOnly + +// ============================================================================ +// datetime +// ============================================================================ + +/// A naive or aware date and time (year, month, day, hour, minute, second, microsecond, tzinfo). +/// See https://docs.python.org/3/library/datetime.html#datetime.datetime +[] +type datetime = + /// Year in range [MINYEAR, MAXYEAR] + abstract year: int + /// Month in range [1, 12] + abstract month: int + /// Day in range [1, number of days in the month and year] + abstract day: int + /// Hour in range [0, 23] + abstract hour: int + /// Minute in range [0, 59] + abstract minute: int + /// Second in range [0, 59] + abstract second: int + /// Microsecond in range [0, 999999] + abstract microsecond: int + /// Fold value (0 or 1) for disambiguating wall-clock times that repeat during DST transitions + abstract fold: int + /// Return the date part as a date object + /// See https://docs.python.org/3/library/datetime.html#datetime.datetime.date + abstract date: unit -> date + /// Return the time part as a time object (tzinfo is not included) + /// See https://docs.python.org/3/library/datetime.html#datetime.datetime.time + abstract time: unit -> time + /// Return a string in ISO 8601 format, e.g. "2026-04-21T14:30:00" + /// See https://docs.python.org/3/library/datetime.html#datetime.datetime.isoformat + abstract isoformat: unit -> string + /// Return a string in ISO 8601 format with a custom separator between date and time + [] + abstract isoformat: sep: string -> string + /// Return a string representing the datetime, formatted with format + /// See https://docs.python.org/3/library/datetime.html#datetime.datetime.strftime + abstract strftime: format: string -> string + /// Return the POSIX timestamp corresponding to this datetime as a float + /// See https://docs.python.org/3/library/datetime.html#datetime.datetime.timestamp + abstract timestamp: unit -> float + /// Return the UTC offset as a timedelta for aware datetimes; None for naive + abstract utcoffset: unit -> timedelta option + /// Return the timezone abbreviation string for aware datetimes; None for naive + abstract tzname: unit -> string option + /// Return the day of the week as an integer; Monday is 0 and Sunday is 6 + abstract weekday: unit -> int + /// Return the day of the week as an integer; Monday is 1 and Sunday is 7 + abstract isoweekday: unit -> int + /// Return a datetime with the date fields replaced + /// See https://docs.python.org/3/library/datetime.html#datetime.datetime.replace + [] + abstract replaceDate: year: int * month: int * day: int -> datetime + /// Return a datetime with the time fields replaced + [] + abstract replaceTime: hour: int * minute: int * second: int -> datetime + /// Return a datetime converted to the given timezone + /// See https://docs.python.org/3/library/datetime.html#datetime.datetime.astimezone + abstract astimezone: tz: obj -> datetime + +/// Static factory for datetime instances +[] +type datetimeStatic = + /// Create a datetime for the given date components (time components default to 0) + /// See https://docs.python.org/3/library/datetime.html#datetime.datetime + [] + abstract Create: year: int * month: int * day: int -> datetime + /// Create a datetime for the given date and time components + [] + abstract Create: year: int * month: int * day: int * hour: int * minute: int * second: int -> datetime + /// Return the current local date and time + /// See https://docs.python.org/3/library/datetime.html#datetime.datetime.now + abstract now: unit -> datetime + /// Return the current local date and time in the given timezone + [] + abstract now: tz: obj -> datetime + /// Return the current UTC date and time as a naive datetime + /// See https://docs.python.org/3/library/datetime.html#datetime.datetime.utcnow + abstract utcnow: unit -> datetime + /// Return the local datetime corresponding to a POSIX timestamp + /// See https://docs.python.org/3/library/datetime.html#datetime.datetime.fromtimestamp + abstract fromtimestamp: timestamp: float -> datetime + /// Return the datetime corresponding to a POSIX timestamp in the given timezone + [] + abstract fromtimestamp: timestamp: float * tz: obj -> datetime + /// Return a datetime from a string in any valid ISO 8601 format + /// See https://docs.python.org/3/library/datetime.html#datetime.datetime.fromisoformat + abstract fromisoformat: datetime_string: string -> datetime + /// Return a datetime parsed from date_string according to format + /// See https://docs.python.org/3/library/datetime.html#datetime.datetime.strptime + abstract strptime: date_string: string * format: string -> datetime + /// Combine a date and time into a single datetime + /// See https://docs.python.org/3/library/datetime.html#datetime.datetime.combine + abstract combine: date: date * time: time -> datetime + /// The earliest representable datetime + abstract min: datetime + /// The latest representable datetime + abstract max: datetime + +/// Factory for creating datetime values +[] +let datetime: datetimeStatic = nativeOnly + +// ============================================================================ +// timezone +// ============================================================================ + +/// A fixed-offset implementation of tzinfo. +/// See https://docs.python.org/3/library/datetime.html#datetime.timezone +[] +type timezone = + /// Return the UTC offset for this timezone + abstract utcoffset: dt: obj -> timedelta + /// Return the timezone name string for this timezone + abstract tzname: dt: obj -> string + +/// Static factory for timezone instances +[] +type timezoneStatic = + /// Create a timezone with the given fixed UTC offset (as a timedelta) + /// See https://docs.python.org/3/library/datetime.html#datetime.timezone + [] + abstract Create: offset: timedelta -> timezone + /// Create a named timezone with the given fixed UTC offset + [] + abstract Create: offset: timedelta * name: string -> timezone + /// The UTC timezone singleton (offset zero, name "UTC") + abstract utc: timezone + +/// Factory for creating timezone values +[] +let timezone: timezoneStatic = nativeOnly diff --git a/test/Fable.Python.Test.fsproj b/test/Fable.Python.Test.fsproj index f37dca6..441f8a7 100644 --- a/test/Fable.Python.Test.fsproj +++ b/test/Fable.Python.Test.fsproj @@ -32,6 +32,7 @@ + diff --git a/test/TestDatetime.fs b/test/TestDatetime.fs new file mode 100644 index 0000000..fd44a41 --- /dev/null +++ b/test/TestDatetime.fs @@ -0,0 +1,234 @@ +module Fable.Python.Tests.Datetime + +open Fable.Python.Testing +open Fable.Python.Datetime + +// ============================================================================ +// timedelta tests +// ============================================================================ + +[] +let ``test timedelta days property`` () = + let td = timedelta.Create(days = 3.0) + td.days |> equal 3 + +[] +let ``test timedelta hours to seconds`` () = + let td = timedelta.Create(hours = 2.0) + td.seconds |> equal 7200 + +[] +let ``test timedelta minutes to seconds`` () = + let td = timedelta.Create(minutes = 90.0) + td.seconds |> equal 5400 + +[] +let ``test timedelta total_seconds`` () = + let td = timedelta.Create(days = 1.0, hours = 2.0) + td.total_seconds() |> equal 93600.0 + +[] +let ``test timedelta zero`` () = + let td = timedelta.Create() + td.days |> equal 0 + td.seconds |> equal 0 + td.microseconds |> equal 0 + +// ============================================================================ +// date tests +// ============================================================================ + +[] +let ``test date year month day properties`` () = + let d = date.Create(2026, 4, 21) + d.year |> equal 2026 + d.month |> equal 4 + d.day |> equal 21 + +[] +let ``test date isoformat`` () = + let d = date.Create(2026, 4, 21) + d.isoformat() |> equal "2026-04-21" + +[] +let ``test date strftime`` () = + let d = date.Create(2026, 4, 21) + d.strftime("%Y/%m/%d") |> equal "2026/04/21" + +[] +let ``test date fromisoformat`` () = + let d = date.fromisoformat "2026-04-21" + d.year |> equal 2026 + d.month |> equal 4 + d.day |> equal 21 + +[] +let ``test date today returns date`` () = + let d = date.today() + d.year > 2024 |> equal true + +[] +let ``test date weekday monday is 0`` () = + // 2026-04-20 is a Monday + let d = date.Create(2026, 4, 20) + d.weekday() |> equal 0 + +[] +let ``test date isoweekday monday is 1`` () = + // 2026-04-20 is a Monday + let d = date.Create(2026, 4, 20) + d.isoweekday() |> equal 1 + +[] +let ``test date replace`` () = + let d = date.Create(2026, 4, 21) + let d2 = d.replace(2027, 1, 15) + d2.year |> equal 2027 + d2.month |> equal 1 + d2.day |> equal 15 + +// ============================================================================ +// time tests +// ============================================================================ + +[] +let ``test time hour minute second properties`` () = + let t = time.Create(hour = 14, minute = 30, second = 45) + t.hour |> equal 14 + t.minute |> equal 30 + t.second |> equal 45 + +[] +let ``test time isoformat`` () = + let t = time.Create(hour = 9, minute = 5, second = 3) + t.isoformat() |> equal "09:05:03" + +[] +let ``test time fromisoformat`` () = + let t = time.fromisoformat "14:30:00" + t.hour |> equal 14 + t.minute |> equal 30 + t.second |> equal 0 + +[] +let ``test time defaults to zero`` () = + let t = time.Create() + t.hour |> equal 0 + t.minute |> equal 0 + t.second |> equal 0 + t.microsecond |> equal 0 + +// ============================================================================ +// datetime tests +// ============================================================================ + +[] +let ``test datetime year month day properties`` () = + let dt = datetime.Create(2026, 4, 21) + dt.year |> equal 2026 + dt.month |> equal 4 + dt.day |> equal 21 + +[] +let ``test datetime hour minute second properties`` () = + let dt = datetime.Create(2026, 4, 21, 14, 30, 59) + dt.hour |> equal 14 + dt.minute |> equal 30 + dt.second |> equal 59 + +[] +let ``test datetime isoformat`` () = + let dt = datetime.Create(2026, 4, 21, 12, 0, 0) + dt.isoformat() |> equal "2026-04-21T12:00:00" + +[] +let ``test datetime fromisoformat`` () = + let dt = datetime.fromisoformat "2026-04-21T14:30:00" + dt.year |> equal 2026 + dt.month |> equal 4 + dt.day |> equal 21 + dt.hour |> equal 14 + dt.minute |> equal 30 + +[] +let ``test datetime now returns current datetime`` () = + let dt = datetime.now() + dt.year > 2024 |> equal true + +[] +let ``test datetime strptime`` () = + let dt = datetime.strptime("21/04/2026", "%d/%m/%Y") + dt.year |> equal 2026 + dt.month |> equal 4 + dt.day |> equal 21 + +[] +let ``test datetime combine date and time`` () = + let d = date.Create(2026, 4, 21) + let t = time.Create(hour = 10, minute = 0, second = 0) + let dt = datetime.combine(d, t) + dt.year |> equal 2026 + dt.hour |> equal 10 + +[] +let ``test datetime date method returns date part`` () = + let dt = datetime.Create(2026, 4, 21, 14, 30, 0) + let d = dt.date() + d.year |> equal 2026 + d.month |> equal 4 + d.day |> equal 21 + +[] +let ``test datetime time method returns time part`` () = + let dt = datetime.Create(2026, 4, 21, 14, 30, 59) + let t = dt.time() + t.hour |> equal 14 + t.minute |> equal 30 + t.second |> equal 59 + +[] +let ``test datetime replaceDate`` () = + let dt = datetime.Create(2026, 4, 21, 12, 0, 0) + let dt2 = dt.replaceDate(2027, 6, 15) + dt2.year |> equal 2027 + dt2.month |> equal 6 + dt2.day |> equal 15 + +[] +let ``test datetime replaceTime`` () = + let dt = datetime.Create(2026, 4, 21, 12, 0, 0) + let dt2 = dt.replaceTime(9, 30, 0) + dt2.hour |> equal 9 + dt2.minute |> equal 30 + +[] +let ``test datetime timestamp roundtrip`` () = + let dt1 = datetime.fromisoformat "2026-01-01T00:00:00" + let ts = dt1.timestamp() + let dt2 = datetime.fromtimestamp ts + dt2.year |> equal dt1.year + dt2.month |> equal dt1.month + dt2.day |> equal dt1.day + +// ============================================================================ +// timezone tests +// ============================================================================ + +[] +let ``test timezone utc name`` () = + let utcName = timezone.utc.tzname(null) + utcName |> equal "UTC" + +[] +let ``test timezone create with offset`` () = + let offset = timedelta.Create(hours = 5.0, minutes = 30.0) + let tz = timezone.Create offset + let name = tz.tzname(null) + name |> equal "UTC+05:30" + +[] +let ``test timezone create with name`` () = + let offset = timedelta.Create(hours = -5.0) + let tz = timezone.Create(offset, "EST") + let name = tz.tzname(null) + name |> equal "EST" From 89f6d697abeb576d0f98e7657d99e3955bcc1598 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Tue, 21 Apr 2026 23:47:42 +0200 Subject: [PATCH 2/2] refactor(stdlib): rework datetime bindings, fix justfile, apply fantomas Datetime: - Switch from paired static-factory types to Queue.fs-style primary-ctor classes. The original factory pattern combined [] with [], which silently dropped all kwargs (generated timedelta() instead of timedelta(days=3.0)). - timedelta: empty primary ctor plus static ofDays/ofHours/ofMinutes/ ofSeconds/ofWeeks/ofMilliseconds/ofMicroseconds factories with float() emit wrappers so Fable's Float64 doesn't reach Python's datetime. Added add/sub/neg arithmetic. - timezone: moved above datetime so datetime.astimezone/now(tz)/ fromtimestamp(tz) can take timezone instead of obj. - date/time/datetime: collapsed replaceDate/replaceTime into a single [] replace that supports partial replacement (any subset of fields). Added time.replace. - datetime: arithmetic via sub(datetime)->timedelta, sub(timedelta)-> datetime, add(timedelta)->datetime. utcnow marked [] pointing at now(tz = timezone.utc). - Module-level doc note about the `time` identifier collision with Fable.Python.Time. - Tests expanded from 30 to 37 covering partial replace, arithmetic, datetime.now(tz), and the ofX factories. justfile: - Drop -r from `dotnet fantomas` invocations in `format` and `format-check`; fantomas has no -r flag and it caused both recipes to fail. Other files: fantomas reformatting applied by `just format` to pre-existing files touched by the formatter. Co-Authored-By: Claude Opus 4.7 (1M context) --- justfile | 8 +- src/fable/Testing.fs | 2 +- src/stdlib/Builtins.fs | 3 +- src/stdlib/Datetime.fs | 365 ++++++++++++++++++++++------------------- src/stdlib/Math.fs | 2 + src/stdlib/Queue.fs | 3 + test/TestDatetime.fs | 179 ++++++++++++++------ test/TestFastAPI.fs | 56 +++---- test/TestFunctools.fs | 13 +- test/TestHeapq.fs | 12 +- test/TestItertools.fs | 30 +--- test/TestJson.fs | 2 +- test/TestMath.fs | 64 +++----- test/TestString.fs | 9 +- test/TestSys.fs | 18 +- test/TestTesting.fs | 32 +--- test/TestTime.fs | 3 +- 17 files changed, 426 insertions(+), 375 deletions(-) diff --git a/justfile b/justfile index de53b89..f0de065 100644 --- a/justfile +++ b/justfile @@ -83,13 +83,13 @@ shipit *args: # Format code with Fantomas format: - dotnet fantomas {{src_path}} -r - dotnet fantomas {{test_path}} -r + dotnet fantomas {{src_path}} + dotnet fantomas {{test_path}} # Check code formatting without making changes format-check: - dotnet fantomas {{src_path}} -r --check - dotnet fantomas {{test_path}} -r --check + dotnet fantomas {{src_path}} --check + dotnet fantomas {{test_path}} --check # Install .NET tools (Fable, Fantomas) and Python dependencies setup: diff --git a/src/fable/Testing.fs b/src/fable/Testing.fs index 70272ae..9f1aa02 100644 --- a/src/fable/Testing.fs +++ b/src/fable/Testing.fs @@ -47,7 +47,7 @@ let throwsError (expected: string) (f: unit -> 'a) : unit = let throwsErrorContaining (expected: string) (f: unit -> 'a) : unit = match run f with | Error _ when String.IsNullOrEmpty expected -> () - | Error (actual: string) when actual.Contains expected -> () + | Error(actual: string) when actual.Contains expected -> () | Error actual -> equal (sprintf "Error containing '%s'" expected) actual | Ok _ -> equal (sprintf "Error containing '%s'" expected) "No error was thrown" diff --git a/src/stdlib/Builtins.fs b/src/stdlib/Builtins.fs index 8260362..f7ee0eb 100644 --- a/src/stdlib/Builtins.fs +++ b/src/stdlib/Builtins.fs @@ -353,7 +353,8 @@ let __name__: string = nativeOnly let print obj = builtins.print obj /// Return the value of the named attribute of object with a default. -let getattr obj name defaultValue = builtins.getattr (obj, name, defaultValue) +let getattr obj name defaultValue = + builtins.getattr (obj, name, defaultValue) /// Sets the named attribute on the given object to the specified value. let setattr obj name value = builtins.setattr (obj, name, value) diff --git a/src/stdlib/Datetime.fs b/src/stdlib/Datetime.fs index 90dc928..7cc0c42 100644 --- a/src/stdlib/Datetime.fs +++ b/src/stdlib/Datetime.fs @@ -1,9 +1,13 @@ /// Type bindings for Python datetime module: https://docs.python.org/3/library/datetime.html +/// +/// Note: this module exposes a `time` class binding for Python's `datetime.time`. If you also +/// open `Fable.Python.Time` (the `time` module), the `time` identifier will collide — qualify +/// one of them, e.g. `Fable.Python.Time.time.time()` vs. `Fable.Python.Datetime.time(...)`. module Fable.Python.Datetime open Fable.Core -// fsharplint:disable MemberNames,InterfaceNames +// fsharplint:disable MemberNames // ============================================================================ // timedelta @@ -11,38 +15,83 @@ open Fable.Core /// A duration expressing the difference between two date, time, or datetime instances. /// See https://docs.python.org/3/library/datetime.html#datetime.timedelta +/// +/// The empty `timedelta()` ctor creates a zero duration. For other durations, use the +/// single-unit factories `ofDays`, `ofHours`, `ofMinutes`, `ofSeconds`, `ofWeeks`, +/// `ofMilliseconds`, `ofMicroseconds`, and combine via `.add` / `.sub`. [] -type timedelta = +type timedelta() = /// Number of full days (may be negative) - abstract days: int + member _.days: int = nativeOnly /// Remaining seconds after full days have been removed; 0 <= seconds < 86400 - abstract seconds: int + member _.seconds: int = nativeOnly /// Remaining microseconds; 0 <= microseconds < 1000000 - abstract microseconds: int + member _.microseconds: int = nativeOnly /// Return the total duration represented in fractional seconds /// See https://docs.python.org/3/library/datetime.html#datetime.timedelta.total_seconds - abstract total_seconds: unit -> float + member _.total_seconds() : float = nativeOnly -/// Static factory for timedelta instances -[] -type timedeltaStatic = - /// Create a timedelta; all arguments default to 0, may be floats, and may be negative. - /// See https://docs.python.org/3/library/datetime.html#datetime.timedelta - [] - [] - abstract Create: - ?days: float * - ?seconds: float * - ?microseconds: float * - ?milliseconds: float * - ?minutes: float * - ?hours: float * - ?weeks: float -> - timedelta - -/// Factory for creating timedelta values -[] -let timedelta: timedeltaStatic = nativeOnly + /// Return the sum of two timedeltas + [] + member _.add(other: timedelta) : timedelta = nativeOnly + + /// Return the difference between two timedeltas + [] + member _.sub(other: timedelta) : timedelta = nativeOnly + + /// Return the negation of this timedelta + [] + member _.neg() : timedelta = nativeOnly + + /// The most negative timedelta representable + static member min: timedelta = nativeOnly + /// The most positive timedelta representable + static member max: timedelta = nativeOnly + /// The smallest positive difference between non-equal timedelta objects + static member resolution: timedelta = nativeOnly + + /// Create a timedelta of N days + [] + static member ofDays(days: float) : timedelta = nativeOnly + + /// Create a timedelta of N seconds + [] + static member ofSeconds(seconds: float) : timedelta = nativeOnly + + /// Create a timedelta of N microseconds + [] + static member ofMicroseconds(microseconds: float) : timedelta = nativeOnly + + /// Create a timedelta of N milliseconds + [] + static member ofMilliseconds(milliseconds: float) : timedelta = nativeOnly + + /// Create a timedelta of N minutes + [] + static member ofMinutes(minutes: float) : timedelta = nativeOnly + + /// Create a timedelta of N hours + [] + static member ofHours(hours: float) : timedelta = nativeOnly + + /// Create a timedelta of N weeks + [] + static member ofWeeks(weeks: float) : timedelta = nativeOnly + +// ============================================================================ +// timezone (defined before datetime so datetime members can reference it) +// ============================================================================ + +/// A fixed-offset implementation of tzinfo. +/// See https://docs.python.org/3/library/datetime.html#datetime.timezone +[] +type timezone(offset: timedelta, ?name: string) = + /// Return the UTC offset for this timezone + member _.utcoffset(dt: obj) : timedelta = nativeOnly + /// Return the timezone name string for this timezone + member _.tzname(dt: obj) : string = nativeOnly + /// The UTC timezone singleton (offset zero, name "UTC") + static member utc: timezone = nativeOnly // ============================================================================ // date @@ -51,58 +100,60 @@ let timedelta: timedeltaStatic = nativeOnly /// A naive date (year, month, day) with no time or timezone component. /// See https://docs.python.org/3/library/datetime.html#datetime.date [] -type date = +type date(year: int, month: int, day: int) = /// Year in range [MINYEAR, MAXYEAR] - abstract year: int + member _.year: int = nativeOnly /// Month in range [1, 12] - abstract month: int + member _.month: int = nativeOnly /// Day in range [1, number of days in the month and year] - abstract day: int + member _.day: int = nativeOnly /// Return a string in ISO 8601 format, e.g. "2026-04-21" /// See https://docs.python.org/3/library/datetime.html#datetime.date.isoformat - abstract isoformat: unit -> string + member _.isoformat() : string = nativeOnly /// Return a string representing the date, formatted with format /// See https://docs.python.org/3/library/datetime.html#datetime.date.strftime - abstract strftime: format: string -> string + member _.strftime(format: string) : string = nativeOnly /// Return the day of the week as an integer; Monday is 0 and Sunday is 6 /// See https://docs.python.org/3/library/datetime.html#datetime.date.weekday - abstract weekday: unit -> int + member _.weekday() : int = nativeOnly /// Return the day of the week as an integer; Monday is 1 and Sunday is 7 /// See https://docs.python.org/3/library/datetime.html#datetime.date.isoweekday - abstract isoweekday: unit -> int + member _.isoweekday() : int = nativeOnly /// Return the proleptic Gregorian ordinal of the date; January 1 of year 1 has ordinal 1 - abstract toordinal: unit -> int - /// Return a date with the given fields replaced + member _.toordinal() : int = nativeOnly + + /// Return a date with the given fields replaced (any subset of year/month/day) /// See https://docs.python.org/3/library/datetime.html#datetime.date.replace - [] - abstract replace: year: int * month: int * day: int -> date + [] + member _.replace(?year: int, ?month: int, ?day: int) : date = nativeOnly + + /// Return the timedelta between this date and other (self - other) + [] + member _.sub(other: date) : timedelta = nativeOnly + + /// Return a date offset by the given timedelta (self - delta) + [] + member _.sub(delta: timedelta) : date = nativeOnly + + /// Return a date offset by the given timedelta (self + delta) + [] + member _.add(delta: timedelta) : date = nativeOnly -/// Static factory for date instances -[] -type dateStatic = - /// Create a date for the given year, month and day - /// See https://docs.python.org/3/library/datetime.html#datetime.date - [] - abstract Create: year: int * month: int * day: int -> date /// Return the current local date /// See https://docs.python.org/3/library/datetime.html#datetime.date.today - abstract today: unit -> date + static member today() : date = nativeOnly /// Return the local date corresponding to a POSIX timestamp /// See https://docs.python.org/3/library/datetime.html#datetime.date.fromtimestamp - abstract fromtimestamp: timestamp: float -> date + static member fromtimestamp(timestamp: float) : date = nativeOnly /// Return the date corresponding to the proleptic Gregorian ordinal - abstract fromordinal: ordinal: int -> date + static member fromordinal(ordinal: int) : date = nativeOnly /// Return a date from a string in any valid ISO 8601 format /// See https://docs.python.org/3/library/datetime.html#datetime.date.fromisoformat - abstract fromisoformat: date_string: string -> date + static member fromisoformat(date_string: string) : date = nativeOnly /// The earliest representable date - abstract min: date + static member min: date = nativeOnly /// The latest representable date - abstract max: date - -/// Factory for creating date values -[] -let date: dateStatic = nativeOnly + static member max: date = nativeOnly // ============================================================================ // time @@ -110,53 +161,43 @@ let date: dateStatic = nativeOnly /// A naive or aware time of day (hour, minute, second, microsecond, tzinfo). /// See https://docs.python.org/3/library/datetime.html#datetime.time +/// +/// Ctor args are positional: `time(h)`, `time(h, m)`, `time(h, m, s)`, `time(h, m, s, us)`. [] -type time = +type time(hour: int, ?minute: int, ?second: int, ?microsecond: int) = /// Hour in range [0, 23] - abstract hour: int + member _.hour: int = nativeOnly /// Minute in range [0, 59] - abstract minute: int + member _.minute: int = nativeOnly /// Second in range [0, 59] - abstract second: int + member _.second: int = nativeOnly /// Microsecond in range [0, 999999] - abstract microsecond: int + member _.microsecond: int = nativeOnly /// Fold value (0 or 1) for disambiguating wall-clock times that repeat during DST transitions - abstract fold: int + member _.fold: int = nativeOnly /// Return a string in ISO 8601 format, e.g. "14:30:00" /// See https://docs.python.org/3/library/datetime.html#datetime.time.isoformat - abstract isoformat: unit -> string + member _.isoformat() : string = nativeOnly /// Return a string representing the time, formatted with format /// See https://docs.python.org/3/library/datetime.html#datetime.time.strftime - abstract strftime: format: string -> string + member _.strftime(format: string) : string = nativeOnly /// Return the UTC offset as a timedelta for aware times; None for naive times - abstract utcoffset: unit -> timedelta option + member _.utcoffset() : timedelta option = nativeOnly /// Return the timezone abbreviation string for aware times; None for naive times - abstract tzname: unit -> string option + member _.tzname() : string option = nativeOnly -/// Static factory for time instances -[] -type timeStatic = - /// Create a time-of-day value; all arguments default to 0 - /// See https://docs.python.org/3/library/datetime.html#datetime.time - [] + /// Return a time with the given fields replaced (any subset) + /// See https://docs.python.org/3/library/datetime.html#datetime.time.replace [] - abstract Create: - ?hour: int * - ?minute: int * - ?second: int * - ?microsecond: int -> - time + member _.replace(?hour: int, ?minute: int, ?second: int, ?microsecond: int) : time = nativeOnly + /// Return a time from a string in ISO 8601 format /// See https://docs.python.org/3/library/datetime.html#datetime.time.fromisoformat - abstract fromisoformat: time_string: string -> time + static member fromisoformat(time_string: string) : time = nativeOnly /// The earliest representable time, time(0, 0, 0, 0) - abstract min: time + static member min: time = nativeOnly /// The latest representable time, time(23, 59, 59, 999999) - abstract max: time - -/// Factory for creating time-of-day values -[] -let time: timeStatic = nativeOnly + static member max: time = nativeOnly // ============================================================================ // datetime @@ -165,129 +206,119 @@ let time: timeStatic = nativeOnly /// A naive or aware date and time (year, month, day, hour, minute, second, microsecond, tzinfo). /// See https://docs.python.org/3/library/datetime.html#datetime.datetime [] -type datetime = +type datetime + ( + year: int, + month: int, + day: int, + ?hour: int, + ?minute: int, + ?second: int, + ?microsecond: int, + ?tzinfo: timezone, + ?fold: int + ) = /// Year in range [MINYEAR, MAXYEAR] - abstract year: int + member _.year: int = nativeOnly /// Month in range [1, 12] - abstract month: int + member _.month: int = nativeOnly /// Day in range [1, number of days in the month and year] - abstract day: int + member _.day: int = nativeOnly /// Hour in range [0, 23] - abstract hour: int + member _.hour: int = nativeOnly /// Minute in range [0, 59] - abstract minute: int + member _.minute: int = nativeOnly /// Second in range [0, 59] - abstract second: int + member _.second: int = nativeOnly /// Microsecond in range [0, 999999] - abstract microsecond: int + member _.microsecond: int = nativeOnly /// Fold value (0 or 1) for disambiguating wall-clock times that repeat during DST transitions - abstract fold: int + member _.fold: int = nativeOnly /// Return the date part as a date object /// See https://docs.python.org/3/library/datetime.html#datetime.datetime.date - abstract date: unit -> date + member _.date() : date = nativeOnly /// Return the time part as a time object (tzinfo is not included) /// See https://docs.python.org/3/library/datetime.html#datetime.datetime.time - abstract time: unit -> time + member _.time() : time = nativeOnly /// Return a string in ISO 8601 format, e.g. "2026-04-21T14:30:00" /// See https://docs.python.org/3/library/datetime.html#datetime.datetime.isoformat - abstract isoformat: unit -> string + member _.isoformat() : string = nativeOnly + /// Return a string in ISO 8601 format with a custom separator between date and time [] - abstract isoformat: sep: string -> string + member _.isoformat(sep: string) : string = nativeOnly + /// Return a string representing the datetime, formatted with format /// See https://docs.python.org/3/library/datetime.html#datetime.datetime.strftime - abstract strftime: format: string -> string + member _.strftime(format: string) : string = nativeOnly /// Return the POSIX timestamp corresponding to this datetime as a float /// See https://docs.python.org/3/library/datetime.html#datetime.datetime.timestamp - abstract timestamp: unit -> float + member _.timestamp() : float = nativeOnly /// Return the UTC offset as a timedelta for aware datetimes; None for naive - abstract utcoffset: unit -> timedelta option + member _.utcoffset() : timedelta option = nativeOnly /// Return the timezone abbreviation string for aware datetimes; None for naive - abstract tzname: unit -> string option + member _.tzname() : string option = nativeOnly /// Return the day of the week as an integer; Monday is 0 and Sunday is 6 - abstract weekday: unit -> int + member _.weekday() : int = nativeOnly /// Return the day of the week as an integer; Monday is 1 and Sunday is 7 - abstract isoweekday: unit -> int - /// Return a datetime with the date fields replaced + member _.isoweekday() : int = nativeOnly + + /// Return a datetime with the given fields replaced (any subset) /// See https://docs.python.org/3/library/datetime.html#datetime.datetime.replace - [] - abstract replaceDate: year: int * month: int * day: int -> datetime - /// Return a datetime with the time fields replaced - [] - abstract replaceTime: hour: int * minute: int * second: int -> datetime + [] + member _.replace + (?year: int, ?month: int, ?day: int, ?hour: int, ?minute: int, ?second: int, ?microsecond: int) + : datetime = + nativeOnly + /// Return a datetime converted to the given timezone /// See https://docs.python.org/3/library/datetime.html#datetime.datetime.astimezone - abstract astimezone: tz: obj -> datetime + member _.astimezone(tz: timezone) : datetime = nativeOnly + + /// Return the timedelta between this datetime and other (self - other) + [] + member _.sub(other: datetime) : timedelta = nativeOnly + + /// Return a datetime offset by the given timedelta (self - delta) + [] + member _.sub(delta: timedelta) : datetime = nativeOnly + + /// Return a datetime offset by the given timedelta (self + delta) + [] + member _.add(delta: timedelta) : datetime = nativeOnly -/// Static factory for datetime instances -[] -type datetimeStatic = - /// Create a datetime for the given date components (time components default to 0) - /// See https://docs.python.org/3/library/datetime.html#datetime.datetime - [] - abstract Create: year: int * month: int * day: int -> datetime - /// Create a datetime for the given date and time components - [] - abstract Create: year: int * month: int * day: int * hour: int * minute: int * second: int -> datetime /// Return the current local date and time /// See https://docs.python.org/3/library/datetime.html#datetime.datetime.now - abstract now: unit -> datetime + static member now() : datetime = nativeOnly + /// Return the current local date and time in the given timezone - [] - abstract now: tz: obj -> datetime - /// Return the current UTC date and time as a naive datetime + [] + static member now(tz: timezone) : datetime = nativeOnly + + /// Return the current UTC date and time as a naive datetime. + /// Deprecated in Python 3.12; prefer `datetime.now(tz = timezone.utc)`. /// See https://docs.python.org/3/library/datetime.html#datetime.datetime.utcnow - abstract utcnow: unit -> datetime + [] + static member utcnow() : datetime = nativeOnly + /// Return the local datetime corresponding to a POSIX timestamp /// See https://docs.python.org/3/library/datetime.html#datetime.datetime.fromtimestamp - abstract fromtimestamp: timestamp: float -> datetime + static member fromtimestamp(timestamp: float) : datetime = nativeOnly + /// Return the datetime corresponding to a POSIX timestamp in the given timezone - [] - abstract fromtimestamp: timestamp: float * tz: obj -> datetime + [] + static member fromtimestamp(timestamp: float, tz: timezone) : datetime = nativeOnly + /// Return a datetime from a string in any valid ISO 8601 format /// See https://docs.python.org/3/library/datetime.html#datetime.datetime.fromisoformat - abstract fromisoformat: datetime_string: string -> datetime + static member fromisoformat(datetime_string: string) : datetime = nativeOnly /// Return a datetime parsed from date_string according to format /// See https://docs.python.org/3/library/datetime.html#datetime.datetime.strptime - abstract strptime: date_string: string * format: string -> datetime + static member strptime(date_string: string, format: string) : datetime = nativeOnly /// Combine a date and time into a single datetime /// See https://docs.python.org/3/library/datetime.html#datetime.datetime.combine - abstract combine: date: date * time: time -> datetime + static member combine(date: date, time: time) : datetime = nativeOnly /// The earliest representable datetime - abstract min: datetime + static member min: datetime = nativeOnly /// The latest representable datetime - abstract max: datetime - -/// Factory for creating datetime values -[] -let datetime: datetimeStatic = nativeOnly - -// ============================================================================ -// timezone -// ============================================================================ - -/// A fixed-offset implementation of tzinfo. -/// See https://docs.python.org/3/library/datetime.html#datetime.timezone -[] -type timezone = - /// Return the UTC offset for this timezone - abstract utcoffset: dt: obj -> timedelta - /// Return the timezone name string for this timezone - abstract tzname: dt: obj -> string - -/// Static factory for timezone instances -[] -type timezoneStatic = - /// Create a timezone with the given fixed UTC offset (as a timedelta) - /// See https://docs.python.org/3/library/datetime.html#datetime.timezone - [] - abstract Create: offset: timedelta -> timezone - /// Create a named timezone with the given fixed UTC offset - [] - abstract Create: offset: timedelta * name: string -> timezone - /// The UTC timezone singleton (offset zero, name "UTC") - abstract utc: timezone - -/// Factory for creating timezone values -[] -let timezone: timezoneStatic = nativeOnly + static member max: datetime = nativeOnly diff --git a/src/stdlib/Math.fs b/src/stdlib/Math.fs index 07a72e0..c83b28c 100644 --- a/src/stdlib/Math.fs +++ b/src/stdlib/Math.fs @@ -84,10 +84,12 @@ type IExports = /// Return True if the values a and b are close to each other /// See https://docs.python.org/3/library/math.html#math.isclose abstract isclose: a: float * b: float -> bool + /// Return True if the values a and b are close to each other with custom tolerances /// See https://docs.python.org/3/library/math.html#math.isclose [] abstract isclose: a: float * b: float * ?rel_tol: float * ?abs_tol: float -> bool + /// Return the least common multiple of the integers /// See https://docs.python.org/3/library/math.html#math.lcm abstract lcm: [] ints: int[] -> int diff --git a/src/stdlib/Queue.fs b/src/stdlib/Queue.fs index ca69760..81b7a32 100644 --- a/src/stdlib/Queue.fs +++ b/src/stdlib/Queue.fs @@ -34,10 +34,12 @@ type Queue<'T>() = /// operation goes into an uninterruptible wait on an underlying lock. This means that no exceptions can occur, and /// in particular a SIGINT will not trigger a KeyboardInterrupt. member x.get(?block: bool, ?timeout: float) : 'T = nativeOnly + /// Equivalent to get(False). /// See https://docs.python.org/3/library/queue.html#queue.Queue.get_nowait [] member x.get_nowait() : 'T = nativeOnly + /// Blocks until all items in the queue have been gotten and processed. /// /// The count of unfinished tasks goes up whenever an item is added to the queue. The count goes down whenever a @@ -74,6 +76,7 @@ type SimpleQueue<'T>() = member x.put(item: 'T, ?block: bool, ?timeout: float) : unit = nativeOnly /// Remove and return an item from the queue. member x.get(?block: bool, ?timeout: float) : 'T = nativeOnly + /// Equivalent to get(False). [] member x.get_nowait() : 'T = nativeOnly diff --git a/test/TestDatetime.fs b/test/TestDatetime.fs index fd44a41..72e0d1b 100644 --- a/test/TestDatetime.fs +++ b/test/TestDatetime.fs @@ -9,51 +9,66 @@ open Fable.Python.Datetime [] let ``test timedelta days property`` () = - let td = timedelta.Create(days = 3.0) + let td = timedelta.ofDays 3.0 td.days |> equal 3 [] let ``test timedelta hours to seconds`` () = - let td = timedelta.Create(hours = 2.0) + let td = timedelta.ofHours 2.0 td.seconds |> equal 7200 [] let ``test timedelta minutes to seconds`` () = - let td = timedelta.Create(minutes = 90.0) + let td = timedelta.ofMinutes 90.0 td.seconds |> equal 5400 [] let ``test timedelta total_seconds`` () = - let td = timedelta.Create(days = 1.0, hours = 2.0) - td.total_seconds() |> equal 93600.0 + let td = (timedelta.ofDays 1.0).add (timedelta.ofHours 2.0) + td.total_seconds () |> equal 93600.0 [] let ``test timedelta zero`` () = - let td = timedelta.Create() + let td = timedelta () td.days |> equal 0 td.seconds |> equal 0 td.microseconds |> equal 0 +[] +let ``test timedelta add`` () = + let sum = (timedelta.ofHours 1.0).add (timedelta.ofMinutes 30.0) + sum.total_seconds () |> equal 5400.0 + +[] +let ``test timedelta sub`` () = + let diff = (timedelta.ofHours 2.0).sub (timedelta.ofMinutes 30.0) + diff.total_seconds () |> equal 5400.0 + +[] +let ``test timedelta neg`` () = + let n = (timedelta.ofHours 1.0).neg () + n.total_seconds () |> equal -3600.0 + // ============================================================================ // date tests // ============================================================================ [] let ``test date year month day properties`` () = - let d = date.Create(2026, 4, 21) + let d = date (2026, 4, 21) d.year |> equal 2026 d.month |> equal 4 d.day |> equal 21 [] let ``test date isoformat`` () = - let d = date.Create(2026, 4, 21) - d.isoformat() |> equal "2026-04-21" + let d = date (2026, 4, 21) + d.isoformat () |> equal "2026-04-21" [] let ``test date strftime`` () = - let d = date.Create(2026, 4, 21) - d.strftime("%Y/%m/%d") |> equal "2026/04/21" + let d = date (2026, 4, 21) + d.strftime ("%Y/%m/%d") |> equal "2026/04/21" [] let ``test date fromisoformat`` () = @@ -64,44 +79,73 @@ let ``test date fromisoformat`` () = [] let ``test date today returns date`` () = - let d = date.today() + let d = date.today () d.year > 2024 |> equal true [] let ``test date weekday monday is 0`` () = // 2026-04-20 is a Monday - let d = date.Create(2026, 4, 20) - d.weekday() |> equal 0 + let d = date (2026, 4, 20) + d.weekday () |> equal 0 [] let ``test date isoweekday monday is 1`` () = // 2026-04-20 is a Monday - let d = date.Create(2026, 4, 20) - d.isoweekday() |> equal 1 + let d = date (2026, 4, 20) + d.isoweekday () |> equal 1 [] -let ``test date replace`` () = - let d = date.Create(2026, 4, 21) - let d2 = d.replace(2027, 1, 15) +let ``test date replace all fields`` () = + let d = date (2026, 4, 21) + let d2 = d.replace (year = 2027, month = 1, day = 15) d2.year |> equal 2027 d2.month |> equal 1 d2.day |> equal 15 +[] +let ``test date replace single field`` () = + let d = date (2026, 4, 21) + let d2 = d.replace (year = 2030) + d2.year |> equal 2030 + d2.month |> equal 4 + d2.day |> equal 21 + +[] +let ``test date add timedelta`` () = + let d = date (2026, 4, 21) + let d2 = d.add (timedelta.ofDays 10.0) + d2.day |> equal 1 + d2.month |> equal 5 + +[] +let ``test date sub date returns timedelta`` () = + let d1 = date (2026, 4, 21) + let d2 = date (2026, 4, 11) + let td = d1.sub d2 + td.days |> equal 10 + +[] +let ``test date sub timedelta returns date`` () = + let d = date (2026, 4, 21) + let d2 = d.sub (timedelta.ofDays 21.0) + d2.month |> equal 3 + d2.day |> equal 31 + // ============================================================================ // time tests // ============================================================================ [] let ``test time hour minute second properties`` () = - let t = time.Create(hour = 14, minute = 30, second = 45) + let t = time (14, 30, 45) t.hour |> equal 14 t.minute |> equal 30 t.second |> equal 45 [] let ``test time isoformat`` () = - let t = time.Create(hour = 9, minute = 5, second = 3) - t.isoformat() |> equal "09:05:03" + let t = time (9, 5, 3) + t.isoformat () |> equal "09:05:03" [] let ``test time fromisoformat`` () = @@ -112,34 +156,42 @@ let ``test time fromisoformat`` () = [] let ``test time defaults to zero`` () = - let t = time.Create() + let t = time 0 t.hour |> equal 0 t.minute |> equal 0 t.second |> equal 0 t.microsecond |> equal 0 +[] +let ``test time replace single field`` () = + let t = time (9, 30, 0) + let t2 = t.replace (hour = 14) + t2.hour |> equal 14 + t2.minute |> equal 30 + t2.second |> equal 0 + // ============================================================================ // datetime tests // ============================================================================ [] let ``test datetime year month day properties`` () = - let dt = datetime.Create(2026, 4, 21) + let dt = datetime (2026, 4, 21) dt.year |> equal 2026 dt.month |> equal 4 dt.day |> equal 21 [] let ``test datetime hour minute second properties`` () = - let dt = datetime.Create(2026, 4, 21, 14, 30, 59) + let dt = datetime (2026, 4, 21, 14, 30, 59) dt.hour |> equal 14 dt.minute |> equal 30 dt.second |> equal 59 [] let ``test datetime isoformat`` () = - let dt = datetime.Create(2026, 4, 21, 12, 0, 0) - dt.isoformat() |> equal "2026-04-21T12:00:00" + let dt = datetime (2026, 4, 21, 12, 0, 0) + dt.isoformat () |> equal "2026-04-21T12:00:00" [] let ``test datetime fromisoformat`` () = @@ -152,83 +204,110 @@ let ``test datetime fromisoformat`` () = [] let ``test datetime now returns current datetime`` () = - let dt = datetime.now() + let dt = datetime.now () + dt.year > 2024 |> equal true + +[] +let ``test datetime now with timezone`` () = + let dt = datetime.now (timezone.utc) dt.year > 2024 |> equal true + dt.tzname () |> equal (Some "UTC") [] let ``test datetime strptime`` () = - let dt = datetime.strptime("21/04/2026", "%d/%m/%Y") + let dt = datetime.strptime ("21/04/2026", "%d/%m/%Y") dt.year |> equal 2026 dt.month |> equal 4 dt.day |> equal 21 [] let ``test datetime combine date and time`` () = - let d = date.Create(2026, 4, 21) - let t = time.Create(hour = 10, minute = 0, second = 0) - let dt = datetime.combine(d, t) + let d = date (2026, 4, 21) + let t = time (10, 0, 0) + let dt = datetime.combine (d, t) dt.year |> equal 2026 dt.hour |> equal 10 [] let ``test datetime date method returns date part`` () = - let dt = datetime.Create(2026, 4, 21, 14, 30, 0) - let d = dt.date() + let dt = datetime (2026, 4, 21, 14, 30, 0) + let d = dt.date () d.year |> equal 2026 d.month |> equal 4 d.day |> equal 21 [] let ``test datetime time method returns time part`` () = - let dt = datetime.Create(2026, 4, 21, 14, 30, 59) - let t = dt.time() + let dt = datetime (2026, 4, 21, 14, 30, 59) + let t = dt.time () t.hour |> equal 14 t.minute |> equal 30 t.second |> equal 59 [] -let ``test datetime replaceDate`` () = - let dt = datetime.Create(2026, 4, 21, 12, 0, 0) - let dt2 = dt.replaceDate(2027, 6, 15) +let ``test datetime replace date fields`` () = + let dt = datetime (2026, 4, 21, 12, 0, 0) + let dt2 = dt.replace (year = 2027, month = 6, day = 15) dt2.year |> equal 2027 dt2.month |> equal 6 dt2.day |> equal 15 + dt2.hour |> equal 12 [] -let ``test datetime replaceTime`` () = - let dt = datetime.Create(2026, 4, 21, 12, 0, 0) - let dt2 = dt.replaceTime(9, 30, 0) +let ``test datetime replace time fields`` () = + let dt = datetime (2026, 4, 21, 12, 0, 0) + let dt2 = dt.replace (hour = 9, minute = 30, second = 0) dt2.hour |> equal 9 dt2.minute |> equal 30 + dt2.year |> equal 2026 [] let ``test datetime timestamp roundtrip`` () = let dt1 = datetime.fromisoformat "2026-01-01T00:00:00" - let ts = dt1.timestamp() + let ts = dt1.timestamp () let dt2 = datetime.fromtimestamp ts dt2.year |> equal dt1.year dt2.month |> equal dt1.month dt2.day |> equal dt1.day +[] +let ``test datetime add timedelta`` () = + let dt = datetime (2026, 4, 21, 12, 0, 0) + let dt2 = dt.add (timedelta.ofHours 3.0) + dt2.hour |> equal 15 + +[] +let ``test datetime sub datetime returns timedelta`` () = + let dt1 = datetime (2026, 4, 21, 15, 0, 0) + let dt2 = datetime (2026, 4, 21, 12, 0, 0) + let td = dt1.sub dt2 + td.total_seconds () |> equal 10800.0 + +[] +let ``test datetime sub timedelta returns datetime`` () = + let dt = datetime (2026, 4, 21, 12, 0, 0) + let dt2 = dt.sub (timedelta.ofHours 2.0) + dt2.hour |> equal 10 + // ============================================================================ // timezone tests // ============================================================================ [] let ``test timezone utc name`` () = - let utcName = timezone.utc.tzname(null) + let utcName = timezone.utc.tzname (null) utcName |> equal "UTC" [] let ``test timezone create with offset`` () = - let offset = timedelta.Create(hours = 5.0, minutes = 30.0) - let tz = timezone.Create offset - let name = tz.tzname(null) + let offset = (timedelta.ofHours 5.0).add (timedelta.ofMinutes 30.0) + let tz = timezone offset + let name = tz.tzname (null) name |> equal "UTC+05:30" [] let ``test timezone create with name`` () = - let offset = timedelta.Create(hours = -5.0) - let tz = timezone.Create(offset, "EST") - let name = tz.tzname(null) + let offset = timedelta.ofHours -5.0 + let tz = timezone (offset, "EST") + let name = tz.tzname (null) name |> equal "EST" diff --git a/test/TestFastAPI.fs b/test/TestFastAPI.fs index 4ec54d8..68ca17f 100644 --- a/test/TestFastAPI.fs +++ b/test/TestFastAPI.fs @@ -52,14 +52,14 @@ let ``test APIRouter can be created with prefix`` () = [] let ``test APIRouter can be created with tags`` () = - let router = APIRouter(tags = ResizeArray ["users"; "admin"]) + let router = APIRouter(tags = ResizeArray [ "users"; "admin" ]) notNull router |> equal true [] let ``test FastAPI app can include router`` () = let app = FastAPI() let router = APIRouter(prefix = "/api") - app.include_router(router) + app.include_router (router) // If we get here without error, the test passes true |> equal true @@ -186,7 +186,7 @@ let ``test Pydantic model works with FastAPI patterns`` () = [] let ``test Pydantic model serialization for FastAPI`` () = let item = Item(Name = "Gadget", Price = 19.99, InStock = false) - let json = item.model_dump_json() + let json = item.model_dump_json () json.Contains("Gadget") |> equal true json.Contains("19.99") |> equal true @@ -202,50 +202,47 @@ let app = FastAPI(title = "Test API", version = "1.0.0") [] type API() = [] - static member root() : obj = - {| message = "Hello World" |} + static member root() : obj = {| message = "Hello World" |} [] static member get_item(item_id: int) : obj = - {| item_id = item_id; name = "Test Item" |} + {| item_id = item_id + name = "Test Item" |} [] - static member create_item(item: Item) : obj = - {| status = "created"; item = item |} + static member create_item(item: Item) : obj = {| status = "created"; item = item |} [] - static member update_item(item_id: int, item: Item) : obj = - {| item_id = item_id; item = item |} + static member update_item(item_id: int, item: Item) : obj = {| item_id = item_id; item = item |} [] - static member delete_item(item_id: int) : obj = - {| deleted = item_id |} + static member delete_item(item_id: int) : obj = {| deleted = item_id |} [] let ``test class-based API methods can be called`` () = - let result = API.root() + let result = API.root () notNull result |> equal true [] let ``test class-based API with path parameter`` () = - let result = API.get_item(42) + let result = API.get_item (42) notNull result |> equal true [] let ``test class-based API POST method`` () = let item = Item(Name = "Test", Price = 10.0, InStock = true) - let result = API.create_item(item) + let result = API.create_item (item) notNull result |> equal true [] let ``test class-based API PUT method`` () = let item = Item(Name = "Updated", Price = 20.0, InStock = false) - let result = API.update_item(1, item) + let result = API.update_item (1, item) notNull result |> equal true [] let ``test class-based API DELETE method`` () = - let result = API.delete_item(1) + let result = API.delete_item (1) notNull result |> equal true // ============================================================================ @@ -262,47 +259,42 @@ let ``test TestClient can be created`` () = // Router-based API pattern // ============================================================================ -let router = APIRouter(prefix = "/users", tags = ResizeArray ["users"]) +let router = APIRouter(prefix = "/users", tags = ResizeArray [ "users" ]) [] type UsersAPI() = [] - static member list_users() : obj = - {| users = [| "Alice"; "Bob" |] |} + static member list_users() : obj = {| users = [| "Alice"; "Bob" |] |} [] - static member get_user(user_id: int) : obj = - {| user_id = user_id |} + static member get_user(user_id: int) : obj = {| user_id = user_id |} [] - static member create_user(name: string) : obj = - {| name = name; id = 1 |} + static member create_user(name: string) : obj = {| name = name; id = 1 |} [] - static member update_user(user_id: int, name: string) : obj = - {| user_id = user_id; name = name |} + static member update_user(user_id: int, name: string) : obj = {| user_id = user_id; name = name |} [] - static member delete_user(user_id: int) : obj = - {| deleted = user_id |} + static member delete_user(user_id: int) : obj = {| deleted = user_id |} [] let ``test router-based API methods work`` () = - let users = UsersAPI.list_users() + let users = UsersAPI.list_users () notNull users |> equal true [] let ``test router can be included in app`` () = let mainApp = FastAPI() let usersRouter = APIRouter(prefix = "/api/v1") - mainApp.include_router(usersRouter) + mainApp.include_router (usersRouter) true |> equal true [] let ``test router can be included with prefix and tags`` () = let mainApp = FastAPI() - let usersRouter = APIRouter(prefix = "/users", tags = ResizeArray ["users"]) - mainApp.include_router_with_prefix_and_tags(usersRouter, "/api/v1", ResizeArray ["api"]) + let usersRouter = APIRouter(prefix = "/users", tags = ResizeArray [ "users" ]) + mainApp.include_router_with_prefix_and_tags (usersRouter, "/api/v1", ResizeArray [ "api" ]) true |> equal true #endif diff --git a/test/TestFunctools.fs b/test/TestFunctools.fs index a0f9bb1..40562a1 100644 --- a/test/TestFunctools.fs +++ b/test/TestFunctools.fs @@ -5,18 +5,15 @@ open Fable.Python.Functools [] let ``test reduce sum works`` () = - functools.reduce ((fun a b -> a + b), [ 1; 2; 3; 4; 5 ]) - |> equal 15 + functools.reduce ((fun a b -> a + b), [ 1; 2; 3; 4; 5 ]) |> equal 15 [] let ``test reduce product works`` () = - functools.reduce ((fun a b -> a * b), [ 1; 2; 3; 4; 5 ]) - |> equal 120 + functools.reduce ((fun a b -> a * b), [ 1; 2; 3; 4; 5 ]) |> equal 120 [] let ``test reduce with initializer works`` () = - functools.reduce ((fun acc x -> acc + x), [ 1; 2; 3 ], 10) - |> equal 16 + functools.reduce ((fun acc x -> acc + x), [ 1; 2; 3 ], 10) |> equal 16 [] let ``test reduce string fold with initializer works`` () = @@ -26,9 +23,11 @@ let ``test reduce string fold with initializer works`` () = [] let ``test lruCache memoises results`` () = let callCount = ResizeArray() + let expensive (x: int) = callCount.Add x x * x + let cached = functools.lruCache (128, expensive) cached 5 |> equal 25 cached 5 |> equal 25 @@ -38,9 +37,11 @@ let ``test lruCache memoises results`` () = [] let ``test cache memoises results`` () = let callCount = ResizeArray() + let expensive (x: int) = callCount.Add x x * 2 + let cached = functools.cache expensive cached 7 |> equal 14 cached 7 |> equal 14 diff --git a/test/TestHeapq.fs b/test/TestHeapq.fs index 6001186..d0dd22d 100644 --- a/test/TestHeapq.fs +++ b/test/TestHeapq.fs @@ -15,13 +15,13 @@ let ``test heappush and heappop work`` () = [] let ``test heapify works`` () = - let heap = ResizeArray [5; 3; 1; 4; 2] + let heap = ResizeArray [ 5; 3; 1; 4; 2 ] heapq.heapify heap heapq.heappop heap |> equal 1 [] let ``test heappushpop works`` () = - let heap = ResizeArray [2; 4; 6] + let heap = ResizeArray [ 2; 4; 6 ] heapq.heapify heap // Push 1, then pop smallest (1) heapq.heappushpop (heap, 1) |> equal 1 @@ -30,10 +30,10 @@ let ``test heappushpop works`` () = [] let ``test nlargest works`` () = - let result = heapq.nlargest (3, [1; 5; 2; 8; 3; 7]) - result |> equal (ResizeArray [8; 7; 5]) + let result = heapq.nlargest (3, [ 1; 5; 2; 8; 3; 7 ]) + result |> equal (ResizeArray [ 8; 7; 5 ]) [] let ``test nsmallest works`` () = - let result = heapq.nsmallest (3, [1; 5; 2; 8; 3; 7]) - result |> equal (ResizeArray [1; 2; 3]) + let result = heapq.nsmallest (3, [ 1; 5; 2; 8; 3; 7 ]) + result |> equal (ResizeArray [ 1; 2; 3 ]) diff --git a/test/TestItertools.fs b/test/TestItertools.fs index 1f9a413..4273d10 100644 --- a/test/TestItertools.fs +++ b/test/TestItertools.fs @@ -5,17 +5,11 @@ open Fable.Python.Itertools [] let ``test count from start works`` () = - itertools.count 1 - |> Seq.take 4 - |> Seq.toList - |> equal [ 1; 2; 3; 4 ] + itertools.count 1 |> Seq.take 4 |> Seq.toList |> equal [ 1; 2; 3; 4 ] [] let ``test count with step works`` () = - itertools.count (0, 2) - |> Seq.take 4 - |> Seq.toList - |> equal [ 0; 2; 4; 6 ] + itertools.count (0, 2) |> Seq.take 4 |> Seq.toList |> equal [ 0; 2; 4; 6 ] [] let ``test cycle works`` () = @@ -26,9 +20,7 @@ let ``test cycle works`` () = [] let ``test repeat with times works`` () = - itertools.repeat ("x", 3) - |> Seq.toList - |> equal [ "x"; "x"; "x" ] + itertools.repeat ("x", 3) |> Seq.toList |> equal [ "x"; "x"; "x" ] [] let ``test accumulate works`` () = @@ -44,9 +36,7 @@ let ``test accumulate with func works`` () = [] let ``test chain two sequences works`` () = - itertools.chain ([ 1; 2 ], [ 3; 4 ]) - |> Seq.toList - |> equal [ 1; 2; 3; 4 ] + itertools.chain ([ 1; 2 ], [ 3; 4 ]) |> Seq.toList |> equal [ 1; 2; 3; 4 ] [] let ``test chain three sequences works`` () = @@ -80,15 +70,11 @@ let ``test filterfalse works`` () = [] let ``test islice with stop works`` () = - itertools.islice ([ 1; 2; 3; 4; 5 ], 3) - |> Seq.toList - |> equal [ 1; 2; 3 ] + itertools.islice ([ 1; 2; 3; 4; 5 ], 3) |> Seq.toList |> equal [ 1; 2; 3 ] [] let ``test islice with start and stop works`` () = - itertools.islice ([ 1; 2; 3; 4; 5 ], 1, 4) - |> Seq.toList - |> equal [ 2; 3; 4 ] + itertools.islice ([ 1; 2; 3; 4; 5 ], 1, 4) |> Seq.toList |> equal [ 2; 3; 4 ] [] let ``test pairwise works`` () = @@ -111,9 +97,7 @@ let ``test combinations works`` () = [] let ``test permutations with r works`` () = - itertools.permutations ([ 1; 2; 3 ], 2) - |> Seq.length - |> equal 6 + itertools.permutations ([ 1; 2; 3 ], 2) |> Seq.length |> equal 6 [] let ``test product two sequences works`` () = diff --git a/test/TestJson.fs b/test/TestJson.fs index 1ba7543..5f2cf5d 100644 --- a/test/TestJson.fs +++ b/test/TestJson.fs @@ -66,7 +66,7 @@ let ``test Json.loads with array works`` () = [] let ``test Json.dumps with indent works`` () = let obj = {| A = 1n |} - let result = Json.dumps(obj, indent = 2) + let result = Json.dumps (obj, indent = 2) result.Contains("\n") |> equal true [] diff --git a/test/TestMath.fs b/test/TestMath.fs index 6141b45..9ff7aea 100644 --- a/test/TestMath.fs +++ b/test/TestMath.fs @@ -93,32 +93,25 @@ let ``test pow works`` () = math.pow (10.0, 2.0) |> equal 100.0 [] -let ``test sin works`` () = - math.sin 0.0 |> equal 0.0 +let ``test sin works`` () = math.sin 0.0 |> equal 0.0 [] -let ``test cos works`` () = - math.cos 0.0 |> equal 1.0 +let ``test cos works`` () = math.cos 0.0 |> equal 1.0 [] -let ``test tan works`` () = - math.tan 0.0 |> equal 0.0 +let ``test tan works`` () = math.tan 0.0 |> equal 0.0 [] -let ``test asin works`` () = - math.asin 0.0 |> equal 0.0 +let ``test asin works`` () = math.asin 0.0 |> equal 0.0 [] -let ``test acos works`` () = - math.acos 1.0 |> equal 0.0 +let ``test acos works`` () = math.acos 1.0 |> equal 0.0 [] -let ``test atan works`` () = - math.atan 0.0 |> equal 0.0 +let ``test atan works`` () = math.atan 0.0 |> equal 0.0 [] -let ``test atan2 works`` () = - math.atan2 (0.0, 1.0) |> equal 0.0 +let ``test atan2 works`` () = math.atan2 (0.0, 1.0) |> equal 0.0 [] let ``test pi constant works`` () = @@ -133,8 +126,7 @@ let ``test tau constant works`` () = math.tau |> fun x -> (x > 6.28318 && x < 6.28319) |> equal true [] -let ``test inf constant works`` () = - math.isinf math.inf |> equal true +let ``test inf constant works`` () = math.isinf math.inf |> equal true [] let ``test sqrt works`` () = @@ -155,8 +147,7 @@ let ``test trunc works`` () = math.trunc -2.7 |> equal -2 [] -let ``test hypot works`` () = - math.hypot (3.0, 4.0) |> equal 5.0 +let ``test hypot works`` () = math.hypot (3.0, 4.0) |> equal 5.0 [] let ``test isqrt works`` () = @@ -168,12 +159,10 @@ let ``test fsum works`` () = math.fsum [ 1.0; 2.0; 3.0 ] |> equal 6.0 [] -let ``test nan constant works`` () = - math.isnan math.nan |> equal true +let ``test nan constant works`` () = math.isnan math.nan |> equal true [] -let ``test prod works`` () = - math.prod [ 1; 2; 3; 4 ] |> equal 24 +let ``test prod works`` () = math.prod [ 1; 2; 3; 4 ] |> equal 24 [] let ``test perm works`` () = @@ -185,36 +174,28 @@ let ``test dist works`` () = math.dist ([| 0.0; 0.0 |], [| 3.0; 4.0 |]) |> equal 5.0 [] -let ``test cosh works`` () = - math.cosh 0.0 |> equal 1.0 +let ``test cosh works`` () = math.cosh 0.0 |> equal 1.0 [] -let ``test sinh works`` () = - math.sinh 0.0 |> equal 0.0 +let ``test sinh works`` () = math.sinh 0.0 |> equal 0.0 [] -let ``test tanh works`` () = - math.tanh 0.0 |> equal 0.0 +let ``test tanh works`` () = math.tanh 0.0 |> equal 0.0 [] -let ``test acosh works`` () = - math.acosh 1.0 |> equal 0.0 +let ``test acosh works`` () = math.acosh 1.0 |> equal 0.0 [] -let ``test asinh works`` () = - math.asinh 0.0 |> equal 0.0 +let ``test asinh works`` () = math.asinh 0.0 |> equal 0.0 [] -let ``test atanh works`` () = - math.atanh 0.0 |> equal 0.0 +let ``test atanh works`` () = math.atanh 0.0 |> equal 0.0 [] -let ``test erf works`` () = - math.erf 0.0 |> equal 0.0 +let ``test erf works`` () = math.erf 0.0 |> equal 0.0 [] -let ``test erfc works`` () = - math.erfc 0.0 |> equal 1.0 +let ``test erfc works`` () = math.erfc 0.0 |> equal 1.0 [] let ``test gamma works`` () = @@ -222,8 +203,7 @@ let ``test gamma works`` () = math.gamma 5.0 |> equal 24.0 [] -let ``test lgamma works`` () = - math.lgamma 1.0 |> equal 0.0 +let ``test lgamma works`` () = math.lgamma 1.0 |> equal 0.0 [] let ``test log with base works`` () = @@ -259,8 +239,8 @@ let ``test isclose works`` () = [] let ``test isclose with tolerances works`` () = - math.isclose (1.0, 1.001, rel_tol=0.01) |> equal true - math.isclose (1.0, 2.0, abs_tol=0.1) |> equal false + math.isclose (1.0, 1.001, rel_tol = 0.01) |> equal true + math.isclose (1.0, 2.0, abs_tol = 0.1) |> equal false [] let ``test nextafter works`` () = diff --git a/test/TestString.fs b/test/TestString.fs index 5d1b808..7cb1c29 100644 --- a/test/TestString.fs +++ b/test/TestString.fs @@ -17,7 +17,8 @@ let ``test string format 2 works`` () = [] let ``test ascii_letters constant`` () = - pyString.ascii_letters |> equal "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + pyString.ascii_letters + |> equal "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" [] let ``test ascii_lowercase constant`` () = @@ -28,16 +29,14 @@ let ``test ascii_uppercase constant`` () = pyString.ascii_uppercase |> equal "ABCDEFGHIJKLMNOPQRSTUVWXYZ" [] -let ``test digits constant`` () = - pyString.digits |> equal "0123456789" +let ``test digits constant`` () = pyString.digits |> equal "0123456789" [] let ``test hexdigits constant`` () = pyString.hexdigits |> equal "0123456789abcdefABCDEF" [] -let ``test octdigits constant`` () = - pyString.octdigits |> equal "01234567" +let ``test octdigits constant`` () = pyString.octdigits |> equal "01234567" [] let ``test punctuation constant`` () = diff --git a/test/TestSys.fs b/test/TestSys.fs index 26b7530..3033dab 100644 --- a/test/TestSys.fs +++ b/test/TestSys.fs @@ -4,28 +4,22 @@ open Fable.Python.Testing open Fable.Python.Sys [] -let ``test sys.platform is non-empty string`` () = - sys.platform.Length > 0 |> equal true +let ``test sys.platform is non-empty string`` () = sys.platform.Length > 0 |> equal true [] -let ``test sys.version is non-empty string`` () = - sys.version.Length > 0 |> equal true +let ``test sys.version is non-empty string`` () = sys.version.Length > 0 |> equal true [] -let ``test sys.maxsize is positive`` () = - sys.maxsize > 0n |> equal true +let ``test sys.maxsize is positive`` () = sys.maxsize > 0n |> equal true [] -let ``test sys.maxunicode is 1114111`` () = - sys.maxunicode |> equal 1114111 +let ``test sys.maxunicode is 1114111`` () = sys.maxunicode |> equal 1114111 [] -let ``test sys.path has at least one element`` () = - sys.path.Count > 0 |> equal true +let ``test sys.path has at least one element`` () = sys.path.Count > 0 |> equal true [] -let ``test sys.argv has at least one element`` () = - sys.argv.Count > 0 |> equal true +let ``test sys.argv has at least one element`` () = sys.argv.Count > 0 |> equal true [] let ``test sys.byteorder is little or big`` () = diff --git a/test/TestTesting.fs b/test/TestTesting.fs index cead90a..a2c0750 100644 --- a/test/TestTesting.fs +++ b/test/TestTesting.fs @@ -11,7 +11,7 @@ let ``test equal passes for equal values`` () = equal 1 1 equal "hello" "hello" equal true true - equal [1; 2; 3] [1; 2; 3] + equal [ 1; 2; 3 ] [ 1; 2; 3 ] [] let ``test equal fails for unequal values`` () = @@ -38,8 +38,7 @@ let ``test notEqual passes for unequal values`` () = notEqual true false [] -let ``test notEqual fails for equal values`` () = - throwsAnyError (fun () -> notEqual 1 1) +let ``test notEqual fails for equal values`` () = throwsAnyError (fun () -> notEqual 1 1) [] let ``test notEqual fails for equal strings`` () = @@ -56,23 +55,18 @@ let ``test throwsAnyError passes when function throws`` () = [] let ``test throwsAnyError fails when function does not throw`` () = // Meta-test: throwsAnyError should fail if the function doesn't throw - throwsAnyError (fun () -> - throwsAnyError (fun () -> 42) - ) + throwsAnyError (fun () -> throwsAnyError (fun () -> 42)) // ============================================================================ // Test doesntThrow // ============================================================================ [] -let ``test doesntThrow passes when function succeeds`` () = - doesntThrow (fun () -> 1 + 1) +let ``test doesntThrow passes when function succeeds`` () = doesntThrow (fun () -> 1 + 1) [] let ``test doesntThrow fails when function throws`` () = - throwsAnyError (fun () -> - doesntThrow (fun () -> failwith "boom") - ) + throwsAnyError (fun () -> doesntThrow (fun () -> failwith "boom")) // ============================================================================ // Test throwsError with exact message @@ -84,15 +78,11 @@ let ``test throwsError passes with matching message`` () = [] let ``test throwsError fails with wrong message`` () = - throwsAnyError (fun () -> - throwsError "expected message" (fun () -> failwith "different message") - ) + throwsAnyError (fun () -> throwsError "expected message" (fun () -> failwith "different message")) [] let ``test throwsError fails when no error thrown`` () = - throwsAnyError (fun () -> - throwsError "expected error" (fun () -> 42) - ) + throwsAnyError (fun () -> throwsError "expected error" (fun () -> 42)) // ============================================================================ // Test throwsErrorContaining @@ -104,12 +94,8 @@ let ``test throwsErrorContaining passes when message contains substring`` () = [] let ``test throwsErrorContaining fails when message does not contain substring`` () = - throwsAnyError (fun () -> - throwsErrorContaining "notfound" (fun () -> failwith "different error message") - ) + throwsAnyError (fun () -> throwsErrorContaining "notfound" (fun () -> failwith "different error message")) [] let ``test throwsErrorContaining fails when no error thrown`` () = - throwsAnyError (fun () -> - throwsErrorContaining "error" (fun () -> 42) - ) + throwsAnyError (fun () -> throwsErrorContaining "error" (fun () -> 42)) diff --git a/test/TestTime.fs b/test/TestTime.fs index e1dba1a..fb9ba4c 100644 --- a/test/TestTime.fs +++ b/test/TestTime.fs @@ -40,8 +40,7 @@ let ``test time.ctime with seconds returns non-empty string`` () = s.Length > 0 |> equal true [] -let ``test time.sleep does not throw`` () = - time.sleep 0.0 +let ``test time.sleep does not throw`` () = time.sleep 0.0 [] let ``test time.timezone is int`` () =