Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
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
43 changes: 43 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,48 @@ 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"

// 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
Loading