将Unix时间转换为日期&不使用任何库或预定义的帮助程序


Convert Unix Time to Date & time without using any library or predefined helpers

我在一次面试中被问到如何将Unix日期时间整数转换为日期& &;时间。我很震惊,因为我一直在使用库,直到今年才能够转换。对于我的知识,我寻求这里的专家来帮助我解决我不会做的难题。我用PHP的编码机制来解决:

 $time = 1471488076; //08/18/2016 @ 2:41am (UTC)
 $SECONDS_IN_YEAR = 365 * 24 * 60 * 60;
 $SECONDS_IN_DAY = 24 * 60 * 60;
 $SECONDS_IN_HOUR = 60 * 60; 
 $s = $time % $SECONDS_IN_DAY;
 $hour = floor($s /  $SECONDS_IN_HOUR); //$hour = 2
 $mins = floor(($s / 60) % 60); //$mins = 41
 $seconds = floor($s % 60); //$seconds = 16
 echo "TIME >> $hours:$mins:$seconds".PHP_EOL; // 2:41:16
 $year = floor(1970 + ($time / $SECONDS_IN_YEAR)); //$year = 2016

然而,我无法解决月和日,因为我迷路了。我向这里的专家寻求关于如何解决月份和年份的建议。我不能再往前走了。结果令人失望。

我不能使用strtotime或编程语言提供的任何预定义的日期时间类。这是给我的限制。这听起来可能很奇怪,但面试就是这么艰难。

要考虑到每个月/每年的可变天数需要做一些工作,但这并不是那么糟糕。基本思想是向下迭代,直到时间戳上没有剩余的天数。

处理负时间戳留给读者作为练习。

例子:

