Skip to content

Commit

Permalink
Save progress made on date.py
Browse files Browse the repository at this point in the history
  • Loading branch information
MangelMaxime committed Jan 3, 2024
1 parent 2d0fe66 commit f22ce40
Show file tree
Hide file tree
Showing 5 changed files with 316 additions and 54 deletions.
31 changes: 22 additions & 9 deletions src/Fable.Cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

#### Python

* [GH-3663](https://github.com/fable-compiler/Fable/pull/3663) Add several missing `DateTime` methods (by @MangelMaxime)
* `dt.Month`
* `dt.Day`
* `dt.Hour`
* `dt.Minute`
* `dt.Second`
* `dt.Millisecond`
* `dt.DayOfWeek`
* `dt.DayOfYear`
* [GH-3663](https://github.com/fable-compiler/Fable/pull/3663) Add several missing `DateTime` API (by @MangelMaxime)
* Instance methods:
* `dt.Month`
* `dt.Day`
* `dt.Hour`
* `dt.Minute`
* `dt.Second`
* `dt.Millisecond`
* `dt.Microsecond`
* `dt.DayOfWeek`
* `dt.DayOfYear`
* `dt.Date`
* `dt.AddYears`
* `dt.AddMonths`
* `dt.AddDays`
* `dt.AddHours`
* `dt.AddMinutes`
* `dt.AddSeconds`
* `dt.AddMilliseconds`
* `dt.AddMicroseconds`
* Static methods:
* `DateTime.Today`

## 4.9.0 - 2023-12-14

Expand Down
116 changes: 108 additions & 8 deletions src/fable-library-py/fable_library/date.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from datetime import datetime, timedelta, timezone
from re import Match
from typing import Any
from math import fmod

from .time_span import TimeSpan, total_microseconds
from .time_span import create as create_time_span
Expand Down Expand Up @@ -84,6 +85,10 @@ def millisecond(d: datetime) -> int:
return d.microsecond // 1000


def microsecond(d: datetime) -> int:
return d.microsecond


def to_universal_time(d: datetime) -> datetime:
return d.astimezone(timezone.utc)

Expand Down Expand Up @@ -229,6 +234,10 @@ 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()

Expand Down Expand Up @@ -269,19 +278,36 @@ def parse(string: str) -> datetime:
except ValueError:
pass

formats = {
# 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",
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)
return datetime.replace(
now(),
hour=hourDate.hour,
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",
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",
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",
# 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
r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{7}": "%Y-%m-%dT%H:%M:%S.%f000",
r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{7}$": "%Y-%m-%dT%H:%M:%S.%f000",
}

for pattern, format in formats.items():
Expand All @@ -299,10 +325,73 @@ def try_parse(string: str, defValue: FSharpRef[datetime]) -> bool:
return False


def date(d: datetime) -> datetime:
# TODO: Should forward d.kind
return create(d.year, d.month, d.day)


def days_in_month(year: int, month: int) -> int:
if month == 2:
return 29 if year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) 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))
# TODO: Should forward d.kind
return create(new_year, new_month, new_day, hour(d), minute(d), second(d), millisecond(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))
# TODO: Should forward d.kind
return create(new_year, new_month, new_day, hour(d), minute(d), second(d), millisecond(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)


__all__ = [
"add",
"op_subtraction",
Expand All @@ -315,6 +404,7 @@ def add_milliseconds(d: datetime, v: int) -> datetime:
"minute",
"second",
"millisecond",
"microsecond",
"day_of_week",
"day_of_year",
"date_to_string_with_custom_format",
Expand All @@ -323,6 +413,7 @@ def add_milliseconds(d: datetime, v: int) -> datetime:
"to_string",
"now",
"utc_now",
"today",
"to_local_time",
"compare",
"equals",
Expand All @@ -332,4 +423,13 @@ def add_milliseconds(d: datetime, v: int) -> datetime:
"parse",
"to_universal_time",
"try_parse",
"date",
"add_years",
"add_months",
"add_days",
"add_hours",
"add_minutes",
"add_seconds",
"add_milliseconds",
"add_microseconds",
]
33 changes: 19 additions & 14 deletions src/quicktest-py/quicktest.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,25 @@ let main argv =
// use file = builtins.``open``(StringPath "data.txt")
// file.read() |> printfn "File contents: %s"

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 thatYearSeconds (dt: DateTime) =
(dt - DateTime(dt.Year, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds

let test ms expected =
let dt1 = DateTime(2014, 10, 9, 13, 23, 30, 234, DateTimeKind.Utc)
let dt2 = dt1.AddMilliseconds(ms)
let res1 = compare dt1 dt2
let res2 = dt1.CompareTo(dt2)
let res3 = DateTime.Compare(dt1, dt2)
equal true (res1 = res2 && res2 = res3)
equal expected res1

test 1000. -1
test -1000. 1
test 0. 0

0

// let t = DayOfWeek.Thursday

let d = DateTime(2014, 10, 9)
DateTime(2014, 10, 9).DayOfYear |> equal 282

DateTime(2020, 10, 9).DayOfYear |> equal 283

0
// TryParse(String, DateTime)
// TryParse(String, IFormatProvider, DateTime)
// TryParse(String, IFormatProvider, DateTimeStyles, DateTime)
11 changes: 8 additions & 3 deletions tests/Js/Main/DateTimeTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -264,11 +264,16 @@ let tests =

testCase "DateTime.Parse with time-only string works" <| fun () -> // See #1045
let d = DateTime.Parse("13:50:34", CultureInfo.InvariantCulture)
d.Hour + d.Minute + d.Second |> equal 97
d.Year + d.Month + d.Day + d.Hour + d.Minute + d.Second |> equal 2124

let d = DateTime.Parse("1:5:34 AM", CultureInfo.InvariantCulture)
d.Hour + d.Minute + d.Second |> equal 40
d.Year + d.Month + d.Day + d.Hour + d.Minute + d.Second |> equal 2067

let d = DateTime.Parse("1:5:34 PM", CultureInfo.InvariantCulture)
d.Hour + d.Minute + d.Second |> equal 52
d.Year + d.Month + d.Day + d.Hour + d.Minute + d.Second |> equal 2079

let d = DateTime.Parse("15:5:34 PM", CultureInfo.InvariantCulture)
d.Year + d.Month + d.Day + d.Hour + d.Minute + d.Second |> equal 2081

testCase "DateTime.TryParse works" <| fun () ->
let f (d: string) =
Expand Down
Loading

0 comments on commit f22ce40

Please sign in to comment.