Skip to content

Commit db5f0b3

Browse files
github-actions[bot]Copilotdbrattliclaude
authored
feat(stdlib): add datetime module bindings (#267)
* 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> * 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 [<Emit("$0($1...)")>] with [<NamedParams>], 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 [<NamedParams>] 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 [<Obsolete>] 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) <noreply@anthropic.com> --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Dag Brattli <dag@brattli.net> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 28194c3 commit db5f0b3

19 files changed

Lines changed: 738 additions & 158 deletions

justfile

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,13 +83,13 @@ shipit *args:
8383

8484
# Format code with Fantomas
8585
format:
86-
dotnet fantomas {{src_path}} -r
87-
dotnet fantomas {{test_path}} -r
86+
dotnet fantomas {{src_path}}
87+
dotnet fantomas {{test_path}}
8888

8989
# Check code formatting without making changes
9090
format-check:
91-
dotnet fantomas {{src_path}} -r --check
92-
dotnet fantomas {{test_path}} -r --check
91+
dotnet fantomas {{src_path}} --check
92+
dotnet fantomas {{test_path}} --check
9393

9494
# Install .NET tools (Fable, Fantomas) and Python dependencies
9595
setup:

src/Fable.Python.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
<Compile Include="stdlib/Os.fs" />
2626
<Compile Include="stdlib/Heapq.fs" />
2727
<Compile Include="stdlib/Itertools.fs" />
28+
<Compile Include="stdlib/Datetime.fs" />
2829
<Compile Include="stdlib/Functools.fs" />
2930
<Compile Include="stdlib/Queue.fs" />
3031
<Compile Include="stdlib/String.fs" />

src/fable/Testing.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ let throwsError (expected: string) (f: unit -> 'a) : unit =
4747
let throwsErrorContaining (expected: string) (f: unit -> 'a) : unit =
4848
match run f with
4949
| Error _ when String.IsNullOrEmpty expected -> ()
50-
| Error (actual: string) when actual.Contains expected -> ()
50+
| Error(actual: string) when actual.Contains expected -> ()
5151
| Error actual -> equal (sprintf "Error containing '%s'" expected) actual
5252
| Ok _ -> equal (sprintf "Error containing '%s'" expected) "No error was thrown"
5353

src/stdlib/Builtins.fs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,8 @@ let __name__: string = nativeOnly
353353
let print obj = builtins.print obj
354354

355355
/// Return the value of the named attribute of object with a default.
356-
let getattr obj name defaultValue = builtins.getattr (obj, name, defaultValue)
356+
let getattr obj name defaultValue =
357+
builtins.getattr (obj, name, defaultValue)
357358

358359
/// Sets the named attribute on the given object to the specified value.
359360
let setattr obj name value = builtins.setattr (obj, name, value)

src/stdlib/Datetime.fs

Lines changed: 324 additions & 0 deletions
Large diffs are not rendered by default.

src/stdlib/Math.fs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,12 @@ type IExports =
8484
/// Return True if the values a and b are close to each other
8585
/// See https://docs.python.org/3/library/math.html#math.isclose
8686
abstract isclose: a: float * b: float -> bool
87+
8788
/// Return True if the values a and b are close to each other with custom tolerances
8889
/// See https://docs.python.org/3/library/math.html#math.isclose
8990
[<NamedParams(fromIndex = 2)>]
9091
abstract isclose: a: float * b: float * ?rel_tol: float * ?abs_tol: float -> bool
92+
9193
/// Return the least common multiple of the integers
9294
/// See https://docs.python.org/3/library/math.html#math.lcm
9395
abstract lcm: [<ParamArray>] ints: int[] -> int

src/stdlib/Queue.fs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,12 @@ type Queue<'T>() =
3434
/// operation goes into an uninterruptible wait on an underlying lock. This means that no exceptions can occur, and
3535
/// in particular a SIGINT will not trigger a KeyboardInterrupt.
3636
member x.get(?block: bool, ?timeout: float) : 'T = nativeOnly
37+
3738
/// Equivalent to get(False).
3839
/// See https://docs.python.org/3/library/queue.html#queue.Queue.get_nowait
3940
[<Emit("$0.get_nowait()")>]
4041
member x.get_nowait() : 'T = nativeOnly
42+
4143
/// Blocks until all items in the queue have been gotten and processed.
4244
///
4345
/// 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>() =
7476
member x.put(item: 'T, ?block: bool, ?timeout: float) : unit = nativeOnly
7577
/// Remove and return an item from the queue.
7678
member x.get(?block: bool, ?timeout: float) : 'T = nativeOnly
79+
7780
/// Equivalent to get(False).
7881
[<Emit("$0.get_nowait()")>]
7982
member x.get_nowait() : 'T = nativeOnly

test/Fable.Python.Test.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
<Compile Include="TestSys.fs" />
3333
<Compile Include="TestTime.fs" />
3434
<Compile Include="TestString.fs" />
35+
<Compile Include="TestDatetime.fs" />
3536
<Compile Include="TestPydantic.fs" />
3637
<Compile Include="TestFastAPI.fs" />
3738
<Compile Include="TestTypes.fs" />

0 commit comments

Comments
 (0)