Skip to content

Commit

Permalink
Merge pull request #31 from imclerran/utc-to-signed
Browse files Browse the repository at this point in the history
Convert Utc to signed integer
  • Loading branch information
bhansconnect authored Feb 1, 2024
2 parents a50ebcd + 120ab7c commit d592b4e
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 50 deletions.
122 changes: 79 additions & 43 deletions platform/InternalDateTime.roc
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ interface InternalDateTime
]
imports []

DateTime : { year : U128, month : U128, day : U128, hours : U128, minutes : U128, seconds : U128 }
DateTime : { year : I128, month : I128, day : I128, hours : I128, minutes : I128, seconds : I128 }

toIso8601Str : DateTime -> Str
toIso8601Str = \{ year, month, day, hours, minutes, seconds } ->
Expand All @@ -19,7 +19,7 @@ toIso8601Str = \{ year, month, day, hours, minutes, seconds } ->

"\(yearStr)-\(monthStr)-\(dayStr)T\(hourStr):\(minuteStr):\(secondsStr)Z"

yearWithPaddedZeros : U128 -> Str
yearWithPaddedZeros : I128 -> Str
yearWithPaddedZeros = \year ->
yearStr = Num.toStr year
if year < 10 then
Expand All @@ -31,27 +31,27 @@ yearWithPaddedZeros = \year ->
else
yearStr

monthWithPaddedZeros : U128 -> Str
monthWithPaddedZeros : I128 -> Str
monthWithPaddedZeros = \month ->
monthStr = Num.toStr month
if month < 10 then
"0\(monthStr)"
else
monthStr

dayWithPaddedZeros : U128 -> Str
dayWithPaddedZeros : I128 -> Str
dayWithPaddedZeros = monthWithPaddedZeros

hoursWithPaddedZeros : U128 -> Str
hoursWithPaddedZeros : I128 -> Str
hoursWithPaddedZeros = monthWithPaddedZeros

minutesWithPaddedZeros : U128 -> Str
minutesWithPaddedZeros : I128 -> Str
minutesWithPaddedZeros = monthWithPaddedZeros

secondsWithPaddedZeros : U128 -> Str
secondsWithPaddedZeros : I128 -> Str
secondsWithPaddedZeros = monthWithPaddedZeros

isLeapYear : U128 -> Bool
isLeapYear : I128 -> Bool
isLeapYear = \year ->
(year % 4 == 0)
&& # divided evenly by 4 unless...
Expand All @@ -68,7 +68,7 @@ expect !(isLeapYear 2015)
expect List.map [2023, 1988, 1992, 1996] isLeapYear == [Bool.false, Bool.true, Bool.true, Bool.true]
expect List.map [1700, 1800, 1900, 2100, 2200, 2300, 2500, 2600] isLeapYear == [Bool.false, Bool.false, Bool.false, Bool.false, Bool.false, Bool.false, Bool.false, Bool.false]

daysInMonth : U128, U128 -> U128
daysInMonth : I128, I128 -> I128
daysInMonth = \year, month ->
if List.contains [1, 3, 5, 7, 8, 10, 12] month then
31
Expand All @@ -93,55 +93,91 @@ expect daysInMonth 2023 10 == 31 # October
expect daysInMonth 2023 11 == 30 # November
expect daysInMonth 2023 12 == 31 # December

epochMillisToDateTime : U128 -> DateTime
epochMillisToDateTime : I128 -> DateTime
epochMillisToDateTime = \millis ->
seconds = Num.divTrunc millis 1000
minutes = Num.divTrunc seconds 60
hours = Num.divTrunc minutes 60
day = 1 + Num.divTrunc hours 24
seconds = millis // 1000
minutes = seconds // 60
hours = minutes // 60
day = 1 + hours // 24
month = 1
year = 1970

epochMillisToDateTimeHelp {
year,
month,
day,
hours,
minutes,
seconds,
hours: hours % 24,
minutes: minutes % 60,
seconds: seconds % 60,
}

epochMillisToDateTimeHelp : DateTime -> DateTime
epochMillisToDateTimeHelp = \current ->

countDaysInYear = if isLeapYear current.year then 366 else 365
countDaysInMonth = daysInMonth current.year current.month
countDaysInPrevMonth =
if current.month == 1 then daysInMonth (current.year - 1) 12
else daysInMonth current.year (current.month - 1)

if current.day < 1 then
epochMillisToDateTimeHelp {
current &
year: if current.month == 1 then current.year - 1 else current.year,
month: if current.month == 1 then 12 else current.month - 1,
day: current.day + countDaysInPrevMonth,
}
else if current.hours < 0 then
epochMillisToDateTimeHelp {
current &
day: current.day - 1,
hours: current.hours + 24
}
else if current.minutes < 0 then
epochMillisToDateTimeHelp {
current &
hours: current.hours - 1,
minutes: current.minutes + 60,
}
else if current.seconds < 0 then
epochMillisToDateTimeHelp {
current &
minutes: current.minutes - 1,
seconds: current.seconds + 60,
}
else if current.day > countDaysInMonth then
epochMillisToDateTimeHelp {
current &
year: if current.month == 12 then current.year + 1 else current.year,
month: if current.month == 12 then 1 else current.month + 1,
day: current.day - countDaysInMonth,
}
else
current


# test 1000 ms before epoch
expect
str = -1000 |> epochMillisToDateTime |> toIso8601Str
str == "1969-12-31T23:59:59Z"

