Complete NodaTime migration (#41)

- Tweak field layout (#38)
This commit is contained in:
Daniel J. Summers 2022-08-08 20:39:19 -04:00
parent e0d2be41d8
commit eb947a48af
8 changed files with 119 additions and 107 deletions

View File

@ -952,7 +952,8 @@ module PrayerRequest =
| Automatic, Expecting -> false | Automatic, Expecting -> false
| Automatic, _ -> | Automatic, _ ->
// Automatic expiration // Automatic expiration
asOf.PlusDays -group.Preferences.DaysToExpire > req.UpdatedDate.InZone(SmallGroup.timeZone group).Date Period.Between(req.UpdatedDate.InZone(SmallGroup.timeZone group).Date, asOf, PeriodUnits.Days).Days
>= group.Preferences.DaysToExpire
/// Is an update required for this long-term request? /// Is an update required for this long-term request?
let updateRequired asOf group req = let updateRequired asOf group req =

View File

@ -143,6 +143,8 @@ let memberTests =
[<Tests>] [<Tests>]
let prayerRequestTests = let prayerRequestTests =
let instantNow = SystemClock.Instance.GetCurrentInstant
let localDateNow () = (instantNow ()).InUtc().Date
testList "PrayerRequest" [ testList "PrayerRequest" [
test "empty is as expected" { test "empty is as expected" {
let mt = PrayerRequest.empty let mt = PrayerRequest.empty
@ -150,8 +152,8 @@ let prayerRequestTests =
Expect.equal mt.RequestType CurrentRequest "The request type should have been Current" Expect.equal mt.RequestType CurrentRequest "The request type should have been Current"
Expect.equal mt.UserId.Value Guid.Empty "The user ID should have been an empty GUID" Expect.equal mt.UserId.Value Guid.Empty "The user ID should have been an empty GUID"
Expect.equal mt.SmallGroupId.Value Guid.Empty "The small group ID should have been an empty GUID" Expect.equal mt.SmallGroupId.Value Guid.Empty "The small group ID should have been an empty GUID"
Expect.equal mt.EnteredDate DateTime.MinValue "The entered date should have been the minimum" Expect.equal mt.EnteredDate Instant.MinValue "The entered date should have been the minimum"
Expect.equal mt.UpdatedDate DateTime.MinValue "The updated date should have been the minimum" Expect.equal mt.UpdatedDate Instant.MinValue "The updated date should have been the minimum"
Expect.isNone mt.Requestor "The requestor should not exist" Expect.isNone mt.Requestor "The requestor should not exist"
Expect.equal mt.Text "" "The request text should have been blank" Expect.equal mt.Text "" "The request text should have been blank"
Expect.isFalse mt.NotifyChaplain "The notify chaplain flag should not have been set" Expect.isFalse mt.NotifyChaplain "The notify chaplain flag should not have been set"
@ -159,62 +161,60 @@ let prayerRequestTests =
Expect.equal mt.User.Id.Value Guid.Empty "The user should have been an empty one" Expect.equal mt.User.Id.Value Guid.Empty "The user should have been an empty one"
Expect.equal mt.SmallGroup.Id.Value Guid.Empty "The small group should have been an empty one" Expect.equal mt.SmallGroup.Id.Value Guid.Empty "The small group should have been an empty one"
} }
test "IsExpired always returns false for expecting requests" { test "isExpired always returns false for expecting requests" {
let req = { PrayerRequest.empty with RequestType = Expecting } PrayerRequest.isExpired (localDateNow ()) SmallGroup.empty
Expect.isFalse (req.IsExpired DateTime.Now 0) "An expecting request should never be considered expired" { PrayerRequest.empty with RequestType = Expecting }
|> Flip.Expect.isFalse "An expecting request should never be considered expired"
} }
test "IsExpired always returns false for manually-expired requests" { test "isExpired always returns false for manually-expired requests" {
let req = { PrayerRequest.empty with UpdatedDate = DateTime.Now.AddMonths -1; Expiration = Manual } PrayerRequest.isExpired (localDateNow ()) SmallGroup.empty
Expect.isFalse (req.IsExpired DateTime.Now 4) "A never-expired request should never be considered expired" { PrayerRequest.empty with UpdatedDate = (instantNow ()) - Duration.FromDays 1; Expiration = Manual }
|> Flip.Expect.isFalse "A never-expired request should never be considered expired"
} }
test "IsExpired always returns false for long term/recurring requests" { test "isExpired always returns false for long term/recurring requests" {
let req = { PrayerRequest.empty with RequestType = LongTermRequest } PrayerRequest.isExpired (localDateNow ()) SmallGroup.empty
Expect.isFalse (req.IsExpired DateTime.Now 0) { PrayerRequest.empty with RequestType = LongTermRequest }
"A recurring/long-term request should never be considered expired" |> Flip.Expect.isFalse "A recurring/long-term request should never be considered expired"
} }
test "IsExpired always returns true for force-expired requests" { test "isExpired always returns true for force-expired requests" {
let req = { PrayerRequest.empty with UpdatedDate = DateTime.Now; Expiration = Forced } PrayerRequest.isExpired (localDateNow ()) SmallGroup.empty
Expect.isTrue (req.IsExpired DateTime.Now 5) "A force-expired request should always be considered expired" { PrayerRequest.empty with UpdatedDate = (instantNow ()); Expiration = Forced }
|> Flip.Expect.isTrue "A force-expired request should always be considered expired"
} }
test "IsExpired returns false for non-expired requests" { test "isExpired returns false for non-expired requests" {
let now = DateTime.Now let now = instantNow ()
let req = { PrayerRequest.empty with UpdatedDate = now.AddDays -5. } PrayerRequest.isExpired (now.InUtc().Date) SmallGroup.empty
Expect.isFalse (req.IsExpired now 7) "A request updated 5 days ago should not be considered expired" { PrayerRequest.empty with UpdatedDate = now - Duration.FromDays 5 }
|> Flip.Expect.isFalse "A request updated 5 days ago should not be considered expired"
} }
test "IsExpired returns true for expired requests" { test "isExpired returns true for expired requests" {
let now = DateTime.Now let now = instantNow ()
let req = { PrayerRequest.empty with UpdatedDate = now.AddDays -8. } PrayerRequest.isExpired (now.InUtc().Date) SmallGroup.empty
Expect.isTrue (req.IsExpired now 7) "A request updated 8 days ago should be considered expired" { PrayerRequest.empty with UpdatedDate = now - Duration.FromDays 15 }
|> Flip.Expect.isTrue "A request updated 15 days ago should be considered expired"
} }
test "IsExpired returns true for same-day expired requests" { test "isExpired returns true for same-day expired requests" {
let now = DateTime.Now let now = instantNow ()
let req = { PrayerRequest.empty with UpdatedDate = now.Date.AddDays(-7.).AddSeconds -1. } PrayerRequest.isExpired (now.InUtc().Date) SmallGroup.empty
Expect.isTrue (req.IsExpired now 7) { PrayerRequest.empty with UpdatedDate = now - (Duration.FromDays 14) - (Duration.FromSeconds 1L) }
"A request entered a second before midnight should be considered expired" |> Flip.Expect.isTrue "A request entered a second before midnight should be considered expired"
} }
test "UpdateRequired returns false for expired requests" { test "updateRequired returns false for expired requests" {
let req = { PrayerRequest.empty with Expiration = Forced } PrayerRequest.updateRequired (localDateNow ()) SmallGroup.empty
Expect.isFalse (req.UpdateRequired DateTime.Now 7 4) "An expired request should not require an update" { PrayerRequest.empty with Expiration = Forced }
|> Flip.Expect.isFalse "An expired request should not require an update"
} }
test "UpdateRequired returns false when an update is not required for an active request" { test "updateRequired returns false when an update is not required for an active request" {
let now = DateTime.Now let now = instantNow ()
let req = PrayerRequest.updateRequired (localDateNow ()) SmallGroup.empty
{ PrayerRequest.empty with { PrayerRequest.empty with RequestType = LongTermRequest; UpdatedDate = now - Duration.FromDays 14 }
RequestType = LongTermRequest |> Flip.Expect.isFalse "An active request updated 14 days ago should not require an update until 28 days"
UpdatedDate = now.AddDays -14.
}
Expect.isFalse (req.UpdateRequired now 7 4)
"An active request updated 14 days ago should not require an update until 28 days"
} }
test "UpdateRequired returns true when an update is required for an active request" { test "UpdateRequired returns true when an update is required for an active request" {
let now = DateTime.Now let now = instantNow ()
let req = PrayerRequest.updateRequired (localDateNow ()) SmallGroup.empty
{ PrayerRequest.empty with { PrayerRequest.empty with RequestType = LongTermRequest; UpdatedDate = now - Duration.FromDays 34 }
RequestType = LongTermRequest |> Flip.Expect.isTrue "An active request updated 34 days ago should require an update (past 28 days)"
UpdatedDate = now.AddDays -34.
}
Expect.isTrue (req.UpdateRequired now 7 4)
"An active request updated 34 days ago should require an update (past 28 days)"
} }
] ]
@ -288,9 +288,9 @@ let requestSortTests =
[<Tests>] [<Tests>]
let smallGroupTests = let smallGroupTests =
testList "SmallGroup" [ testList "SmallGroup" [
let now = DateTime (2017, 5, 12, 12, 15, 0, DateTimeKind.Utc) let now = Instant.FromDateTimeUtc (DateTime (2017, 5, 12, 12, 15, 0, DateTimeKind.Utc))
let withFakeClock f () = let withFakeClock f () =
FakeClock (Instant.FromDateTimeUtc now) |> f FakeClock now |> f
yield test "empty is as expected" { yield test "empty is as expected" {
let mt = SmallGroup.empty let mt = SmallGroup.empty
Expect.equal mt.Id.Value Guid.Empty "The small group ID should have been an empty GUID" Expect.equal mt.Id.Value Guid.Empty "The small group ID should have been an empty GUID"
@ -311,10 +311,12 @@ let smallGroupTests =
{ SmallGroup.empty with { SmallGroup.empty with
Preferences = { ListPreferences.empty with TimeZoneId = TimeZoneId "Europe/Berlin" } Preferences = { ListPreferences.empty with TimeZoneId = TimeZoneId "Europe/Berlin" }
} }
Expect.isGreaterThan (grp.LocalTimeNow clock) now "UTC to Europe/Berlin should have added hours" let tz = DateTimeZoneProviders.Tzdb["Europe/Berlin"]
Expect.isGreaterThan (SmallGroup.localTimeNow clock grp) (now.InUtc().LocalDateTime)
"UTC to Europe/Berlin should have added hours"
"LocalTimeNow adjusts the time behind UTC", "LocalTimeNow adjusts the time behind UTC",
fun clock -> fun clock ->
Expect.isLessThan (SmallGroup.empty.LocalTimeNow clock) now Expect.isLessThan (SmallGroup.localTimeNow clock SmallGroup.empty) (now.InUtc().LocalDateTime)
"UTC to America/Denver should have subtracted hours" "UTC to America/Denver should have subtracted hours"
"LocalTimeNow returns UTC when the time zone is invalid", "LocalTimeNow returns UTC when the time zone is invalid",
fun clock -> fun clock ->
@ -322,16 +324,17 @@ let smallGroupTests =
{ SmallGroup.empty with { SmallGroup.empty with
Preferences = { ListPreferences.empty with TimeZoneId = TimeZoneId "garbage" } Preferences = { ListPreferences.empty with TimeZoneId = TimeZoneId "garbage" }
} }
Expect.equal (grp.LocalTimeNow clock) now "UTC should have been returned for an invalid time zone" Expect.equal (SmallGroup.localTimeNow clock grp) (now.InUtc().LocalDateTime)
"UTC should have been returned for an invalid time zone"
] ]
yield test "LocalTimeNow fails when clock is not passed" { yield test "localTimeNow fails when clock is not passed" {
Expect.throws (fun () -> (SmallGroup.empty.LocalTimeNow >> ignore) null) Expect.throws (fun () -> (SmallGroup.localTimeNow null SmallGroup.empty |> ignore))
"Should have raised an exception for null clock" "Should have raised an exception for null clock"
} }
yield test "LocalDateNow returns the date portion" { yield test "LocalDateNow returns the date portion" {
let now' = DateTime (2017, 5, 12, 1, 15, 0, DateTimeKind.Utc) let clock = FakeClock (Instant.FromDateTimeUtc (DateTime (2017, 5, 12, 1, 15, 0, DateTimeKind.Utc)))
let clock = FakeClock (Instant.FromDateTimeUtc now') Expect.isLessThan (SmallGroup.localDateNow clock SmallGroup.empty) (now.InUtc().Date)
Expect.isLessThan (SmallGroup.empty.LocalDateNow clock) now.Date "The date should have been a day earlier" "The date should have been a day earlier"
} }
] ]

View File

@ -3,6 +3,7 @@
open System open System
open Expecto open Expecto
open Microsoft.AspNetCore.Html open Microsoft.AspNetCore.Html
open NodaTime
open PrayerTracker.Entities open PrayerTracker.Entities
open PrayerTracker.Tests.TestLocalization open PrayerTracker.Tests.TestLocalization
open PrayerTracker.Utils open PrayerTracker.Utils
@ -121,7 +122,7 @@ let appViewInfoTests =
Expect.isNone vi.HelpLink "The help link should have been set to none" Expect.isNone vi.HelpLink "The help link should have been set to none"
Expect.isEmpty vi.Messages "There should have been no messages set" Expect.isEmpty vi.Messages "There should have been no messages set"
Expect.equal vi.Version "" "The version should have been blank" Expect.equal vi.Version "" "The version should have been blank"
Expect.isGreaterThan vi.RequestStart DateTime.MinValue.Ticks "The request start time should have been set" Expect.equal vi.RequestStart Instant.MinValue "The request start time should have been the minimum value"
Expect.isNone vi.User "There should not have been a user" Expect.isNone vi.User "There should not have been a user"
Expect.isNone vi.Group "There should not have been a small group" Expect.isNone vi.Group "There should not have been a small group"
} }
@ -497,30 +498,31 @@ let messageLevelTests =
let requestListTests = let requestListTests =
testList "RequestList" [ testList "RequestList" [
let withRequestList f () = let withRequestList f () =
{ Requests = [ let today = SystemClock.Instance.GetCurrentInstant ()
{ PrayerRequest.empty with { Requests = [
RequestType = CurrentRequest { PrayerRequest.empty with
Requestor = Some "Zeb" RequestType = CurrentRequest
Text = "zyx" Requestor = Some "Zeb"
UpdatedDate = DateTime.Today Text = "zyx"
} UpdatedDate = today
{ PrayerRequest.empty with }
RequestType = CurrentRequest { PrayerRequest.empty with
Requestor = Some "Aaron" RequestType = CurrentRequest
Text = "abc" Requestor = Some "Aaron"
UpdatedDate = DateTime.Today - TimeSpan.FromDays 9. Text = "abc"
} UpdatedDate = today - Duration.FromDays 9
{ PrayerRequest.empty with }
RequestType = PraiseReport { PrayerRequest.empty with
Text = "nmo" RequestType = PraiseReport
UpdatedDate = DateTime.Today Text = "nmo"
} UpdatedDate = today
] }
Date = DateTime.Today ]
SmallGroup = SmallGroup.empty Date = today.InUtc().Date
ShowHeader = false SmallGroup = SmallGroup.empty
Recipients = [] ShowHeader = false
CanEmail = false Recipients = []
CanEmail = false
} }
|> f |> f
yield! testFixture withRequestList [ yield! testFixture withRequestList [
@ -574,7 +576,7 @@ let requestListTests =
[ """<div style="text-align:center;font-family:Century Gothic,Tahoma,Luxi Sans,sans-serif">""" [ """<div style="text-align:center;font-family:Century Gothic,Tahoma,Luxi Sans,sans-serif">"""
"""<span style="font-size:16pt;"><strong>Prayer Requests</strong></span><br>""" """<span style="font-size:16pt;"><strong>Prayer Requests</strong></span><br>"""
"""<span style="font-size:12pt;"><strong>Test HTML Group</strong><br>""" """<span style="font-size:12pt;"><strong>Test HTML Group</strong><br>"""
htmlList.Date.ToString "MMMM d, yyyy" htmlList.Date.ToString ("MMMM d, yyyy", null)
"</span></div><br>" "</span></div><br>"
] ]
|> String.concat "" |> String.concat ""
@ -592,7 +594,7 @@ let requestListTests =
} }
let html = htmlList.AsHtml _s let html = htmlList.AsHtml _s
let expected = let expected =
htmlList.Requests[0].UpdatedDate.ToShortDateString () htmlList.Requests[0].UpdatedDate.InUtc().Date.ToString ("d", null)
|> sprintf """<strong>Zeb</strong> &mdash; zyx<i style="font-size:9.60pt">&nbsp; (as of %s)</i>""" |> sprintf """<strong>Zeb</strong> &mdash; zyx<i style="font-size:9.60pt">&nbsp; (as of %s)</i>"""
// spot check; if one request has it, they all should // spot check; if one request has it, they all should
Expect.stringContains html expected "Expected short as-of date not found" Expect.stringContains html expected "Expected short as-of date not found"
@ -607,7 +609,7 @@ let requestListTests =
} }
let html = htmlList.AsHtml _s let html = htmlList.AsHtml _s
let expected = let expected =
htmlList.Requests[0].UpdatedDate.ToLongDateString () htmlList.Requests[0].UpdatedDate.InUtc().Date.ToString ("D", null)
|> sprintf """<strong>Zeb</strong> &mdash; zyx<i style="font-size:9.60pt">&nbsp; (as of %s)</i>""" |> sprintf """<strong>Zeb</strong> &mdash; zyx<i style="font-size:9.60pt">&nbsp; (as of %s)</i>"""
// spot check; if one request has it, they all should // spot check; if one request has it, they all should
Expect.stringContains html expected "Expected long as-of date not found" Expect.stringContains html expected "Expected long as-of date not found"
@ -617,7 +619,8 @@ let requestListTests =
let text = textList.AsText _s let text = textList.AsText _s
Expect.stringContains text $"{textList.SmallGroup.Name}\n" "Small group name not found" Expect.stringContains text $"{textList.SmallGroup.Name}\n" "Small group name not found"
Expect.stringContains text "Prayer Requests\n" "List heading not found" Expect.stringContains text "Prayer Requests\n" "List heading not found"
Expect.stringContains text ((textList.Date.ToString "MMMM d, yyyy") + "\n \n") "List date not found" Expect.stringContains text ((textList.Date.ToString ("MMMM d, yyyy", null)) + "\n \n")
"List date not found"
Expect.stringContains text "--------------------\n CURRENT REQUESTS\n--------------------\n" Expect.stringContains text "--------------------\n CURRENT REQUESTS\n--------------------\n"
"""Heading for category "Current Requests" not found""" """Heading for category "Current Requests" not found"""
Expect.stringContains text " + Zeb - zyx\n" "First request not found" Expect.stringContains text " + Zeb - zyx\n" "First request not found"
@ -637,7 +640,7 @@ let requestListTests =
} }
let text = textList.AsText _s let text = textList.AsText _s
let expected = let expected =
textList.Requests[0].UpdatedDate.ToShortDateString () textList.Requests[0].UpdatedDate.InUtc().Date.ToString ("d", null)
|> sprintf " + Zeb - zyx (as of %s)" |> sprintf " + Zeb - zyx (as of %s)"
// spot check; if one request has it, they all should // spot check; if one request has it, they all should
Expect.stringContains text expected "Expected short as-of date not found" Expect.stringContains text expected "Expected short as-of date not found"
@ -652,7 +655,7 @@ let requestListTests =
} }
let text = textList.AsText _s let text = textList.AsText _s
let expected = let expected =
textList.Requests[0].UpdatedDate.ToLongDateString () textList.Requests[0].UpdatedDate.InUtc().Date.ToString ("D", null)
|> sprintf " + Zeb - zyx (as of %s)" |> sprintf " + Zeb - zyx (as of %s)"
// spot check; if one request has it, they all should // spot check; if one request has it, they all should
Expect.stringContains text expected "Expected long as-of date not found" Expect.stringContains text expected "Expected long as-of date not found"

