Skip to content

Commit

Permalink
Merge pull request #7 from imclerran/add-date-time-support
Browse files Browse the repository at this point in the history
Add Date, Time, and DateTime types.
  • Loading branch information
imclerran authored Apr 18, 2024
2 parents 7d3f618 + 4d08ed8 commit 538ab0e
Show file tree
Hide file tree
Showing 20 changed files with 1,904 additions and 8 deletions.
25 changes: 24 additions & 1 deletion ISO_8601.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,27 @@ Examples:
- Extended: `YYYY-Www-DThh:mm±hh`

## Time Interval
...
1) A start and an end
- Basic: `YYYYMMDDThhmmss/YYYYMMDDThhmmss`
- Extended: `YYYY-MM-DDThh:mm:ss/YYYY-MM-DDThh:mm:ss`
2) A duration and context info (only duration provided by ISO str)
- Basic/Extended:
- `PnnYnnMnnDTnnHnnMnnS`
- `PnnW`
- Alternate:
- Basic: `PYYYYMMDDThhmmss`
- Extended: `PYYYY-MM-DDThh:mm:ss`
3) A start and a duration
- Basic:
- `YYYYMMDDThhmmss/PnnYnnMnnDTnnHnnMnnS`
- `YYYYMMDDThhmmss/PYYYYMMDDThhmmss`
- Extended:
- `YYYY-MM-DDThh:mm:ss/PnnYnnMnnDTnnHnnMnnS`
- `YYYY-MM-DDThh:mm:ss/PYYYY-MM-DDThh:mm:ss`
4) A duration and an end
- Basic:
- `PnnYnnMnnDTnnHnnMnnS/YYYYMMDDThhmmss`
- `PYYYYMMDDThhmmss/YYYYMMDDThhmmss`
- Extended:
- `PnnYnnMnnDTnnHnnMnnS/YYYY-MM-DDThh:mm:ss`
- `PYYYY-MM-DDThh:mm:ss/YYYY-MM-DDThh:mm:ss`
2 changes: 1 addition & 1 deletion ci/all_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ for roc_file in $package_dir*.roc; do
done

$roc test ./package/Tests.roc
$roc test ./package/Utc.roc
$roc test ./package/main.roc
137 changes: 137 additions & 0 deletions package/Date.roc
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
interface Date
exposes [
Date,
fromUtc,
fromYd,
fromYmd,
fromYw,
fromYwd,
toUtc,
unixEpoch,
]
imports [
Const,
Utc,
Utils.{
isLeapYear,
numDaysSinceEpoch,
ymdToDaysInYear,
calendarWeekToDaysInYear,
}
]

Date : { year: I64, dayOfYear: U16 }

unixEpoch : Date
unixEpoch = { year: 1970, dayOfYear: 1 }

fromYd : Int *, Int * -> Date
fromYd = \year, dayOfYear -> { year: Num.toI64 year, dayOfYear: Num.toU16 dayOfYear }

fromYmd : Int *, Int *, Int * -> Date
fromYmd =\year, month, day ->
{ year: Num.toI64 year, dayOfYear: ymdToDaysInYear year month day }

fromYwd : Int *, Int *, Int * -> Date
fromYwd = \year, week, day ->
daysInYear = if isLeapYear year then 366 else 365
d = calendarWeekToDaysInYear week year |> Num.add (Num.toU64 day)
if d > daysInYear then
{ year: Num.toI64 (year + 1), dayOfYear: Num.toU16 (d - daysInYear) }
else
{ year: Num.toI64 year, dayOfYear: Num.toU16 d }

fromYw : Int *, Int * -> Date
fromYw = \year, week ->
fromYwd year week 1

fromUtc : Utc.Utc -> Date
fromUtc =\utc ->
days = Utc.toNanosSinceEpoch utc |> Num.divTrunc (Const.nanosPerHour * 24) |> \d ->
if Utc.toNanosSinceEpoch utc |> Num.rem (Const.nanosPerHour * 24) < 0 then d - 1 else d
fromUtcHelper days 1970

