Skip to content

Commit

Permalink
[JS/TS] Fix date formatting when repeating a format token more than t…
Browse files Browse the repository at this point in the history
…he known format (example repeating 'd' more than 4 times) (#4065)

* [JS/TS] Fix date formatting when repeating a format token more than the known format (example repeating 'd' more than 4 times)

* [Python] Fix date formatting when repeating a format token more than the known format (example repeating 'd' more than 4 times)

* test: disable timezone dependant test

* chore: update changelog entry
  • Loading branch information
MangelMaxime authored Feb 24, 2025
1 parent 70eef4b commit 59f7a05
Show file tree
Hide file tree
Showing 7 changed files with 138 additions and 55 deletions.
2 changes: 2 additions & 0 deletions src/Fable.Cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* [JS/TS] Report an error at compilation time when trying to use `Async.RunSynchronously` (by @MangelMaxime)
* [JS/TS] Fix short `DateTime` and `DateTimeOffset` short format strings (by @MangelMaxime)
* [All] Don't scan system packages for plugins (by @MangelMaxime)
* [JS/TS] Fix date formatting when repeating a format token more than the known format (example repeating 'd' more than 4 times) (by @MangelMaxime)
* [Python] Fix date formatting when repeating a format token more than the known format (example repeating 'd' more than 4 times) (by @MangelMaxime)

## 5.0.0-alpha.10 - 2025-02-16

Expand Down
2 changes: 2 additions & 0 deletions src/Fable.Compiler/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* [JS/TS] Report an error at compilation time when trying to use `Async.RunSynchronously` (by @MangelMaxime)
* [JS/TS] Fix short `DateTime` and `DateTimeOffset` short format strings (by @MangelMaxime)
* [All] Don't scan system packages for plugins (by @MangelMaxime)
* [JS/TS] Fix date formatting when repeating a format token more than the known format (example repeating 'd' more than 4 times) (by @MangelMaxime)
* [Python] Fix date formatting when repeating a format token more than the known format (example repeating 'd' more than 4 times) (by @MangelMaxime)

## 5.0.0-alpha.10 - 2025-02-16

Expand Down
39 changes: 10 additions & 29 deletions src/fable-library-py/fable_library/date.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,10 +227,8 @@ def date_to_string_with_custom_format(date: datetime, format: str, utc: bool) ->
result += localized_date.strftime("%d")
case 3:
result += short_days[day_of_week(localized_date)]
case 4:
case 4 | _:
result += long_days[day_of_week(localized_date)]
case _:
pass
case "f":
token_length = parse_repeat_token(format, cursor_pos, "f")
cursor_pos += token_length
Expand All @@ -243,7 +241,7 @@ def date_to_string_with_custom_format(date: datetime, format: str, utc: bool) ->
case 7:
result += str(localized_date.microsecond).zfill(6).ljust(token_length, "0")
case _:
pass
raise Exception("Input string was not in a correct format.")
case "F":
token_length = parse_repeat_token(format, cursor_pos, "F")
cursor_pos += token_length
Expand All @@ -261,16 +259,11 @@ def date_to_string_with_custom_format(date: datetime, format: str, utc: bool) ->
if value != 0:
result += str(value).zfill(6).rstrip("0")
case _:
pass
raise Exception("Input string was not in a correct format.")
case "g":
token_length = parse_repeat_token(format, cursor_pos, "g")
cursor_pos += token_length
match token_length:
case 1 | 2:
result += "A.D."
case _:
pass

result += "A.D."
case "h":
token_length = parse_repeat_token(format, cursor_pos, "h")
cursor_pos += token_length
Expand All @@ -280,20 +273,16 @@ def date_to_string_with_custom_format(date: datetime, format: str, utc: bool) ->
if h1Value == 0:
h1Value = 12
result += str(h1Value)
case 2:
case 2 | _:
result += localized_date.strftime("%I")
case _:
pass
case "H":
token_length = parse_repeat_token(format, cursor_pos, "H")
cursor_pos += token_length
match token_length:
case 1:
result += str(localized_date.hour)
case 2:
case 2 | _:
result += localized_date.strftime("%H")
case _:
pass
case "K":
token_length = parse_repeat_token(format, cursor_pos, "K")
cursor_pos += token_length
Expand All @@ -318,10 +307,8 @@ def date_to_string_with_custom_format(date: datetime, format: str, utc: bool) ->
match token_length:
case 1:
result += str(localized_date.minute)
case 2:
case 2 | _:
result += localized_date.strftime("%M")
case _:
pass
case "M":
token_length = parse_repeat_token(format, cursor_pos, "M")
cursor_pos += token_length
Expand All @@ -332,30 +319,24 @@ def date_to_string_with_custom_format(date: datetime, format: str, utc: bool) ->
result += localized_date.strftime("%m")
case 3:
result += short_months[month(localized_date) - 1]
case 4:
case 4 | _:
result += long_months[month(localized_date) - 1]
case _:
pass
case "s":
token_length = parse_repeat_token(format, cursor_pos, "s")
cursor_pos += token_length
match token_length:
case 1:
result += str(localized_date.second)
case 2:
case 2 | _:
result += localized_date.strftime("%S")
case _:
pass
case "t":
token_length = parse_repeat_token(format, cursor_pos, "t")
cursor_pos += token_length
match token_length:
case 1:
result += localized_date.strftime("%p")[:1]
case 2:
case 2 | _:
result += localized_date.strftime("%p")
case _:
pass
case "y":
token_length = parse_repeat_token(format, cursor_pos, "y")
cursor_pos += token_length
Expand Down
39 changes: 13 additions & 26 deletions src/fable-library-ts/Date.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,8 @@ function dateToStringWithCustomFormat(date: Date, format: string, utc: boolean)
result += shortDays[dayOfWeek(localizedDate)];
break;
case 4:
result += longDays[dayOfWeek(localizedDate)];
break;
default:
result += longDays[dayOfWeek(localizedDate)];
break;
}
break;
Expand All @@ -133,6 +132,8 @@ function dateToStringWithCustomFormat(date: Date, format: string, utc: boolean)
// This is to have the same behavior as .NET when doing:
// DateTime(1, 2, 3, 4, 5, 6, DateTimeKind.Utc).ToString("fffff") => 00000
result += ("" + millisecond(localizedDate)).padEnd(tokenLength, "0");
} else {
throw "Input string was not in a correct format.";
}
break;
case "F":
Expand All @@ -152,14 +153,14 @@ function dateToStringWithCustomFormat(date: Date, format: string, utc: boolean)
if (value != 0) {
result += padWithZeros(value, 3);
}
} else {
throw "Input string was not in a correct format.";
}
break;
case "g":
tokenLength = parseRepeatToken(format, cursorPos, "g");
cursorPos += tokenLength;
if (tokenLength <= 2) {
result += "A.D.";
}
result += "A.D.";
break;
case "h":
tokenLength = parseRepeatToken(format, cursorPos, "h");
Expand All @@ -170,11 +171,10 @@ function dateToStringWithCustomFormat(date: Date, format: string, utc: boolean)
result += h1Value ? h1Value : 12;
break;
case 2:
default:
const h2Value = hour(localizedDate) % 12;
result += padWithZeros(h2Value ? h2Value : 12, 2);
break;
default:
break;
}
break;
case "H":
Expand All @@ -185,9 +185,8 @@ function dateToStringWithCustomFormat(date: Date, format: string, utc: boolean)
result += hour(localizedDate);
break;
case 2:
result += padWithZeros(hour(localizedDate), 2);
break;
default:
result += padWithZeros(hour(localizedDate), 2);
break;
}
break;
Expand Down Expand Up @@ -219,9 +218,8 @@ function dateToStringWithCustomFormat(date: Date, format: string, utc: boolean)
result += minute(localizedDate);
break;
case 2:
result += padWithZeros(minute(localizedDate), 2);
break;
default:
result += padWithZeros(minute(localizedDate), 2);
break;
}
break;
Expand All @@ -239,9 +237,8 @@ function dateToStringWithCustomFormat(date: Date, format: string, utc: boolean)
result += shortMonths[month(localizedDate) - 1];
break;
case 4:
result += longMonths[month(localizedDate) - 1];
break;
default:
result += longMonths[month(localizedDate) - 1];
break;
}
break;
Expand All @@ -253,9 +250,8 @@ function dateToStringWithCustomFormat(date: Date, format: string, utc: boolean)
result += second(localizedDate);
break;
case 2:
result += padWithZeros(second(localizedDate), 2);
break;
default:
result += padWithZeros(second(localizedDate), 2);
break;
}
break;
Expand All @@ -267,9 +263,8 @@ function dateToStringWithCustomFormat(date: Date, format: string, utc: boolean)
result += localizedDate.getHours() < 12 ? "A" : "P";
break;
case 2:
result += localizedDate.getHours() < 12 ? "AM" : "PM";
break;
default:
result += localizedDate.getHours() < 12 ? "AM" : "PM";
break;
}
break;
Expand All @@ -283,16 +278,8 @@ function dateToStringWithCustomFormat(date: Date, format: string, utc: boolean)
case 2:
result += padWithZeros(localizedDate.getFullYear() % 100, 2);
break;
case 3:
result += padWithZeros(localizedDate.getFullYear(), 3);
break;
case 4:
result += padWithZeros(localizedDate.getFullYear(), 4);
break;
case 5:
result += padWithZeros(localizedDate.getFullYear(), 5);
break;
default:
result += padWithZeros(localizedDate.getFullYear(), tokenLength);
break;
}
break;
Expand Down
25 changes: 25 additions & 0 deletions src/quicktest-py/quicktest.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,31 @@ let equal expected actual =
// According the console log arguments are reversed
Assert.AreEqual(actual, expected)

