-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Feature/times fixup #7664
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/times fixup #7664
Conversation
|
Need to review carefully, submitted to be sure to not forget |
|
Thanks for taking the time to do this :) I'll do a proper review later, but one thing I noticed now: that For some more information about why it's designed this way, take a look at the documentation for the Java classes Duration and Period, which are similiar to our |
|
Interesting. Then isn't it a proper thing to divide it into TimeInterval and NormalizedTimeInterval? |
|
|
|
Okay, valid point. The case not covered by |
|
And, of cause, if the non-normalization of TI is by design, I want to have a valid point WHY. In essence, I don't see a difference in |
|
I am copying from the documentation of Period linked above:
|
|
hm. Thanks for clarification. But that's one case and i'm not sure it's very useful for comparisons. Like, i think almost anyone seeing comparison |
pull-23-04-18
|
Ah. I see the human-readable duration's been made while I've been working on my branch. But i'm a little against that it's string-only. I'd better have it as a |
I have been trained to never expect something like that from dates and times. A day is not always 24 hours, a year is not always 365 days, a minute is not always 60 seconds, timezone changes are sometimes 5 minutes and 52 seconds, and so on... |
|
I think this use case should be supported by implementing the equivalent of Javas Period.between. The |
|
Maybe (and most likely) you're right. But than it must be as explicit as it may be. Than we must add this to any So, I think my goal for now is:
edit Point 3 may be made via between function, as suggested by GULPF. But there's a but here - should it use a strait ariths like Any more suggestions/comments? |
Sounds great
Please implement it as proc between(startDt, endDt: DateTime): TimeInterval
Don't do this though. |
|
GULPF understood. But read edit in my last comment and confirm it's validity, please. Then i'll continue working on this PR. |
I don't fully understand the question. |
|
@GULPF Yeah, that's it. But i mean something else. I want to get something human-readable (and manipulable, still) in format of at least (, seconds, minutes, hours). May be even months and years, but that's a little less convenient as you're right on that that duration heavily depends on the starting point. |
This sounds good :) |
24ba34f to
b27bbf7
Compare
|
In several proc format*(time: Time, f: string): string {.tags: [].} =
## converts a `Time` value to a string representation. It will use the local
## time zone and use the format from ``format(dt: DateTime, f: string)``.
runnableExamples:
let tm = fromUnix(0) + now().utcOffset.seconds
doAssert format(tm, "yyyy-MM-dd'T'HH:mm:ss") == "1970-01-01T00:00:00"
time.local.format(f)Many people living in areas where DST is currently in effect will see an AssertionError here, if they try to run |
|
@skilchen okay, i'll look into it a little more closely |
|
|
|
Your commendable attempt to solve the relatively difficult task to write import times
var a = initDateTime(year = 2018, month = Month(3), monthday = 25,
hour = 0, minute = 59, second = 59, nanosecond = 999_999_999,
zone = utc())
var b = initDateTime(year = 2018, month = Month(3), monthday = 25,
hour = 1, minute = 0, second = 0, nanosecond = 0,
zone = utc())
echo between(a, b)gives: You are trying to set second = -1 in the result. Maybe you can simply turn the overflowchecks off, otherwise you have to bubble-up these overflows from nanoseconds to years... Now in the local timezone of one of the european countries (eg. Europe/Paris) where the switch to DST happened between 00:59:59 and 01:00:00 UTC on 2018-03-25 and setting second and minute to 1 to avoid the overflows from above: import times
var a = initDateTime(year = 2018, month = Month(3), monthday = 25,
hour = 0, minute = 59, second = 59, nanosecond = 999_999_999,
zone = utc()).local
var b = initDateTime(year = 2018, month = Month(3), monthday = 25,
hour = 1, minute = 1, second = 1, nanosecond = 0,
zone = utc()).local
var ti = between(a, b)
echo tigives: which is wrong even when considering the fact that there are probably infinitely many ways to express a time interval. A correctnes test would be: A minor issue: when handling the seconds you have a copy/paste error in your code (both in the positive and the negative branch): #Seconds
if end_dt_offseted.second < start_dt.second:
result.seconds = end_dt_offseted.nanosecond.int() +
convert(Minutes, Seconds, 1) - start_dt.second.int()
end_dt_offseted.minute -= 1
else:
result.seconds = end_dt_offseted.second.int() -
start_dt.second.int()the line My own attempt to write a from math import floor
proc floorDiv*[T, U](m: T, n:U): int =
## Return the whole part of m/n rounded towards negative infinity.
## (also known as "floor division")
return int(floor(m.float / n.float))
proc modulo*[T, U](x: T, y: U): U =
## The modulo operation using floor division
##
## x - floor(x / y) * y
return x.U - floorDiv(x.U, y.U).U * y
template toEpochDays(dt: DateTime): int =
toEpochDay(dt.monthday, dt.month, dt.year).int
proc between_b*(dt1, dt2: DateTime): TimeInterval =
## calculate the `TimeInterval` between two `DateTime`
## a loopless implementation inspired in the date part
## by the until Method of the new java.time.LocalDate class
##
var dt1 = dt1.utc()
var dt2 = dt2.utc()
var sign = 1
if dt2 < dt1:
swap(dt1, dt2)
sign = -1
let ts1 = initTime(unix = dt1.hour * 3600 + dt1.minute * 60 + dt1.second,
nanoseconds = dt1.nanosecond)
let ts2 = initTime(unix = dt2.hour * 3600 + dt2.minute * 60 + dt2.second,
nanoseconds = dt2.nanosecond)
let difftime = ts2 - ts1
let diffdays = floorDiv(difftime.seconds, 86400)
var diffseconds = int(difftime.seconds - 86400 * diffdays)
let diffhours = floorDiv(diffseconds, 3600)
let diffminutes = floorDiv(diffseconds - 3600 * diffhours, 60)
diffseconds = diffseconds - 3600 * diffhours - 60 * diffminutes
dt2 = dt2 + initTimeInterval(days = diffdays)
var totalMonths = dt2.year * 12 - dt1.year * 12 + ord(dt2.month) - ord(dt1.month)
var days = dt2.monthday - dt1.monthday
if (totalMonths > 0 and days < 0):
totalMonths.dec
let tmpDate = dt1 + initTimeInterval(months = totalMonths)
days = int(dt2.toEpochDays() - tmpDate.toEpochDays())
elif (totalMonths < 0 and days > 0):
totalMonths.inc
days = days - getDaysInMonth(year = dt2.year, month = dt2.month) + 1
let years = totalMonths div 12
let months = totalMonths mod 12
return initTimeInterval(years = sign * years,
months = sign * months,
days = sign * days,
hours = sign * diffhours,
minutes = sign * diffminutes,
seconds = sign * diffseconds,
nanoseconds = sign * difftime.nanoseconds)Maybe you get some inspiration from my attempt which most likely could also be improved. You should be able to simply insert my code at the bottom of times.nim to play with it. (It uses toEpochDay which times.nim doesn't export, therefore the need to insert the code into times.nim). |
|
Agreed that I don't think However, I think @skilchen's version is nearly correct, but it fails to handle this case: let dt1 = initDateTime(10, mJan, 2018, 13, 00, 00, utc())
let dt2 = initDateTime(11, mJan, 2018, 12, 00, 00, utc())
echo between(dt1, dt2) # hours = 23, days = 1Also, I think the result should be normalized as much as possible. This would mean that nanoseconds should be split into micro-/milli-/nanoseconds and that days should be split into weeks/days. |
|
@GULPF can you please give an example of the situation there conversion to unicode breaks the work? |
2f67356 to
05fb08d
Compare
|
Posted. Hopefully this works, though i'm still not sure about edge scenarios. Not sure should split into weeks, however, but the idea with ns, mks and ms is sound |
Pull 25-04-18
|
@dom96 Still waiting your comments on mutator funcs |
|
Ah, i see the problem with mutators, never mind |
6edf79f to
2ccebdb
Compare
|
Hopefully fixed the mutators problem. But still don't like it, but my suggestion on {default} pragma for the generics to be used in choice only AFTER anything else failed (no more specific proc) has been turned down, AFAIK |
|
|
||
| proc `*=`*[T,U: Duration or Time or DateTime or TimeInterval](a:var T, b:U) = | ||
| a = a * b | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i would prefer something like:
type TimesTypes = DateTime | Time | Duration | TimeInterval
proc `+=`*[T: TimesTypes, U](a: var T, b: U) =
a = a + b
proc `-=`*[T: TimesTypes, U](a: var T, b: U) =
a = a - b
proc `*=`*[T: TimesTypes, U](a: var T, b: U) =
a = a * bThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer to move these into a separate PR so that we can discuss them there. Like I mentioned I think we need a generic solution to this, if we simply merge this now we will end up forgetting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
What about a stringification procedure for proc `$`*(ti: TimeInterval): string =
template addSep() =
if len(result) != 0:
result.add(", ")
template addUnit(value: SomeNumber, unit: string) =
result.add($value)
result.add(" ")
result.add(unit)
if abs(value) != 1:
result.add("s")
result = ""
if ti.years != 0:
addUnit(ti.years, "year")
if ti.months != 0:
addSep()
addUnit(ti.months, "month")
if ti.weeks != 0:
addSep()
addUnit(ti.weeks, "week")
if ti.days != 0:
addSep()
addunit(ti.days, "day")
if ti.hours != 0:
addSep()
addUnit(ti.hours, "hour")
if ti.minutes != 0:
addSep()
addUnit(ti.minutes, "minute")
if ti.seconds != 0:
addSep()
addUnit(ti.seconds, "second")
if ti.milliseconds != 0:
addSep()
addUnit(ti.milliseconds, "millisecond")
if ti.microseconds != 0:
addSep()
addUnit(ti.microseconds, "microsecond")
if ti.nanoseconds != 0:
addSep()
addUnit(ti.nanoseconds, "nanosecond") |
|
Why not add the badly missing feature to format/parse fractional seconds in this PR? My proposals are: of "f", "ff", "fff":
var numStr = ""
let n = parseWhile(value[j..len(value) - 1], numStr, {'0'..'9'})
dt.nanosecond = parseInt(numStr) * (10 ^ (9 - n))
j += nand for of "f":
buf.add(intToStr(convert(Nanoseconds, Milliseconds, dt.nanosecond), 3))
of "ff":
buf.add(intToStr(convert(Nanoseconds, Microseconds, dt.nanosecond), 6))
of "fff":
buf.add(intToStr(dt.nanosecond, 9))Personally i would also change the format String in the default DateTime stringification from: result = format(dt, "yyyy-MM-dd'T'HH:mm:sszzz")to result = format(dt, "yyyy-MM-dd'T'HH:mm:ss'.'fzzz")but that might be too controversial. |
|
|
||
| proc `*=`*[T,U: Duration or Time or DateTime or TimeInterval](a:var T, b:U) = | ||
| a = a * b | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer to move these into a separate PR so that we can discuss them there. Like I mentioned I think we need a generic solution to this, if we simply merge this now we will end up forgetting.
lib/pure/times.nim
Outdated
|
|
||
| inc(i) | ||
|
|
||
| proc format*(time: Time, f: string, zone_info: proc(t: Time): DateTime = local): string {.tags: [].} = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't fix my concern. The default is still local and that is a problem. Taking this conscious explicit choice away from the developer is a bad idea.
b06efc2 to
9e2602e
Compare
|
Okay. Looks like i'm done with the improvements, except separate pr on generics |
9e2602e to
a266554
Compare
|
Thanks @survivorm for the implementation of my proposals! Now lets see, what the experts think ... It gets somewhat difficult to keep the overview... echo initDuration(days = -1)now gives (again): What you had before in # Ensure the same sign for seconds and nanoseconds
if remS < 0 and remNs > 0:
remNs -= convert(Seconds, Nanoseconds, 1)
remS.inc 1what is indeed not needed if nanoseconds can't be negative is the |
|
@skilchen Thanks. Yeah, i've missed it, thanks for rememberance. Will fix |
a266554 to
2af9342
Compare
|
@dom96 waiting for your opinion on the latest additions |
|
@dom96 Ping. Are you still busy? |
…FixedTimeUnit, int64] of it's human-readable parts, use it in `$Duration`
2af9342 to
5da74d5
Compare
No description provided.