fromUtcHelper : I128, I64 -> Date
fromUtcHelper =\days, year ->
if days < 0 then
fromUtcHelper (days + if Utils.isLeapYear (year - 1) then 366 else 365) (year - 1)
else
daysInYear = if Utils.isLeapYear year then 366 else 365
if days >= daysInYear then
fromUtcHelper (days - daysInYear) (year + 1)
else
{ year: year, dayOfYear: days + 1 |> Num.toU16 }

toUtc : Date -> Utc.Utc
toUtc =\date ->
days = numDaysSinceEpoch {year: date.year |> Num.toU64, month: 1, day: 1} + (date.dayOfYear - 1 |> Num.toI64)
Utc.fromNanosSinceEpoch (days |> Num.toI128 |> Num.mul (Const.nanosPerHour * 24))


# <==== TESTS ====>
# <---- fromYmd ---->
expect fromYmd 1970 1 1 == { year: 1970, dayOfYear: 1 }
expect fromYmd 1970 12 31 == { year: 1970, dayOfYear: 365 }
expect fromYmd 1972 3 1 == { year: 1972, dayOfYear: 61 }

# <---- fromYwd ---->
expect fromYwd 1970 1 1 == { year: 1970, dayOfYear: 1 }
expect fromYwd 1970 52 5 == { year: 1971, dayOfYear: 1 }

# <---- fromYw ---->
expect fromYw 1970 1 == { year: 1970, dayOfYear: 1 }
expect fromYw 1971 1 == { year: 1971, dayOfYear: 4 }

# <---- fromUtc ---->
expect
utc = Utc.fromNanosSinceEpoch 0
fromUtc utc == { year: 1970, dayOfYear: 1 }

expect
utc = Utc.fromNanosSinceEpoch (Const.nanosPerHour * 24 * 365)
fromUtc utc == { year: 1971, dayOfYear: 1 }

expect
utc = Utc.fromNanosSinceEpoch (Const.nanosPerHour * 24 * 365 * 2 + Const.nanosPerHour * 24 * 366)
fromUtc utc == { year: 1973, dayOfYear: 1 }

expect
utc = Utc.fromNanosSinceEpoch (Const.nanosPerHour * 24 * -1)
fromUtc utc == { year: 1969, dayOfYear: 365 }

expect
utc = Utc.fromNanosSinceEpoch (Const.nanosPerHour * 24 * -365)
fromUtc utc == { year: 1969, dayOfYear: 1 }

expect
utc = Utc.fromNanosSinceEpoch (Const.nanosPerHour * 24 * -365 - Const.nanosPerHour * 24 * 366)
fromUtc utc == { year: 1968, dayOfYear: 1 }

expect
utc = Utc.fromNanosSinceEpoch -1
fromUtc utc == { year: 1969, dayOfYear: 365 }

# <---- toUtc ---->
expect
utc = toUtc { year: 1970, dayOfYear: 1 }
utc == Utc.fromNanosSinceEpoch 0

expect
utc = toUtc { year: 1970, dayOfYear: 365 }
utc == Utc.fromNanosSinceEpoch (Const.nanosPerHour * 24 * 364)

expect
utc = toUtc { year: 1973, dayOfYear: 1 }
utc == Utc.fromNanosSinceEpoch (Const.nanosPerHour * 24 * 365 * 2 + Const.nanosPerHour * 24 * 366)

expect
utc = toUtc { year: 1969, dayOfYear: 365 }
utc == Utc.fromNanosSinceEpoch (Const.nanosPerHour * 24 * -1)

expect
utc = toUtc { year: 1969, dayOfYear: 1 }
utc == Utc.fromNanosSinceEpoch (Const.nanosPerHour * 24 * -365)

