This is a quick refresher about time and date values and how to calculate them on modern computers.
A Unix timestamp represents a moment in time. The moment happens everywhere on earth at the same time, and never again will that number be used for a different moment. This is the most important thing to remember as you do date/time calculations. When your computer or your cellphone reaches out to set the time on its clock, it gets it as a timestamp in Universal Time. Internally, your computer or cell keeps time in Universal Time. It only converts to local time, and to days, hours and minutes, to show us slow humans who have trouble doing 10-digit arithmetic in our heads.
A Unix timestamp is an integer that counts the number of seconds since Midnight, Jan 1, 1970, in Greenwich, London, England (say grennich). That date and time is called the epoch - the zero time. Originally a timestamp was a 32-bit signed integer (9.3 digits), so it could represent 1902 through 2038, granularity down to one second. The start of 2016 in Greenwich, England, was at 1451606400.
Notice how the last digit of the timestamp is the same as the last seconds digit.
Modern computers have more heft, so sometimes they use 64 bit integers (19 digits) or double floats (16 digits). Javascript and Java actually count milliseconds, so you multiply or divide by 1000 to convert.
Timestamps are properly always in the Universal Time or UT1 timezone (formerly called GMT or Greenwich Mean Time). You can go and add a timezone offset to one, but don't store it or send it that way as it leads to confusion if used in a different timezone, or even the same time zone if the DST season changes. Generally, Universal Time times are stored in servers that may be accessed from anywhere in the world. Then, one formats a time from the Unix timestamp just before it's displayed to the user, and one converts back right after reading dates or times from the user. Part of the formatting/conversion is to adjust for the timezone the user is in - if there's no user, you should probably use Universal Time.
Often, Universal Time timestamps are the easiest way to do time/date calculations; you don't have to worry about minutes or the last day of the month or DST transitions, it's all just a number that refers to actual global moments. Each second is another consecutive integer, and it's correct all over the world in that second. You can always figure out if event A came before event B, and by how much, even if they're in different timezones, by simply subtracting Universal Time timestamps.
Timezones are humans' way of making time feel right in each location around the world. At noon, the sun should be directly above, at its highest point of the day (High Noon). The sun should rise sometime like 6am, and should set something like 6pm, give or take a few hours. Universal Time won't do this except for England and its time zone, and directly north and south of it, so everybody uses local times, measured from Universal Time.
To visualize how it all works, think of a globe of the world, with the sun shining directly over England. That is noon, in GMT or Universal Time. As the earth turns to the right, the moment of noon moves left, across the Atlantic to North America. So New York experiences Noon 5 hours after England; Los Angeles 8 hours. Therefore, to get the New York time, take the Universal Time time and subtract 5 hours: UTC-0500.
Simplified, there's 24 timezones around the world, each covering a 15° wedge of the world, each separated by an hour. Each timezone adds or subtracts this offset (number of hours) from Universal Time to get the local time. This way, noon is when the sun is highest in the sky (give or take).
So, for instance, a time in the US Pacific time zone in winter might look like this:
02/04/16 11:07 UTC-0800
The UTC-0800 tells which timezone; you take the time in Universal Time (in this case 19:07, just after 7pm), subtract 8 hours, and you'll get this local time 11:07, which is late morning. Offsets in the US and the rest of the western hemisphere are negative; eastern hemisphere offsets are positive. The International Date Line separates them, on the left side of the Pacific Ocean, where the -1200 and +1200 timezones have a gap of 1 day between them, despite the fact that they're supposed to be at the same place.
In reality, it's more complicated because different places make up different timezones for themselves, turning DST on and off at different times, often carved to national, provincial or even county boundaries. Some timezones are a half-hour (Newfoundland, UTC−03:30) or even a quarter-hour (Nepal, UTC+5:45) off from an even hour. The International Date Line is jagged and unintuitive; some pacific island chains near it have time zones 13 or 14 hours off from Universal Time when they should really have offsets of -1100 or -1000.
These specific timezones have names. The new names (Olson names) have a slash in them, frequently naming a specific city or island. Here are some timezone names:
See the full list:
https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
maps:
http://www.worldtimezone.com/
by offset:
http://joda-time.sourceforge.net/timezones.html
Many timezones shift an hour back and forth for Daylight Savings Time in summer; the exact dates of this transition change in different time zones. Europe switches to DST in the last Sunday in March; in the US, it's the second Sunday. Europe ends in the last week of October, whereas the US ends in the first week of November. But, those three timezones in Europe switch over at the same time, at 1am Universal Time (2am in Paris, 3am in Athens). The US, on the other hand, switches over at the same local time in each timezone: 2am, which happens at different times in each timezone: 7am Universal Time in New York, an hour later in Chicago at 8am Universal Time (still 2am in Chicago local time), and 10am Universal Time in San Francisco (at 2am Pacific time). All of this information is part of the timezone definition.
The US used 2-hour DST shifts in the 1970s, by order of President Richard Nixon, because of the energy crisis. Sydney, Australia and other areas south of the equator do DST during the months October through April, which is their Summer time. Governments around the world turn on and off DST depending on political winds.
You can see the timezone files your Unix or Mac machine supports by going to /usr/share/zoneinfo. You can set your Mac's timezone with the System Preferences, or your Unix machine's time on the command line using the date command.
For most normal things, like setting and getting dates, inputting and displaying them, you shouldn't have to mess around with the timezone offset. Most modern time/date software correctly converts for you, a Universal Time timestamp and local time, formatted or just individual numbers for days, hours, etc.
Timezones that do DST are actually a little indeterminate. For instance, in California, DST started at 2am on March 8, 2015. That means, at 1:59 am, it was still on winter time (offset -8 hours). One minute later, it was 3:00am, seemingly jumping an hour ahead. That day has only 23 hours, and the local times 2:00am thru 2:59am never existed, although converting software might remap that hour onto 3:00-3:59.
new Date('2015-03-08 2:44') Sun Mar 08 2015 03:44:00 GMT-0700 (PDT)
In all of this, Universal Time is your friend, because it chugs along, one second after the next, with no times omitted or ambiguous. Here you can see that both of the above local times map to the same Universal Time timestamp, and therefore the same time:
+new Date('2015-03-08 2:44') 1425811440000 +new Date('2015-03-08 3:44') 1425811440000
Back to 2015, DST ended in California at 2am on Nov 1. That is, after 1:59am, the next minute was 1:00am, seeming to jump back an hour, and the times 1:00 through 1:59 get re-lived. That day has 25 hours, and the local times that are between 1:00 and 1:59 are ambiguous, they could refer to either of two times separated by an hour. Conversion software usually just decides one way or another. Here you can see that the hours starting at 12:44am and 2:44am have 3600 seconds in them, but starting at 1:44, there's two hours to 2:44.
+new Date('2015-11-01 1:44') - new Date('2015-11-01 0:44') 3600000 +new Date('2015-11-01 2:44') - new Date('2015-11-01 1:44') 7200000 +new Date('2015-11-01 3:44') - new Date('2015-11-01 2:44') 3600000
All of these time shifts are just illusions - time doesn't really stand still or repeat for an hour. It's just that we set our wall clocks to these goofy timezones and they slosh back and forth. Universal Time keeps chugging straight ahead, one second at a time.
Timestamp calculations are best for continuous time. For instance, if you start at Noon today, and add 7 hours, it'll be 7pm (19:00). Seven more hours will take you to 2am (2:00) the next day. And so on throughout the morning, afternoon and evening of each day; it won't hit noon again until 24 periods of 7 hours, or exactly 7 days later.
The New York Stock Exchange doesn't work that way, however. They open at 9:30am EDT (17:30 Universal Time in winter, 16:30 summer) and closes at 4pm (16:00 local, midnight Universal Time in winter or 23:00 summer). They are open only monday - friday, and they skip holidays. And, after the WTC collapsed Tuesday September 11 2001, it was closed for the other 4 days of the week. For these calculations, and others such as the normal 40-hour work week, you might be better off handling the days and hours as separate numbers.
If you're adding or subtracting times that are broken down into days, hours and minutes, you can't just take shortcuts if the time is formatted into a date, hours and minutes. For instance, if you want to add 30 minutes, you can't just add 30 to the minutes number, you have to carry anything over 60 to the hours. Then if the hours wrap past midnight (24), you have to increment the day (unless you know all your times are in the middle of the day). And indeed, if you increment the day, that might roll over to the next month if they exceed 28, 29, 30 or 31. Which also might roll over to the next year. You might incorporate bugs that nobody will notice for a long time, because they only happen once a day, week, month, 4 years, or whatever.
But for a workday, you take 4:30pm, add an hour, and it becomes 5:30pm, after work. You might never need to wrap around to the next day for your situation if all times are midday.
Remember that if you just convert to a timestamp, do your addition, then convert back, all of that is taken care of for you automatically, if you're running 24/7.
1 minute = 60 seconds. 1 hour = 3600 seconds. 1 day = 86400 seconds. 365 days = 31536000 seconds.
For Java or JavaScript, use milliseconds (ms) instead of seconds. 1 minute = 60,000 ms. 1 hour = 3,600,000 ms. 1 day = 86,400,000 ms. 365 days = 31,536,000,000 ms.
If you're calculating multiple years, use this: 4 years (with 1 leap) = 126230400 seconds (but see the Gregorian Calendar in the Calendars section).
Timezone offsets are usually added onto Universal Time times to get a timestamp that looks like local time.
That timezone offset must be in seconds (or milliseconds)! Often you get them in minutes because all offsets are multiples of 15 min. Sometimes, they give you offsets negated, so for instance in JavaScript, date.getTimezoneOffset() returns the negative of the number of minutes; +3000 for New York which has an offset of -5 hours. North and South American timezone offsets are always negative, Eurasia, Africa and Australia are always positive (except for a thin zone off the west coast of Europe).
For most work, you shouldn't have to add/subtract the timezone offset - most code works fine if you hand it a Universal Time timestamp, and if you add on the offset somewhere, chances are you'll have to subtract it off somewhere else. After hours of confusing debugging. I've fixed a lot of bugs by just removing code that added or subtracted the timezone offset.
Always test your code as you do time/date calculations to make sure you know what you're doing - it's so easy to goof it up! Set your machine to different times (before/after DST transitions) and different timezones and see how it works, by stepping through in the debugger. (If you do so, some of your running apps will complain or malfunction, but they'll probably recover when you set it back. It might be safer to quit a lot of these apps.) Or, even better, make unit tests that can fake timezones, and you can test them all quickly. Many unit test frameworks (mocha, jest) let you set phony times, just within the test. Much easier. You can test leap years, DST, etc.
To round off a floating number to the nearest integer, your language probably has these functions:
In Java and JS, they are Math.round(), etc.
Round itself is just floor(x + 0.5), and ceil(x) is just -floor(-x). If your language only has one or another, you can make the rest yourself.
You could make your own variation of rounding. This function, for instance, will round .0000 through .6999 down, but .7 through .999 up: floor(x + 0.3).
To round to the nearest multiple of, say, 10, do this: 10 * round(x / 10).
If your language has integers (not javascript or php), you can get a floor() by just dividing integers:
77 / 10 * 10 = 70
The only problem with this is that in some languages, division rounds towards zero, rather than always down:
-77 / 10 * 10 = -70 whereas
floor(-77 / 10) * 10 = -80
so do some experimentation if you'll be using negative numbers - usually you'll want a floor rather than integer division.
To round down to the previous half-way point, offset before and after, like this:
floor((x - 5) / 10) * 10 + 5
The result will always be 5 off of a multiple of ten:
15, 25, 35...
Some examples:
30 -> 25, 34 -> 25, 36 -> 35, 44 -> 35
Don't confuse that with rounding off to a multiple of 5 like floor(x / 5) * 5;
15, 20, 25, 30, 35, ...:
30 -> 30, 34 -> 30, 36 -> 35, 44 -> 40
To round off a timestamp to the start of the day, use this: floor(x / 86400) * 86400 . The problem, though, is this will get you midnight in Greenwich, England. For local time, convert to your offset given your timezone, then convert back to utc. Like this
floor((x + offset) / 86400) * 86400 - offset
You want to subtract the offset at the end to keep it Universal Time, as in the formula above. As usual, the offset must be in seconds or milliseconds.
To bracket a number, you make sure it can't go over an upper limit, or under a lower limit. Remember these (seemingly unintuitive) rules:
So say you have a variable x, and you need to pin it so it doesn't go above 10:
x = min(10, x)
you can see that if x is above 10, then 10 will be returned instead.
Similarly, say x shouldn't go below zero:
x = max(0, x)
If x goes below 0, you get 0 instead of x. Combining them:
x = max(0, min(10, x))
in Java and JS, use the functions Math.min() and Math.max().
Although you shouldn't try to format times yourself from timestamps, sometimes you want to calculate time differences. So if you started playing Call of Duty at 1456923041 and ended at 1456976277, you played for 1456976277 - 1456923041 = 53236 seconds. Maybe you'd like to know how long that was in hours, minutes and seconds. Some systems have library routines to do that work, but you can do it by hand for simple cases.
(note the time difference is the same in Universal Time as it is in local time - the timezone offset just cancels out.)
The fundamental operation you want here is integer division, with remainder. Like you learned in 2nd grade. Unless you're writing in C or C++, you can't do that in one step, and you have to use the / and % operators in your language. First, find the seconds:
So now you know it's 887 minutes and 16 seconds. Work on the 887 next.
Now you know it's 14 hours, 47 minutes and 16 seconds.
What if you want to go in the opposite direction? You have hours, minutes and seconds. You can convert this to seconds in one expression:
(hours * 60 + minutes) * 60 + seconds
in our example:
(14 * 60 + 47) * 60 + 16 = 53236 seconds again.
This expression can be extended to any number of units:
((((years * 12 + months) * 30 + days) * 24 + hours) * 60 + minutes) * 60 + seconds
Note: the above assumes 30 days in every month! For some situations that's close enough.
This is similar to the arithmetic you need to evaluate a number from its digits:
53236 = (((5 * 10 + 3) * 10 + 2) * 10 + 3) * 10 + 6
If you want to find the time that's halfway between two, you simply find the average, (A + B) / 2. If we want the average to lean to one side, say for instance, twice as much space on one side as the other, we might use a formula like (A + A + B) / 3, or (2A + B) / 3. This is an average where we count a twice, so the result will be closer to A, 1/3 of the distance. Then, the other 2/3 of the distance to B (kindof the opposite of what you might expect).
We can make any proportion we want, actually. For instance, this will give us 3/8 space on one side and 5/8 on the other side: (3A + 5B) / 8. The two numbers on top must add up to the number beneath - the whole space is divided into 8 segments, and one side gets 3 and the other gets 5.
Another way of writing this is (3/8) A + (5/8) B. In this case, the two weights (3/8 and 5/8) have to add up to 1. We can extend this to any numbers, not just fractions, as long as they add up to 1, for instance, 0.237 A + 0.763 B. As before, the biggest weight tells you which one it's closest to; you reverse the two weights to see the proportion of space for each.
For global web development, servers should only deal with times as Universal Time timestamps. Use the local time zone only for displaying to the user or inputting from the user - usually on the browser. The Java and JavaScript class Date() will create a new date if you pass it the timestamp (in milliseconds), and it'll figure out if that moment is in daylight savings time and do the correct offset, as long as it knows what local timezone you want. You don't want to mess with the timezone offset, as the Date stores the Universal Time timestamp that you passed in anyway.
Other ways of doing it are more complex and more confusing. If you're simply handling dates rather than doing anything fancy, like figuring out the first Wednesday in July, you shouldn't be worrying about DST or timezones; probably there's off-the-shelf functions in the Date functions to do what you need to do. Even if you are calculating with dates and times, if you do all calculations in Universal Time with timestamps, it's usually much easier.
For debugging, this chart shows some handy ways to convert back and forth.
show: | linux command line | mac command line | javascript console |
---|---|---|---|
timestamp in local time | $ date -d @1454707781 Fri Feb 5 13:29:41 PST 2016 |
$ date -r 1454707781 Fri Feb 5 13:29:41 PST 2016 |
> new Date(1454707781000) Fri Feb 05 2016 13:29:41 GMT-0800 (PST) |
timestamp in utc | $ date -ud @1454707781 Fri Feb 5 21:29:41 UTC 2016 |
$ date -ur 1454707781 Fri Feb 5 21:29:41 UTC 2016 |
> new Date(1454707781000).toUTCString() Fri, 05 Feb 2016 21:29:41 GMT |
local time as timestamp | $ date -d '2016-05-07 01:02:03' +%s 1462608123 |
$ date -j 0102030405 +%s 1104663840 $ date -jf '%F %T' '2016-05-07 11:07:09' +%s 1462644429 |
> +new Date('2016-05-07 11:07:09') 1462644429000 |
Universal Time time to timestamp | $ date -ud '2016-05-07 01:02:03' +%s 1462582923 |
$ date -ju 0102030405 +%s 1104635040 $ date -juf '%F %T' '2016-05-07 11:07:09' +%s 1462619229 |
> +new Date('2016-05-07 11:07:09 UTC') 1462619229000 |
echo back local time | $ date -j 0102030405 Sun Jan 2 03:04:00 PST 2005 $ date -jf '%F %T' '2016-05-07 11:07:09' Sat May 7 11:07:09 PDT 2016 |
||
echo back Universal Time time | $ date -juf '%F %T' '2016-05-07 11:07:09' Sat May 7 11:07:09 UTC 2016 |
||
kind of timestamp | Universal Time seconds | Universal Time seconds | Universal Time milliseconds |
notes | '+format' defines the format for output; %s is a format string for the Universal Time timestamp. | input format for time is primitive unless you specify format as above; use the echo back examples if you're unsure | + will convert a date object to its timestamp Z works just like Universal Time for timezone |
The worldwide standard timezone is UT, stands for Universal Time. There's a few variations:
UT1 follows the sun, and is most convenient to use around the world. This is the same as GMT (greenwich mean) and is almost the same as Universal Time. Every day is exactly 86400 seconds (24 × 60 × 60), in Universal Time. Years are always 31536000 seconds long, or 31622400 for a leap year. Date calculations are easy.
UTC (coordinated) is measured by atomic clocks, which are precise to microscopic fractions of an atomic second. Every UTC second is an atomically accurate second. Unfortunately, the earth's rotation drifts faster and slower compared to atomic seconds. Every few years, they add a leap second to UTC so that the last minute of Dec 31 is actually 61 UTC seconds long instead of 60; so the day contains 86401 atomic seconds. But, every year always starts at Jan 1, 00:00:00, for both systems.
This way, UTC is very close to UT1, but the leap seconds complicate calculations. There's been about two dozen leap seconds added since 1972. So, the length of an atomic second, the amount of time, is always exactly the same, but the count of seconds in a UTC day or year may or may not include one extra second. UTC is actually defined by hours, minutes and seconds; a leap second happens at time 23:59:60, which will wrap around to 00:00:00 if you just convert it normally, as if it were a regular date/time. Software around the world accommodates this exception. If your timestamp at even hours doesn't end with 00, it's probably real UTC.
Meanwhile, in UT1, the length of a second stretches a microscopic amount from one year to another (but not the number of seconds). For most practical purposes, you can ignore this; most digital and analog clocks used by humans are only accurate within a minute, and nobody notices a second here or there. If you arrive to a meeting a second late, nobody will care.
Usually, timestamps are in Universal Time, although people usually mistakenly call it UTC.
The calendar we all use is called the Gregorian Calendar. It was invented by scholars under Pope Gregory in 1582, because the previous calendar, the Julian calendar, had slipped the day of Easter by 10 days since Julius Caesar. For the transition to the new calendar, Thursday, 4 October 1582, was followed by Friday, 15 October 1582, and the new calendar worked like this:
The Julian calendar was invented by scholars under Julius Caesar in 46bce because the previous calendar was a mess. (bce=before common era, same as bc=before Christ) To do date calculations for that era, usually the Julian calendar is extended backwards from that time, called the Proleptic Julian Calendar. Calculations like this are usually done in days. The year 0 doesn't exist, year 1bce is followed by year 1ce. This century, the Julian calendar is behind the Gregorian calendar by 13 days, so our Mar 3, 2016 becomes February 19, 2016 in Julian. Don't try to do multi-century arithmetic yourself; download a package.
Before the Julian, Rome worked on a lunar calendar where leap-months (a whole 13th month added to the year) were decided by the Pontifex Maximus, the Roman pope. Sometimes this was decided at the last minute, so nobody could reliably make appointments in the future. The pontifex also had political friends and enemies whose terms in office could be lengthened or shortened by this decision; and often was. The calendar had slipped about 1/3 year this way, when Julius took over. Days started and ended at noon. Years were counted from the ascendance of the King, or maybe just a regional governor. Or maybe just when someone thought the King ascended. News traveled slowly, so there was plenty of room for confusion. Time wasn't a big concern back then.
Many other calendars are, or have been, in use, mostly tied to religions or countries. Usually, the older calendars' years and months do not correspond to ours in the Gregorian system. The Chinese, Jewish and Islamic calendars are lunar - months start on a new (hidden) moon, and they have 29 or 30 days to a month. Indeed, the Gregorian 7-day week comes from the Jewish religion (God creating the world in 6 days, then resting for a day which was Sunday), and the numbering of years comes from Christianity (assumed birth of Christ, The King).
The Traditional Chinese Calendar is still used in China and its diaspora for holidays and social functions. They also use the Gregorian calendar for civil use. Traditional Japanese, Korean, Vietnamese and Mongolian calendars descended from the Chinese; other nearby areas' calendars were influenced by the Chinese calendar. It's a very complicated mixture of solar and lunar, also depending on the other traditional planets. It descended from more than a dozen previous calendars used in different places and times in China's history, stretching back 2400 years or more.The Islamic calendar has an epoch of Friday, 16 July 622ce (on the Julian calendar), and slips 10 days every year relative to the Gregorian calendar, because they always have 12 lunar months, each just 29 or 30 days. Up until recently, the decision as to 29 days or 30 days was made by a guy on a hilltop who sees or doesn't see a crescent moon that evening. Mar 3, 2016 becomes 23 Jumada al-awwal 1437.
The Hebrew calendar is also lunar, but they have 7 leap-months inserted every 19 years on a Metonic cycle that synchronizes with astronomy remarkably well over the centuries. Around 200ce they narrowed down the number of new-years days to 4. Their epoch is Monday, 7 October 3761bce, about a year before the presumed creation of the world. Mar 3, 2016 becomes 23 Adar I 5776; the month name Adar I indicates that it is a leap-month.
The French Republican Calendar had 12 months in the year, three weeks per month, ten days per week, 10 hours per day, 100 minutes per hour, and 100 seconds per minute. The epoch was 22 September 1792, the start of the French Republic, during the French Revolution. (They were really into the Metric system back then.) It was abandoned after 12 years.
I hope this article has helped clear up some confusion you might have had about the details of time calculations.