View File

@ -38,15 +38,13 @@ let edit (model : EditRequest) today ctx viewInfo =
inputField "date" (nameof model.EnteredDate) "" [ _placeholder today ] inputField "date" (nameof model.EnteredDate) "" [ _placeholder today ]
] ]
else else
// TODO: do these need to be nested like this?
div [ _inputField ] [ div [ _inputField ] [
br []
div [ _checkboxField ] [ div [ _checkboxField ] [
br []
inputField "checkbox" (nameof model.SkipDateUpdate) "True" [] inputField "checkbox" (nameof model.SkipDateUpdate) "True" []
label [ _for (nameof model.SkipDateUpdate) ] [ locStr s["Check to not update the date"] ] label [ _for (nameof model.SkipDateUpdate) ] [ locStr s["Check to not update the date"] ]
br []
small [] [ em [] [ str (s["Typo Corrections"].Value.ToLower ()); rawText ", etc." ] ]
] ]
small [] [ em [] [ str (s["Typo Corrections"].Value.ToLower ()); rawText ", etc." ] ]
] ]
] ]
div [ _fieldRow ] [ div [ _fieldRow ] [
@ -57,7 +55,7 @@ let edit (model : EditRequest) today ctx viewInfo =
let radioId = String.concat "_" [ nameof model.Expiration; fst exp ] let radioId = String.concat "_" [ nameof model.Expiration; fst exp ]
span [ _class "text-nowrap" ] [ span [ _class "text-nowrap" ] [
radio (nameof model.Expiration) radioId (fst exp) model.Expiration radio (nameof model.Expiration) radioId (fst exp) model.Expiration
label [ _for radioId ] [ locStr (snd exp) ] label [ _for radioId ] [ space; locStr (snd exp) ]
rawText " &nbsp; &nbsp; " rawText " &nbsp; &nbsp; "
]) ])
|> div [ _class "pt-center-text" ] |> div [ _class "pt-center-text" ]

