Yeah this doesn't hold water. If you need the precision, float isn't suitable since he precision drops of. If you don't need the precision, no need to use float.
Postgres's time handling types are elaborate, and probably the best I've personally worked with.
Timestamp with TimeZone (including DST + timezone offset) is a different type from timestamp without TimeZone (ie: normalized to GMT). Timestamp - Timestamp results in "interval".
Perhaps my method I was discussing doesn't work. But the overall idea that "Timestamp" needs to be a different type than "Interval", is pretty key IMO to handling date/times appropriately.
time_t will not handle daylight savings time conversions like Postgresql does.
Unambiguously resolving "Timestamp at Eastern Time - Timestamp at Arizona Time" is not as simple as you might think. Postgresql handles this case.
Trying to make "time_t" handle all cases is a mistake. We need like 3 to 4 types at a minimum to handle all of the edge cases that occur in the real world. (And if anyone teaches me of any more complications, maybe we'll need even more types)
The idea is to store all times as time_t in UCT, and do all time calculations in UTC.
Do conversions to/from local time only as a first and last step.
It's the only sane way to do it. The D runtime library does that, and it has proven to be robust and sane. We're also recommending that users do the same thing with different character representations - do all calculation and processing in UTF-8. Other sets are translated upon input into UTF-8, and the UTF-8 is translated to other character sets only on output.
> Do conversions to/from local time only as a first and last step.
Cool.
Now how do you enforce that? Spoiler: have the local-time be reported as a TimestampWithTimeZone.
When you're doing calculations, make them all Timestamps.
When you're done, you can remember that you've converted from internal UTC-timestamps into the proper output format because you'll have a TimestampWithTimezone again.
-----------
We're programmers. We have problems that are simple and enforceable through compiler constructs called "The Type System".
Why have the human figure out if they have time-zone back-and-forth correctly or otherwise made a mistake (ex: adding code to the wrong "part of the process" and accidentally mucking with a TimestampWithTimeZone input/output variable rather than the internal Timestamp-UTC-only type variables?)
Just... leave the job of checking this entire process to the compiler. If your n00b coworker messes up, the type system catches it and we're all set.
For lots of use cases you want to record precise events in a universal sense, and be able to add and subtract them or compare whether one timestamp is the same exact time as another timestamp regardless of timezones.
UNIX timestamps have no timezones. They are just the number of actual seconds that have elapsed since a certain commonly-agreed time. You measure the number of seconds between two events just by subtracting their timestamps arithmetically, and don't need to do any parsing or interpreting of timezones. You can just say "this hacker in Russia breached the firewall 35 seconds after this dev committed code in the US" just by subtracting two numbers.
The time an airplane takes off, the time your firewall detected a security event, the time a cross-timezone meeting starts, these are all good to store as UNIX timestamps.
Birthdays are different because they are inherently imprecise; humans tend to celebrate their birthdays when it's a certain day of the month in their local time zone, with little to no regard for the time zone they were born in. Someone born in Singapore (UTC+8) and lives in Alaska (UTC-8) might still celebrate their 30th birthday on the month and day of their birthday in Alaska time even though their body might need another 16 hours before it has technically experienced 30 years of life.
> UNIX timestamps have no timezones. They are just the number of actual seconds that have elapsed since a certain commonly-agreed time. You measure the number of seconds between two events just by subtracting their timestamps arithmetically, and don't need to do any parsing or interpreting of timezones
If only. UNIX mishandling of leap seconds makes this all a illusion, in reality you need to have logic to handle them
I don't think that UNIX mishandles leap seconds. It properly ignores them because they aren't relevant to it. The UNIX timestamp is literally the count of units past the epoch. The existence of leap seconds don't affect that count, it only affects the timekeeping of a completely different scheme.
So yes, you have to handle leap seconds when you're converting from the timestamp to/from the time scheme you are interested in, just as you have to handle leap years, etc. The UNIX timestamp is just a different way of marking time, and is blessed with not worrying at all about how that time relates to the actual motion of the Earth. That makes it easy and consistent for common operations. It is not mishandling anything. It just can't shield you from the complexity of the forms of timekeeping that make it a point to be synchronized with the Earth.
That's not true. Unix time is specifically defined as "A value that approximates the number of seconds that have elapsed since the Epoch."
Are you perhaps confusing that with the standard algorithms for converting between Unix time and "human" time? Because what you say isn't wrong for that case, and this is a classic trap for new players:
"A Coordinated Universal Time name (specified in terms of seconds (tm_sec), minutes (tm_min), hours (tm_hour), days since January 1 of the year (tm_yday), and calendar year minus 1900 (tm_year)) is related to a time represented as seconds since the Epoch, according to the expression below.
If the year is <1970 or the value is negative, the relationship is undefined. If the year is >=1970 and the value is non-negative, the value is related to a Coordinated Universal Time name according to the C-language expression, where tm_sec, tm_min, tm_hour, tm_yday, and tm_year are all integer types:
Do you think UNIX epoch offset is different for "the second before the leap second", "the leap second" and the "second after the leap second"? If so, how? If not, then we can't rely on differences between UNIX epoch offsets for measuring intervals can we?
Moreover, if the units you are referring to are "seconds" then the statement "[t]he UNIX timestamp is literally the count of units past the epoch" is also wrong. If not, what are the units here?
> That's not true. Unix time is specifically defined as "A value that approximates the number of seconds that have elapsed since the Epoch."
It's missing 27 seconds that elapsed since the epoch. (Plus whatever happened with rubber seconds before 1972). Maybe that fits within the definition of approximate.
But if you need to record events that happen on a leap second or the second before a leap second, unixtime is unhelpful.
If you want to record a duration that includes a leapseconds, unixtime is unhelpful, although if the duration is long enough, the difference may be of no consequence.
There is no such thing as "exact same time" due to relativity. The most perfect of clocks in use on the planet will drift, and both are correct.
Mail an atomic clock easy to west, and a different one west to east, and they'll drift different amounts. Put one in a plane, it will disagree with one on the ground. Put one on a mountain, it will disagree with one at sea level. Put one at the pole, it will differ with one on the equator.
There is little sense in claiming one time is exactly equal to another. Close to some tolerance is useful.
This might seem like a silly question but could you give me an example use-case?
I'm struggling to think of a context where we basically want to create timestamps for things that happened in the past. Especially things that we want to dynamically localize rather than e.g. just store as a straight up timestamped string.
If you are creating a relationship between times in the past, you have to convert from string to something else. And the something else is always subject to some other detail - the calendar of the past is not defined identically throughout history, so you can't just extrapolate year-month-day backwards to get a time.
Nobody's really solved this, it has to be tackled anew for each case of "comparing dates from long ago".
Sure but 1970 (or 1899 or 1582 or whatever) is a hell of a lot closer than 2^63 or what have you so why double the end already ridiculously far away instead of the one extremely close?
i32 is used for Unix time, resulting in overflow sometime in 2038. If that was changed to u32, overflow would happen instead in 2106. 15 years is close enough that such a change would actually "make or break" timekeeping on human timescales.
Using i64 for nanoseconds with the same origin (1970-01-01), we could cover roughly 1677-2262. Using u64 for nanoseconds we could cover roughly 1970-2554, or more generally about 585 years of timestamps from wherever a new proposed origin would be. Likely we don't need nanoscale precision for times far enough in the past, so I guess "far enough" is the definition worth arguing over here.
You're looking far out for 2106 while ignoring the signing change already broke all date calculation from prior to the epoch. This means, with an unsigned version, you start out immediately butted against the problem that many real dates are not representable instead of running into this problem many years away. The advantage that future dates will be representable twice as far out doesn't make up for being butted up against the same problem in the other direction from the start.
The same story is present with 64 bit nanosecond based timestamps, albeit we're keeping the mid date ever so slightly in the past for ease of conversion. If dates in 2264+ are important then just use a 128 bit timestamp, it'll fix the problem for many multiples the age of the universe instead of causing issues now to gain a few hundred years of padding in the future. Another common approach is to keep u64 second based timestamps and save the nanoseconds as another integer. This comes at the cost of "only" solving the problem for hundreds of billions of years but being interchangeable with those using non-fractional timestamps.
If we're really talking about timestamps, then by definition they only need to represent the current date and time. It's inherently a more limited use-case than general representation of arbitrary dates/times, as timestamps don't require the ability to store dates that occurred prior to the system's existence.
For example, the unix epoch can safely be 1970 because the operating system did not exist prior to 1970, so there's no real-world situation in which you'd be time-stamping a system event or log entry prior to 1970.
That said, time_t is usually a signed type in C. There are several reasonable historical explanations for that though, including the need to use -1 for an error return, and the fact that unsigned ints weren't present in C until 1978.
The danger comes when you need to compare your timestamps to something that may have happened before your system initialized. If you can both guarantee your time type will only ever be used for timestamps on your system and these will never in the future interact with or be compared to timestamps from prior to your system going online then you can get away with an epoch at the start of your system.
The Unix epoch was never an issue being 1970 because they knew they were making it as signed, not because they thought Unix systems would never deal with e.g. files from before Unix was created.
Again, unsigned ints weren't even present in C until 1978, so of course it was signed -- there was no alternative.
As far as I've ever read, negative unix timestamp values generally were not used. Since -1 was used as an error value, you cannot safely determine whether that value represents an error vs a legitimate timestamp one second before the epoch.
I'm not familiar with if/how early versions of Unix were able to interact with non-Unix files/filesystems, but I doubt they would preserve metadata such as mtime if so.
Even if you count in microseconds, a signed 64-bit timestamp gives you a range of roughly 292,000 years in each direction. Being able to represent times in the past is a lot more useful than doubling that range.
No, because times before 1970 exist. With signed timestamps, you have to figure out weird ways to represent when you need to talk about times in the past, with signed 64-bit timestamps you can represent any moment since well before big bang.
If you use signed int64 to represent differences, then in order to be able to represent all possible differences, timestamps need to be restricted to a 63-bit range. Might as well use a signed int64 restricted to nonnegative numbers as an uint63, or restricted to 62 bits of each negative and nonnegative numbers as an int63.
> if you subtract two `time` types, the result must be a valid `time` type, so you can add/subtract to it. The result cannot be a `timediff` type.
Huh? Of course `time` - `time` gives a `timediff` type. You can add/subtract to it: you add/subtract timediffs, or you can add/subtract a timediff to/from a time. Why would you ever be directly adding times? What is 100 AD + 100 AD? 200 AD? Not if you pick a different 0-point for your calendar ...
As an aside dang would probably ban my account if I let rip about what I think about hardware engineers designing real time clocks that count time in HH:MM:SS + day/month/two digit year. And 100 or 128 hz subseconds.
But yeah started using int64 30 years ago and all the fuckedness went away.
I suspect the hardware engineer would explain the great favour he’s doing you by handling all the handling of all the weird splits between things like hours, minutes and days for you in hardware logic, rather than you having to waste previous cycles of your MCU’s core to do that sort of thing. But it is indeed a real pain when you want dates in more abstract than human forms…
The rest of us, including a number of hardware engineers who have learned from history, would point to the real-time clock chip that did hardware rules for things right for only one country for a mere three years in the 1980s, until the legislation changed, and whose behaviour has been wrong ever since.
Step forward the MC146818, used in the PC/AT and emulated by chipsets since, whose built-in daylight savings time change rule implemented in hardware was rendered obsolete in 1987.
Old electrical engineer I worked under in the 1980's mentioned he designed a clock calendar STD Bus card. Next design he replaced the whole thing with a 32 bit counter on the processor card and 250 bytes of Z80 assembly.
Unrelated I think the lobbyists that got daylight savings time changed charged the BBQ manufacturers $50k.
I remember one colleague concerned with casting a Java 64-bit millisecond timestamp to double seconds when crossing languages (over to a somewhat TypeScript-like financial modeling DSL developed in the mid-late 1990s, where it's a bit clunky to deal with numbers in any format other than doubles).
I added a comment apologizing to software archaeologists on 12 October 287,396 C.E., when the precision of double timestamps (with 1970-01-01T00:00:00 UTC epoch) drops to 2 milliseconds, and the reviewer was fine after that.
I think they are implying that storing time as a float is riddled with problems (like arithmetic needs to think about hardware bugs and implementations)
The problem being that OP kinda glosses over them.
I strongly disagree with this. It only makes sense to use floats if you want time to have increasing precision the closer you get to 1970. From a symmetry group perspective, that makes no sense; the amount you care about the incremental precision of time measurement is invariant with respect to the present date. (Translation invariance). This means you want uniformly distributed time samples, hence an integer format.
If you think microsecond precision is good enough for anybody, then you could consider using an integer where the LSB represents 1 microsecond. But don't use floating point time, unless your specific application cares more about time scale invariance than time translation invariance.
You can even go to nanosecond. The largest uint64 is
18446744073709551615
and epoch time now in nanoseconds is less than a tenth of that i.e.
1686340765688397824
You can go for another 500+ years safely on nanoseconds-as-uint64.
I fully disagree with using float64. It wastes bits on an exponent and you cannot safely use == to compare timestamps that have math done on them or have been converted to/from a string (including e.g. sent as part of a JSON).
Me too. I thought it was normal already to just store the unix nanos. At least that's what I do and it seems to work. Yes I have stuff with nano granularity.
You don't want to get into floating point, it gets all fuzzy and then when you need to change it, there's a bunch of code to change.
You're both making too much of this. I mean, Javascript applications are going to be doing this naturally, have been for decades now, and... it's fine. Floating point can indeed be a footgun, but generally not in the space of single numbers like this.
And integer representations have traps too. To pick on your particular example: "timestamps" are not the same thing as "times" and if your code predicated on that, it likely has bugs already with monotonicity and uniqueness assumptions.
And the benefit is that, as the article points out, you can have easy subsecond precision, something that is much harder (and pervasively unsupported by existing libraries) with fixed point math.
Should one go to the mattresses to defend either of these designs? No. But on the whole my heart agrees with the linked article. If you have to make a choice today, for new code, stick with a double.
> Javascript applications are going to be doing this naturally
Only because its timestamp (as in `Date.getTime`) is actually the number of milliseconds since the UNIX epoch assuming no leap seconds. You can't keep the current scheme in a nanosecond precision (2^31 * 10^9 ~= 2^61, so float64 will truncate lower bits). Python had the exact same issue and it now has a separate `time.time_ns()` function as opposed to the traditional `time.time()` function.
> You can't keep the current scheme in a nanosecond precision (2^31 * 10^9 ~= 2^61, so float64 will truncate lower bits).
What integer system do you expect to work well with nanosecond time values? As you just showed, a 64 bit ns value will rollover in O(2^32) seconds, which is already universally considered to be insufficient for an epoch time.
Again, this is splitting hairs. In fact as I see it doubles would be preferable in in that regime, as while you'll be dancing at the edge of their representation capability, they at least won't blow trying to record dates in the 1420's or whatever. You can get as close as is practical without worrying about representation space.
I think it was Microsoft BASIC that stored time as a floating point number. What a bad idea. Converting from it to an integer and then converting back to float would cause the time to "drift" due to rounding errors.
It's the same reason why you should never use floating point numbers for account balances.
The VARIANT date type uses a floating point number; the whole number portion is the day (days past 1900 January 1) and the fractional part is the time.
Not sure about classic BASIC; I don't really recall there being a specific time type, though I know later things like Visual Basic and etc would just use the VARIANT date type above with a 0 day component.
I like Windows' FILETIME which stores time as 100-nanosecond intervals since the beginning of the year 1601. C#'s DateTime is similar but uses an epoch of 0001 January 1.
I know JavaScript and others simply use the usual unix Epoch but with milliseconds rather than seconds, so time() * 1000.
I don’t know why but this just reminded me of how I discovered that SQL Server stores times in increments of .000, .003, .007 seconds. I wasn’t even using SQL Server (directly), just trying to understand what looked like data corruption as an API consumer. That discovery was a precursor to probably the second worst six-month stretch of my entire life (this one was caused by mishandling of time zones, and not the fault of SQL Server, but the previous experience helped me identify the issue).
int64 "nanoseconds since the epoch" seems to be emerging as the de facto standard.
used in Golang [0] and adopted by Python starting in 3.7 [1]
that PEP also spells out some of the problems with using a float64:
> The Python time.time() function returns the current time as a floating-point number which is usually a 64-bit binary floating-point number (in the IEEE 754 format).
> The problem is that the float type starts to lose nanoseconds after 104 days. Converting from nanoseconds (int) to seconds (float) and then back to nanoseconds (int) to check if conversions lose precision:
> Even through the 90s, long after many system calls became formalized, floating point math was much more expensive than integer math.
IIRC the main reason floating point numbers weren’t used in system calls back in the 90-ties was that kernel code couldn’t use the CPUs floating point registers, or they would have to be saved at the entry point of each system call and restored before returning to user space. This was deemed too expensive.
Yes, but it's just a typealias intended to represent a span of time in APIs. As the docs say, "On its own, a time interval does not specify a unique point in time, or even a span between specific times."
Use 128 bit integer in femptoseconds, with epoch being the begining of the universe. And it should be signed, because what if there was a previous universe.
Jokes aside, Unix Epoch shouls be replaced by Tai time on every computer.
That gets us to ~5.392 * 10ˆ15 years after the Big Bang, which doesn't nearly get you to the heat death of the universe ~1.7 * 10ˆ106 years from now, but is probably good enough.
If we're going to go this far, why not go all the way? [1]
I.e., use a 536-bit unsigned integer to store Planck time with one Planck time following the big bang as the epoch, and use the top three bits as a tetration height instead of an exponent, and you can get real far.
At first glance I thought you were talking about "Thai" time, which is a surprising calendar when you have dates with years in the 2500 range. Looking at you Symantec AntiVirus, storing AV definitions date using the user's locale...
The problem with Tai time, in offline systems it’s impossible to implement calendars on top. To translate Tai time to human-readable one, you gonna need a database of adjustments. We only have that data for very small interval of dates between 1972, and now()+6 months.
For the past, it’s possible to workaround by saying “no leap seconds” but this means that 12PM 1000 years ago might be any time of the day.
But that workaround not gonna work for the future. For many use cases it’s important to be able to represent date-time for many years in the future. The currently used formats allow that easily, at the price of these unpredictable leap second adjustments.
> Why am I not using float64 time already??? [fp used ot be slow and was not standardized, but it got faster and after a while you could count on having IEEE-754]
This is not the reason.
Until IEEE-754 fp was terrible. As in buggy terrible. It took years of pain to learn how to get it right. And you can see this in the standard: it's a very short standard, super easy to understand, trivial, really...if you ignore the enormous amount explanations, corner cases, special cases, surprising symmetries, and other copious testament to the well-meaning Mistakes that Were Made in the years before. Intel provided a ton of trade secret understanding from the mistakes in the 8087, and UCB maths prof was intimately involved. It's an incredible achievement.
The history of the standard is quite exciting but has been covered on HN several times before.
on February 25, 1991 American Patriot Missile battery in Dhara failed to track incoming missile because they used floating point time instead of integers. 28 dead 100 injured
time precision was decreasing with time since system was booted and after about 100h of operation this accumulated enough to system believe that incoming missile is already unreachable using existing calculations and caused it to not react at all
I think that is great example why changing precision depending on time might be very bad idea.
imagine that afer Y2038 that slight drop in precision would cause some critical system to fail in unexpected way
The point is, double precision fixes this for timescales within reach of human existence.
A 24 bit mantissa isn't enough for anybody. A 53-bit mantissa is enough unless you need +/- one nanosecond precision at 100 days. If you need that -- and some people do -- your application is too exotic to influence the standards that apply to everyone else.
UUIDv7 uses a 36-bit unsigned field to represent seconds since 1970-01-01 (and optionally other fields to enable sub-second precision).
It doesn't overflow until the year 4147 -- but since it's unsigned it can't represent times before 1970.
It's not designed to store times in the past, so that's not an issue for its intended use, but using it as a general time representation format could be problematic (say, if you want to represent a person's birth date).
(An aside: At a previous job, I worked on some code that would display a person's age given their stored birth date. The code didn't handle dates before 1900. I did a little research and found that that wouldn't be a problem, since everyone born before 1900 is dead. Ten years earlier, it would have been an issue.)
No thanks. For one, hashing becomes ambiguous. Under no circumstances are NaN and NaN equal to each other. That will cause mayhem in any system looking for an iota of resiliency.
I might be missing something but is this actually arguing for blindly and naively taking time-since-epoch as fp value and then think of the smallest (which would change over time!) possible epsilon??
Disappointing to me how visually appealing this site is for how useless the words/idea themselves are.
Floating-point time loses the ability to represent milliseconds, microseconds, and nanoseconds exactly.
C already has (since the 2011 standard) `struct timespec`, a structure consisting of a `time_t` representing seconds since the epoch (typically 64 bits signed) and a `long` representing nanoseconds. It's admittedly awkward for arithmetic, but it doesn't overflow for another 292 billion years. It gives about 200 times better precision than 64-bit floating-point (for the interval from 2004 to 2038).
32-bit signed time_t is still a problem, particularly for embedded systems that are difficult to update. (A lot of such systems probably don't need to know the current time.)
A 64-bit signed integer representing nanoseconds since the epoch doesn't overflow until 2554.
Floating-point time is likely to be good enough in a lot of contexts -- but the fact that it's non-standard, unlike `time_t` and `struct timespec`, is a big disadvantage.
Measuring time as a combination of time_t and a fraction of a second count goes back far longer than that. 4.3BSD had time_t plus microseconds, for example.
103 comments
[ 4.2 ms ] story [ 189 ms ] threadJust use int64.
I'll go one step further. Time should be represented as unsigned integers, while time-differences should be represented as signed integers.
"10 minutes ago" is int64. June 9th 2023 should be unsigned integer64.
I was under impression unixtime is best for timestamps. But no reason to store something like a birthday using them; just do a string YYYY-MM-DD?
Timestamp with TimeZone (including DST + timezone offset) is a different type from timestamp without TimeZone (ie: normalized to GMT). Timestamp - Timestamp results in "interval".
Perhaps my method I was discussing doesn't work. But the overall idea that "Timestamp" needs to be a different type than "Interval", is pretty key IMO to handling date/times appropriately.
A time_t can handle intervals just fine.
Unambiguously resolving "Timestamp at Eastern Time - Timestamp at Arizona Time" is not as simple as you might think. Postgresql handles this case.
Trying to make "time_t" handle all cases is a mistake. We need like 3 to 4 types at a minimum to handle all of the edge cases that occur in the real world. (And if anyone teaches me of any more complications, maybe we'll need even more types)
Do conversions to/from local time only as a first and last step.
It's the only sane way to do it. The D runtime library does that, and it has proven to be robust and sane. We're also recommending that users do the same thing with different character representations - do all calculation and processing in UTF-8. Other sets are translated upon input into UTF-8, and the UTF-8 is translated to other character sets only on output.
Cool.
Now how do you enforce that? Spoiler: have the local-time be reported as a TimestampWithTimeZone.
When you're doing calculations, make them all Timestamps.
When you're done, you can remember that you've converted from internal UTC-timestamps into the proper output format because you'll have a TimestampWithTimezone again.
-----------
We're programmers. We have problems that are simple and enforceable through compiler constructs called "The Type System".
Why have the human figure out if they have time-zone back-and-forth correctly or otherwise made a mistake (ex: adding code to the wrong "part of the process" and accidentally mucking with a TimestampWithTimeZone input/output variable rather than the internal Timestamp-UTC-only type variables?)
Just... leave the job of checking this entire process to the compiler. If your n00b coworker messes up, the type system catches it and we're all set.
UNIX timestamps have no timezones. They are just the number of actual seconds that have elapsed since a certain commonly-agreed time. You measure the number of seconds between two events just by subtracting their timestamps arithmetically, and don't need to do any parsing or interpreting of timezones. You can just say "this hacker in Russia breached the firewall 35 seconds after this dev committed code in the US" just by subtracting two numbers.
The time an airplane takes off, the time your firewall detected a security event, the time a cross-timezone meeting starts, these are all good to store as UNIX timestamps.
Birthdays are different because they are inherently imprecise; humans tend to celebrate their birthdays when it's a certain day of the month in their local time zone, with little to no regard for the time zone they were born in. Someone born in Singapore (UTC+8) and lives in Alaska (UTC-8) might still celebrate their 30th birthday on the month and day of their birthday in Alaska time even though their body might need another 16 hours before it has technically experienced 30 years of life.
If only. UNIX mishandling of leap seconds makes this all a illusion, in reality you need to have logic to handle them
So yes, you have to handle leap seconds when you're converting from the timestamp to/from the time scheme you are interested in, just as you have to handle leap years, etc. The UNIX timestamp is just a different way of marking time, and is blessed with not worrying at all about how that time relates to the actual motion of the Earth. That makes it easy and consistent for common operations. It is not mishandling anything. It just can't shield you from the complexity of the forms of timekeeping that make it a point to be synchronized with the Earth.
Nope. UNIX timestamp is days since epoch × 86400 + seconds since midnight. And that is the crux of the issue.
Are you perhaps confusing that with the standard algorithms for converting between Unix time and "human" time? Because what you say isn't wrong for that case, and this is a classic trap for new players:
"A Coordinated Universal Time name (specified in terms of seconds (tm_sec), minutes (tm_min), hours (tm_hour), days since January 1 of the year (tm_yday), and calendar year minus 1900 (tm_year)) is related to a time represented as seconds since the Epoch, according to the expression below.
If the year is <1970 or the value is negative, the relationship is undefined. If the year is >=1970 and the value is non-negative, the value is related to a Coordinated Universal Time name according to the C-language expression, where tm_sec, tm_min, tm_hour, tm_yday, and tm_year are all integer types:
tm_sec + tm_min60 + tm_hour3600 + tm_yday86400 + (tm_year-70)31536000 + ((tm_year-69)/4)86400 - ((tm_year-1)/100)86400 + ((tm_year+299)/400)*86400"
(Quotes from https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1...)
Moreover, if the units you are referring to are "seconds" then the statement "[t]he UNIX timestamp is literally the count of units past the epoch" is also wrong. If not, what are the units here?
> That's not true. Unix time is specifically defined as "A value that approximates the number of seconds that have elapsed since the Epoch."
It's missing 27 seconds that elapsed since the epoch. (Plus whatever happened with rubber seconds before 1972). Maybe that fits within the definition of approximate.
But if you need to record events that happen on a leap second or the second before a leap second, unixtime is unhelpful.
If you want to record a duration that includes a leapseconds, unixtime is unhelpful, although if the duration is long enough, the difference may be of no consequence.
Mail an atomic clock easy to west, and a different one west to east, and they'll drift different amounts. Put one in a plane, it will disagree with one on the ground. Put one on a mountain, it will disagree with one at sea level. Put one at the pole, it will differ with one on the equator.
There is little sense in claiming one time is exactly equal to another. Close to some tolerance is useful.
I'm struggling to think of a context where we basically want to create timestamps for things that happened in the past. Especially things that we want to dynamically localize rather than e.g. just store as a straight up timestamped string.
Nobody's really solved this, it has to be tackled anew for each case of "comparing dates from long ago".
Why artificially limit yourself to only representing times after the epoch? It's not like an extra bit of range is going to make or break anything.
Using i64 for nanoseconds with the same origin (1970-01-01), we could cover roughly 1677-2262. Using u64 for nanoseconds we could cover roughly 1970-2554, or more generally about 585 years of timestamps from wherever a new proposed origin would be. Likely we don't need nanoscale precision for times far enough in the past, so I guess "far enough" is the definition worth arguing over here.
The same story is present with 64 bit nanosecond based timestamps, albeit we're keeping the mid date ever so slightly in the past for ease of conversion. If dates in 2264+ are important then just use a 128 bit timestamp, it'll fix the problem for many multiples the age of the universe instead of causing issues now to gain a few hundred years of padding in the future. Another common approach is to keep u64 second based timestamps and save the nanoseconds as another integer. This comes at the cost of "only" solving the problem for hundreds of billions of years but being interchangeable with those using non-fractional timestamps.
For example, the unix epoch can safely be 1970 because the operating system did not exist prior to 1970, so there's no real-world situation in which you'd be time-stamping a system event or log entry prior to 1970.
That said, time_t is usually a signed type in C. There are several reasonable historical explanations for that though, including the need to use -1 for an error return, and the fact that unsigned ints weren't present in C until 1978.
The Unix epoch was never an issue being 1970 because they knew they were making it as signed, not because they thought Unix systems would never deal with e.g. files from before Unix was created.
As far as I've ever read, negative unix timestamp values generally were not used. Since -1 was used as an error value, you cannot safely determine whether that value represents an error vs a legitimate timestamp one second before the epoch.
I'm not familiar with if/how early versions of Unix were able to interact with non-Unix files/filesystems, but I doubt they would preserve metadata such as mtime if so.
mmm I don’t think that’s true.
- your computer
Alternatively, you’d need an int65.
> "10 minutes ago" is int64. June 9th 2023 should be unsigned integer64.
Won't you run into problems with trying to turn "ten minutes before 1970" into a time?
IOW, if you subtract two `time` types, the result must be a valid `time` type, so you can add/subtract to it. The result cannot be a `timediff` type.
Huh? Of course `time` - `time` gives a `timediff` type. You can add/subtract to it: you add/subtract timediffs, or you can add/subtract a timediff to/from a time. Why would you ever be directly adding times? What is 100 AD + 100 AD? 200 AD? Not if you pick a different 0-point for your calendar ...
But yeah started using int64 30 years ago and all the fuckedness went away.
Step forward the MC146818, used in the PC/AT and emulated by chipsets since, whose built-in daylight savings time change rule implemented in hardware was rendered obsolete in 1987.
http://catless.ncl.ac.uk./Risks/24.60.html#subj12.1
Unrelated I think the lobbyists that got daylight savings time changed charged the BBQ manufacturers $50k.
I added a comment apologizing to software archaeologists on 12 October 287,396 C.E., when the precision of double timestamps (with 1970-01-01T00:00:00 UTC epoch) drops to 2 milliseconds, and the reviewer was fine after that.
The problem being that OP kinda glosses over them.
If you think microsecond precision is good enough for anybody, then you could consider using an integer where the LSB represents 1 microsecond. But don't use floating point time, unless your specific application cares more about time scale invariance than time translation invariance.
I fully disagree with using float64. It wastes bits on an exponent and you cannot safely use == to compare timestamps that have math done on them or have been converted to/from a string (including e.g. sent as part of a JSON).
You don't want to get into floating point, it gets all fuzzy and then when you need to change it, there's a bunch of code to change.
https://cr.yp.to/proto/utctai.html
And integer representations have traps too. To pick on your particular example: "timestamps" are not the same thing as "times" and if your code predicated on that, it likely has bugs already with monotonicity and uniqueness assumptions.
And the benefit is that, as the article points out, you can have easy subsecond precision, something that is much harder (and pervasively unsupported by existing libraries) with fixed point math.
Should one go to the mattresses to defend either of these designs? No. But on the whole my heart agrees with the linked article. If you have to make a choice today, for new code, stick with a double.
Only because its timestamp (as in `Date.getTime`) is actually the number of milliseconds since the UNIX epoch assuming no leap seconds. You can't keep the current scheme in a nanosecond precision (2^31 * 10^9 ~= 2^61, so float64 will truncate lower bits). Python had the exact same issue and it now has a separate `time.time_ns()` function as opposed to the traditional `time.time()` function.
What integer system do you expect to work well with nanosecond time values? As you just showed, a 64 bit ns value will rollover in O(2^32) seconds, which is already universally considered to be insufficient for an epoch time.
Again, this is splitting hairs. In fact as I see it doubles would be preferable in in that regime, as while you'll be dancing at the edge of their representation capability, they at least won't blow trying to record dates in the 1420's or whatever. You can get as close as is practical without worrying about representation space.
It's the same reason why you should never use floating point numbers for account balances.
Not sure about classic BASIC; I don't really recall there being a specific time type, though I know later things like Visual Basic and etc would just use the VARIANT date type above with a 0 day component.
I like Windows' FILETIME which stores time as 100-nanosecond intervals since the beginning of the year 1601. C#'s DateTime is similar but uses an epoch of 0001 January 1.
I know JavaScript and others simply use the usual unix Epoch but with milliseconds rather than seconds, so time() * 1000.
int64 "nanoseconds since the epoch" seems to be emerging as the de facto standard.
used in Golang [0] and adopted by Python starting in 3.7 [1]
that PEP also spells out some of the problems with using a float64:
> The Python time.time() function returns the current time as a floating-point number which is usually a 64-bit binary floating-point number (in the IEEE 754 format).
> The problem is that the float type starts to lose nanoseconds after 104 days. Converting from nanoseconds (int) to seconds (float) and then back to nanoseconds (int) to check if conversions lose precision:
0: https://pkg.go.dev/time#Time
1: https://peps.python.org/pep-0564/
IIRC the main reason floating point numbers weren’t used in system calls back in the 90-ties was that kernel code couldn’t use the CPUs floating point registers, or they would have to be saved at the entry point of each system call and restored before returning to user space. This was deemed too expensive.
(in other words a terrible choice, though not for the reason above).
Jokes aside, Unix Epoch shouls be replaced by Tai time on every computer.
https://en.m.wikipedia.org/wiki/International_Atomic_Time
I.e., use a 536-bit unsigned integer to store Planck time with one Planck time following the big bang as the epoch, and use the top three bits as a tetration height instead of an exponent, and you can get real far.
All the best,
[1] https://halosgho.st/blog/planck/
https://en.wikipedia.org/wiki/Buddhist_calendar
For the past, it’s possible to workaround by saying “no leap seconds” but this means that 12PM 1000 years ago might be any time of the day.
But that workaround not gonna work for the future. For many use cases it’s important to be able to represent date-time for many years in the future. The currently used formats allow that easily, at the price of these unpredictable leap second adjustments.
> Why am I not using float64 time already??? [fp used ot be slow and was not standardized, but it got faster and after a while you could count on having IEEE-754]
This is not the reason.
Until IEEE-754 fp was terrible. As in buggy terrible. It took years of pain to learn how to get it right. And you can see this in the standard: it's a very short standard, super easy to understand, trivial, really...if you ignore the enormous amount explanations, corner cases, special cases, surprising symmetries, and other copious testament to the well-meaning Mistakes that Were Made in the years before. Intel provided a ton of trade secret understanding from the mistakes in the 8087, and UCB maths prof was intimately involved. It's an incredible achievement.
The history of the standard is quite exciting but has been covered on HN several times before.
https://cr.yp.to/proto/utctai.html
time precision was decreasing with time since system was booted and after about 100h of operation this accumulated enough to system believe that incoming missile is already unreachable using existing calculations and caused it to not react at all
I think that is great example why changing precision depending on time might be very bad idea.
imagine that afer Y2038 that slight drop in precision would cause some critical system to fail in unexpected way
A 24 bit mantissa isn't enough for anybody. A 53-bit mantissa is enough unless you need +/- one nanosecond precision at 100 days. If you need that -- and some people do -- your application is too exotic to influence the standards that apply to everyone else.
It doesn't overflow until the year 4147 -- but since it's unsigned it can't represent times before 1970.
It's not designed to store times in the past, so that's not an issue for its intended use, but using it as a general time representation format could be problematic (say, if you want to represent a person's birth date).
(An aside: At a previous job, I worked on some code that would display a person's age given their stored birth date. The code didn't handle dates before 1900. I did a little research and found that that wouldn't be a problem, since everyone born before 1900 is dead. Ten years earlier, it would have been an issue.)
Disappointing to me how visually appealing this site is for how useless the words/idea themselves are.
C already has (since the 2011 standard) `struct timespec`, a structure consisting of a `time_t` representing seconds since the epoch (typically 64 bits signed) and a `long` representing nanoseconds. It's admittedly awkward for arithmetic, but it doesn't overflow for another 292 billion years. It gives about 200 times better precision than 64-bit floating-point (for the interval from 2004 to 2038).
32-bit signed time_t is still a problem, particularly for embedded systems that are difficult to update. (A lot of such systems probably don't need to know the current time.)
A 64-bit signed integer representing nanoseconds since the epoch doesn't overflow until 2554.
Floating-point time is likely to be good enough in a lot of contexts -- but the fact that it's non-standard, unlike `time_t` and `struct timespec`, is a big disadvantage.