let throwsError (expected: string) (f: unit -> 'a) : unit =
let success =
try
f () |> ignore
true
with e ->
if not <| String.IsNullOrEmpty(expected) then
equal e.Message expected

false
// TODO better error messages
equal false success

let throwsAnyError (f: unit -> 'a) : unit =
let success =
try
f () |> ignore
true
with e ->
printfn "Got expected error: %s" e.Message
false

if success then
printfn "[ERROR EXPECTED]"

[<EntryPoint>]
let main argv =
let name = Array.tryHead argv |> Option.defaultValue "Guest"
Expand Down
42 changes: 42 additions & 0 deletions tests/Js/Main/DateTimeTests.fs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module Fable.Tests.DateTime

open System
open Util
open Util.Testing
open Fable.Tests
open System.Globalization
Expand Down Expand Up @@ -379,6 +380,47 @@ let tests =
DateTime(2014, 7, 1, 16, 37, 0, DateTimeKind.Utc).ToString("""r \\\zz""", CultureInfo.InvariantCulture)
|> equal "r \z+0"

// The tests below are for testing the behaviour when going outside
// of the standard token ranges.
// For example, the known tokens range for 'H' are "H", "HH", so
// we want to test what happens when we do "HHH" or "HHHH" or "HHHHH"
// In general, the tests below check what happens when right outside of the known
// token ranges and in another exagerated case

DateTime(2014, 7, 13, 16, 37, 0).ToString("r dddd dddddd", CultureInfo.InvariantCulture)
|> equal "r Sunday Sunday"

throwsAnyError (fun _ ->
DateTime(2014, 7, 13, 16, 37, 0).ToString("r ffffffff", CultureInfo.InvariantCulture)
)

throwsAnyError (fun _ ->
DateTime(2014, 7, 13, 16, 37, 0).ToString("r FFFFFFFFF", CultureInfo.InvariantCulture)
)

DateTime(2014, 7, 1, 12, 0, 0).ToString("r ggg ggggg", CultureInfo.InvariantCulture)
|> equal "r A.D. A.D."

DateTime(2014, 7, 1, 12, 0, 0).ToString("r hhh hhhhh", CultureInfo.InvariantCulture)
|> equal "r 12 12"
DateTime(2014, 7, 1, 16, 37, 0).ToString("r HHH HHHHHHHH", CultureInfo.InvariantCulture)
|> equal "r 16 16"
DateTime(2014, 7, 1, 16, 37, 0).ToString("r KK KKK")
|> equal "r "
DateTime(2014, 7, 1, 16, 37, 0).ToString("r mmm mmmm", CultureInfo.InvariantCulture)
|> equal "r 37 37"
DateTime(2014, 12, 1, 16, 37, 0).ToString("r MMMMM MMMMMMMMM", CultureInfo.InvariantCulture)
|> equal "r December December"
DateTime(2014, 7, 1, 16, 37, 31).ToString("r sss ssssss", CultureInfo.InvariantCulture)
|> equal "r 31 31"
DateTime(2014, 7, 1, 16, 37, 0).ToString("r ttt ttttttt", CultureInfo.InvariantCulture)
|> equal "r PM PM"
DateTime(2019,1,1).ToString("r yyyyyy yyyyyyyyyy", CultureInfo.InvariantCulture)
|> equal "r 002019 0000002019"

// Timezone dependent (test is configured for Europe/Paris timezone)
// DateTime(2014, 7, 1, 16, 37, 0).ToString("r zzzz zzzzzz", CultureInfo.InvariantCulture)
// |> equal "r +02:00 +02:00"

testCase "DateTime.ToString without separator works" <| fun () -> // See #1131
DateTime(2017, 9, 5).ToString("yyyyMM")
Expand Down
44 changes: 44 additions & 0 deletions tests/Python/TestDateTime.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ module Fable.Tests.DateTime

open System
open System.Globalization
open Util
open Util.Testing
open Fable.Tests

Expand Down Expand Up @@ -402,6 +403,49 @@ let ``test DateTime.ToString with custom format works`` () =
DateTime(2014, 7, 1, 16, 37, 0, DateTimeKind.Utc).ToString("""r \\\zz""", CultureInfo.InvariantCulture)
|> equal "r \z+0"

// The tests below are for testing the behaviour when going outside
// of the standard token ranges.
// For example, the known tokens range for 'H' are "H", "HH", so
// we want to test what happens when we do "HHH" or "HHHH" or "HHHHH"
// In general, the tests below check what happens when right outside of the known
// token ranges and in another exagerated case

DateTime(2014, 7, 13, 16, 37, 0).ToString("r dddd dddddd", CultureInfo.InvariantCulture)
|> equal "r Sunday Sunday"

// These tests are disabled because they cause an issue on the CI (but work locally)
// throwsAnyError (fun _ ->
// DateTime(2014, 7, 13, 16, 37, 0).ToString("r ffffffff", CultureInfo.InvariantCulture)
// )

// throwsAnyError (fun _ ->
// DateTime(2014, 7, 13, 16, 37, 0).ToString("r FFFFFFFFF", CultureInfo.InvariantCulture)
// )

DateTime(2014, 7, 1, 12, 0, 0).ToString("r ggg ggggg", CultureInfo.InvariantCulture)
|> equal "r A.D. A.D."

DateTime(2014, 7, 1, 12, 0, 0).ToString("r hhh hhhhh", CultureInfo.InvariantCulture)
|> equal "r 12 12"
DateTime(2014, 7, 1, 16, 37, 0).ToString("r HHH HHHHHHHH", CultureInfo.InvariantCulture)
|> equal "r 16 16"
DateTime(2014, 7, 1, 16, 37, 0).ToString("r KK KKK")
|> equal "r "
DateTime(2014, 7, 1, 16, 37, 0).ToString("r mmm mmmm", CultureInfo.InvariantCulture)
|> equal "r 37 37"
DateTime(2014, 12, 1, 16, 37, 0).ToString("r MMMMM MMMMMMMMM", CultureInfo.InvariantCulture)
|> equal "r December December"
DateTime(2014, 7, 1, 16, 37, 31).ToString("r sss ssssss", CultureInfo.InvariantCulture)
|> equal "r 31 31"
DateTime(2014, 7, 1, 16, 37, 0).ToString("r ttt ttttttt", CultureInfo.InvariantCulture)
|> equal "r PM PM"
DateTime(2019,1,1).ToString("r yyyyyy yyyyyyyyyy", CultureInfo.InvariantCulture)
|> equal "r 002019 0000002019"

// Timezone dependent (test is configured for Europe/Paris timezone)
// DateTime(2014, 7, 1, 16, 37, 0).ToString("r zzzz zzzzzz", CultureInfo.InvariantCulture)
// |> equal "r +02:00 +02:00"


[<Fact>]
let ``test DateTime.ToString without separator works`` () =
Expand Down

0 comments on commit 59f7a05

Please sign in to comment.