if current.day > countDaysInYear then
epochMillisToDateTimeHelp {
year: current.year + 1,
month: current.month,
day: current.day - countDaysInYear,
hours: current.hours - (countDaysInYear * 24),
minutes: current.minutes - (countDaysInYear * 24 * 60),
seconds: current.seconds - (countDaysInYear * 24 * 60 * 60),
}
else if current.day > countDaysInMonth then
epochMillisToDateTimeHelp {
year: current.year,
month: current.month + 1,
day: current.day - countDaysInMonth,
hours: current.hours - (countDaysInMonth * 24),
minutes: current.minutes - (countDaysInMonth * 24 * 60),
seconds: current.seconds - (countDaysInMonth * 24 * 60 * 60),
}
else
{ current &
hours: current.hours % 24,
minutes: current.minutes % 60,
seconds: current.seconds % 60,
}
# test 1 hour, 1 minute, 1 second before epoch
expect
str = (-3600 * 1000 - 60 * 1000 - 1000) |> epochMillisToDateTime |> toIso8601Str
str == "1969-12-31T22:58:59Z"

# test 1 month before epoch
expect
str = (-1 * 31 * 24 * 60 * 60 * 1000) |> epochMillisToDateTime |> toIso8601Str
str == "1969-12-01T00:00:00Z"

# test 1 year before epoch
expect
str = (-1 * 365 * 24 * 60 * 60 * 1000) |> epochMillisToDateTime |> toIso8601Str
str == "1969-01-01T00:00:00Z"

# test 1st leap year before epoch
expect
str = (-1 * (365 + 366) * 24 * 60 * 60 * 1000) |> epochMillisToDateTime |> toIso8601Str
str == "1968-01-01T00:00:00Z"

# test last day of 1st year after epoch
expect
Expand Down
31 changes: 24 additions & 7 deletions platform/Utc.roc
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@ interface Utc
]

## Stores a timestamp as nanoseconds since UNIX EPOCH
Utc := U128
Utc := I128

## Duration since UNIX EPOCH
now : Task Utc *
now =
Effect.posixTime
|> Effect.map Num.toI128
|> Effect.map @Utc
|> Effect.map Ok
|> InternalTask.fromEffect
Expand All @@ -41,30 +42,46 @@ toIso8601Str = \@Utc nanos ->
nanosPerMilli = 1_000_000

## Convert Utc timestamp to milliseconds
toMillisSinceEpoch : Utc -> U128
toMillisSinceEpoch : Utc -> I128
toMillisSinceEpoch = \@Utc nanos ->
nanos // nanosPerMilli

## Convert milliseconds to Utc timestamp
fromMillisSinceEpoch : U128 -> Utc
fromMillisSinceEpoch : I128 -> Utc
fromMillisSinceEpoch = \millis ->
@Utc (millis * nanosPerMilli)

## Convert Utc timestamp to nanoseconds
toNanosSinceEpoch : Utc -> U128
toNanosSinceEpoch : Utc -> I128
toNanosSinceEpoch = \@Utc nanos ->
nanos

## Convert nanoseconds to Utc timestamp
fromNanosSinceEpoch : U128 -> Utc
fromNanosSinceEpoch : I128 -> Utc
fromNanosSinceEpoch = @Utc

## Calculate milliseconds between two Utc timestamps
deltaAsMillis : Utc, Utc -> U128
deltaAsMillis = \@Utc first, @Utc second ->
(Num.absDiff first second) // nanosPerMilli
firstCast = Num.bitwiseXor (Num.toU128 first) (Num.shiftLeftBy 1 127)
secondCast = Num.bitwiseXor (Num.toU128 second) (Num.shiftLeftBy 1 127)
(Num.absDiff firstCast secondCast) // nanosPerMilli

## Calculate nanoseconds between two Utc timestamps
deltaAsNanos : Utc, Utc -> U128
deltaAsNanos = \@Utc first, @Utc second ->
Num.absDiff first second
firstCast = Num.bitwiseXor (Num.toU128 first) (Num.shiftLeftBy 1 127)
secondCast = Num.bitwiseXor (Num.toU128 second) (Num.shiftLeftBy 1 127)
Num.absDiff firstCast secondCast

# TESTS
expect deltaAsNanos (fromNanosSinceEpoch 0) (fromNanosSinceEpoch 0) == 0
expect deltaAsNanos (fromNanosSinceEpoch 1) (fromNanosSinceEpoch 2) == 1
expect deltaAsNanos (fromNanosSinceEpoch -1) (fromNanosSinceEpoch 1) == 2
expect deltaAsNanos (fromNanosSinceEpoch Num.minI128) (fromNanosSinceEpoch Num.maxI128) == Num.maxU128

expect deltaAsMillis (fromMillisSinceEpoch 0) (fromMillisSinceEpoch 0) == 0
expect deltaAsMillis (fromNanosSinceEpoch 1) (fromNanosSinceEpoch 2) == 0
expect deltaAsMillis (fromMillisSinceEpoch 1) (fromMillisSinceEpoch 2) == 1
expect deltaAsMillis (fromMillisSinceEpoch -1) (fromMillisSinceEpoch 1) == 2
expect deltaAsMillis (fromNanosSinceEpoch Num.minI128) (fromNanosSinceEpoch Num.maxI128) == Num.maxU128 // nanosPerMilli

0 comments on commit d592b4e

Please sign in to comment.