View File

@ -768,7 +768,7 @@ with
/// Is this request new? /// Is this request new?
member this.IsNew (req : PrayerRequest) = member this.IsNew (req : PrayerRequest) =
let reqDate = req.UpdatedDate.InZone(SmallGroup.timeZone this.SmallGroup).Date let reqDate = req.UpdatedDate.InZone(SmallGroup.timeZone this.SmallGroup).Date
Period.Between(this.Date, reqDate, PeriodUnits.Days).Days <= this.SmallGroup.Preferences.DaysToKeepNew Period.Between(reqDate, this.Date, PeriodUnits.Days).Days <= this.SmallGroup.Preferences.DaysToKeepNew
/// Generate this list as HTML /// Generate this list as HTML
member this.AsHtml (s : IStringLocalizer) = member this.AsHtml (s : IStringLocalizer) =
@ -797,6 +797,7 @@ with
] ]
] ]
] ]
let tz = SmallGroup.timeZone this.SmallGroup
reqs reqs
|> List.map (fun req -> |> List.map (fun req ->
let bullet = if this.IsNew req then "circle" else "disc" let bullet = if this.IsNew req then "circle" else "disc"
@ -804,7 +805,7 @@ with
match req.Requestor with match req.Requestor with
| Some r when r <> "" -> | Some r when r <> "" ->
strong [] [ str r ] strong [] [ str r ]
rawText " &mdash; " rawText " &ndash; "
| Some _ -> () | Some _ -> ()
| None -> () | None -> ()
rawText req.Text rawText req.Text
@ -814,8 +815,8 @@ with
| LongDate -> | LongDate ->
let dt = let dt =
match p.AsOfDateDisplay with match p.AsOfDateDisplay with
| ShortDate -> req.UpdatedDate.ToString ("d", null) | ShortDate -> req.UpdatedDate.InZone(tz).Date.ToString ("d", null)
| LongDate -> req.UpdatedDate.ToString ("D", null) | LongDate -> req.UpdatedDate.InZone(tz).Date.ToString ("D", null)
| _ -> "" | _ -> ""
i [ _style $"font-size:%.2f{asOfSize}pt" ] [ i [ _style $"font-size:%.2f{asOfSize}pt" ] [
rawText "&nbsp; ("; str s["as of"].Value; str " "; str dt; rawText ")" rawText "&nbsp; ("; str s["as of"].Value; str " "; str dt; rawText ")"
@ -828,6 +829,7 @@ with
/// Generate this list as plain text /// Generate this list as plain text
member this.AsText (s : IStringLocalizer) = member this.AsText (s : IStringLocalizer) =
let tz = SmallGroup.timeZone this.SmallGroup
seq { seq {
this.SmallGroup.Name this.SmallGroup.Name
s["Prayer Requests"].Value s["Prayer Requests"].Value
@ -846,8 +848,8 @@ with
| _ -> | _ ->
let dt = let dt =
match this.SmallGroup.Preferences.AsOfDateDisplay with match this.SmallGroup.Preferences.AsOfDateDisplay with
| ShortDate -> req.UpdatedDate.ToString ("d", null) | ShortDate -> req.UpdatedDate.InZone(tz).Date.ToString ("d", null)
| LongDate -> req.UpdatedDate.ToString ("D", null) | LongDate -> req.UpdatedDate.InZone(tz).Date.ToString ("D", null)
| _ -> "" | _ -> ""
$""" ({s["as of"].Value} {dt})""" $""" ({s["as of"].Value} {dt})"""
|> sprintf " %s %s%s%s" bullet requestor (htmlToPlainText req.Text) |> sprintf " %s %s%s%s" bullet requestor (htmlToPlainText req.Text)

View File

@ -4,19 +4,26 @@ module PrayerTracker.Extensions
open Microsoft.AspNetCore.Http open Microsoft.AspNetCore.Http
open Microsoft.FSharpLu open Microsoft.FSharpLu
open Newtonsoft.Json open Newtonsoft.Json
open NodaTime
open NodaTime.Serialization.JsonNet
open PrayerTracker.Entities open PrayerTracker.Entities
open PrayerTracker.ViewModels open PrayerTracker.ViewModels
/// JSON.NET serializer settings for NodaTime
let private jsonSettings = JsonSerializerSettings().ConfigureForNodaTime DateTimeZoneProviders.Tzdb
/// Extensions on the .NET session object /// Extensions on the .NET session object
type ISession with type ISession with
/// Set an object in the session /// Set an object in the session
member this.SetObject key value = member this.SetObject key value =
this.SetString (key, JsonConvert.SerializeObject value) this.SetString (key, JsonConvert.SerializeObject (value, jsonSettings))
/// Get an object from the session /// Get an object from the session
member this.GetObject<'T> key = member this.GetObject<'T> key =
match this.GetString key with null -> Unchecked.defaultof<'T> | v -> JsonConvert.DeserializeObject<'T> v match this.GetString key with
| null -> Unchecked.defaultof<'T>
| v -> JsonConvert.DeserializeObject<'T> (v, jsonSettings)
/// The currently logged on small group /// The currently logged on small group
member this.CurrentGroup member this.CurrentGroup
@ -63,7 +70,6 @@ type ClaimsPrincipal with
open Giraffe open Giraffe
open NodaTime
open PrayerTracker open PrayerTracker
/// Extensions on the ASP.NET Core HTTP context /// Extensions on the ASP.NET Core HTTP context

View File

@ -27,6 +27,7 @@
<PackageReference Include="Giraffe" Version="6.0.0" /> <PackageReference Include="Giraffe" Version="6.0.0" />
<PackageReference Include="Giraffe.Htmx" Version="1.8.0" /> <PackageReference Include="Giraffe.Htmx" Version="1.8.0" />
<PackageReference Include="NeoSmart.Caching.Sqlite" Version="6.0.1" /> <PackageReference Include="NeoSmart.Caching.Sqlite" Version="6.0.1" />
<PackageReference Include="NodaTime.Serialization.JsonNet" Version="3.0.0" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.6" /> <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.6" />
<PackageReference Update="FSharp.Core" Version="6.0.5" /> <PackageReference Update="FSharp.Core" Version="6.0.5" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime" Version="6.0.6" /> <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime" Version="6.0.6" />

View File

@ -238,6 +238,7 @@ footer a:hover {
flex-flow: row wrap; flex-flow: row wrap;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
gap: 2rem;
} }
.pt-field { .pt-field {
display: flex; display: flex;
@ -255,9 +256,6 @@ footer a:hover {
text-transform: uppercase; text-transform: uppercase;
color: #777; color: #777;
} }
.pt-field ~ .pt-field {
margin-left: 3rem;
}
.pt-center-text { .pt-center-text {
text-align: center; text-align: center;
} }