expect
utc = toUtc { year: 1968, dayOfYear: 1 }
utc == Utc.fromNanosSinceEpoch (Const.nanosPerHour * 24 * -365 - Const.nanosPerHour * 24 * 366)
73 changes: 73 additions & 0 deletions package/DateTime.roc
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
interface DateTime
exposes [
fromUtc,
fromYd,
fromYmd,
fromYw,
fromYwd,
fromYmdhms,
fromYmdhmsn,
toUtc,
unixEpoch,
]
imports [
Const,
Date,
Time,
Utc,
Utc.{ Utc },
UtcTime,
]

DateTime : { date : Date.Date, time : Time.Time }

unixEpoch : DateTime
unixEpoch = { date: Date.unixEpoch, time: Time.midnight }

fromYd : Int *, Int * -> DateTime
fromYd = \year, day -> { date: Date.fromYd year day, time: Time.midnight }

fromYmd : Int *, Int *, Int * -> DateTime
fromYmd = \year, month, day -> { date: Date.fromYmd year month day, time: Time.midnight }

fromYwd : Int *, Int *, Int * -> DateTime
fromYwd = \year, week, day -> { date: Date.fromYwd year week day, time: Time.midnight }

fromYw : Int *, Int * -> DateTime
fromYw = \year, week -> { date: Date.fromYw year week, time: Time.midnight }

fromYmdhms : Int *, Int *, Int *, Int *, Int *, Int * -> DateTime
fromYmdhms = \year, month, day, hour, minute, second ->
{ date: Date.fromYmd year month day, time: Time.fromHms hour minute second }

fromYmdhmsn : Int *, Int *, Int *, Int *, Int *, Int *, Int * -> DateTime
fromYmdhmsn = \year, month, day, hour, minute, second, nanosecond ->
{ date: Date.fromYmd year month day, time: Time.fromHmsn hour minute second nanosecond }

toUtc : DateTime -> Utc
toUtc =\ dateTime ->
dateNanos = Date.toUtc dateTime.date |> Utc.toNanosSinceEpoch
timeNanos = Time.toUtcTime dateTime.time |> UtcTime.toNanosSinceMidnight |> Num.toI128
Utc.fromNanosSinceEpoch (dateNanos + timeNanos)

expect
utc = toUtc (fromYmdhmsn 1970 12 31 12 34 56 5)
utc == Utc.fromNanosSinceEpoch (364 * 24 * 60 * 60 * 1_000_000_000 + 12 * 60 * 60 * 1_000_000_000 + 34 * 60 * 1_000_000_000 + 56 * 1_000_000_000 + 5)

fromUtc : Utc -> DateTime
fromUtc = \utc ->
nanos = Utc.toNanosSinceEpoch utc
timeNanos = if nanos < 0 && nanos % (Const.nanosPerHour * 24) != 0 then
nanos % (Const.nanosPerHour * 24) + Const.nanosPerHour * 24 else nanos % (Const.nanosPerHour * 24)
dateNanos = nanos - timeNanos |> Num.toI128
date = dateNanos |> Num.toI128 |> Utc.fromNanosSinceEpoch |> Date.fromUtc
time = timeNanos |> Num.toI64 |> UtcTime.fromNanosSinceMidnight |> Time.fromUtcTime
{ date, time }

expect
dateTime = fromUtc (Utc.fromNanosSinceEpoch (364 * 24 * Const.nanosPerHour + 12 * Const.nanosPerHour + 34 * Const.nanosPerMinute + 56 * Const.nanosPerSecond + 5))
dateTime == fromYmdhmsn 1970 12 31 12 34 56 5

