Well... thanks. But not because I'm lazy, for I had no idea about this bug there. I just thought OP had an off topic moment based on random connection.
But now, after reading your link and giving it a bit of thought, what s/he wrote makes perfect sense even without your link.
Almost all the edge cases with time have to do with localization.
Build your time library on (un)signed 64 bit integers representing the number of nanoseconds since the utc epoch. Adjust above sentence to reflect the level of precision and range your use case needs. You are now done for 80% of use cases (perf timing, logging, timeouts, event storage, event ordering within jitter).
If you need to parse/display for humans or have something happen at a particular time in a particular timezone, things get gross. But that's no different than any other situation where you eventually have to interface machine data with humans. Either it's your particular expertise or it's a distraction and you should use someone else's solution.
> Build your time library on (un)signed 64 bit integers representing the number of nanoseconds since the utc epoch.
You poor sweet summer child. Has no one told you about the leap seconds yet? Unix time is not the number of seconds since epoch. It deliberately excludes leap seconds, which happen unpredictably whenever scientists measure the Earth as having spun at a different enough speed for long enough.
Time is fucked on every level:
- Philosophical: What is time? We just don't know.
- Physical: Turns out there is no such thing as simultaneity, and time flows differently at different locations. Time may be discrete at the Planck level, but we don't really know yet.
- Cosmological: The Earth does not rotate at a constant speed, the Earth does not orbit the Sun at a fractional component of its rotation, and the Moon does not orbit at even ratio either.
- Historical: Humans have not used time or calendars consistently.
- Notational: Some time notations are ambiguous (e.g. during daylight savings transitions) and others are skipped.
- Regional: Different regions use subtly different clocks and calendars.
- Political: Different political actors choose to change time whenever they feel like it with little or no warning.
- Religious: Many religions come with their own system for timekeeping, and people don't like when outsiders impose other systems.
haha. Google is way ahead of you and your "leap seconds".
"Since 2008, instead of applying leap seconds to our servers using clock steps, we have "smeared" the extra second across the hours before and after each leap. The leap smear applies to all Google services, including all our APIs."
indicate that leap seconds are not included in Unix time as seconds-since-the-epoch. Leap seconds are included in UTC, and thus the Unix time appears to skip a second relative to UTC.
> Unix time is not the number of seconds since epoch
I assumed that is (part of) why he ended up wanting to build his own. The way UNIX time handles leap seconds was arguably a mistake, GPS (and Galileo) time does it right; have the leap second information in a separate field. So as a time scale I imagine OPs one would be similar to GPS but with different epoch. Of course everyone loves having informally defined ad-hoc timescales around
hence it counts the number of seconds that would have elapsed, if they didn't exist. In effect, it's timescale has time that never happened, and time that happened twice.
It may be determined that the computer's clock got too far ahead. For example, it booted and you cared about time, but NTP hadn't yet made corrections. Suddenly the time runs backwards.
Leap seconds may get interesting too, especially if you have to predict ahead or if the OS isn't updated often enough. (there is a 6-month warning) If you want to call leap seconds an issue for humans, then you aren't using UTC at all. You're using TAI. Software interfaces often ignore the distinction between UTC and TAI, and even between UTC and UT1, preferring to pretend these issues don't exist. POSIX is in conflict with international timekeeping, effectively requiring that there are zero leap seconds.
It is my understanding that the way NTP deamons work is that time is never adjusted backward. Instead, the ticks are "slowed down" on the local machine until it is in sync with the NTP time. However, if the difference is too great then I think NTP deamons might refuse to correct the time all together. So then, if my understanding is correct, your machine is "stuck in the future". But it will never make a jump backwards because of NTP.
However, I am not familiar with the intricate details of NTP so do take this with a grain of salt.
My understanding (i am also not an expert!) is that common NTP implementations will do the slowing down ("slewing") to correct small errors, but will just change the time ("stepping") to correct large errors. It will even do this if that means time going backwards.
Subject to configuration, of course. man ntpd [1] says:
> Sometimes, in particular when ntpd is first started, the error might exceed 128 ms. This may on occasion cause the clock to be set backwards if the local clock time is more than 128 s in the future relative to the server. In some applications, this behavior may be unacceptable. If the -x option is included on the command line, the clock will never be stepped and only slew corrections will be used.
timesyncd also does this for "large offsets", but what "large" is is neither configurable nor documented, but in the source:
/*
* Maximum delta in seconds which the system clock is gradually adjusted
* (slewed) to approach the network time. Deltas larger that this are set by
* letting the system time jump. The kernel's limit for adjtime is 0.5s.
*/
#define NTP_MAX_ADJUST 0.4
Leap seconds are only a problem for wall clocks (localization) and deciding whether to call something utc or tai.
The ntp case is contrived. Either you care and wait until ntp has connected to do your stuff. Or you care and don't let ntp rewind and instead smear. Or you don't care and deal with the consequences.
Leap seconds affect Unix time too, because leap seconds are excluded from "number of seconds since Unix epoch". You can't measure the length of time intervals spanning leap seconds with simple subtraction.
"Number of seconds since the unix epoch" describes a physical measurement (that can be related directly to TAI) that would not be affected by leap seconds. You could store that number to allow you to recover TAI timestamps (I'm not aware of any OS that does this, but there's no reason it wouldn't be possible).
Unix time does indeed repeat itself so as to remain in alignment with UTC. But unix time is not the number of seconds since the unix epoch.
Thank you, I think I understand, but disagree on terminology.
Leap seconds are not excluded from number of seconds since Unix epoch, interpreted on a physical, TAI, UTC, typical local time scales.
However, they are indeed excluded from "number of seconds since Unix epoch", when interpreted in unix time. In unix time, those seconds simply don't exists (never happened) and the events of those seconds are smooshed sometime. Unix time representation forms a (quirky) timescale.
I'll quote a few excerpts from 'date' man page of linux, openbsd and for gettimeofday:
Convert seconds since the epoch (1970-01-01 UTC) to a date
Print out (in specified format) the date and time represented by seconds from the Epoch.
The time is expressed in seconds and microseconds since midnight (0 hour), January 1, 1970
Hmm. The first definition I saw of "unix timestamp" explicitly defined it as "86400 * number of days since 1970-01-01 + number of seconds since midnight", which I thought was clever and clear, but I can't find it now. Having looked up the POSIX standard at https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1... it seems you're right and I'm wrong; they define "Seconds Since the Epoch" as "A value that approximates the number of seconds that have elapsed since the Epoch...". In my view this is an extremely poor choice of terminology, because "seconds since the epoch" should have its plain English meaning of the number of SI seconds that have passed since that event; defining some "second" that is almost, but not quite, an SI second seems like a recipe for disaster.
That good reason is that there is no general "formula" for leap seconds, unlike for leap years, they have to be looked up. So you can't do "offline" date calculations if they included leap seconds.
I think that UNIX time stamps are generally a very good approximation, and if you are comparing long enough time intervals for the error to get over one second, and/or that error to matter, you are doing something wrong anyway.
For exact time interval measurements that you have to get exactly right, don't use UNIX time stamps.
The lookup database is incredibly small and updated very infrequently. Certainly smaller and less frequent than tzinfo which is also included in the kernel
AFAICT leap seconds are announced about 6 months in advance (January/June). How would you process times that are to occur after a leap second that may or may not be?
You could store something as '2022-02-02 02:22:22 UTC', but try storing it as seconds since epoch, if you don't know if there will be leap seconds. Not to mention software that may be years or decades old.
It depends on the purpose of the app, but you often have to store user entered times with time zone information. The rules for things like daylight savings time can and do change, and you don't want future scheduled events drifting by an hour.
Let's not forget events spanning time zones. Just some random thing that came to my mind: how would you handle calendar entries where half the participants made a DST transition since the entry was created and the other half didn't? This happens for example when half of the team is in the US and the other half in the EU. The transition dates are a week apart.
I remember seeing a thick dead tree type of book with the history of time zones in the US, for figuring out times in historical documents when things were less standardized. It was practically the size of a phone book; I think it probably covered county level history or something like that.
I have a book covering, among other things, Indiana time zones for a few years during IIRC the 1960s.
It’s frankly amazing how much they changed every year. Different counties, and sometimes towns/cities within counties, would jump back and forth year to year. It would have been awful to manage if computers had been more important at that time.
None of these edge cases have a chance of being handled correctly without an accurate time zone database to detect the problem at all. Good luck maintaining that on the side! I am fairly certain that you would get it wrong. There's a reason why most software relies on zoneinfo.
When you need some system activity to happen regularly (e.g. every 10 minutes) but not at a specifically human-meaningful time. When you need to know what order events occurred in or how far apart two events are, but don't need to correlate those times with external events (or can easily establish "system" times for any relevant external events). You can cover a lot of cases without having to touch "human time" at all.
I've come to the same conclusion as you: Keep time as a purely monotonic integer for everything and convert that as needed to display to humans.
This also pushes all the madness to the edges and out of the business logic.
That being said, this works well for applications that are not "date intensive" so to speak. If your business logic has to deal specifically with calendar dates, e.g., monthly events, then you have to deal with calendar months and all that this involves, including explicitly dealing with the 29th February.
You want to store both local time (and timezone and/or some proxy for location if possible) and Unix / UTC / integer time. The latter is what your application relies on 99% of the time, but if, say, a given country gets rid of daylights savings time (as Brazil did last year), having the local time is helpful for recomputing your Unix time.
The monotonic integer approach doesn't work for most healthcare applications. Due to safety and compliance requirements we typically need to record both the local time and the zone offset which applied at that instant.
This is surprising for event recording since they should be equivalent but time + zone is strictly more likely to be messed up. E.g 2:30 a.m. is two times during daylight changeover so you must correctly specify est or edt.
You're assuming a source of monotonic nanotime is easy to get and will always be available. This is not the case. As far as I know you have either "wall time" as usually defined, with all the problems associated with clock drift, NTP syncs, computer going into sleep mode, etc, or "nanotime" which is some time delta from some arbitrary reference and which can only be used to compute time differences and not arbitrary instants in time. One can not be converted into the other easily, or at all.
Your percentages are backwards, IME. Timeouts and logging and such are definitely not “80% of use cases”. Interfacing with humans and human systems are.
When you encounter someone saying "no one should ever", you can substitute "fewer than a half dozen groups of people should tackle this problem (in systems they want to use in production). It will take each such group a tremendous number of hours, involving a multi-year process of slowly finding edge cases and missing functionality."
If that's not true, then there's room to complain, but dates and times fit the bill. In some ways they're worse than other "harder" problems, because people are more likely to think those harder problems are too hard for them. And while the vast majority of companies don't need a proprietary database, it's more likely to be a competitive advantage than your own datetime library.
I think I could eventually write a good datetime library. But I certainly should not, unless I decide that's going to be one of my major efforts to help a language that doesn't already have one.
I believe accurately conveying identical information in a manner everyone can understand is an impossible, or at least unreasonable, burden to place on individuals.
Instead, what if people tried to hear graciously and "assume good faith"?
Can you please explain more about how your response relates to my comment?
For instance, I've not placed the burden of "conveying identical information in a manner everyone can understand" on anyone, nor have I assumed bad faith. So it seems like a weird comment to tack onto mine, but I am assuming I just don't correctly understand.
Hyperbole and precision can get tricky in human language because different cultural groups have different language encodings for the same sets of words.
For example, if you live in London then "9 in the morning" means when the world synchronized clocks agree that, locally for you, the time is 9am. But if you say "9 in the morning" to someone in Belize, it means "first thing after you are finished with your morning and ready to start your day," which can mean 1pm in some cases.
Here's a lovely article on these kind of time-keeping differences, around something that you might expect to have a precise meaning:
More to the point at hand, however, you suggested that people not speak in hyperbole but instead speak accurately. Although you can request that others adjust their use of language while in your presence to better meet your needs for a certain kind of precision, policing other people's language isn't possible. However, re-interpreting what people say into what they mean is somewhat possible for an astute listener who understands the context.
You are making my simple statement really complicated.
I'm not walking around demanding people change their communication to accommodate me, I'm suggesting that trying to speak precisely can be a useful exercise.
I assumed you were being precise when you asked "why can't people speak more precisely" and also when you asked "can you explain how your comment relates to mine?"
I am not the person you originally responded to, fwiw, and I agree that trying to speak precisely is a useful exercise, even if I believe it is impossible except in highly formalized languages.
The sibling replies by IggleSniggle I fully endorse as supporting my meaning in the original post.
As to explaining more about my thought process: You asked a hypothetical "what if" question which, given the question itself is imprecise, I interpreted as you wishing information was always conveyed to your desired precision/accuracy, and extrapolated that (given this is a public forum) into general communication.
I shared my thoughts, specifically that it seems impossible for a person to always communicate perfectly to an unknown audience, and offered a different hypothetical. The last line explains the punctuation use in my "what if."
I think .replace() is a mistake, and it shouldn't exist in the first place. The way dates work, replacing a single component is almost always going to create problems in specific cases.
And then you have to implement business directives with “the same date in 2025” in documents already signed by all parties (and no one got confused, except math guys). Replace is not a mistake, it just should state what it does, so that a programmer could test and use it.
It does state what it does, but people rarely read documentations, and unlike static languages, Python doesn't force you to deal with the exception. And since it's a very rare problem, most people don't catch the bug.
You get April 30. Idk about all libraries, but date-fns and few others I worked with do it right.
Also, if you add one month to Apr 30 and two months to Mar 31, you’ll see that month addition is not commutative-y and one should operate on distances from a base date, not in an incremental way.
No another leap year would be fine. But yeah a very common (wrong) pattern is, if you want to find the same day a year in advance, is to do `d.replace(year=d.year + 1)`, and that would break on Feb 29 only, so one day every 4 years. It's a very common pattern unfortunately.
It returns a new datetime object which includes its own validity check and raises an exception for a bad leap day the same way it would for March 32nd or Blurnsuary 12th. (However, year must be an integer in [1, 9999].)
Sears appears to have have taken this to a new level. I got two emails from them today. The first arrived at 6:36 AM, with subject
> Confirmed! Your mystery Leap Year offer awaits inside. How much will you save?
The second arrived at 9:54 AM, with subject
> Oops! Your code is fixed. (We shoulda looked before we leap-yeared...)
The messages themselves appear to be identical except for some query parameters on some URLs, so I'm guessing that whatever they botched for leap year was on the server when one tried to respond to the offer.
Botching Feb 29 in general date handling code is embarrassing, but there is it least a somewhat plausible excuse that you just forgot about that special case. But botching Feb 29 in code that is meant to only work on Feb 29? Wow.
Botching is pretty ungenerous. All of the above can easily happen in the context of a complicated software system.
Experienced people who avoid these issues are mostly experienced with smashing headfirst into one of these tricky areas and approaching them with an attitude of "here be dragons" afterwards. One bitten, twice shy.
I haven't been able to figure out if this is a Google issue or King County Metro issue yet, but google maps transit directions are completely broken today. Every suggestion is "take Lyft" or wait until 4 am tomorrow.
I played around with it a little on google maps. It says it pulls route information directly from the King County transit website[1] which lists routes by the day of the week, not by the date, and seems to be displaying the routes for today just fine. My guess is that it's a Google issue, but maybe the King County transit website was broken earlier today and gmaps is just serving the cached routes now.
For anyone curious, this is what it currently looks like to get from Bellevue to Redmond in google maps.
https://i.imgur.com/wMTIzBL.png
I ran into this already on January first. Someone in the last 4 years changed/created a function in a MUD that I now maintain, to convert Unix timestamps into iso8601 strings for MySQL.
They did the math for which year is a leap year incorrectly, which wasn't the bug that bit us, they helpfully added the leap day regardless of where in the year it was. The tests they wrote targeted the middle of the year and missed it. I just pulled in date2j and related match from postgres, added more test cases.
It's already here...I saw an issue a few weeks ago a bunch of production systems failed upon seeing a certificate signed by a CA root that expires after 2038.
If you store time as seconds since the Unix epoch (1st Jan 1970), you'll overflow a 32 bit unsigned integer in 2038 (around March iirc) and time will suddenly be back in 1970 again. I believe the Linux kernel did some work to circumvent this on 32 bit systems in a recent release, but if you're running an old 32 bit system you're probably out of luck.
Actually, it's signed 32-bit integers that overflow in 2038. Signed integers have been used because people wanted to store dates earlier than 1970 too.
And probably because signed integers are a default choice in certain languages and/or maybe on certain architectures.
Java, for example, famously doesn't even have an unsigned 32-bit integer primitive type. (But it has library functions you can use to treat signed integers as unsigned.) Ultimately not a good design choice, but the fact that it actually wasn't that limiting and relatively few people care or notice tells you that many people have a mindset where they use signed integers unless there's a great reason to do something different.
Aside from just mindset and inertia, if your language doesn't support it well, it can be error-prone to use unsigned integers. In C, you can freely assign from an int to an unsigned int variable, with no warnings. And you can do a printf() with "%d" instead of "%u" by mistake. And I'm fairly sure that converting an out of range unsigned int to an int results in random-ish (implementation-defined) behavior, so if you accidentally declare a function parameter as int instead of unsigned int, thereby accidentally doing an unsigned to signed and back to unsigned conversion, you could corrupt certain values without any compiler warning.
> Ultimately not a good design choice, but the fact that it actually wasn't that limiting and relatively few people care or notice
It was a horrific design choice, and people do notice and do hate it.
Not so much because of how it applies to ints, but because the design choice Java made was to not support any unsigned integer types. So the byte type is signed, conveniently offering you a range of values from -128 to 127. Try comparing a byte to the literal 0xA0. Try initializing a byte to 0xA0!
In contrast, C# more sensibly offers the types int / uint, short / ushort, long / ulong, and byte / sbyte.
I think he's referring to an unsigned integer value that's out of range for the signed integer type of the same width—usually a bit pattern that would be interpreted as a negative number when cast to a signed integer type, but which negative number is not defined by the C standard because it doesn't mandate two's complement representation.
One of the biggest mistakes in IT ever, in my opinion.
I'd even go so far to say that defaulting to signed instead of unsigned was also one of the biggest blunders ever. I would've never defaulted to a type that inherently poses the risk of UB if I have another type that doesn't.
Though it's also possible that precisely that was the reasoning for it.
A line in one of my Python daemons has been crashing it repeatedly all day:
isotime = datetime.strptime(time.get('title'), '%a %d %b %I:%M:%S %p').replace(year=YEAR, tzinfo=TZ).isoformat()
ValueError: day is out of range for month
It's not mission-critical, so I'm just going to wait it out until tomorrow.
Interesting. Given the documentation of strptime, I would have expected that to not be possible at all (since constructing a datetime without a year isn't possible directly)
Python 2.7.17 (default, Dec 31 2019, 23:59:25)
Type "help", "copyright", "credits" or "license" for more information.
>>> from datetime import datetime
>>> datetime.strptime('Sat 29 Feb 12:00:00 PM', '%a %d %b %I:%M:%S %p')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: day is out of range for month
LinkedIn has one! Yesterday it listed my time with my current employer as 2 years and 9 months. Today, it’s 2 years and 8 months! Can anyone venture a guess on how I time traveled?
So frustrating that my embedded code works without a hitch and our company is nearly bankrupt, while our bank with billions of dollars in annual profit goes offline since it cant handle leap years.
huh! i just noticed few hours ago a pi i use has had the wrong date which was set to a day earlier. I quickly determined a faulty ntp server i had hard configured was giving the bogus time. In case it is of interest its address was "144.76.60.190" and was chosen by random chance because i cant resolve names without working time on that machine (don't ask). It appears to be taken offline by now though but i checked its giving the wrong time before changing to another server.
Also, but probably unrelated, my WiFi stopped working exactly 00:00AM CET but that might be unrelated as it does that all the time :)
I think the answer you were looking for was, apparently NO there is nothing special about 2020 in relation to this problem. Despite the misleading use of year 2020 in the title and the title of the linked page, what is being experienced is a simple mis-coding of year+1 calculations. The title could have been,
"Leap day bugs, again! People, must we go through this every 4 years?"
So this is a crop of bugs that are either code written in the last 4 years or untested/unnoticed/unreported 4 years ago.
One of the Sprint examples (someone roaming between two cell towers and their date changing back and forth) is especially disturbing.
Or there's an unfinished ticket in the backlog that says "Oh gosh, let's just bodge all these values in the database and make sure we fix it by the next leap year".
Over 4 years there are a lot more apps and a lot more reliance on apps. There's lots more spaghetti out there since 2016 so more likelihood of bugs. I assume 2024 will have more.
I do wonder whether the fact that some systems aren’t really 4-digit-year compliant is making this uglier this year. I know Splunk got hit with a 2020 bug.
I'm kinda surprized that date libraries break with a simple leap year like 2020. I'd have more understanding if it happened with the year 2000(leap year) or 2100(not a leap year), but the basic "is year divisible by 4" check should be good enough until 2100.
I hear this is a firmware bug, and should automatically sort itself out once we hit March (but there is a bugfixed f/w due too). My pet theory/guess was that there was a misimplementation of some algorithm like Zeller's congruence (https://en.m.wikipedia.org/wiki/Zeller%27s_congruence) for calculating the day of the week, which works on an adjusted year starting in March, because I couldn't come up with any other reason why a leap year bug would manifest for all days before feb 29 and then not thereafter. But there is probably a duller explanation :-)
I deposited a cheque using the ScotiaBank mobile app on March 1, and when I looked at it on my online banking page, it said that it was deposited on March 2!
Seems hard to believe that a mobile app would be allowed to tell the server what time the cheque was deposited!
Why doesn't the syslog protocol (RFC5424) deal with leap seconds (the seconds field goes to 00-59, not 00-60)? Are they using UTC (they would have to ignore LS and have crappier logs) or TAI (doesn't have LS)?
Unix systems usually do not have extra seconds after 23:59:59. Leap seconds are normally dealt with by slowing your clock down. So Unixes use neither UTC or TAI, they use something else.
It's something that does not matter in 99.99% of the cases, and that other 00.01% need specialized hardware and software for dealing with it anyway.
More specifically, POSIX explicitly defines a day as having precisely 86400 seconds. This makes calendar arithmetic trivial, including into the future, which would otherwise be impossible as leap seconds aren't predictable. Any program with a leap day bug is almost certainly not making use of Unix timestamps.
The corollary is that for the most commonly used APIs a Unix second isn't the same thing as an SI second. A Unix second is effectively defined in terms of the civil calendar, not as a fixed quantum of physical time.
Technically this doesn't preclude Unix date-time strings from displaying a 60th second. (And maybe some do.) But it would require unnecessary extra work, introduce inconsistencies (i.e. a generated string for a future date-time that happened to be a leap second would show :59 today but :60 at the moment of the leap second), and invite bugs.
Precisely, get yourself five digital devices that track time, and see how many of them are even accurate to the second. Probably none of them. But implementation of leap seconds would only make sense on systems that at least fulfill that minimum criteria.
Leap seconds are things for stock market servers, atom clocks, scientific devices and the like.
Is there a real need for syslog to handle leap seconds? As that email you linked to points out, most implementations are likely to screw it up to some extent; requiring everyone to ignore leap seconds seems to narrow the range of possible behaviors. It also strikes me that syslog timestamps aren't something you should be depending on too heavily in the first place, especially not for things like calculating precise time durations between separate messages.
it's bad enough guessing dmy or mdy, without wondering if we earned interest on that 10 billion euros that appears to have spent an entire second in a transit account earning 2.7% per annum (legal think 'annum' implicitly includes the leapsecond, an assurance resting on dozens of assumptions and misunderstandings of many ibscure treaties and standards and policies, modulo the case law in jurisdictions where precedents can redefine)
I think I found one. My Amex bill is due on the 29th and I have a direct debit to pay it automatically. However it never got taken. First time this has ever happened.
Let's see if it happens today since it's now the 1st.
Considering billions of devices and services not being affected, these bugs are very insignificant. At least we figured out leap day bugs and not having y2k crisis' every four years.
That's fairly normal for older or more basic digital watches - they don't know anything about the year, so every four years you have to manually set them back the 29th of February. I think that people see this as a bug says something about changing expectations. Pre-digital watches generally didn't know about months either, so required manual date adjustment every other month. The framing of this manual adjustment as a bug feels like it comes from our experience of dealing with computer software all the time.
In mechanical watches, that's the difference between an annual calendar, and a perpetual calendar. Annual calenders don't take leap years into account, perpetual calenders do.
Timex Ironman bought within the last two years, almost certainly less than $20, says 3/1 Sat today. The numbers on the back might say "029 36". Or maybe "029 56". I'm sure its innards are the same as most of the other sub-$20 Ironmans.
I learnt the following test for leap year early in my software engineering career from the book "The C Programming Language" by Kernighan & Ritchie (see section 2.5 Arithmetic Operators):
(year % 4 == 0 && year % 100 != 0) || year % 400 == 0
The following test also works:
year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
For those who were curious why these could both work:
(A & B) | C differs from A & (B | C) in two cases: when A is false, C is true, and B is any value.
But in this case in particular, those differences would create a bug when A is false (the year is not divisible by 4) at the same time that C is true (the year _is_ divisible by 400). Since that can't happen, the bug cannot occur.
Anyone still using a Zune out there?