Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions content/core/date.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
---
title: Working with Dates
description: Documentation for WebSharper Date utilities (System.DateTime and DateFNS)
---

import { FSharpSnippetTabs } from "@/lib/components/FSharpSnippetTabs";

## Dates in WebSharper

WebSharper supports date and time manipulation through two complementary approaches:

- **Native support** via the .NET `System.DateTime` API, fully usable in WebSharper.
- **Functional, JavaScript-style operations** via the [`WebSharper.DateFNS`](https://github.com/dotnet-websharper/datefns) binding, which exposes the [`date-fns`](https://date-fns.org/) library.

---

### Why Use `System.DateTime`?

The .NET `System.DateTime` type is ideal when you need:

- Strong typing and .NET compatibility
- Server-side consistency
- Access to the full breadth of the standard `DateTime` API

Example usage:

```fsharp
// Native .NET System.DateTime
let today = System.DateTime.Now
let fiveDaysLater = today.AddDays(5.0)
let startOfMonth = System.DateTime(today.Year, today.Month, 1)
let formatted = today.ToString("yyyy-MM-dd")
```

<FSharpSnippetTabs
snippet="SystemDateTime"
liveSnippetHeight="400"
highlightLines="1,2,3"
defTab="preview"
/>

---

### Why Use the `DateFNS`?

[`date-fns`](https://date-fns.org/) is a popular JavaScript date utility library that provides functional, immutable operations.
With the official [`WebSharper.DateFNS`](https://github.com/dotnet-websharper/datefns) binding, you can use its concise APIs directly from F#:

```fsharp
open WebSharper.DateFNS

let today = DateFNS.Now()
let afterWeek = DateFNS.AddDays(today, 7)
let formatted = DateFNS.Format(today, "yyyy-MM-dd")
```

This style is especially convenient in client-side F# where you want short, declarative date code similar to JavaScript.

<FSharpSnippetTabs
snippet="DateFNS"
liveSnippetHeight="400"
highlightLines="1,2,3"
defTab="preview"
/>

---

## When to Use Which

| Your Priority | Choose `System.DateTime` | Choose `DateFNS` (WebSharper binding) |
| --------------------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------ |
| Full compatibility with .NET APIs | Excellent fit | More awkward to interop |
| Immutability & functional code style | Manual handling required | Built‑in immutable functions |
| Bundle size & tree‑shaking | Minimal overhead | Only imports used functions |
| Rich client-side date toolkit | Basic features | Extensive helper functions |
| Need performant date arithmetic parsing | OK for common use | Typically faster in benchmarks ([dev.to][1], [stackoverflow.com][2], [spin.atomicobject.com][3]) |

---

### Quick Decision Flow

* **Is this server-side or .NET-heavy logic?** → Stick with **`System.DateTime`**.
* **Is this client-side UI code where concise, functional style matters?** → Prefer **`DateFNS`**.
* **Need to intermix both?** → You can compute with either and convert between them as needed.
1 change: 1 addition & 0 deletions content/core/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"interface-generator",
"testing",
"math",
"date",
"ws-ai-plugin"
]
}
49 changes: 49 additions & 0 deletions snippets/DateFNS/Client.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
namespace DateFNS

open WebSharper
open WebSharper.JavaScript
open WebSharper.UI
open WebSharper.UI.Client
open WebSharper.UI.Templating
open WebSharper.DateFNS

[<JavaScript>]
module Client =
// The templates are loaded from the DOM, so you just can edit index.html
// and refresh your browser, no need to recompile unless you add or remove holes.
type IndexTemplate = Template<"wwwroot/index.html", ClientLoad.FromDocument>

let private format = "yyyy-MM-dd"

[<SPAEntryPoint>]
let Main () =
let current = Var.Create (Date())
let nDays = Var.Create 3

let viewTomorrow = current.View |> View.Map (fun date -> DateFNS.AddDays(date, 1))
let viewStartMonth = current.View |> View.Map (fun d -> DateFNS.StartOfMonth(d))

let setNow () = current.Value <- Date()
let addDay () = current.Value <- DateFNS.AddDays(current.Value, 1)
let addWeek () = current.Value <- DateFNS.AddWeeks(current.Value, 1)
let toStartMo () = current.Value <- DateFNS.StartOfMonth(current.Value)
let addNDays () = current.Value <- DateFNS.AddDays(current.Value, nDays.Value)

IndexTemplate.Main()
.NowText(
current.View |> View.Map (fun date -> DateFNS.Format(date, format))
)
.TomorrowText(
viewTomorrow |> View.Map (fun date -> DateFNS.Format(date, format))
)
.StartOfMonthText(
viewStartMonth |> View.Map (fun date -> DateFNS.Format(date, format))
)
.NDays(nDays)
.SetNow(fun _ -> setNow())
.AddDay(fun _ -> addDay())
.AddWeek(fun _ -> addWeek())
.ToStartOfMonth(fun _ -> toStartMo())
.AddNDays(fun _ -> addNDays())
.Doc()
|> Doc.RunById "main"
21 changes: 21 additions & 0 deletions snippets/DateFNS/DateFNS.fsproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<Compile Include="Client.fs" />
<Compile Include="Startup.fs" />
<None Include="package.json" />
<None Include="esbuild.config.mjs" />
<None Include="vite.config.js" />
<None Include="wsconfig.json" />
<None Include="paket.references" />
</ItemGroup>
<Target Name="ESBuildBundle" AfterTargets="WebSharperCompile" Condition=" '$(Configuration)' == 'Release' ">
<Exec Command="npm install" />
<Exec Command="node ./esbuild.config.mjs" />
</Target>
<Import Project="..\.paket\Paket.Restore.targets" />
</Project>
12 changes: 12 additions & 0 deletions snippets/DateFNS/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"profiles": {
"DateFNS": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:51604;http://localhost:51605"
}
}
}
40 changes: 40 additions & 0 deletions snippets/DateFNS/Startup.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
open System
open Microsoft.AspNetCore.Builder
open Microsoft.Extensions.Hosting
open Microsoft.AspNetCore.Http
open Microsoft.Extensions.DependencyInjection
open WebSharper.AspNetCore
open DateFNS