const SECONDS_PER_MINUTE = 60;
const SECONDS_PER_HOUR = 60 * SECONDS_PER_MINUTE;
const SECONDS_PER_DAY = 24 * SECONDS_PER_HOUR;
const DAYS_PER_YEAR = 365;
const DAYS_PER_LEAP_YEAR = DAYS_PER_YEAR + 1;
const EPOCH_MONTH = 1;
const EPOCH_YEAR = 1970;
function getDateTime(int $timestamp) : array
{
    $days = intdiv($timestamp, SECONDS_PER_DAY);
    $year = EPOCH_YEAR;
    while ($days >= getDaysForYear($year)) {
        $days -= getDaysForYear($year);
        $year++;
    }
    $daysPerMonth = getDaysPerMonth($year);
    $month = EPOCH_MONTH;
    while ($days >= $daysPerMonth[$month]) {
        $days -= $daysPerMonth[$month];
        $month++;
    }
    $day = $days + 1;
    $secondsRemaining = $timestamp % SECONDS_PER_DAY;
    $hour = intdiv($secondsRemaining, SECONDS_PER_HOUR);
    $minute = intdiv($secondsRemaining, SECONDS_PER_MINUTE) % SECONDS_PER_MINUTE;
    $second = $secondsRemaining % SECONDS_PER_MINUTE;
    return [
        'year' => $year,
        'month' => $month,
        'day' => $day,
        'hour' => $hour,
        'minute' => $minute,
        'second' => $second
    ];
}
function isLeapYear(int $year) : bool
{
    return $year % 400 === 0 || ($year % 4 === 0 && $year % 100 !== 0);
}
function getDaysForYear(int $year) : int {
    return isLeapYear($year) ? DAYS_PER_LEAP_YEAR : DAYS_PER_YEAR;
}
function getDaysPerMonth(int $year) : array
{
    return [0, 31, isLeapYear($year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
}

$time = 1471488076; //08/18/2016 @ 2:41am (UTC)
print_r(getDateTime($time));

输出:

Array
(
    [year] => 2016
    [month] => 8
    [day] => 18
    [hour] => 2
    [minute] => 41
    [second] => 16
)

你可以按照你喜欢的格式来选择。


在线演示。

例如(这将得到一个近似值),通过查找整个单位,使用下一组中的余数来查找整个单位。每次将秒数减少到余数。

$time = 1471488076; //08/18/2016 @ 2:41am (UTC)
$SECONDS_IN_YEAR = 365 * 24 * 60 * 60;
$SECONDS_IN_MONTH = $SECONDS_IN_YEAR / 12;
$SECONDS_IN_DAY = 24 * 60 * 60;
$SECONDS_IN_HOUR = 60 * 60;
$num = floor($time / $SECONDS_IN_YEAR);
echo "'nYears: ".$num . "'n";
$remainingSeconds = $time - ($num * $SECONDS_IN_YEAR);
echo "'nremainder: ".$remainingSeconds . "'n";
$year = 1970 + $num;
$num = floor( $remainingSeconds / $SECONDS_IN_MONTH);
echo "'nMonth: ".$num . "'n";
$remainingSeconds = $remainingSeconds - ($num * $SECONDS_IN_MONTH);
echo "'nremainder: ".$remainingSeconds . "'n";
$month = $num;
$num = floor( $remainingSeconds / $SECONDS_IN_DAY);
echo "'nDay: ".$num . "'n";
$remainingSeconds = $remainingSeconds - ($num * $SECONDS_IN_DAY);
echo "'nremainder: ".$remainingSeconds . "'n";
$day = $num;
$num = floor( $remainingSeconds / $SECONDS_IN_HOUR);
echo "'nHour: ".$num . "'n";
$remainingSeconds = $remainingSeconds - ($num * $SECONDS_IN_HOUR);
echo "'nremainder: ".$remainingSeconds . "'n";
$hour = $num;
$num = floor( $remainingSeconds / 60);
echo "'nMinute: ".$num . "'n";
$remainingSeconds = $remainingSeconds - ($num * 60);
 echo "'nremainder: ".$remainingSeconds . "'n";
 $min = $num;
 $sec = floor($remainingSeconds);
 echo $month."/".$day."/".$year." ".$hour.":".$min.":".$sec;

输出
Years: 46
remainder: 20832076
Month: 7
remainder: 2436076
Day: 28
remainder: 16876
Hour: 4
remainder: 2476
Minute: 41
remainder: 16
7/28/2016 4:41:16

但是正如我所说的每月平均秒数,它永远不会是正确的,你需要一个像数组一样的东西,每个月的秒数你可以在数组中循环减去,直到你得到负的,然后是前一个月。然后把这些秒加起来,剩下的就是小时、分钟和秒。

基本上每过一个偶数单位就减少一个。

如果没有这个,它充其量只是一个近似值。即使考虑到它不能解释闰年。

可能有更好的方法来做到这一点,但这只是最简单的方法来获得一个粗略的数字。

我用c++创建了2段代码,但经过一些小的调整,它也可以在其他语言中工作。我还专注于优化它的速度,花了一整天的时间检查代码,并用各种解决方案测试它,以使它更快。

One piece is complete and works with:

  1. 负输入。
  2. 纪元前时间(BCE)。
  3. 400年闰周期规则。
  4. 100年无跳循环规则。
  5. 4年闰周期规则。

另一部分,与第一部分基本相同。但我放弃了400,100和BCE规则,以优化速度。从:1970-01-01 00:00:00到210002-28 23:59:59。

享受吧!

/*
disclaimer about leap seconds:
unix seconds actually differ from real seconds.
this means there are always either 31,536,000 seconds or 31,622,400 seconds in an unix year/unix leap year.
therefore the unix second is ever so slightly slower than a real second.
fortunately for us, this keeps unix time synchronised with real time without the hussle of dealing with leap seconds (Hurray! *insert party hat*).
Anyway, to determine the year, month, monthday, weekday, hour, minute and second, we use the following approach:
1.  create a new epoch based on the unixepoch where the new epoch is always before the unixtime. (to make it work with negative inputs.)
    note: this adjusted epoch is always on a date where the 400, 100, 4, and 1 year cycles all repeat. Examples: 1601-01-01 00:00:00, 2001-01-01 00:00:00, etc.
2.  calculate how many days have passed since this new epoch.
3.  calculate how many 146097 days (400 year cycle including 97 leap days) fit inside it and subtract to create a remainder.
4.  calculate how many 365024 days (100 year cycle including 24 leap days) fit inside the remainder and subtract to create the second remainder.
5.  calculate how many 1461 days (4 year cycle including 1 leap day) fit inside the second remainder and subtract to create the third remainder.
6.  calculate how many 365 days (1 year cycle) fit inside the third remainder to create the final remainder.
    what you are left with is total amount of cycles of each cycle length, and the days that have passed this year.
7.  now add the 400, 100, 4, and 1 year cycles multiplied by their respective year lengths together to get the years that passed since the new epoch.
8.  and add them to this new epoch to get the current year.
9.  check whether the year is smaller than 1 (before common era) and subtract an additional year from the final date to correct for it.
10. now to calculate the month and monthday, check whether the year is a leapyear.
11. if it's leap, we use 1 method, if it's not, we use the other.
12. use the remainder to calculate the current hour.
13. use the remainder to calculate the current minute.
14. use the remainder to calculate the current second.
*/
#include <iostream>
using namespace std; //so you dont have to type std:: every darn time.
    
long long int unixTS1 = 0;  //the amount of seconds since 1970-01-01 00:00:00, (using long long int so it also works for outdated compilers)?
long long int unixTS2 = 0;  //adjusted timestamp.
int fits = 0;               //necessary to calculate the new epoch.
int days = 0;               //days that passed since adjusted epoch.
int hms = 0;                //remaining hours minutes and seconds (hms) in seconds after subtracting the days in seconds that passed.
int newepoch = 2001;        //in order to make the code work with negative inputs.
int y400 = 0;               //400 year cycle repeats.
int r400 = 0;               //remaining days after the 400 year cycles.
int y100 = 0;               //100 year cycle repeats.
int r100 = 0;               //remaining days after the 100 year cycles.
int y4 = 0;                 //4 year cycle repeats.
int r4 = 0;                 //remaining days after the 4 year cycles.
int y1 = 0;                 //1 year cycle repeats.
int r1 = 0;                 //remaining days after the 1 year cycles.
int year = 0;               //actual year.
int yearday = 0;            //day of the year       (1 to 366)
int month = 0;              //month of the year     (1 to 12)
int monthday = 0;           //day of the month      (1 to 31)
int weekday = 0;            //day of the week       (1 to 7)
int hour = 0;               //hour of the day       (0 to 23)
int minute = 0;             //minute of the hour    (0 to 59)
int second = 0;             //second of the minute  (0 to 59)
int unixdate() //1,000,000,000 repeats on intel i7-7700k = approximately 23 seconds.
{
        unixTS2 = unixTS1 - 978307200; //time in seconds since 2001-01-01 00:00:00. (new epoch).
        
        if(unixTS2 < 0) //we need a positive timestamp to make the code work, if it's negative, we create a new epoch to make it positive.
        {
            fits = (unixTS2 / 12622780800) + 1; //calculating how many 400 year cycles fit inside the negative timestamp.
            newepoch = 2001 - fits * 400; //subtracting the 400 year cycles from the epoch to create a new one.
            unixTS2 += fits * 12622780800; //adding the 400 year cycles to the timestamp to make it positive.
        }
        else{newepoch = 2001;}
        
        /*
        to calculate the remainder I don't use the modulus operator (%) because subtracting an already calculated value is faster, with the exception of the long long int variable.
        I do not know why this is, but tests reveal a significant speed difference.
        I suspect conversion necesseties from the native variables of my CPU but let it be known if you found the reason, cheers.
        */
        
        days = unixTS2 / 86400;
        hms = unixTS2 % 86400;
        
        hour = hms / 3600;
        minute = (hms - hour * 3600) / 60;
        second = (hms - hour * 3600) - minute * 60;
        
        y400 = days / 146097;
        r400 = days - 146097 * y400;
        y100 = r400 / 36524;
        r100 = r400 - 36524 * y100;
        y4 = r100 / 1461;
        r4 = r100 - 1461 * y4;
        y1 = r4 / 365;
        r1 = r4 - 365 * y1;
        yearday = r1 + 1;
        weekday = r400 % 7 + 1;
        
        if(y100 > 3) //special occasion that happens at the last day in the 400 year cycle.
        {
            year = newepoch + y100 * 100 - 1;
            yearday = 366;
            month = 12;
            monthday = 31;
        }
        else
        {
            if(y1 > 3) //special occasion that happens at the last day in the 4 year cycle.
            {
                year = newepoch + y400 * 400 + y100 * 100 + y4 * 4 + y1 - 1;
                yearday = 366;
                month = 12;
                monthday = 31;
            }
            else
            {
                year = newepoch + y400 * 400 + y100 * 100 + y4 * 4 + y1;
                /*
                now we need to select the month.
                I created and tested 3 types of ways to get to the correct month.
                1.  option 1 involves directly calculating the month with a relative simple formula with 2 subtractive checks.
                    worked for every day of the year including leap years but even after careful optimisation was still about 60% slower than option 3.
                2.  option 2 involves a single division calculation to approximate the month with a minimum.
                    followed by checking the month after and choosing either depending on the result. this was quite memory intensive, but still faster than option 1.
                3.  the third and best option I could come up with involves little calculation at all, and uses just 4 to 5 subtractive comparisons to get the right month and monthday.
                    the first 2 check gets you down to 4 possible months, another check to 2, and the final check to 1.
                    it seems a bit messy though, but I tried to make it compact and still readable in 1 overview, so enjoy.
                */
                if(y1 > 2 && (y4 < 24 || y100 > 2)) //if leap, select month and calculate monthday.
                {
                    if(r1 > 243)                    {if(r1 > 304)   {if(r1 > 334)   {month = 12;monthday = r1 - 334;}
                                                                    else            {month = 11;monthday = r1 - 304;}}
                                                    else            {if(r1 > 273)   {month = 10;monthday = r1 - 273;}
                                                                    else            {month = 9;monthday = r1 - 243;}}}
                    else            {if(r1 > 120)   {if(r1 > 181)   {if(r1 > 212)   {month = 8;monthday = r1 - 212;}
                                                                    else            {month = 7;monthday = r1 - 181;}}
                                                    else            {if(r1 > 151)   {month = 6;monthday = r1 - 151;}
                                                                    else            {month = 5;monthday = r1 - 120;}}}
                                    else            {if(r1 > 59)    {if(r1 > 90)    {month = 4;monthday = r1 - 90;}
                                                                    else            {month = 3;monthday = r1 - 59;}}
                                                    else            {if(r1 > 30)    {month = 2;monthday = r1 - 30;}
                                                                    else            {month = 1;monthday = r1 + 1;}}}}
                }
                else
                {
                    if(r1 > 242)                    {if(r1 > 303)   {if(r1 > 333)   {month = 12;monthday = r1 - 333;}
                                                                    else            {month = 11;monthday = r1 - 303;}}
                                                    else            {if(r1 > 272)   {month = 10;monthday = r1 - 272;}
                                                                    else            {month = 9;monthday = r1 - 242;}}}
                    else            {if(r1 > 119)   {if(r1 > 180)   {if(r1 > 211)   {month = 8;monthday = r1 - 211;}
                                                                    else            {month = 7;monthday = r1 - 180;}}
                                                    else            {if(r1 > 150)   {month = 6;monthday = r1 - 150;}
                                                                    else            {month = 5;monthday = r1 - 119;}}}
                                    else            {if(r1 > 58)    {if(r1 > 89)    {month = 4;monthday = r1 - 89;}
                                                                    else            {month = 3;monthday = r1 - 58;}}
                                                    else            {if(r1 > 30)    {month = 2;monthday = r1 - 30;}
                                                                    else            {month = 1;monthday = r1 + 1;}}}}
                }
            }
        }
        // and finally.
        if(year < 1){year--;} //this one line accounts for years before common era (BCE) because there is no year 0 apparently.
    return 0;
}
/*
however, one might not care about accuracy far in the past and future and simply needs a simple and fast piece of code.
so by ditching some rules we can make the code 110% faster and still make it work from:
1970-01-01 00:00:00 till:
2100-02-28 23:59:59.
*/
int unixdatefast() //1,000,000,000 repeats on intel i7-7700k = approximately 11 seconds.
{
        days = unixTS1 / 86400;
        hms = unixTS1 % 86400;
        hour = hms / 3600;
        minute = (hms - hour * 3600) / 60;
        second = (hms - hour * 3600) - minute * 60;
        
        y4 = days / 1461;
        r4 = days - 1461 * y4;
        y1 = r4 / 365;
        r1 = r4 - 365 * y1;
        
        weekday = days % 7 + 3;
        yearday = r1 + 1;
        
            if(y1 > 3)
            {
                year = 1972 + y4 * 4;
                yearday = 366;
                month = 12;
                monthday = 31;
            }
            else
            {   
                year = 1969 + y4 * 4 + y1;          
                if(y1 > 2)
                {
                    if(r1 > 243)                    {if(r1 > 304)   {if(r1 > 334)   {month = 12;monthday = r1 - 334;}
                                                                    else            {month = 11;monthday = r1 - 304;}}
                                                    else            {if(r1 > 273)   {month = 10;monthday = r1 - 273;}
                                                                    else            {month = 9;monthday = r1 - 243;}}}
                    else            {if(r1 > 120)   {if(r1 > 181)   {if(r1 > 212)   {month = 8;monthday = r1 - 212;}
                                                                    else            {month = 7;monthday = r1 - 181;}}
                                                    else            {if(r1 > 151)   {month = 6;monthday = r1 - 151;}
                                                                    else            {month = 5;monthday = r1 - 120;}}}
                                    else            {if(r1 > 59)    {if(r1 > 90)    {month = 4;monthday = r1 - 90;}
                                                                    else            {month = 3;monthday = r1 - 59;}}
                                                    else            {if(r1 > 30)    {month = 2;monthday = r1 - 30;}
                                                                    else            {month = 1;monthday = r1 + 1;}}}}
                }
                else
                {
                    if(r1 > 242)                    {if(r1 > 303)   {if(r1 > 333)   {month = 12;monthday = r1 - 333;}
                                                                    else            {month = 11;monthday = r1 - 303;}}
                                                    else            {if(r1 > 272)   {month = 10;monthday = r1 - 272;}
                                                                    else            {month = 9;monthday = r1 - 242;}}}
                    else            {if(r1 > 119)   {if(r1 > 180)   {if(r1 > 211)   {month = 8;monthday = r1 - 211;}
                                                                    else            {month = 7;monthday = r1 - 180;}}
                                                    else            {if(r1 > 150)   {month = 6;monthday = r1 - 150;}
                                                                    else            {month = 5;monthday = r1 - 119;}}}
                                    else            {if(r1 > 58)    {if(r1 > 89)    {month = 4;monthday = r1 - 89;}
                                                                    else            {month = 3;monthday = r1 - 58;}}
                                                    else            {if(r1 > 30)    {month = 2;monthday = r1 - 30;}
                                                                    else            {month = 1;monthday = r1 + 1;}}}}
                }
            }
    return 0;
}
int main()
{
    unixdate();
    //unixdatefast();
    cout << "unixTS1: "         << unixTS1      << endl;
    cout << "unixTS2: "         << unixTS2      << endl;    
    cout << "Fits: "            << fits         << endl;
    cout << "newepoch: "        << newepoch     << endl;
    cout << "days: "            << days         << endl;
    cout << "hms: "             << hms          << endl << endl;
    cout << "y400: "            << y400         << endl;
    cout << "r400: "            << r400         << endl;
    cout << "y100: "            << y100         << endl;
    cout << "r100: "            << r100         << endl;
    cout << "y4: "              << y4           << endl;
    cout << "r4: "              << r4           << endl;
    cout << "y1: "              << y1           << endl;
    cout << "r1: "              << r1           << endl << endl;
    
    cout << "Weekday: "         << weekday      << endl << endl;    
    
    cout << "Date: "            << year << "-" << month << "-" << monthday << " " << hour << ":" << minute << ":" << second << endl;
    cout << "Yearday: "         << yearday      << endl;
    return 0;
}