expect
dateTime = fromUtc (Utc.fromNanosSinceEpoch (-1))
dateTime == fromYmdhmsn 1969 12 31 23 59 59 (Const.nanosPerSecond - 1)
1 change: 1 addition & 0 deletions package/DateTimes/ABOUT.MD
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
hasnept/roc-datetimes
98 changes: 98 additions & 0 deletions package/DateTimes/Conversion.roc
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
interface DateTimes.Conversion
exposes [
daysInAWeek,
daysToSeconds,
hoursInADay,
hoursToSeconds,
microsecondsInAMillisecond,
microsecondsInASecond,
microsecondsToNanoseconds,
microsecondsToWholeMilliseconds,
microsecondsToWholeSeconds,
millisecondsInASecond,
millisecondsToMicroseconds,
millisecondsToNanoseconds,
millisecondsToWholeSeconds,
minutesInAnHour,
minutesToSeconds,
nanosecondsInAMicrosecond,
nanosecondsInAMillisecond,
nanosecondsInASecond,
nanosecondsToWholeMilliseconds,
nanosecondsToWholeSeconds,
nanosecondsToWholeMicroseconds,
secondsInAMinute,
secondsInAWeek,
secondsInADay,
secondsInAnHour,
secondsToMicroseconds,
secondsToMilliseconds,
secondsToNanoseconds,
secondsToWholeDays,
secondsToWholeHours,
secondsToWholeMinutes,
secondsToWholeWeeks,
weeksToSeconds,
]
imports []

# Constants

nanosecondsInAMicrosecond = 1_000
microsecondsInAMillisecond = 1_000
millisecondsInASecond = 1_000
secondsInAMinute = 60
minutesInAnHour = 60
hoursInADay = 24
daysInAWeek = 7

microsecondsInASecond = microsecondsInAMillisecond * millisecondsInASecond
nanosecondsInAMillisecond = nanosecondsInAMicrosecond * microsecondsInAMillisecond
nanosecondsInASecond = nanosecondsInAMicrosecond * microsecondsInASecond
secondsInADay = secondsInAMinute * minutesInAnHour * hoursInADay
secondsInAnHour = secondsInAMinute * minutesInAnHour
secondsInAWeek = secondsInAMinute * minutesInAnHour * hoursInADay * daysInAWeek

# Nanoseconds

nanosecondsToWholeMilliseconds = \nanoseconds -> nanoseconds // nanosecondsInAMillisecond
nanosecondsToWholeSeconds = \nanoseconds -> nanoseconds // nanosecondsInASecond
nanosecondsToWholeMicroseconds = \nanoseconds -> nanoseconds // nanosecondsInAMicrosecond

# Microseconds

microsecondsToNanoseconds = \microseconds -> microseconds * nanosecondsInAMicrosecond
microsecondsToWholeMilliseconds = \microseconds -> microseconds // microsecondsInAMillisecond
microsecondsToWholeSeconds = \microseconds -> microseconds // microsecondsInASecond

# Milliseconds

millisecondsToMicroseconds = \milliseconds -> milliseconds * microsecondsInAMillisecond
millisecondsToNanoseconds = \milliseconds -> milliseconds * nanosecondsInAMillisecond
millisecondsToWholeSeconds = \milliseconds -> milliseconds // millisecondsInASecond

# Seconds

secondsToMicroseconds = \seconds -> seconds * microsecondsInASecond
secondsToMilliseconds = \seconds -> seconds * millisecondsInASecond
secondsToNanoseconds = \seconds -> seconds * nanosecondsInASecond
secondsToWholeDays = \seconds -> seconds // secondsInADay
secondsToWholeHours = \seconds -> seconds // secondsInAnHour
secondsToWholeMinutes = \seconds -> seconds // secondsInAMinute
secondsToWholeWeeks = \seconds -> seconds // secondsInAWeek

# Minutes

minutesToSeconds = \minutes -> minutes * secondsInAMinute

# Hours

hoursToSeconds = \hours -> hours * secondsInAnHour

# Days

daysToSeconds = \days -> days * secondsInADay

# Weeks

weeksToSeconds = \weeks -> (Num.toI64 weeks) * secondsInAWeek
Loading

0 comments on commit 538ab0e

Please sign in to comment.