diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 0f8006030a..3b47477b69 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -26,7 +26,7 @@ RUN apt-get update && apt-get install -y xz-utils # Let it here to avoid conflicts with Microsoft options !!! ARG DOTNET_VERSION=8.0.100 ARG NODE_VERSION=18.8.0 -ARG PYTHON_VERSION=3.11.5 +ARG PYTHON_VERSION=3.10.0 ARG DART_VERSION=3.1.2 USER vscode @@ -38,8 +38,20 @@ SHELL ["/bin/zsh", "-c"] WORKDIR /home/vscode RUN sudo apt install wget RUN wget -qO- https://dot.net/v1/dotnet-install.sh | bash -s - --version $DOTNET_VERSION +# Because .NET doesn't setup the path for us, we need to do it +# Bash is the default shell for the dev container, we need it for the extensions +# to work properly +RUN echo "# Load dotnet path" >> .bashrc +RUN echo "export DOTNET_ROOT=/home/vscode/.dotnet" >> .bashrc +RUN echo 'export PATH=$PATH:$DOTNET_ROOT:~/.dotnet/tools' >> .bashrc +RUN echo 'export DOTNET_RUNNING_IN_CONTAINER=true' >> .bashrc +Run echo 'export DOTNET_USE_POLLING_FILE_WATCHER=true' >> .bashrc +# Zsh is the shell used by the user, so we need to setup the path for it too RUN echo "# Load dotnet path" >> .zshrc -RUN echo "export PATH=$PATH:/home/vscode/.dotnet" >> .zshrc +RUN echo "export DOTNET_ROOT=/home/vscode/.dotnet" >> .zshrc +RUN echo 'export PATH=$PATH:$DOTNET_ROOT:~/.dotnet/tools' >> .zshrc +RUN echo 'export DOTNET_RUNNING_IN_CONTAINER=true' >> .zshrc +Run echo 'export DOTNET_USE_POLLING_FILE_WATCHER=true' >> .zshrc # Trigger the dotnet first run experience by running a command RUN source .zshrc && dotnet --help # Add dotnet zsh completion @@ -60,10 +72,6 @@ RUN echo '[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # RUN source .zshrc && nvm install $NODE_VERSION # Install python -# Install Python build dependencies -RUN sudo apt-get install -y build-essential libssl-dev zlib1g-dev \ -libbz2-dev libreadline-dev libsqlite3-dev curl \ -libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev # Install pyenv RUN curl https://pyenv.run | bash RUN echo "# Load pyenv path" >> .zshrc diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index e6495d66d9..19b4c3cea0 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -24,16 +24,7 @@ "tamasfe.even-better-toml" ], "settings": { - "terminal.integrated.defaultProfile.linux": "zsh", - // I don't know why but Ionide and C# extension don't find - // the installed dotnet version. - // For Ionide, we guide it using the following setting - "FSharp.dotnetRoot": "/home/vscode/.dotnet", - // For C#, we disable the warning as we don't seems to need it. - // If this is a problem, we can probably use the C#/F# dockerfile - // as the base and keep our way to install the different target version - // .NET included to respect the global.json requirements - "csharp.suppressDotnetInstallWarning": true + "terminal.integrated.defaultProfile.linux": "zsh" } } }, diff --git a/poetry.lock b/poetry.lock index 1518019d30..1e6fc5ca6a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.0 and should not be changed by hand. [[package]] name = "colorama" @@ -13,13 +13,13 @@ files = [ [[package]] name = "exceptiongroup" -version = "1.1.3" +version = "1.2.0" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, - {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, + {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, + {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, ] [package.extras] @@ -64,13 +64,13 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pytest" -version = "7.4.3" +version = "7.4.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"}, - {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"}, + {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, + {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, ] [package.dependencies] @@ -100,28 +100,28 @@ six = ">=1.5" [[package]] name = "ruff" -version = "0.1.5" +version = "0.1.14" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.1.5-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:32d47fc69261c21a4c48916f16ca272bf2f273eb635d91c65d5cd548bf1f3d96"}, - {file = "ruff-0.1.5-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:171276c1df6c07fa0597fb946139ced1c2978f4f0b8254f201281729981f3c17"}, - {file = "ruff-0.1.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17ef33cd0bb7316ca65649fc748acc1406dfa4da96a3d0cde6d52f2e866c7b39"}, - {file = "ruff-0.1.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b2c205827b3f8c13b4a432e9585750b93fd907986fe1aec62b2a02cf4401eee6"}, - {file = "ruff-0.1.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bb408e3a2ad8f6881d0f2e7ad70cddb3ed9f200eb3517a91a245bbe27101d379"}, - {file = "ruff-0.1.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f20dc5e5905ddb407060ca27267c7174f532375c08076d1a953cf7bb016f5a24"}, - {file = "ruff-0.1.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aafb9d2b671ed934998e881e2c0f5845a4295e84e719359c71c39a5363cccc91"}, - {file = "ruff-0.1.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a4894dddb476597a0ba4473d72a23151b8b3b0b5f958f2cf4d3f1c572cdb7af7"}, - {file = "ruff-0.1.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a00a7ec893f665ed60008c70fe9eeb58d210e6b4d83ec6654a9904871f982a2a"}, - {file = "ruff-0.1.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a8c11206b47f283cbda399a654fd0178d7a389e631f19f51da15cbe631480c5b"}, - {file = "ruff-0.1.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:fa29e67b3284b9a79b1a85ee66e293a94ac6b7bb068b307a8a373c3d343aa8ec"}, - {file = "ruff-0.1.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9b97fd6da44d6cceb188147b68db69a5741fbc736465b5cea3928fdac0bc1aeb"}, - {file = "ruff-0.1.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:721f4b9d3b4161df8dc9f09aa8562e39d14e55a4dbaa451a8e55bdc9590e20f4"}, - {file = "ruff-0.1.5-py3-none-win32.whl", hash = "sha256:f80c73bba6bc69e4fdc73b3991db0b546ce641bdcd5b07210b8ad6f64c79f1ab"}, - {file = "ruff-0.1.5-py3-none-win_amd64.whl", hash = "sha256:c21fe20ee7d76206d290a76271c1af7a5096bc4c73ab9383ed2ad35f852a0087"}, - {file = "ruff-0.1.5-py3-none-win_arm64.whl", hash = "sha256:82bfcb9927e88c1ed50f49ac6c9728dab3ea451212693fe40d08d314663e412f"}, - {file = "ruff-0.1.5.tar.gz", hash = "sha256:5cbec0ef2ae1748fb194f420fb03fb2c25c3258c86129af7172ff8f198f125ab"}, + {file = "ruff-0.1.14-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:96f76536df9b26622755c12ed8680f159817be2f725c17ed9305b472a757cdbb"}, + {file = "ruff-0.1.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ab3f71f64498c7241123bb5a768544cf42821d2a537f894b22457a543d3ca7a9"}, + {file = "ruff-0.1.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7060156ecc572b8f984fd20fd8b0fcb692dd5d837b7606e968334ab7ff0090ab"}, + {file = "ruff-0.1.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a53d8e35313d7b67eb3db15a66c08434809107659226a90dcd7acb2afa55faea"}, + {file = "ruff-0.1.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bea9be712b8f5b4ebed40e1949379cfb2a7d907f42921cf9ab3aae07e6fba9eb"}, + {file = "ruff-0.1.14-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:2270504d629a0b064247983cbc495bed277f372fb9eaba41e5cf51f7ba705a6a"}, + {file = "ruff-0.1.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80258bb3b8909b1700610dfabef7876423eed1bc930fe177c71c414921898efa"}, + {file = "ruff-0.1.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:653230dd00aaf449eb5ff25d10a6e03bc3006813e2cb99799e568f55482e5cae"}, + {file = "ruff-0.1.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87b3acc6c4e6928459ba9eb7459dd4f0c4bf266a053c863d72a44c33246bfdbf"}, + {file = "ruff-0.1.14-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6b3dadc9522d0eccc060699a9816e8127b27addbb4697fc0c08611e4e6aeb8b5"}, + {file = "ruff-0.1.14-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1c8eca1a47b4150dc0fbec7fe68fc91c695aed798532a18dbb1424e61e9b721f"}, + {file = "ruff-0.1.14-py3-none-musllinux_1_2_i686.whl", hash = "sha256:62ce2ae46303ee896fc6811f63d6dabf8d9c389da0f3e3f2bce8bc7f15ef5488"}, + {file = "ruff-0.1.14-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b2027dde79d217b211d725fc833e8965dc90a16d0d3213f1298f97465956661b"}, + {file = "ruff-0.1.14-py3-none-win32.whl", hash = "sha256:722bafc299145575a63bbd6b5069cb643eaa62546a5b6398f82b3e4403329cab"}, + {file = "ruff-0.1.14-py3-none-win_amd64.whl", hash = "sha256:e3d241aa61f92b0805a7082bd89a9990826448e4d0398f0e2bc8f05c75c63d99"}, + {file = "ruff-0.1.14-py3-none-win_arm64.whl", hash = "sha256:269302b31ade4cde6cf6f9dd58ea593773a37ed3f7b97e793c8594b262466b67"}, + {file = "ruff-0.1.14.tar.gz", hash = "sha256:ad3f8088b2dfd884820289a06ab718cde7d38b94972212cc4ba90d5fbc9955f3"}, ] [[package]] diff --git a/src/Fable.Cli/CHANGELOG.md b/src/Fable.Cli/CHANGELOG.md index a06669092a..ddc10fbdb0 100644 --- a/src/Fable.Cli/CHANGELOG.md +++ b/src/Fable.Cli/CHANGELOG.md @@ -17,6 +17,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Fixed nested type with custom hashcode (by @dbrattli) * Add 'Double.IsPositiveInfinity' (by @PierreYvesR) +* [GH-3666](https://github.com/fable-compiler/Fable/pull/3666) Fix for `DateTime` and `TimeSpan` addition (by @dbrattli) +* [GH-3663](https://github.com/fable-compiler/Fable/pull/3663) Fix `DateTime.Parse` and `DateTime.TryParse` (by @MangelMaxime) + +#### JavaScript + +* Fix `DateTime.Parse` when providing a 1 digit hour for PM times (`3:5:34 PM`) (by @MangelMaxime) #### Rust @@ -24,6 +30,60 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Fixed excluding signature files from imports (by @ncave) * Fixed generic try_catch closure trait (by @ncave) +#### Dart + +* Fix `DateTime.DayOfWeek` (by @MangelMaxime) + +### Added + +#### Python + +* [GH-3663](https://github.com/fable-compiler/Fable/pull/3663) Complete rewrite of `DateTime` supports (by @MangelMaxime) + + *Special thanks to @dbrattli and @ncave for their help* + + * Constructors + * From `(year, month, day)` up to `(year, month, day, hour, minute, second, millisecond, microsecond)` (with and without `DateTimeKind`) + * From `ticks` (with and without `DateTimeKind`) + * Instance methods: + * `dt.Year` + * `dt.Month` + * `dt.Day` + * `dt.Hour` + * `dt.Minute` + * `dt.Second` + * `dt.Millisecond` + * `dt.Microsecond` + * `dt.ToUniversalTime` + * `dt.DayOfWeek` + * `dt.DayOfYear` + * `dt.ToShortDateString` + * `dt.ToShortTimeString` + * `dt.ToLongDateString` + * `dt.ToLongTimeString` + * `dt.ToString` + * `dt.ToLocalTime` + * `dt.Date` + * `dt.AddYears` + * `dt.AddMonths` + * `dt.AddDays` + * `dt.AddHours` + * `dt.AddMinutes` + * `dt.AddSeconds` + * `dt.AddMilliseconds` + * `dt.AddMicroseconds` + * `dt.Kind` + * Static methods: + * `DateTime.Today` + * `DateTime.Now` + * `DateTime.Now` + * `DateTime.UtcNow` + * `DateTime.MinValue` + * `DateTime.MaxValue` + * `DateTime.Parse` + * `DateTime.TryParse` + * `DateTime.SpecifyKind` + ## 4.9.0 - 2023-12-14 ### Fixed @@ -32,7 +92,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * [GH-3655](https://github.com/fable-compiler/Fable/issues/3655) Fix for Python output file names (by @dbrattli) * [GH-3660](https://github.com/fable-compiler/Fable/issues/3660) Fix for decimal to string with culture (by @dbrattli) -* [GH-3666](https://github.com/fable-compiler/Fable/pull/3666) Fix for `DateTime` and `TimeSpan` addition (by @dbrattli) ## 4.8.1 - 2023-12-12 diff --git a/src/Fable.Transforms/Python/Replacements.fs b/src/Fable.Transforms/Python/Replacements.fs index 496f8ed033..0e1dca575e 100644 --- a/src/Fable.Transforms/Python/Replacements.fs +++ b/src/Fable.Transforms/Python/Replacements.fs @@ -4953,6 +4953,56 @@ let debug IfThenElse(cond, makeDebugger r, unit, r) |> Some | _ -> None +let private ignoreFormatProvider + com + (ctx: Context) + r + (moduleName: string) + meth + args + = + match meth, args with + // Ignore IFormatProvider + | "Parse", arg :: _culture :: _styles :: _ -> + addWarning + com + ctx.InlinePath + r + $"%s{moduleName}.Parse will ignore culture and styles" + + [ arg ] + | "Parse", arg :: _culture :: _ -> + addWarning + com + ctx.InlinePath + r + $"%s{moduleName}.Parse will ignore culture" + + [ arg ] + | "TryParse", input :: _culture :: _styles :: defVal :: _ -> + addWarning + com + ctx.InlinePath + r + $"%s{moduleName}.TryParse will ignore culture and styles" + + [ + input + defVal + ] + | "TryParse", input :: _culture :: defVal :: _ -> + addWarning + com + ctx.InlinePath + r + $"%s{moduleName}.TryParse will ignore culture" + + [ + input + defVal + ] + | _ -> args + let dates (com: ICompiler) (ctx: Context) @@ -5009,12 +5059,41 @@ let dates let args = (List.take 6 args) @ [ + makeIntConst 0 makeIntConst 0 last ] let argTypes = (List.take 6 i.SignatureArgTypes) + @ [ + Int32.Number + Int32.Number + last.Type + ] + + Helper.LibCall( + com, + "Date", + "create", + t, + args, + argTypes, + ?loc = r + ) + |> Some + | 8, Number(_, NumberInfo.IsEnum ent) when + ent.FullName = "System.DateTimeKind" + -> + let args = + (List.take 7 args) + @ [ + makeIntConst 0 + last + ] + + let argTypes = + (List.take 7 i.SignatureArgTypes) @ [ Int32.Number last.Type @@ -5053,7 +5132,6 @@ let dates ?loc = r ) |> Some - | "get_Kind" | "get_Offset" -> Naming.removeGetSetPrefix i.CompiledName |> Naming.lowerFirst @@ -5146,17 +5224,6 @@ let dates [ ms ] Helper.LibCall(com, "Long", "fromNumber", t, args, ?loc = r) |> Some - | "get_Ticks" -> - Helper.LibCall( - com, - "Date", - "getTicks", - t, - [ thisArg.Value ], - [ thisArg.Value.Type ], - ?loc = r - ) - |> Some | "get_UtcTicks" -> Helper.LibCall( com, @@ -5215,6 +5282,9 @@ let dates |> Some | _ -> None | meth -> + let args = + ignoreFormatProvider com ctx r i.DeclaringEntityFullName meth args + let meth = Naming.removeGetSetPrefix meth |> Naming.lowerFirst Helper.LibCall( diff --git a/src/fable-library-dart/Date.dart b/src/fable-library-dart/Date.dart index ebec219fbd..d207eba7b9 100644 --- a/src/fable-library-dart/Date.dart +++ b/src/fable-library-dart/Date.dart @@ -176,7 +176,7 @@ int day(DateTime d) => d.day; int hour(DateTime d) => d.hour; int minute(DateTime d) => d.minute; int millisecond(DateTime d) => d.millisecond; -int dayOfWeek(DateTime d) => d.weekday; +int dayOfWeek(DateTime d) => d.weekday % 7; int dayOfYear(DateTime d) { final _year = d.year; diff --git a/src/fable-library-py/fable_library/date.py b/src/fable-library-py/fable_library/date.py index 756e6ab6c1..0002f5d28e 100644 --- a/src/fable-library-py/fable_library/date.py +++ b/src/fable-library-py/fable_library/date.py @@ -2,16 +2,49 @@ import re from datetime import datetime, timedelta, timezone -from re import Match +from math import fmod from typing import Any +from .singleton_local_time_zone import local_time_zone from .time_span import TimeSpan, total_microseconds from .time_span import create as create_time_span from .types import FSharpRef from .util import DateKind -FORMAT_REGEXP = re.compile(r"(\w)\1*") +short_days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] + +long_days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"] + +short_months = [ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", +] + +long_months = [ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", +] def subtract(x: datetime, y: datetime | TimeSpan) -> datetime | TimeSpan: @@ -37,6 +70,7 @@ def create( m: int = 0, s: int = 0, ms: int = 0, + mc: int = 0, kind: DateKind | None = None, ) -> datetime: if kind == DateKind.UTC: @@ -47,11 +81,13 @@ def create( hour=h, minute=m, second=s, - microsecond=ms * 1000, + microsecond=mc + ms * 1_000, tzinfo=timezone.utc, ) else: - date = datetime(year, month, day, h, m, s, ms * 1000) + date = datetime(year, month, day, h, m, s, mc + ms * 1_000) + if kind == DateKind.Local: + date = date.astimezone(local_time_zone) return date @@ -60,98 +96,321 @@ def year(d: datetime) -> int: return d.year +def month(d: datetime) -> int: + return d.month + + +def day(d: datetime) -> int: + return d.day + + +def hour(d: datetime) -> int: + return d.hour + + +def minute(d: datetime) -> int: + return d.minute + + +def second(d: datetime) -> int: + return d.second + + +def millisecond(d: datetime) -> int: + return d.microsecond // 1_000 + + +def microsecond(d: datetime) -> int: + return d.microsecond + + def to_universal_time(d: datetime) -> datetime: return d.astimezone(timezone.utc) +def day_of_week(d: datetime) -> int: + return (d.weekday() + 1) % 7 + + +def day_of_year(d: datetime) -> int: + return d.timetuple().tm_yday + + +def to_short_date_string(date: datetime) -> str: + return datetime.strftime(date, "%m/%d/%Y") + + +def to_long_date_string(date: datetime) -> str: + return datetime.strftime(date, "%A, %d %B %Y") + + +def to_short_time_string(date: datetime) -> str: + return datetime.strftime(date, "%H:%M") + + +def to_long_time_string(date: datetime) -> str: + return datetime.strftime(date, "%H:%M:%S") + + +def parse_repeat_token(format: str, pos: int, pattern_char: str) -> int: + token_length = 0 + internal_pos = pos + while internal_pos < len(format) and format[internal_pos] == pattern_char: + internal_pos += 1 + token_length += 1 + + return token_length + + +# Get the next character at the index of 'pos' in the 'format' string. +# Return value of -1 means 'pos' is already at the end of the 'format' string. +# Otherwise, return value is the int value of the next character. +def parse_next_char(format: str, pos: int) -> int: + if pos >= len(format) - 1: + return -1 + + return ord(format[pos + 1]) + + +def parse_quoted_string(format: str, pos: int) -> tuple[str, int]: + begin_pos = pos + format_length = len(format) + # Get the character used to quote the string + quote_char = format[pos] + + result = "" + found_quote = False + + while pos < format_length: + pos += 1 + current_char = format[pos] + if current_char == quote_char: + found_quote = True + break + elif current_char == "\\": + if pos < format_length: + pos += 1 + result += format[pos] + else: + # This means that '\'is the last character in the format string. + raise ValueError("Invalid string format") + else: + result += current_char + + if not found_quote: + # We couldn't find the matching quote + raise ValueError(f"Invalid string format could not find matching quote for {quote_char}") + + return (result, pos - begin_pos + 1) + + def date_to_string_with_custom_format(date: datetime, format: str, utc: bool) -> str: - def match(match: Match[str]) -> str: - group = match.group() - m = group[:1] - rep = None - - if m == "y": - y = date.astimezone(timezone.utc).year if utc else date.year - rep = y % 100 if len(group) < 4 else y - elif m == "M": - rep = date.astimezone(timezone.utc).month if utc else date.month - elif m == "H": - rep = date.astimezone(timezone.utc).hour if utc else date.hour - elif m == "m": - rep = date.astimezone(timezone.utc).minute if utc else date.minute - elif m == "s": - rep = date.astimezone(timezone.utc).second if utc else date.second - elif m == "f": - rep = date.astimezone(timezone.utc).microsecond if utc else date.microsecond - rep = rep // 1000 - - if rep: - return f"0{rep}" if (rep < 10 and len(group) > 1) else f"{rep}" - - return group - - ret = FORMAT_REGEXP.sub(match, format) - return ret - - # return format.replace(/(\w)\1*/g, (match) => { - # let rep = Number.NaN; - # switch (match.substring(0, 1)) { - # case "y": - # const y = utc ? date.getUTCFullYear() : date.getFullYear(); - # rep = match.length < 4 ? y % 100 : y; - # break; - # case "M": - # rep = (utc ? date.getUTCMonth() : date.getMonth()) + 1; - # break; - # case "d": - # rep = utc ? date.getUTCDate() : date.getDate(); - # break; - # case "H": - # rep = utc ? date.getUTCHours() : date.getHours(); - # break; - # case "h": - # const h = utc ? date.getUTCHours() : date.getHours(); - # rep = h > 12 ? h % 12 : h; - # break; - # case "m": - # rep = utc ? date.getUTCMinutes() : date.getMinutes(); - # break; - # case "s": - # rep = utc ? date.getUTCSeconds() : date.getSeconds(); - # break; - # case "f": - # rep = utc ? date.getUTCMilliseconds() : date.getMilliseconds(); - # break; - # } - # if (Number.isNaN(rep)) { - # return match; - # } - # else { - # return (rep < 10 && match.length > 1) ? "0" + rep : "" + rep; - # } - - -# def dateToStringWithOffset(date, format=None): -# d = new Date(date.getTime() + ((_a = date.offset) !== null && _a !== void 0 ? _a : 0)); -# if (typeof format !== "string") { -# return d.toISOString().replace(/\.\d+/, "").replace(/[A-Z]|\.\d+/g, " ") + dateOffsetToString(((_b = date.offset) !== null && _b !== void 0 ? _b : 0)); -# } -# else if (format.length === 1) { -# switch (format) { -# case "D": -# case "d": return dateToHalfUTCString(d, "first"); -# case "T": -# case "t": return dateToHalfUTCString(d, "second"); -# case "O": -# case "o": return dateToISOStringWithOffset(d, ((_c = date.offset) !== null && _c !== void 0 ? _c : 0)); -# default: throw new Error("Unrecognized Date print format"); -# } - -# else: -# return dateToStringWithCustomFormat(d, format, True) + cursor_pos = 0 + token_length = 0 + result = "" + localized_date = date.astimezone(timezone.utc) if utc else date + + while cursor_pos < len(format): + token = format[cursor_pos] + + match token: + case "d": + token_length = parse_repeat_token(format, cursor_pos, "d") + cursor_pos += token_length + match token_length: + case 1: + result += str(localized_date.day) + case 2: + result += localized_date.strftime("%d") + case 3: + result += short_days[day_of_week(localized_date)] + 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 + match token_length: + case 1 | 2 | 3 | 4 | 5 | 6: + precision = 10 ** (6 - token_length) + result += str(localized_date.microsecond // precision).zfill(token_length) + # Python datetime only support precision up to the microsecond + # so we can't support fffffff + case _: + pass + case "F": + token_length = parse_repeat_token(format, cursor_pos, "F") + cursor_pos += token_length + match token_length: + case 1 | 2 | 3 | 4 | 5 | 6: + precision = 10 ** (6 - token_length) + value = localized_date.microsecond // precision + if value != 0: + result += str(value).zfill(token_length) + # Python datetime only support precision up to the microsecond + # so we can't support FFFFFFF + case _: + pass + 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 + + 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 % 12) + 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: + result += localized_date.strftime("%H") + case _: + pass + case "K": + token_length = parse_repeat_token(format, cursor_pos, "K") + cursor_pos += token_length + match token_length: + case 1: + match kind(date): + case DateKind.UTC: + result += "Z" + case DateKind.Local: + # %:z is not a perfect match for the .NET equivalent + # but it seems to do the job + # If needed we can probably compute the offset manually + # and format it ourselves + result += date.strftime("%:z") + case DateKind.Unspecified: + result += "" + case _: + pass + case "m": + token_length = parse_repeat_token(format, cursor_pos, "m") + cursor_pos += token_length + match token_length: + case 1: + result += str(localized_date.minute) + case 2: + result += localized_date.strftime("%M") + case _: + pass + case "M": + token_length = parse_repeat_token(format, cursor_pos, "M") + cursor_pos += token_length + match token_length: + case 1: + result += str(localized_date.month) + case 2: + result += localized_date.strftime("%m") + case 3: + result += short_months[month(localized_date) - 1] + 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: + 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: + result += localized_date.strftime("%p") + case _: + pass + case "y": + token_length = parse_repeat_token(format, cursor_pos, "y") + cursor_pos += token_length + match token_length: + case 1: + result += str(localized_date.year % 100) + case 2: + result += str(localized_date.year)[-2:].zfill(2) + case length: + result += str(localized_date.year).zfill(length) + case "z": + token_length = parse_repeat_token(format, cursor_pos, "z") + cursor_pos += token_length + + match kind(date): + case DateKind.UTC: + utc_offet_text = localized_date.strftime("%z") + case DateKind.Local: + utc_offet_text = localized_date.strftime("%z") + case DateKind.Unspecified: + utc_offet_text = to_local_time(date).strftime("%z") + + sign = utc_offet_text[:1] + hours = int(utc_offet_text[1:3]) + minutes = int(utc_offet_text[3:5]) + + match token_length: + case 1: + result += f"{sign}{hours}" + case 2: + result += f"{sign}{hours:02}" + case 3 | _: + result += f"{sign}{hours:02}:{minutes:02}" + case ":": + cursor_pos += 1 + result += ":" + case "/": + cursor_pos += 1 + result += "/" + case "'" | '"': + quoted_string, quoted_string_length = parse_quoted_string(format, cursor_pos) + cursor_pos += quoted_string_length + result += quoted_string + case "%": + next_char = parse_next_char(format, cursor_pos) + if next_char >= 0 and next_char != ord("%"): + cursor_pos += 2 + result += date_to_string_with_custom_format(date, chr(next_char), utc) + else: + raise Exception("Invalid string format") + case "\\": + next_char = parse_next_char(format, cursor_pos) + if next_char >= 0: + result += chr(next_char) + cursor_pos += 2 + else: + raise Exception("Invalid string format") + case _: + cursor_pos += 1 + result += token + pass + + return result def date_to_string_with_offset(date: datetime, format: str | None = None) -> str: + utc = date.tzinfo == timezone.utc + match format: case None: raise NotImplementedError("date_to_string_with_offset") @@ -160,19 +419,23 @@ def date_to_string_with_offset(date: datetime, format: str | None = None) -> str case _ if len(format) == 1: raise Exception("Unrecognized Date print format") case _: - return date_to_string_with_custom_format(date, format, True) + return date_to_string_with_custom_format(date, format, utc) def date_to_string_with_kind(date: datetime, format: str | None = None) -> str: utc = date.tzinfo == timezone.utc + if not format: return date.isoformat() if utc else str(date) - elif len(format) == 1: - if format == "D" or format == "d": - return dateToHalfUTCString(date, "first") if utc else str(date.date()) - elif format == "T" or format == "t": - return dateToHalfUTCString(date, "second") if utc else str(date.time()) + if format == "d": + return to_short_date_string(date) + elif format == "D": + return to_long_date_string(date) + elif format == "T": + return to_long_time_string(date) + elif format == "t": + return to_short_time_string(date) elif format == "O" or format == "o": return date.astimezone().isoformat(timespec="milliseconds") else: @@ -197,8 +460,12 @@ def utc_now() -> datetime: return datetime.utcnow() +def today() -> datetime: + return datetime.replace(now(), hour=0, minute=0, second=0, microsecond=0) + + def to_local_time(date: datetime) -> datetime: - return date.astimezone() + return date.astimezone().replace(tzinfo=local_time_zone) def compare(x: datetime, y: datetime) -> int: @@ -231,43 +498,272 @@ def add(x: datetime, y: TimeSpan) -> datetime: return op_addition(x, y) -def parse(string: str, detectUTC: bool = False) -> datetime: - from dateutil import parser - - return parser.parse(string) - - -def try_parse(string: str, style: int, unsigned: bool, bitsize: int, defValue: FSharpRef[datetime]) -> bool: +def parse(string: str) -> datetime: + try: + return datetime.fromisoformat(string).astimezone() + except ValueError: + pass + + # For the time-only formats, needs a special treatment + # because in Python, they are set in 1900-01-01 while + # in F# they are set in the current date + hoursFormats = { + # Matches a time string in 24-hour format "HH:MM:SS" + r"^\d{1,2}:\d{1,2}:\d{2}$": "%H:%M:%S", + # Matches a time string in 12-hour format with AM/PM "HH:MM:SS AM" or "HH:MM:SS PM" + r"^(0?[1-9]|1[0-2]):([0-5][1-9]|0?[0-9]):([0-5][0-9]|0?[0-9]) [AP]M$": "%I:%M:%S %p", + r"^\d{1,2}:\d{1,2}:\d{2} [AP]M$": "%H:%M:%S %p", + } + + for pattern, format in hoursFormats.items(): + if re.fullmatch(pattern, string): + hourDate = datetime.strptime(string, format) + + # If the hour is 0 PM, then in .NET it is 12 PM (python keeps it as 0) + hourOffset = 12 if hourDate.hour == 0 and string.endswith(" PM") else 0 + + return datetime.replace( + now(), + hour=hourDate.hour + hourOffset, + minute=hourDate.minute, + second=hourDate.second, + microsecond=0, + ) + + formats = { + # 9/10/2014 1:50:34 PM + r"^(0?[1-9]|1[0-2])\/(0?[1-9]|1[0-2])\/\d{4} ([0-9]|(0|1)[0-9]|2[0-4]):([0-5][0-9]|0?[0-9]):([0-5][0-9]|0?[0-9]) [AP]M$": "%m/%d/%Y %I:%M:%S %p", + # 9/10/2014 1:50:34 + r"^(0?[1-9]|1[0-2])\/(0?[1-9]|1[0-2])\/\d{4} ([0-9]|(0|1)[0-9]|2[0-4]):([0-5][0-9]|0?[0-9]):([0-5][0-9]|0?[0-9])$": "%m/%d/%Y %H:%M:%S", + } + + for pattern, format in formats.items(): + if re.fullmatch(pattern, string): + return datetime.strptime(string, format) + + # Matches a datetime string in the format "YYYY-MM-DDTHH:MM:SS.ffffff". This format usually parses with + # `fromisoformat`, but not with Python 3.10 + iso_format_regex = r"(?P
\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.)(?P\d{0,7})" + + # In Python %f only supports exactly 6 digits so we need to adapt the string + # If microseconds has 7 digits, we remove the last one + # If microseconds has less than 6 digits, we pad it with 0 + def adapt_microseconds(m: re.Match[str]) -> str: + microseconds = m.group("microseconds") + header_text = m.group("header") + + if len(microseconds) == 7: + microseconds = microseconds[:-1] + + return header_text + microseconds.ljust(6, "0") + + if re.fullmatch(iso_format_regex, string): + adapted_string = re.sub(iso_format_regex, adapt_microseconds, string) + return datetime.strptime(adapted_string, "%Y-%m-%dT%H:%M:%S.%f") + + raise ValueError("Unsupported format by Fable: %s" % (string)) + + +def try_parse(string: str, def_value: FSharpRef[datetime]) -> bool: try: - defValue.contents = parse(string) + def_value.contents = parse(string) return True except Exception: return False +def date(d: datetime) -> datetime: + return create(d.year, d.month, d.day, 0, 0, 0, 0, 0, kind(d)) + + +def is_leap_year(year: int) -> bool: + return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) + + +def days_in_month(year: int, month: int) -> int: + if month == 2: + return 29 if is_leap_year(year) else 28 + + if month in (4, 6, 9, 11): + return 30 + + return 31 + + +def add_years(d: datetime, v: int) -> datetime: + new_month = month(d) + new_year = year(d) + v + _days_in_month = days_in_month(new_year, new_month) + new_day = min(_days_in_month, day(d)) + return create(new_year, new_month, new_day, hour(d), minute(d), second(d), millisecond(d), microsecond(d), kind(d)) + + +def add_months(d: datetime, v: int) -> datetime: + new_month = month(d) + v + new_month_ = 0 + year_offset = 0 + if new_month > 12: + new_month_ = int(fmod(new_month, 12)) + year_offset = new_month // 12 + new_month = new_month_ + elif new_month < 1: + new_month_ = 12 + int(fmod(new_month, 12)) + year_offset = new_month // 12 + (-1 if new_month_ == 12 else 0) + new_month = new_month_ + new_year = year(d) + year_offset + _days_in_month = days_in_month(new_year, new_month) + new_day = min(_days_in_month, day(d)) + return create(new_year, new_month, new_day, hour(d), minute(d), second(d), millisecond(d), microsecond(d), kind(d)) + + +def add_days(d: datetime, v: int) -> datetime: + return d + timedelta(days=v) + + +def add_hours(d: datetime, v: int) -> datetime: + return d + timedelta(hours=v) + + +def add_minutes(d: datetime, v: int) -> datetime: + return d + timedelta(minutes=v) + + +def add_seconds(d: datetime, v: int) -> datetime: + return d + timedelta(seconds=v) + + def add_milliseconds(d: datetime, v: int) -> datetime: return d + timedelta(milliseconds=v) +def add_microseconds(d: datetime, v: int) -> datetime: + return d + timedelta(microseconds=v) + + +def kind(d: datetime) -> DateKind: + if d.tzinfo == timezone.utc: + return DateKind.UTC + + elif d.tzinfo is None: + return DateKind.Unspecified + + # Should we check that tzinfo is local_time_zone? + return DateKind.Local + + +def specify_kind(d: datetime, kind: DateKind) -> datetime: + return create(year(d), month(d), day(d), hour(d), minute(d), second(d), millisecond(d), microsecond(d), kind) + + +def ticks(d: datetime) -> int: + # Note: It can happens that Ticks differs a little bit from the .NET implementation + # because of some rounding/precision issues in Python + # DateTime(1, 1, 1, 0, 0, 0, 0, 99, DateTimeKind.Utc).Ticks should be 990 + # but Python returns 1040 + # because d.timestamp(): + # - returns -62135596799.9999 + # - instead of -62135596800000 + # compute timestamp in microseconds + return unix_epoch_microseconds_to_ticks(int(d.timestamp() * 1_000_000), date_offset(d) * 1_000) + + +def unix_epoch_microseconds_to_ticks(us: int, offset: int) -> int: + return int(((us + 62135596800000000) + offset) * 10) + + +def ticks_to_unix_epoch_microseconds(ticks: int) -> int: + return int((ticks - 621355968000000000) // 10) + + +def date_offset(d: datetime) -> int: + if d.tzinfo == timezone.utc: + return 0 + else: + utc_offset = d.utcoffset() + + # Is it correct to force an offset to local time + # for DateKind.Unspecified? + if utc_offset is None: + forced_utc_offset = d.astimezone().utcoffset() + assert forced_utc_offset is not None + return int(forced_utc_offset.total_seconds() * 1_000) + else: + return int(utc_offset.total_seconds() * 1_000) + + # return 0 if d.tzinfo == timezone.utc else + + +def create_from_epoch_microseconds(us: int, kind: DateKind | None = None) -> datetime: + if kind == DateKind.UTC: + date = datetime.fromtimestamp(us / 1_000_000, timezone.utc) + else: + date = datetime.fromtimestamp(us / 1_000_000) + if kind == DateKind.Local: + date = date.astimezone() + + return date + + +def from_ticks(ticks: int, kind: DateKind | None = None) -> datetime: + # Better default than Unspecified + kind = kind or DateKind.Local + date = create_from_epoch_microseconds(ticks_to_unix_epoch_microseconds(ticks), kind) + + # Ticks are local to offset (in this case, either UTC or Local/Unknown). + # If kind is anything but UTC, that means that the tick number was not + # in utc, thus getTime() cannot return UTC, and needs to be shifted. + if kind != DateKind.UTC: + date = create_from_epoch_microseconds(int(date.timestamp() * 1_000_000 - date_offset(date) * 1_000), kind) + + return date + + __all__ = [ - "add", - "op_subtraction", "subtract", + "op_subtraction", "create", "year", - "date_to_string_with_custom_format", - "date_to_string_with_offset", - "date_to_string_with_kind", + "month", + "day", + "hour", + "minute", + "second", + "millisecond", + "microsecond", + "to_universal_time", + "day_of_week", + "day_of_year", + "to_short_date_string", + "to_long_date_string", + "to_short_time_string", + "to_long_time_string", "to_string", "now", "utc_now", + "today", "to_local_time", "compare", "equals", "max_value", "min_value", "op_addition", + "add", "parse", - "to_universal_time", "try_parse", + "date", + "is_leap_year", + "days_in_month", + "add_years", + "add_months", + "add_days", + "add_hours", + "add_minutes", + "add_seconds", + "add_milliseconds", + "add_microseconds", + "kind", + "specify_kind", + "ticks", + "date_offset", + "from_ticks", ] diff --git a/src/fable-library-py/fable_library/singleton_local_time_zone.py b/src/fable-library-py/fable_library/singleton_local_time_zone.py new file mode 100644 index 0000000000..b1b9e6445c --- /dev/null +++ b/src/fable-library-py/fable_library/singleton_local_time_zone.py @@ -0,0 +1,36 @@ +from __future__ import annotations + +import time as _time +from datetime import datetime, timedelta, tzinfo + + +class LocalTimezone(tzinfo): + def utcoffset(self, dt: datetime | None = None): + # Return offset of local timezone from UTC + if dt is not None and self._isdst(dt): + return timedelta(seconds=-_time.altzone) + else: + return timedelta(seconds=-_time.timezone) + + def dst(self, dt: datetime | None = None): + # Return the daylight saving time (DST) adjustment + if dt is not None and self._isdst(dt): + return timedelta(seconds=-_time.altzone) + else: + return timedelta(seconds=-_time.timezone) + + def tzname(self, dt: datetime | None = None) -> str: + # Return the name of the time zone + return "localtime" + + def _isdst(self, dt: datetime): + # Determine whether or not DST is in effect + tt = (dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.weekday(), 0, 0) + stamp = _time.mktime(tt) + tt = _time.localtime(stamp) + return tt.tm_isdst > 0 + + +local_time_zone = LocalTimezone() + +__all__ = ["local_time_zone"] diff --git a/src/fable-library-py/fable_library/string_.py b/src/fable-library-py/fable_library/string_.py index 0da3b120f6..5fdd2da3f3 100644 --- a/src/fable-library-py/fable_library/string_.py +++ b/src/fable-library-py/fable_library/string_.py @@ -552,7 +552,7 @@ def index_of_any(string: str, any_of: list[str], *args: int): string = string[start_index:length] any_of_str = "".join(any_of) - for i,c in enumerate(string): + for i, c in enumerate(string): index = any_of_str.find(c) if index > -1: return i + start_index diff --git a/src/fable-library-py/fable_library/util.py b/src/fable-library-py/fable_library/util.py index c8197b8f0b..9db7df52a1 100644 --- a/src/fable-library-py/fable_library/util.py +++ b/src/fable-library-py/fable_library/util.py @@ -683,7 +683,7 @@ def curry3(f: Callable[[_T1, _T2, _T3], _TResult]) -> Callable[[_T1], Callable[[ def uncurry4( - f: Callable[[_T1], Callable[[_T2], Callable[[_T3], Callable[[_T4], _TResult]]]] + f: Callable[[_T1], Callable[[_T2], Callable[[_T3], Callable[[_T4], _TResult]]]], ) -> Callable[[_T1, _T2, _T3, _T4], _TResult]: def f2(a1: _T1, a2: _T2, a3: _T3, a4: _T4) -> _TResult: return f(a1)(a2)(a3)(a4) @@ -693,7 +693,7 @@ def f2(a1: _T1, a2: _T2, a3: _T3, a4: _T4) -> _TResult: def curry4( - f: Callable[[_T1, _T2, _T3, _T4], _TResult] + f: Callable[[_T1, _T2, _T3, _T4], _TResult], ) -> Callable[[_T1], Callable[[_T2], Callable[[_T3], Callable[[_T4], _TResult]]]]: f2 = _curried.get(f) if f2 is None: @@ -716,7 +716,7 @@ def f2(a1: _T1, a2: _T2, a3: _T3, a4: _T4, a5: _T5) -> _TResult: def curry5( - f: Callable[[_T1, _T2, _T3, _T4, _T5], _TResult] + f: Callable[[_T1, _T2, _T3, _T4, _T5], _TResult], ) -> Callable[[_T1], Callable[[_T2], Callable[[_T3], Callable[[_T4], Callable[[_T5], _TResult]]]]]: f2 = _curried.get(f) if f2 is None: @@ -742,7 +742,7 @@ def f2(a1: _T1, a2: _T2, a3: _T3, a4: _T4, a5: _T5, a6: _T6) -> _TResult: def curry6( - f: Callable[[_T1, _T2, _T3, _T4, _T5, _T6], _TResult] + f: Callable[[_T1, _T2, _T3, _T4, _T5, _T6], _TResult], ) -> Callable[ [_T1], Callable[ @@ -777,7 +777,7 @@ def f2(a1: _T1, a2: _T2, a3: _T3, a4: _T4, a5: _T5, a6: _T6, a7: _T7) -> _TResul def curry7( - f: Callable[[_T1, _T2, _T3, _T4, _T5, _T6, _T7], _TResult] + f: Callable[[_T1, _T2, _T3, _T4, _T5, _T6, _T7], _TResult], ) -> Callable[ [_T1], Callable[ @@ -823,7 +823,7 @@ def f2(a1: _T1, a2: _T2, a3: _T3, a4: _T4, a5: _T5, a6: _T6, a7: _T7, a8: _T8) - def curry8( - f: Callable[[_T1, _T2, _T3, _T4, _T5, _T6, _T7, _T8], _TResult] + f: Callable[[_T1, _T2, _T3, _T4, _T5, _T6, _T7, _T8], _TResult], ) -> Callable[ [_T1], Callable[ @@ -875,7 +875,7 @@ def f2(a1: _T1, a2: _T2, a3: _T3, a4: _T4, a5: _T5, a6: _T6, a7: _T7, a8: _T8, a def curry9( - f: Callable[[_T1, _T2, _T3, _T4, _T5, _T6, _T7, _T8, _T9], _TResult] + f: Callable[[_T1, _T2, _T3, _T4, _T5, _T6, _T7, _T8, _T9], _TResult], ) -> Callable[ [_T1], Callable[ @@ -947,7 +947,7 @@ def f2( def curry10( - f: Callable[[_T1, _T2, _T3, _T4, _T5, _T6, _T7, _T8, _T9, _T10], _TResult] + f: Callable[[_T1, _T2, _T3, _T4, _T5, _T6, _T7, _T8, _T9, _T10], _TResult], ) -> Callable[ [_T1], Callable[ @@ -1031,7 +1031,7 @@ def f2( def curry11( - f: Callable[[_T1, _T2, _T3, _T4, _T5, _T6, _T7, _T8, _T9, _T10, _T11], _TResult] + f: Callable[[_T1, _T2, _T3, _T4, _T5, _T6, _T7, _T8, _T9, _T10, _T11], _TResult], ) -> Callable[ [_T1], Callable[ @@ -1122,7 +1122,7 @@ def f2( def curry12( - f: Callable[[_T1, _T2, _T3, _T4, _T5, _T6, _T7, _T8, _T9, _T10, _T11, _T12], _TResult] + f: Callable[[_T1, _T2, _T3, _T4, _T5, _T6, _T7, _T8, _T9, _T10, _T11, _T12], _TResult], ) -> Callable[ [_T1], Callable[ @@ -1223,7 +1223,7 @@ def f2( def curry13( - f: Callable[[_T1, _T2, _T3, _T4, _T5, _T6, _T7, _T8, _T9, _T10, _T11, _T12, _T13], _TResult] + f: Callable[[_T1, _T2, _T3, _T4, _T5, _T6, _T7, _T8, _T9, _T10, _T11, _T12, _T13], _TResult], ) -> Callable[ [_T1], Callable[ diff --git a/src/fable-library-py/pyproject.toml b/src/fable-library-py/pyproject.toml index e96a843a1a..9f2baa1031 100644 --- a/src/fable-library-py/pyproject.toml +++ b/src/fable-library-py/pyproject.toml @@ -9,7 +9,6 @@ homepage = "https://fable.io" [tool.poetry.dependencies] python = ">= 3.10, < 4.0" -python-dateutil = "^2.8.2" [tool.poetry.dev-dependencies] pytest = "^7.2.0" diff --git a/src/fable-library/Date.ts b/src/fable-library/Date.ts index b9f5b8d96d..d06fdbf7ce 100644 --- a/src/fable-library/Date.ts +++ b/src/fable-library/Date.ts @@ -202,11 +202,12 @@ export function parseRaw(input: string): [Date, Offset] { let timeInSeconds = 0; if (m[2] != null) { const timeParts = m[2].split(":"); + const hourPart = parseInt(timeParts[0], 10); timeInSeconds = - parseInt(timeParts[0], 10) * 3600 + + hourPart * 3600 + parseInt(timeParts[1] || "0", 10) * 60 + parseFloat(timeParts[2] || "0"); - if (m[3] != null && m[3].toUpperCase() === "PM") { + if (m[3] != null && m[3].toUpperCase() === "PM" && hourPart < 12) { timeInSeconds += 720; } } diff --git a/src/quicktest-py/quicktest.fsx b/src/quicktest-py/quicktest.fsx index 974b9a53f7..7eb4f8187c 100644 --- a/src/quicktest-py/quicktest.fsx +++ b/src/quicktest-py/quicktest.fsx @@ -5,6 +5,7 @@ open Fable.Core.Testing open Fable.Core.PyInterop open Fable.Python.Builtins open System +open System.Globalization let equal expected actual = // According the console log arguments are reversed @@ -19,4 +20,6 @@ let main argv = // use file = builtins.``open``(StringPath "data.txt") // file.read() |> printfn "File contents: %s" + printfn "All tests passed!" + 0 diff --git a/tests/Dart/src/DateTimeTests.fs b/tests/Dart/src/DateTimeTests.fs index e815be9a00..af5cdac4c9 100644 --- a/tests/Dart/src/DateTimeTests.fs +++ b/tests/Dart/src/DateTimeTests.fs @@ -265,12 +265,19 @@ let tests() = d.Day + d'.Day |> equal 18 testCase "DateTime.DayOfWeek works" <| fun () -> - let d = DateTime(2014, 10, 9) - d.DayOfWeek |> equal DayOfWeek.Thursday + DateTime(2014, 10, 5).DayOfWeek |> equal DayOfWeek.Sunday + DateTime(2014, 10, 6).DayOfWeek |> equal DayOfWeek.Monday + DateTime(2014, 10, 7).DayOfWeek |> equal DayOfWeek.Tuesday + DateTime(2014, 10, 8).DayOfWeek |> equal DayOfWeek.Wednesday + DateTime(2014, 10, 9).DayOfWeek |> equal DayOfWeek.Thursday + DateTime(2014, 10, 10).DayOfWeek |> equal DayOfWeek.Friday + DateTime(2014, 10, 11).DayOfWeek |> equal DayOfWeek.Saturday testCase "DateTime.DayOfYear works" <| fun () -> - let d = DateTime(2014, 10, 9) - d.DayOfYear |> equal 282 + // Standard year + DateTime(2014, 10, 9).DayOfYear |> equal 282 + // Leap year + DateTime(2020, 10, 9).DayOfYear |> equal 283 testCase "DateTime.Millisecond works" <| fun () -> let d = DateTime(2014, 10, 9, 13, 23, 30, 999) diff --git a/tests/Js/Main/DateTimeTests.fs b/tests/Js/Main/DateTimeTests.fs index e1d6a26a4b..b2d2a22907 100644 --- a/tests/Js/Main/DateTimeTests.fs +++ b/tests/Js/Main/DateTimeTests.fs @@ -258,26 +258,94 @@ let tests = d > DateTime.MinValue |> equal true testCase "DateTime.Parse works" <| fun () -> + let d = + DateTime.Parse( + "2014-09-10T13:50:34.0000000", + CultureInfo.InvariantCulture + ) + + d.Year + d.Month + d.Day + d.Hour + d.Minute + d.Second |> equal 2130 + let d = DateTime.Parse("9/10/2014 1:50:34 PM", CultureInfo.InvariantCulture) - d.Year + d.Month + d.Day + d.Hour + d.Minute - |> equal 2096 + d.Year + d.Month + d.Day + d.Hour + d.Minute + d.Second + |> equal 2130 + + let d = DateTime.Parse("9/10/2014 1:50:34 AM", CultureInfo.InvariantCulture) + d.Year + d.Month + d.Day + d.Hour + d.Minute + d.Second + |> equal 2118 + + let d = DateTime.Parse("9/10/2014 13:50:34", CultureInfo.InvariantCulture) + d.Year + d.Month + d.Day + d.Hour + d.Minute + d.Second + |> equal 2130 + + let d = DateTime.Parse("9/10/2014 1:50:34", CultureInfo.InvariantCulture) + d.Year + d.Month + d.Day + d.Hour + d.Minute + d.Second + |> equal 2118 + + // Disabled because it is timezone dependent + // I left it here in case, we need to test it in the future + // Currently, it is setup for Europe/Paris timezone + // let d = DateTime.Parse("2016-07-07T01:00:00.000Z", CultureInfo.InvariantCulture) + // d.Year + d.Month + d.Day + d.Hour + d.Minute + d.Second + // |> equal 2033 testCase "DateTime.Parse with time-only string works" <| fun () -> // See #1045 + // Time-only should use now as the reference date + let now = DateTime.Now + let d = DateTime.Parse("13:50:34", CultureInfo.InvariantCulture) + d.Year |> equal now.Year + d.Month |> equal now.Month + d.Day |> equal now.Day d.Hour + d.Minute + d.Second |> equal 97 + let d = DateTime.Parse("1:5:34 AM", CultureInfo.InvariantCulture) + d.Year |> equal now.Year + d.Month |> equal now.Month + d.Day |> equal now.Day d.Hour + d.Minute + d.Second |> equal 40 + let d = DateTime.Parse("1:5:34 PM", CultureInfo.InvariantCulture) + d.Year |> equal now.Year + d.Month |> equal now.Month + d.Day |> equal now.Day d.Hour + d.Minute + d.Second |> equal 52 + let d = DateTime.Parse("0:5:34 PM") + d.Hour + d.Minute + d.Second |> equal 51 + + let d1 = DateTime.Parse("12:5:34 PM") + d1.Hour + d1.Minute + d1.Second |> equal 51 + + let d2 = DateTime.Parse("3:5:34 PM") + d2.Hour + d2.Minute + d2.Second |> equal 54 + + let d3 = DateTime.Parse("15:5:34 PM") + d3.Hour + d3.Minute + d3.Second |> equal 54 + + testCase "DateTime.TryParse works" <| fun () -> - let f (d: string) = - match DateTime.TryParse(d, CultureInfo.InvariantCulture, DateTimeStyles.None) with - | true, _ -> true - | false, _ -> false - f "foo" |> equal false - f "9/10/2014 1:50:34 PM" |> equal true - f "1:50:34" |> equal true + let (isSuccess, _) = DateTime.TryParse("foo", CultureInfo.InvariantCulture, DateTimeStyles.None) + isSuccess |> equal false + + let (isSuccess, dateTime) = DateTime.TryParse("9/10/2014 1:50:34 PM", CultureInfo.InvariantCulture, DateTimeStyles.None) + isSuccess |> equal true + dateTime.Year + dateTime.Month + dateTime.Day + dateTime.Hour + dateTime.Minute + dateTime.Second |> equal 2130 + + let (isSuccess, _) = DateTime.TryParse("foo", CultureInfo.InvariantCulture) + isSuccess |> equal false + + let (isSuccess, dateTime) = DateTime.TryParse("9/10/2014 1:50:34 PM", CultureInfo.InvariantCulture) + isSuccess |> equal true + dateTime.Year + dateTime.Month + dateTime.Day + dateTime.Hour + dateTime.Minute + dateTime.Second |> equal 2130 + + let (isSuccess, _) = DateTime.TryParse("foo") + isSuccess |> equal false + + let (isSuccess, dateTime) = DateTime.TryParse("9/10/2014 1:50:34 PM") + isSuccess |> equal true + dateTime.Year + dateTime.Month + dateTime.Day + dateTime.Hour + dateTime.Minute + dateTime.Second |> equal 2130 + testCase "Parsing doesn't succeed for invalid dates" <| fun () -> let invalidAmericanDate = "13/1/2020" @@ -312,12 +380,19 @@ let tests = d.Day + d'.Day |> equal 18 testCase "DateTime.DayOfWeek works" <| fun () -> - let d = DateTime(2014, 10, 9) - d.DayOfWeek |> equal DayOfWeek.Thursday + DateTime(2014, 10, 5).DayOfWeek |> equal DayOfWeek.Sunday + DateTime(2014, 10, 6).DayOfWeek |> equal DayOfWeek.Monday + DateTime(2014, 10, 7).DayOfWeek |> equal DayOfWeek.Tuesday + DateTime(2014, 10, 8).DayOfWeek |> equal DayOfWeek.Wednesday + DateTime(2014, 10, 9).DayOfWeek |> equal DayOfWeek.Thursday + DateTime(2014, 10, 10).DayOfWeek |> equal DayOfWeek.Friday + DateTime(2014, 10, 11).DayOfWeek |> equal DayOfWeek.Saturday testCase "DateTime.DayOfYear works" <| fun () -> - let d = DateTime(2014, 10, 9) - d.DayOfYear |> equal 282 + // Standard year + DateTime(2014, 10, 9).DayOfYear |> equal 282 + // Leap year + DateTime(2020, 10, 9).DayOfYear |> equal 283 testCase "DateTime.Millisecond works" <| fun () -> let d = DateTime(2014, 10, 9, 13, 23, 30, 999) diff --git a/tests/Python/TestDateTime.fs b/tests/Python/TestDateTime.fs index 85a70568f0..ad2770cd26 100644 --- a/tests/Python/TestDateTime.fs +++ b/tests/Python/TestDateTime.fs @@ -1,6 +1,7 @@ module Fable.Tests.DateTime open System +open System.Globalization open Util.Testing open Fable.Tests @@ -17,10 +18,365 @@ let thatYearSeconds (dt: DateTime) = let thatYearMilliseconds (dt: DateTime) = (dt - DateTime(dt.Year, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds +[] +let ``test DateTime constructor replacements adaptation works`` () = + // Fable detects when a DateTime constructor is called with 7 or 8 arguments + // and the last one is DateTimeKind and adapt the call to the + // call the 9 arguments constructor with the DateTimeKind as the last + DateTime(2014, 7, 1, 16, 37, 3, DateTimeKind.Utc).Kind + |> equal DateTimeKind.Utc + + DateTime(2014, 7, 1, 16, 37, 3, 7, DateTimeKind.Utc).Kind + |> equal DateTimeKind.Utc + + DateTime(2014, 7, 1, 16, 37, 3, 7, 8, DateTimeKind.Utc).Kind + |> equal DateTimeKind.Utc + [] let ``test DateTime.ToString with custom format works`` () = - DateTime(2014, 9, 11, 16, 37, 0).ToString("HH:mm", System.Globalization.CultureInfo.InvariantCulture) - |> equal "16:37" + DateTime(2014, 7, 1, 16, 37, 1, 2, 3).ToString("r d", CultureInfo.InvariantCulture) + |> equal "r 1" + DateTime(2014, 7, 13, 16, 37, 1, 2, 3).ToString("r d", CultureInfo.InvariantCulture) + |> equal "r 13" + + DateTime(2014, 7, 1, 16, 37, 0).ToString("r dd", CultureInfo.InvariantCulture) + |> equal "r 01" + DateTime(2014, 7, 21, 16, 37, 0).ToString("r dd", CultureInfo.InvariantCulture) + |> equal "r 21" + + DateTime(2014, 7, 7, 16, 37, 0).ToString("r ddd", CultureInfo.InvariantCulture) + |> equal "r Mon" + DateTime(2014, 7, 8, 16, 37, 0).ToString("r ddd", CultureInfo.InvariantCulture) + |> equal "r Tue" + DateTime(2014, 7, 9, 16, 37, 0).ToString("r ddd", CultureInfo.InvariantCulture) + |> equal "r Wed" + DateTime(2014, 7, 10, 16, 37, 0).ToString("r ddd", CultureInfo.InvariantCulture) + |> equal "r Thu" + DateTime(2014, 7, 11, 16, 37, 0).ToString("r ddd", CultureInfo.InvariantCulture) + |> equal "r Fri" + DateTime(2014, 7, 12, 16, 37, 0).ToString("r ddd", CultureInfo.InvariantCulture) + |> equal "r Sat" + DateTime(2014, 7, 13, 16, 37, 0).ToString("r ddd", CultureInfo.InvariantCulture) + |> equal "r Sun" + + DateTime(2014, 7, 7, 16, 37, 0).ToString("r dddd", CultureInfo.InvariantCulture) + |> equal "r Monday" + DateTime(2014, 7, 8, 16, 37, 0).ToString("r dddd", CultureInfo.InvariantCulture) + |> equal "r Tuesday" + DateTime(2014, 7, 9, 16, 37, 0).ToString("r dddd", CultureInfo.InvariantCulture) + |> equal "r Wednesday" + DateTime(2014, 7, 10, 16, 37, 0).ToString("r dddd", CultureInfo.InvariantCulture) + |> equal "r Thursday" + DateTime(2014, 7, 11, 16, 37, 0).ToString("r dddd", CultureInfo.InvariantCulture) + |> equal "r Friday" + DateTime(2014, 7, 12, 16, 37, 0).ToString("r dddd", CultureInfo.InvariantCulture) + |> equal "r Saturday" + DateTime(2014, 7, 13, 16, 37, 0).ToString("r dddd", CultureInfo.InvariantCulture) + |> equal "r Sunday" + + DateTime.Parse("2009-06-15T13:45:30.6175425").ToString("r f", CultureInfo.InvariantCulture) + |> equal "r 6" + DateTime.Parse("2009-06-15T13:45:30.05").ToString("r f", CultureInfo.InvariantCulture) + |> equal "r 0" + DateTime.Parse("2009-06-15T13:45:30.6175425").ToString("r ff", CultureInfo.InvariantCulture) + |> equal "r 61" + DateTime.Parse("2009-06-15T13:45:30.0050000").ToString("r ff", CultureInfo.InvariantCulture) + |> equal "r 00" + DateTime.Parse("2009-06-15T13:45:30.6175425").ToString("r fff", CultureInfo.InvariantCulture) + |> equal "r 617" + DateTime.Parse("2009-06-15T13:45:30.0005000").ToString("r fff", CultureInfo.InvariantCulture) + |> equal "r 000" + DateTime.Parse("2009-06-15T13:45:30.6175425").ToString("r ffff", CultureInfo.InvariantCulture) + |> equal "r 6175" + DateTime.Parse("2009-06-15T13:45:30.0000500").ToString("r ffff", CultureInfo.InvariantCulture) + |> equal "r 0000" + DateTime.Parse("2009-06-15T13:45:30.6175425").ToString("r fffff", CultureInfo.InvariantCulture) + |> equal "r 61754" + DateTime.Parse("2009-06-15T13:45:30.0000050").ToString("r fffff", CultureInfo.InvariantCulture) + |> equal "r 00000" + DateTime.Parse("2009-06-15T13:45:30.6175425").ToString("r ffffff", CultureInfo.InvariantCulture) + |> equal "r 617542" + DateTime.Parse("2009-06-15T13:45:30.0000005").ToString("r ffffff", CultureInfo.InvariantCulture) + |> equal "r 000000" + // We only have a precision up to the microsecond + // DateTime.Parse("2009-06-15T13:45:30.6175425").ToString("r fffffff", CultureInfo.InvariantCulture) + // |> equal "r 6175425" + + DateTime.Parse("2009-06-15T13:45:30.6175425").ToString("r F", CultureInfo.InvariantCulture) + |> equal "r 6" + DateTime.Parse("2009-06-15T13:45:30.05").ToString("r F", CultureInfo.InvariantCulture) + |> equal "r " + DateTime.Parse("2009-06-15T13:45:30.6175425").ToString("r FF", CultureInfo.InvariantCulture) + |> equal "r 61" + DateTime.Parse("2009-06-15T13:45:30.0050000").ToString("r FF", CultureInfo.InvariantCulture) + |> equal "r " + DateTime.Parse("2009-06-15T13:45:30.6175425").ToString("r FFF", CultureInfo.InvariantCulture) + |> equal "r 617" + DateTime.Parse("2009-06-15T13:45:30.0005000").ToString("r FFF", CultureInfo.InvariantCulture) + |> equal "r " + DateTime.Parse("2009-06-15T13:45:30.6175425").ToString("r FFFF", CultureInfo.InvariantCulture) + |> equal "r 6175" + DateTime.Parse("2009-06-15T13:45:30.0000500").ToString("r FFFF", CultureInfo.InvariantCulture) + |> equal "r " + DateTime.Parse("2009-06-15T13:45:30.6175425").ToString("r FFFFF", CultureInfo.InvariantCulture) + |> equal "r 61754" + DateTime.Parse("2009-06-15T13:45:30.0000050").ToString("r FFFFF", CultureInfo.InvariantCulture) + |> equal "r " + DateTime.Parse("2009-06-15T13:45:30.0617425").ToString("r FFFFFF", CultureInfo.InvariantCulture) + |> equal "r 061742" + DateTime.Parse("2009-06-15T13:45:30.0000005").ToString("r FFFFFF", CultureInfo.InvariantCulture) + |> equal "r " + // We only have a precision up to the microsecond + // DateTime.Parse("2009-06-15T13:45:30.6175425").ToString("r FFFFFFF", CultureInfo.InvariantCulture) + // |> equal "r 6175425" + + DateTime(2014, 7, 1, 16, 37, 0).ToString("r g", CultureInfo.InvariantCulture) + |> equal "r A.D." + + DateTime(2014, 7, 1, 16, 37, 0).ToString("r gg", CultureInfo.InvariantCulture) + |> equal "r A.D." + + DateTime(2014, 7, 1, 16, 37, 0).ToString("r h", CultureInfo.InvariantCulture) + |> equal "r 4" + DateTime(2014, 7, 1, 4, 37, 0).ToString("r h", CultureInfo.InvariantCulture) + |> equal "r 4" + + DateTime(2014, 7, 1, 16, 37, 0).ToString("r hh", CultureInfo.InvariantCulture) + |> equal "r 04" + DateTime(2014, 7, 1, 4, 37, 0).ToString("r hh", CultureInfo.InvariantCulture) + |> equal "r 04" + + DateTime(2014, 7, 1, 4, 37, 0).ToString("r H", CultureInfo.InvariantCulture) + |> equal "r 4" + DateTime(2014, 7, 1, 16, 37, 0).ToString("r H", CultureInfo.InvariantCulture) + |> equal "r 16" + + DateTime(2014, 7, 1, 4, 37, 0).ToString("r HH", CultureInfo.InvariantCulture) + |> equal "r 04" + DateTime(2014, 7, 1, 16, 37, 0).ToString("r HH", CultureInfo.InvariantCulture) + |> equal "r 16" + + DateTime(2014, 7, 1, 16, 37, 0).ToString("r K", CultureInfo.InvariantCulture) + |> equal "r " + DateTime(2014, 7, 1, 16, 37, 0, DateTimeKind.Utc).ToString("r K", CultureInfo.InvariantCulture) + |> equal "r Z" + + // Timezone dependent (test is configured for Europe/Paris timezone) + // DateTime(2014, 7, 1, 16, 37, 0, DateTimeKind.Local).ToString("r K", CultureInfo.InvariantCulture) + // |> equal "r +02:00" + + DateTime(2014, 7, 1, 16, 3, 0).ToString("r m", CultureInfo.InvariantCulture) + |> equal "r 3" + DateTime(2014, 7, 1, 16, 37, 0).ToString("r m", CultureInfo.InvariantCulture) + |> equal "r 37" + + DateTime(2014, 7, 1, 16, 3, 0).ToString("r mm", CultureInfo.InvariantCulture) + |> equal "r 03" + DateTime(2014, 7, 1, 16, 37, 0).ToString("r mm", CultureInfo.InvariantCulture) + |> equal "r 37" + + DateTime(2014, 7, 1, 16, 37, 0).ToString("r M", CultureInfo.InvariantCulture) + |> equal "r 7" + DateTime(2014, 11, 1, 16, 37, 0).ToString("r M", CultureInfo.InvariantCulture) + |> equal "r 11" + + DateTime(2014, 7, 1, 16, 37, 0).ToString("r MM", CultureInfo.InvariantCulture) + |> equal "r 07" + DateTime(2014, 11, 1, 16, 37, 0).ToString("r MM", CultureInfo.InvariantCulture) + |> equal "r 11" + + DateTime(2014, 1, 1, 16, 37, 0).ToString("r MMM", CultureInfo.InvariantCulture) + |> equal "r Jan" + DateTime(2014, 2, 1, 16, 37, 0).ToString("r MMM", CultureInfo.InvariantCulture) + |> equal "r Feb" + DateTime(2014, 3, 1, 16, 37, 0).ToString("r MMM", CultureInfo.InvariantCulture) + |> equal "r Mar" + DateTime(2014, 4, 1, 16, 37, 0).ToString("r MMM", CultureInfo.InvariantCulture) + |> equal "r Apr" + DateTime(2014, 5, 1, 16, 37, 0).ToString("r MMM", CultureInfo.InvariantCulture) + |> equal "r May" + DateTime(2014, 6, 1, 16, 37, 0).ToString("r MMM", CultureInfo.InvariantCulture) + |> equal "r Jun" + DateTime(2014, 7, 1, 16, 37, 0).ToString("r MMM", CultureInfo.InvariantCulture) + |> equal "r Jul" + DateTime(2014, 8, 1, 16, 37, 0).ToString("r MMM", CultureInfo.InvariantCulture) + |> equal "r Aug" + DateTime(2014, 9, 1, 16, 37, 0).ToString("r MMM", CultureInfo.InvariantCulture) + |> equal "r Sep" + DateTime(2014, 10, 1, 16, 37, 0).ToString("r MMM", CultureInfo.InvariantCulture) + |> equal "r Oct" + DateTime(2014, 11, 1, 16, 37, 0).ToString("r MMM", CultureInfo.InvariantCulture) + |> equal "r Nov" + DateTime(2014, 12, 1, 16, 37, 0).ToString("r MMM", CultureInfo.InvariantCulture) + |> equal "r Dec" + + DateTime(2014, 1, 1, 16, 37, 0).ToString("r MMMM", CultureInfo.InvariantCulture) + |> equal "r January" + DateTime(2014, 2, 1, 16, 37, 0).ToString("r MMMM", CultureInfo.InvariantCulture) + |> equal "r February" + DateTime(2014, 3, 1, 16, 37, 0).ToString("r MMMM", CultureInfo.InvariantCulture) + |> equal "r March" + DateTime(2014, 4, 1, 16, 37, 0).ToString("r MMMM", CultureInfo.InvariantCulture) + |> equal "r April" + DateTime(2014, 5, 1, 16, 37, 0).ToString("r MMMM", CultureInfo.InvariantCulture) + |> equal "r May" + DateTime(2014, 6, 1, 16, 37, 0).ToString("r MMMM", CultureInfo.InvariantCulture) + |> equal "r June" + DateTime(2014, 7, 1, 16, 37, 0).ToString("r MMMM", CultureInfo.InvariantCulture) + |> equal "r July" + DateTime(2014, 8, 1, 16, 37, 0).ToString("r MMMM", CultureInfo.InvariantCulture) + |> equal "r August" + DateTime(2014, 9, 1, 16, 37, 0).ToString("r MMMM", CultureInfo.InvariantCulture) + |> equal "r September" + DateTime(2014, 10, 1, 16, 37, 0).ToString("r MMMM", CultureInfo.InvariantCulture) + |> equal "r October" + DateTime(2014, 11, 1, 16, 37, 0).ToString("r MMMM", CultureInfo.InvariantCulture) + |> equal "r November" + DateTime(2014, 12, 1, 16, 37, 0).ToString("r MMMM", CultureInfo.InvariantCulture) + |> equal "r December" + + DateTime(2014, 7, 1, 16, 37, 3).ToString("r s", CultureInfo.InvariantCulture) + |> equal "r 3" + DateTime(2014, 7, 1, 16, 37, 31).ToString("r s", CultureInfo.InvariantCulture) + |> equal "r 31" + + DateTime(2014, 7, 1, 16, 37, 3).ToString("r ss", CultureInfo.InvariantCulture) + |> equal "r 03" + DateTime(2014, 7, 1, 16, 37, 31).ToString("r ss", CultureInfo.InvariantCulture) + |> equal "r 31" + + DateTime(2014, 7, 1, 1, 37, 0).ToString("r t", CultureInfo.InvariantCulture) + |> equal "r A" + DateTime(2014, 7, 1, 16, 37, 0).ToString("r t", CultureInfo.InvariantCulture) + |> equal "r P" + DateTime(2014, 7, 1, 1, 37, 0).ToString("r tt", CultureInfo.InvariantCulture) + |> equal "r AM" + DateTime(2014, 7, 1, 16, 37, 0).ToString("r tt", CultureInfo.InvariantCulture) + |> equal "r PM" + + DateTime(1,1,1).ToString("r y", CultureInfo.InvariantCulture) + |> equal "r 1" + DateTime(0900,1,1).ToString("r y", CultureInfo.InvariantCulture) + |> equal "r 0" + DateTime(1900,1,1).ToString("r y", CultureInfo.InvariantCulture) + |> equal "r 0" + DateTime(2009,1,1).ToString("r y", CultureInfo.InvariantCulture) + |> equal "r 9" + DateTime(2019,1,1).ToString("r y", CultureInfo.InvariantCulture) + |> equal "r 19" + + DateTime(1,1,1).ToString("r yy", CultureInfo.InvariantCulture) + |> equal "r 01" + DateTime(0900,1,1).ToString("r yy", CultureInfo.InvariantCulture) + |> equal "r 00" + DateTime(1900,1,1).ToString("r yy", CultureInfo.InvariantCulture) + |> equal "r 00" + DateTime(2009,1,1).ToString("r yy", CultureInfo.InvariantCulture) + |> equal "r 09" + DateTime(2019,1,1).ToString("r yy", CultureInfo.InvariantCulture) + |> equal "r 19" + + DateTime(1,1,1).ToString("r yyy", CultureInfo.InvariantCulture) + |> equal "r 001" + DateTime(0900,1,1).ToString("r yyy", CultureInfo.InvariantCulture) + |> equal "r 900" + DateTime(1900,1,1).ToString("r yyy", CultureInfo.InvariantCulture) + |> equal "r 1900" + DateTime(2009,1,1).ToString("r yyy", CultureInfo.InvariantCulture) + |> equal "r 2009" + DateTime(2019,1,1).ToString("r yyy", CultureInfo.InvariantCulture) + |> equal "r 2019" + + DateTime(1,1,1).ToString("r yyyy", CultureInfo.InvariantCulture) + |> equal "r 0001" + DateTime(0900,1,1).ToString("r yyyy", CultureInfo.InvariantCulture) + |> equal "r 0900" + DateTime(1900,1,1).ToString("r yyyy", CultureInfo.InvariantCulture) + |> equal "r 1900" + DateTime(2009,1,1).ToString("r yyyy", CultureInfo.InvariantCulture) + |> equal "r 2009" + DateTime(2019,1,1).ToString("r yyyy", CultureInfo.InvariantCulture) + |> equal "r 2019" + + + DateTime(1,1,1).ToString("r yyyyy", CultureInfo.InvariantCulture) + |> equal "r 00001" + DateTime(0900,1,1).ToString("r yyyyy", CultureInfo.InvariantCulture) + |> equal "r 00900" + DateTime(1900,1,1).ToString("r yyyyy", CultureInfo.InvariantCulture) + |> equal "r 01900" + DateTime(2009,1,1).ToString("r yyyyy", CultureInfo.InvariantCulture) + |> equal "r 02009" + DateTime(2019,1,1).ToString("r yyyyy", CultureInfo.InvariantCulture) + |> equal "r 02019" + + + DateTime(2014, 7, 1, 16, 37, 0, DateTimeKind.Utc).ToString("r z", CultureInfo.InvariantCulture) + |> equal "r +0" + + // Timezone dependent (test is configured for Europe/Paris timezone) + // DateTime(2014, 7, 1, 16, 37, 0).ToString("r z", CultureInfo.InvariantCulture) + // |> equal "r +2" + // DateTime(2014, 7, 1, 16, 37, 0, DateTimeKind.Local).ToString("r z", CultureInfo.InvariantCulture) + // |> equal "r +2" + + + DateTime(2014, 7, 1, 16, 37, 0, DateTimeKind.Utc).ToString("r zz", CultureInfo.InvariantCulture) + |> equal "r +00" + // Timezone dependent (test is configured for Europe/Paris timezone) + // DateTime(2014, 7, 1, 16, 37, 0).ToString("r zz", CultureInfo.InvariantCulture) + // |> equal "r +02" + // DateTime(2014, 7, 1, 16, 37, 0, DateTimeKind.Local).ToString("r zz", CultureInfo.InvariantCulture) + // |> equal "r +02" + + DateTime(2014, 7, 1, 16, 37, 0, DateTimeKind.Utc).ToString("r zzz", CultureInfo.InvariantCulture) + |> equal "r +00:00" + // Timezone dependent (test is configured for Europe/Paris timezone) + // DateTime(2014, 7, 1, 16, 37, 0).ToString("r zzz", CultureInfo.InvariantCulture) + // |> equal "r +02:00" + // DateTime(2014, 7, 1, 16, 37, 0, DateTimeKind.Local).ToString("r zzz", CultureInfo.InvariantCulture) + // |> equal "r +02:00" + + // Time separator + DateTime(2014, 7, 1, 16, 37, 0).ToString("r :", CultureInfo.InvariantCulture) + |> equal "r :" + + // Date separator + DateTime(2014, 7, 1, 16, 37, 0).ToString("r /", CultureInfo.InvariantCulture) + |> equal "r /" + + // String quotation + DateTime(2014, 7, 1, 16, 37, 0).ToString("r \"hh\" h", CultureInfo.InvariantCulture) + |> equal "r hh 4" + DateTime(2014, 7, 1, 16, 37, 0).ToString("r 'hh' h", CultureInfo.InvariantCulture) + |> equal "r hh 4" + DateTime(2014, 7, 1, 16, 37, 0).ToString("r \'hh\'", CultureInfo.InvariantCulture) + |> equal "r hh" + + // Format character + DateTime(2014, 7, 1, 16, 37, 0).ToString("r %h", CultureInfo.InvariantCulture) + |> equal "r 4" + DateTime(2014, 7, 1, 16, 37, 0).ToString("r %hh", CultureInfo.InvariantCulture) + |> equal "r 44" + + // Escape character + DateTime(2014, 7, 1, 16, 37, 0, DateTimeKind.Utc).ToString("r \zz", CultureInfo.InvariantCulture) + |> equal "r z+0" + DateTime(2014, 7, 1, 16, 37, 0, DateTimeKind.Utc).ToString("r \\zz", CultureInfo.InvariantCulture) + |> equal "r z+0" + DateTime(2014, 7, 1, 16, 37, 0, DateTimeKind.Utc).ToString("r \\\zz", CultureInfo.InvariantCulture) + |> equal "r \+00" + DateTime(2014, 7, 1, 16, 37, 0, DateTimeKind.Utc).ToString("r \\z\\z", CultureInfo.InvariantCulture) + |> equal "r zz" + + // Escape character with verbatim string + DateTime(2014, 7, 1, 16, 37, 0, DateTimeKind.Utc).ToString("""r \zz""", CultureInfo.InvariantCulture) + |> equal "r z+0" + DateTime(2014, 7, 1, 16, 37, 0, DateTimeKind.Utc).ToString("""r \\zz""", CultureInfo.InvariantCulture) + |> equal "r \+00" + DateTime(2014, 7, 1, 16, 37, 0, DateTimeKind.Utc).ToString("""r \\\zz""", CultureInfo.InvariantCulture) + |> equal "r \z+0" + [] let ``test DateTime.ToString without separator works`` () = @@ -32,6 +388,89 @@ let ``test DateTime.ToString with milliseconds`` () = DateTime(2014, 9, 11, 16, 37, 11, 345).ToString("ss.fff") |> equal "11.345" +[] +let ``test DateTime.ToString("d") works`` () = + #if !FABLE_COMPILER + Threading.Thread.CurrentThread.CurrentCulture <- CultureInfo.InvariantCulture + #endif + DateTime(2014, 9, 11, 16, 37, 11, 345).ToString("d") + |> equal "09/11/2014" + + DateTime(2014, 9, 1, 16, 37, 11, 345).ToString("d") + |> equal "09/01/2014" + +// Needs to add (upper) and (lower) in the test name because +// the names of the function are lowered making them equal + +[] +let ``test DateTime.ToShortTimeString works`` () = + #if !FABLE_COMPILER + Threading.Thread.CurrentThread.CurrentCulture <- CultureInfo.InvariantCulture + #endif + + DateTime(2014, 9, 11, 16, 37, 0).ToShortTimeString() + |> equal "16:37" + +[] +let ``test DateTime.ToLongTimeString works`` () = + #if !FABLE_COMPILER + Threading.Thread.CurrentThread.CurrentCulture <- CultureInfo.InvariantCulture + #endif + + DateTime(2014, 9, 11, 16, 37, 0).ToLongTimeString() + |> equal "16:37:00" + +[] +let ``test DateTime.ToLongDateString works`` () = + #if !FABLE_COMPILER + Threading.Thread.CurrentThread.CurrentCulture <- CultureInfo.InvariantCulture + #endif + + DateTime(2014, 9, 11, 16, 37, 0).ToLongDateString() + |> equal "Thursday, 11 September 2014" + +[] +let ``test DateTime.ToShortDateString works`` () = + #if !FABLE_COMPILER + Threading.Thread.CurrentThread.CurrentCulture <- CultureInfo.InvariantCulture + #endif + + DateTime(2014, 9, 11, 16, 37, 0).ToShortDateString() + |> equal "09/11/2014" + +[] +let ``test DateTime.ToString("T") (upper) works`` () = + #if FABLE_COMPILER + DateTime(2014, 9, 11, 3, 37, 11, 345).ToString("T") + #else + DateTime(2014, 9, 11, 3, 37, 11, 345).ToString("T", CultureInfo.InvariantCulture) + #endif + |> equal "03:37:11" + + #if FABLE_COMPILER + DateTime(2014, 9, 11, 16, 37, 11, 345).ToString("T") + #else + DateTime(2014, 9, 11, 16, 37, 11, 345).ToString("T", CultureInfo.InvariantCulture) + #endif + |> equal "16:37:11" + +[] +let ``test DateTime.ToString("t") (lower) works`` () = + #if FABLE_COMPILER + DateTime(2014, 9, 11, 3, 37, 11, 345).ToString("t") + #else + DateTime(2014, 9, 11, 3, 37, 11, 345).ToString("t", CultureInfo.InvariantCulture) + #endif + |> equal "03:37" + + #if FABLE_COMPILER + DateTime(2014, 9, 11, 16, 37, 11, 345).ToString("t") + #else + DateTime(2014, 9, 11, 16, 37, 11, 345).ToString("t", CultureInfo.InvariantCulture) + #endif + |> equal "16:37" + + [] let ``test DateTime.ToString with Round-trip format works for Utc`` () = let str = DateTime(2014, 9, 11, 16, 37, 2, DateTimeKind.Utc).ToString("O") @@ -41,6 +480,13 @@ let ``test DateTime.ToString with Round-trip format works for Utc`` () = str.Replace("0000000Z", "000000Z") |> equal "2014-09-11T16:37:02.000000Z" + let str = DateTime(2014, 9, 11, 16, 37, 2, DateTimeKind.Utc).ToString("o") + // FIXME: missing regex module + // System.Text.RegularExpressions.Regex.Replace(str, "0{3,}", "000") + // Hardcode the replace string so we can test that "O" format is supported + str.Replace("0000000Z", "000000Z") + |> equal "2014-09-11T16:37:02.000000Z" + [] let ``test DateTime from Year 1 to 99 works`` () = let date = DateTime(1, 1, 2) @@ -100,10 +546,423 @@ let ``test DateTime Subtraction with DateTime works`` () = test -1000. 1.0 test 0. 0.0 +[] +let ``test DateTime.Parse works`` () = + let d = + DateTime.Parse( + "2014-09-10T13:50:34.0000000", + CultureInfo.InvariantCulture + ) + + d.Year + d.Month + d.Day + d.Hour + d.Minute + d.Second |> equal 2130 + + let d = DateTime.Parse("9/10/2014 1:50:34 PM", CultureInfo.InvariantCulture) + d.Year + d.Month + d.Day + d.Hour + d.Minute + d.Second + |> equal 2130 + + let d = DateTime.Parse("9/10/2014 1:50:34 AM", CultureInfo.InvariantCulture) + d.Year + d.Month + d.Day + d.Hour + d.Minute + d.Second + |> equal 2118 + + let d = DateTime.Parse("9/10/2014 13:50:34", CultureInfo.InvariantCulture) + d.Year + d.Month + d.Day + d.Hour + d.Minute + d.Second + |> equal 2130 + + let d = DateTime.Parse("9/10/2014 1:50:34", CultureInfo.InvariantCulture) + d.Year + d.Month + d.Day + d.Hour + d.Minute + d.Second + |> equal 2118 + + // Disabled because it is timezone dependent + // I left it here in case, we need to test it in the future + // Currently, it is setup for Europe/Paris timezone + // let d = DateTime.Parse("2016-07-07T01:00:00.000Z", CultureInfo.InvariantCulture) + // d.Year + d.Month + d.Day + d.Hour + d.Minute + d.Second + // |> equal 2033 + +[] +let ``test DateTime.Parse with time-only string works`` () = // See #1045 + // Time-only should use now as the reference date + let now = DateTime.Now + + let d = DateTime.Parse("13:50:34", CultureInfo.InvariantCulture) + d.Year |> equal now.Year + d.Month |> equal now.Month + d.Day |> equal now.Day + d.Hour + d.Minute + d.Second |> equal 97 + + let d = DateTime.Parse("1:5:34 AM", CultureInfo.InvariantCulture) + d.Year |> equal now.Year + d.Month |> equal now.Month + d.Day |> equal now.Day + d.Hour + d.Minute + d.Second |> equal 40 + + let d = DateTime.Parse("1:5:34 PM", CultureInfo.InvariantCulture) + d.Year |> equal now.Year + d.Month |> equal now.Month + d.Day |> equal now.Day + d.Hour + d.Minute + d.Second |> equal 52 + + let d = DateTime.Parse("0:5:34 PM") + d.Hour + d.Minute + d.Second |> equal 51 + + let d1 = DateTime.Parse("12:5:34 PM") + d1.Hour + d1.Minute + d1.Second |> equal 51 + + let d2 = DateTime.Parse("3:5:34 PM") + d2.Hour + d2.Minute + d2.Second |> equal 54 + + let d3 = DateTime.Parse("15:5:34 PM") + d3.Hour + d3.Minute + d3.Second |> equal 54 // [] -// let ``test DateTime.ToLocalTime works`` () = -// let d = DateTime(2014, 10, 9, 13, 23, 30, DateTimeKind.Utc) -// let d' = d.ToLocalTime() -// d.Kind <> d'.Kind -// |> equal true +// let ``test DateTime.Ticks works`` () = +// let d = DateTime(2014, 10, 9, 13, 23, 30, 999, DateTimeKind.Utc) +// d.Ticks |> equal 635484578109990000L +// let d = DateTime(2014, 10, 9, 13, 23, 30, 999, DateTimeKind.Local) +// d.Ticks |> equal 635484578109990000L +// let d = DateTime(2014, 10, 9, 13, 23, 30, 999) +// d.Ticks |> equal 635484578109990000L + +[] +let ``test DateTime.Year works`` () = + let d = DateTime(2014, 10, 9, 13, 23, 30, DateTimeKind.Local) + let d' = DateTime(2014, 10, 9, 13, 23, 30, DateTimeKind.Utc) + d.Year + d'.Year + |> equal 4028 + +[] +let ``test DateTime.Day works`` () = + let d = DateTime(2014, 10, 9, 13, 23, 30, DateTimeKind.Local) + let d' = DateTime(2014, 10, 9, 13, 23, 30, DateTimeKind.Utc) + d.Day + d'.Day |> equal 18 + +[] +let ``test DateTime.Month works`` () = + let d = DateTime(2014, 10, 9, 13, 23, 30, DateTimeKind.Local) + let d' = DateTime(2014, 10, 9, 13, 23, 30, DateTimeKind.Utc) + d.Month + d'.Month + |> equal 20 + +[] +let ``test DateTime.Hour works`` () = + // Summer time (allowed to detect invalid utcoffset for Europe/Paris) + let d = DateTime(2014, 10, 9, 13, 23, 30, DateTimeKind.Local) + d.Hour |> equal 13 + + // Winter time + let d = DateTime(2014, 1, 9, 13, 23, 30, DateTimeKind.Local) + d.Hour |> equal 13 + +[] +let ``test DateTime.Minute works`` () = + let d = DateTime(2014, 10, 9, 13, 23, 30, DateTimeKind.Local) + let d' = DateTime(2014, 10, 9, 13, 23, 30, DateTimeKind.Utc) + d.Minute + d'.Minute + |> equal 46 + +[] +let ``test DateTime.Second works`` () = + let d = DateTime(2014,9,12,0,0,30) + let d' = DateTime(2014,9,12,0,0,59) + d.Second + d'.Second + |> equal 89 + +[] +let ``test DateTime.Millisecond works`` () = + let d = DateTime(2014, 10, 9, 13, 23, 30, 999) + d.Millisecond |> equal 999 + +// [] +// let ``test DateTime.Microsecond works`` () = +// let d = DateTime(2014, 10, 9, 13, 23, 30, 999).AddMicroseconds(2) +// d.Microsecond |> equal 2 + +[] +let ``DateTime.DayOfWeek works`` () = + DateTime(2014, 10, 5).DayOfWeek |> equal DayOfWeek.Sunday + DateTime(2014, 10, 6).DayOfWeek |> equal DayOfWeek.Monday + DateTime(2014, 10, 7).DayOfWeek |> equal DayOfWeek.Tuesday + DateTime(2014, 10, 8).DayOfWeek |> equal DayOfWeek.Wednesday + DateTime(2014, 10, 9).DayOfWeek |> equal DayOfWeek.Thursday + DateTime(2014, 10, 10).DayOfWeek |> equal DayOfWeek.Friday + DateTime(2014, 10, 11).DayOfWeek |> equal DayOfWeek.Saturday + +[] +let ``test DateTime.DayOfYear works`` () = + // Standard year + DateTime(2014, 10, 9).DayOfYear |> equal 282 + // Leap year + DateTime(2020, 10, 9).DayOfYear |> equal 283 + +[] +let ``test DateTime.TryParse works`` () = + let (isSuccess, _) = DateTime.TryParse("foo", CultureInfo.InvariantCulture, DateTimeStyles.None) + isSuccess |> equal false + + let (isSuccess, dateTime) = DateTime.TryParse("9/10/2014 1:50:34 PM", CultureInfo.InvariantCulture, DateTimeStyles.None) + isSuccess |> equal true + dateTime.Year + dateTime.Month + dateTime.Day + dateTime.Hour + dateTime.Minute + dateTime.Second |> equal 2130 + + let (isSuccess, _) = DateTime.TryParse("foo", CultureInfo.InvariantCulture) + isSuccess |> equal false + + let (isSuccess, dateTime) = DateTime.TryParse("9/10/2014 1:50:34 PM", CultureInfo.InvariantCulture) + isSuccess |> equal true + dateTime.Year + dateTime.Month + dateTime.Day + dateTime.Hour + dateTime.Minute + dateTime.Second |> equal 2130 + + let (isSuccess, _) = DateTime.TryParse("foo") + isSuccess |> equal false + + let (isSuccess, dateTime) = DateTime.TryParse("9/10/2014 1:50:34 PM") + isSuccess |> equal true + dateTime.Year + dateTime.Month + dateTime.Day + dateTime.Hour + dateTime.Minute + dateTime.Second |> equal 2130 + + +[] +let ``test "Parsing doesn't succeed for invalid dates`` () = + let invalidAmericanDate = "13/1/2020" + let r, _date = DateTime.TryParse(invalidAmericanDate, CultureInfo.InvariantCulture, DateTimeStyles.None) + r |> equal false + + +[] +let ``test DateTime.Today works`` () = + let d = DateTime.Today + equal 0 d.Hour + equal 0 d.Minute + equal 0 d.Second + equal 0 d.Millisecond + +[] +let ``test DateTime.Date works`` () = + let d = DateTime(2014, 10, 9, 13, 23, 30) + d.Date.Hour |> equal 0 + d.Date.Minute |> equal 0 + d.Date.Second |> equal 0 + d.Date.Millisecond |> equal 0 + d.Date.Day |> equal 9 + d.Date.Month |> equal 10 + d.Date.Year |> equal 2014 + +[] +let ``test DateTime.DaysInMonth works`` () = + DateTime.DaysInMonth(2014, 1) |> equal 31 + DateTime.DaysInMonth(2014, 2) |> equal 28 + DateTime.DaysInMonth(2014, 4) |> equal 30 + DateTime.DaysInMonth(2016, 2) |> equal 29 + +[] +let ``test DateTime.Now works`` () = + let d = DateTime.Now + d > DateTime.MinValue |> equal true + +[] +let ``test DateTime.UtcNow works`` () = + let d = DateTime.UtcNow + d > DateTime.MinValue |> equal true + + +[] +let ``test DateTime.AddYears works`` () = + let test v expected = + let dt = DateTime(2016,2,29,0,0,0,DateTimeKind.Utc).AddYears(v) + equal expected (dt.Month + dt.Day) + test 100 31 + test 1 30 + test -1 30 + test -100 31 + test 0 31 + +[] +let ``test DateTime.AddMonths works`` () = + let test v expected = + let dt = DateTime(2016,1,31,0,0,0,DateTimeKind.Utc).AddMonths(v) + dt.Year + dt.Month + dt.Day + |> equal expected + test 100 2060 + test 20 2056 + test 6 2054 + test 5 2052 + test 1 2047 + test 0 2048 + test -1 2058 + test -5 2054 + test -20 2050 + test -100 2046 + +[] +let ``test DateTime.AddDays works`` () = + let test v expected = + let dt = DateTime(2014,9,12,0,0,0,DateTimeKind.Utc).AddDays(v) + thatYearSeconds dt + |> equal expected + test 100. 30585600.0 + test -100. 13305600.0 + test 0. 21945600.0 + +[] +let ``test DateTime.AddHours works`` () = + let test v expected = + let dt = DateTime(2014,9,12,0,0,0,DateTimeKind.Utc).AddHours(v) + thatYearSeconds dt + |> equal expected + test 100. 22305600.0 + test -100. 21585600.0 + test 0. 21945600.0 + +[] +let ``test DateTime.AddMinutes works`` () = + let test v expected = + let dt = DateTime(2014,9,12,0,0,0,DateTimeKind.Utc).AddMinutes(v) + thatYearSeconds dt + |> equal expected + test 100. 21951600.0 + test -100. 21939600.0 + test 0. 21945600.0 + +[] +let ``test DateTime.AddSeconds works`` () = + let test v expected = + let dt = DateTime(2014,9,12,0,0,0,DateTimeKind.Utc).AddSeconds(v) + thatYearSeconds dt + |> equal expected + test 100. 21945700.0 + test -100. 21945500.0 + test 0. 21945600.0 + +[] +let ``test DateTime.AddMilliseconds works`` () = + let test v expected = + let dt = DateTime(2014,9,12,0,0,0,DateTimeKind.Utc).AddMilliseconds(v) + thatYearMilliseconds dt + |> equal expected + test 100. 2.19456001e+10 + test -100. 2.19455999e+10 + test 0. 2.19456e+10 + +[] +let ``test DateTime Addition works`` () = + let test ms expected = + let dt = DateTime(2014,9,12,0,0,0,DateTimeKind.Utc) + let ts = TimeSpan.FromMilliseconds(ms) + let res1 = dt.Add(ts) |> thatYearSeconds + let res2 = (dt + ts) |> thatYearSeconds + equal true (res1 = res2) + equal expected res1 + test 1000. 21945601.0 + test -1000. 21945599.0 + test 0. 21945600.0 + +[] +let ``test DateTime constructors works`` () = + let d1 = DateTime(2014, 10, 9) + let d2 = DateTime(2014, 10, 9, 13, 23, 30) + let d3 = DateTime(2014, 10, 9, 13, 23, 30, DateTimeKind.Utc) + let d4 = DateTime(2014, 10, 9, 13, 23, 30, 500) + let d5 = DateTime(2014, 10, 9, 13, 23, 30, 500, DateTimeKind.Utc) + d1.Day + d2.Second + d3.Second + d4.Millisecond + d5.Millisecond + |> equal 1069 + +[] +let ``test DateTime constructor from Ticks works`` () = + let d = DateTime(624059424000000000L, DateTimeKind.Utc) + equal 1978 d.Year + equal 7 d.Month + equal 27 d.Day + equal 0 d.Hour + equal 0 d.Minute + + let d = DateTime(624059424000000000L, DateTimeKind.Local) + equal 1978 d.Year + equal 7 d.Month + equal 27 d.Day + equal 0 d.Hour + equal 0 d.Minute + + let d = DateTime(624059424000000000L) + equal 1978 d.Year + equal 7 d.Month + equal 27 d.Day + equal 0 d.Hour + equal 0 d.Minute + +[] +let ``test DateTime.Ticks does not care about kind`` () = + let d1 = DateTime(2014, 10, 9, 13, 23, 30, 500, DateTimeKind.Local) + let d2 = DateTime(2014, 10, 9, 13, 23, 30, 500, DateTimeKind.Utc) + let d3 = DateTime(2014, 10, 9, 13, 23, 30, 500, DateTimeKind.Unspecified) + equal d1.Ticks d2.Ticks + equal d1.Ticks d3.Ticks + equal d2.Ticks d3.Ticks + + let t = DateTime.UtcNow.Ticks + let d1 = DateTime(t, DateTimeKind.Local) + let d2 = DateTime(t, DateTimeKind.Utc) + let d3 = DateTime(t, DateTimeKind.Unspecified) + equal d1.Ticks d2.Ticks + equal d1.Ticks d3.Ticks + equal d2.Ticks d3.Ticks + +[] +let ``test DateTime.ToLocalTime works`` () = + let d = DateTime(2014, 10, 9, 13, 23, 30, DateTimeKind.Utc) + let d' = d.ToLocalTime() + d.Kind <> d'.Kind + |> equal true + +[] +let ``test DateTime.ToUniversalTime works`` () = + let d = DateTime(2014, 10, 9, 13, 23, 30, DateTimeKind.Local) + d.ToUniversalTime().Kind <> d.Kind + |> equal true + +[] +let ``test DateTime.SpecifyKind works`` () = // See #1844 + let d = DateTime(2014, 10, 9, 13, 23, 30, DateTimeKind.Local) + let d2 = DateTime.SpecifyKind(d, DateTimeKind.Utc) + d2.Kind |> equal DateTimeKind.Utc + d.Ticks = d2.Ticks |> equal true + // Not sure why this is failing on .NET runtime in the CI + // let d3 = d.ToUniversalTime() + // d.Ticks = d3.Ticks |> equal false + +[] +let ``test DateTime constructor works with Microseconds`` () = + let d = DateTime(2014, 7, 1, 16, 37, 1, 2, 3) + + d.Year + d.Month + d.Day + d.Hour + d.Minute + d.Second + d.Millisecond |> equal 2078 + d.Ticks |> equal 635398294210020030L + +// Disabled: It seems that there a loss of precision somewhere +// Can be revisited later +// [] +// let ``test DateTime <-> Ticks isomorphism`` () = +// let checkIsomorphism (d: DateTime) = +// try +// let ticks = d.Ticks +// let kind = d.Kind +// let fromTicks = DateTime ticks +// let fromTicksWithKind = DateTime (ticks, kind) + +// equal d fromTicks +// equal ticks fromTicks.Ticks +// equal d fromTicksWithKind +// equal ticks fromTicksWithKind.Ticks +// equal kind fromTicksWithKind.Kind +// with e -> +// failwithf "%A: %O" d e + +// try +// equal d.Ticks (DateTime d.Ticks).Ticks +// with e -> +// failwithf "%s%O" "replacement bug. " e + +// checkIsomorphism DateTime.MinValue +// checkIsomorphism DateTime.MaxValue +// checkIsomorphism DateTime.Now +// checkIsomorphism DateTime.UtcNow +// checkIsomorphism <| DateTime(2014, 10, 9) +// checkIsomorphism <| DateTime(2014, 10, 9, 13, 23, 30) +// checkIsomorphism <| DateTime(2014, 10, 9, 13, 23, 30, DateTimeKind.Utc) +// checkIsomorphism <| DateTime(2014, 10, 9, 13, 23, 30, 500) +// checkIsomorphism <| DateTime(2014, 10, 9, 13, 23, 30, 500, DateTimeKind.Utc) diff --git a/tests/Rust/tests/src/DateTimeTests.fs b/tests/Rust/tests/src/DateTimeTests.fs index ed67af29b6..3173a81dcc 100644 --- a/tests/Rust/tests/src/DateTimeTests.fs +++ b/tests/Rust/tests/src/DateTimeTests.fs @@ -348,13 +348,20 @@ let ``DateTime.Day works`` () = [] let ``DateTime.DayOfWeek works`` () = - let d = DateTime(2014, 10, 9) - d.DayOfWeek |> equal DayOfWeek.Thursday + DateTime(2014, 10, 5).DayOfWeek |> equal DayOfWeek.Sunday + DateTime(2014, 10, 6).DayOfWeek |> equal DayOfWeek.Monday + DateTime(2014, 10, 7).DayOfWeek |> equal DayOfWeek.Tuesday + DateTime(2014, 10, 8).DayOfWeek |> equal DayOfWeek.Wednesday + DateTime(2014, 10, 9).DayOfWeek |> equal DayOfWeek.Thursday + DateTime(2014, 10, 10).DayOfWeek |> equal DayOfWeek.Friday + DateTime(2014, 10, 11).DayOfWeek |> equal DayOfWeek.Saturday [] let ``DateTime.DayOfYear works`` () = - let d = DateTime(2014, 10, 9) - d.DayOfYear |> equal 282 + // Standard year + DateTime(2014, 10, 9).DayOfYear |> equal 282 + // Leap year + DateTime(2020, 10, 9).DayOfYear |> equal 283 [] let ``DateTime.Millisecond works`` () =