[<EntryPoint>]
let main args =
let builder = WebApplication.CreateBuilder(args)

// Add services to the container.
builder.Services.AddWebSharper()
.AddAuthentication("WebSharper")
.AddCookie("WebSharper", fun options -> ())
|> ignore

let app = builder.Build()

// Configure the HTTP request pipeline.
if not (app.Environment.IsDevelopment()) then
app.UseExceptionHandler("/Error")
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
.UseHsts()
|> ignore

app.UseHttpsRedirection()
#if DEBUG
.UseWebSharperScriptRedirect(startVite = true)
#endif
.UseDefaultFiles()
.UseStaticFiles()
//Enable if you want to make RPC calls to server
//.UseWebSharperRemoting()
|> ignore

app.Run()

0 // Exit code
6 changes: 6 additions & 0 deletions snippets/DateFNS/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"websharper": {
"UseDownloadedResources": false,
"DebugScriptRedirectUrl": "http://localhost:52393"
}
}
43 changes: 43 additions & 0 deletions snippets/DateFNS/esbuild.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { cpSync, readdirSync, existsSync } from 'fs'
import { build } from 'esbuild'

cpSync('./build/', './wwwroot/Scripts/', { recursive: true });

const prebundles = readdirSync('./build/');

prebundles.forEach(file => {
if (file.endsWith('.js')) {
var options =
{
entryPoints: ['./build/' + file],
bundle: true,
minify: true,
format: 'iife',
outfile: 'wwwroot/Scripts/' + file,
globalName: 'wsbundle'
};

console.log("Bundling:", file);
build(options);
}
});

if (existsSync('./build/workers/')) {
const workers = readdirSync('./build/workers/');

workers.forEach(file => {
if (file.endsWith('.js')) {
var options =
{
entryPoints: ['./build/workers/' + file],
bundle: true,
minify: true,
format: 'iife',
outfile: 'wwwroot/Scripts/workers/' + file,
};

console.log("Bundling worker:", file);
build(options);
}
});
}
Loading