Jump to content

Getting timestamps of period boundaries


NotionCommotion

Recommended Posts

Creating a class to find the start and end time stamps of various periods.  strtotime() seems so amazing some of the time but quirky others.  Likely it is mostly user operator but I've read of some gotchas.  Think I should use mktime() as I do so in my year boundary class?  Or some other strategy?  Thanks

 

PS.  Should I be using an instance class?  If so, why?

 

class DateBoundaries
{


    /* following 5 methods return [startTimestamp, endTimestamp]*/
    public static function getDayBoundaries($u, $v, $ts=null)
    {
        $a=self::sanitizeBoundaries($u, $v, $ts);
        $s1="midnight -$a[v] $a[u]";
        $s2="tomorrow -$a[v] $a[u]";
        return (object)['start' => strtotime($s1, $a['ts']), 'end' => strtotime($s2, $a['ts'])-1];
    }
    public static function getWeekBoundaries($u, $v, $ts=null)
    {
        $a=self::sanitizeBoundaries($u, $v, $ts);
        $s1="midnight last sunday -$a[v] $a[u]";
        $s2="tomorrow next sunday -$a[v] $a[u]";
        return (object)['start' => strtotime($s1, $a['ts']), 'end' => strtotime($s2, $a['ts'])-1];
    }
    public static function getMonthBoundaries($u, $v, $ts=null)
    {
        $a=self::sanitizeBoundaries($u, $v, $ts);
        $v2=$a['v']+1;
        $s1="midnight -$a[v] $a[u] first day of";
        $s2="tomorrow -$a[v] $a[u] last day of";
        //$s2="-$v2 $a[u] first day of";
        return (object)['start' => strtotime($s1, $a['ts']), 'end' => strtotime($s2, $a['ts'])-1];
    }
    public static function getQuarterBoundaries($u, $v, $ts=null)
    {
        $a=self::sanitizeBoundaries($u, $v, $ts);
        $a['v']=ceil($a['v']/3);
        $v2=$a['v']+1;
        $s1="-$a[v] $a[u] first day of";
        $s2="-$v2 $a[u] first day of";
        return (object)['start' => strtotime($s1, $a['ts']), 'end' => strtotime($s2, $a['ts'])-1];
    }
    public static function getYearBoundaries($u, $v, $ts=null)
    {
        $a=self::sanitizeBoundaries($u, $v, $ts);
        $year = date('Y',strtotime("$a[v] $a[u]", $a['ts']));
        return (object)['start' => mktime(0, 0, 0, 1, 1, $year), 'end' => mktime(0, 0, 0, 1, 1, $year+1)];
    }


    //Following provides date details from timestamp
    public static function getDateDetails($ts)
    {
        self::validateTimestamp($ts);
        return (object) self::_getDateDetails($ts);
    }
    public static function getDateDetailsWithOffset($u, $v, $ts=null)
    {
        $a=self::sanitizeBoundaries($u, $v, $ts);
        $t=strtotime("$a[v] $a[u] ago", $a['ts']);
        return (object) self::_getDateDetails($t);
    }


    //General units converstion
    public static function convertUnitsToLong($u)
    {
        $u=self::sanitizeUnits($u, 'convertUnitsToShort');
        if(!in_array($u, self::$unitMap)) {
            throw new Exception("Invalid units '$u' for convertUnitsToLong");
        }
        $arr=array_flip(self::$unitMap);
        return $arr[$u];
    }
    public static function convertUnitsToShort($u)
    {
        $u=self::sanitizeUnits($u, 'convertUnitsToShort');
        if(!isset(self::$unitMap[$u])) {
            throw new Exception("Invalid units '$u' for convertUnitsToLong");
        }
        return self::$unitMap[$u];
    }


    /* Private properties and methods */
    private static $unitMap=['days'=>'d','weeks'=>'w','months'=>'m','quarters'=>'v','years'=>'y','day'=>'d','week'=>'w','month'=>'m','quarter'=>'v','year'=>'y'];


    private static function sanitizeUnits($u, $function) {
        if(!is_string($u)) {
            throw new Exception("Invalid date units '$u' for $function");
        }
        return strtolower($u);
    }
    private static function validateTimestamp($ts) {
        if($ts && ( (!ctype_digit($ts) && !is_int($ts)) || $ts < ~PHP_INT_MAX || $ts > PHP_INT_MAX ) ) {
            //Doesn't validate whether out of range after applying offsets
            throw new Exception("Invalid timestamp '$ts' for ".debug_backtrace()[1]['function']);
        }
    }
    private static function sanitizeBoundaries($u, $v, $ts) {
        $u=self::sanitizeUnits($u, 'one of the getBoundaries methods');
        if(!ctype_digit($v) && !is_int($v)) {
            throw new Exception("Invalid date value '$v' for ".debug_backtrace()[1]['function']);
        }
        self::validateTimestamp($ts);
        if(in_array($u,['quarter','quarters'])) {
            $u='month';
            $v=3*$v;
        }
        return ['u'=>$u, 'v'=>$v, 'ts'=>$ts?$ts:time()];
    }


    private static function _getDateDetails($t)
    {
        $m=date("n", $t);
        $a=[
            'dayOfWeek' => date("N", $t),   // 1 to 7 where 1 is Monday
            'dayOfMonth'=> date("j", $t),   // 1 to 31
            'day'       => date("z", $t)+1, // 1 to 366
            'week'      => date("W", $t),   // 1 to 52 starting on Monday (ISO-8601)
            'month'     => $m,              // 1 to 12
            'quarter'   => ceil($m/3),      // 1 to 4
            'year'      => date("Y", $t),   // 2017 (or use ISO-8601 "o" for ISO-8601 week to fall in year)
        ];
        return $a;
    }


}
Link to comment
Share on other sites

This is what I ended up doing.  Maybe strtotime() magic could do better, but at least this works.

class DateBoundaries
{


    /* following 5 methods return ['start' => startTimestamp, 'end' => endTimestamp]*/
    public static function getDayBoundaries($u, $v, $ts=null)
    {
        $a=self::sanitizeBoundaries($u, $v, $ts);
        return (object)['start' => strtotime("midnight -$a[v] $a[u]", $a['ts']), 'end' => strtotime("tomorrow -$a[v] $a[u]", $a['ts'])-1];
    }
    public static function getWeekBoundaries($u, $v, $ts=null)
    {
        $a=self::sanitizeBoundaries($u, $v, $ts);
        $t=strtotime("$a[v] $a[u]", $a['ts']);
        $week = date('W',$t);
        $year = date('o',$t);
        $start=strtotime($year.'W'.$week);
        $end=strtotime('+1 week',$start)-1;
        return (object)['start' => $start, 'end' => $end];
    }
    public static function getMonthBoundaries($u, $v, $ts=null)
    {
        $a=self::sanitizeBoundaries($u, $v, $ts);
        $t=strtotime("$a[v] $a[u]", $a['ts']);
        $month = date('n',$t);
        $year = date('o',$t);
        $start=mktime(0, 0, 0, $month, 1, $year);
        $end=strtotime('+1 month',$start)-1;
        return (object)['start' => $start, 'end' => $end];
    }
    public static function getQuarterBoundaries($u, $v, $ts=null)
    {
        $a=self::sanitizeBoundaries($u, $v, $ts);   //Will convert quarters to three months
        $t=strtotime("$a[v] $a[u]", $a['ts']);
        $month = 3*ceil(date('n',$t))-2;
        $year = date('o',$t);
        $start=mktime(0, 0, 0, $month, 1, $year);
        $end=strtotime('+3 month',$start)-1;
        return (object)['start' => $start, 'end' => $end];
    }
    public static function getYearBoundaries($u, $v, $ts=null)
    {
        $a=self::sanitizeBoundaries($u, $v, $ts);
        $year = date('o',strtotime("$a[v] $a[u]", $a['ts']));
        return (object)['start' => mktime(0, 0, 0, 1, 1, $year), 'end' => mktime(0, 0, 0, 1, 1, $year+1)];
    }


    //Following provides date details from timestamp
    public static function getDateDetails($ts)
    {
        self::validateTimestamp($ts);
        return (object) self::_getDateDetails($ts);
    }
    public static function getDateDetailsWithOffset($u, $v, $ts=null)
    {
        $a=self::sanitizeBoundaries($u, $v, $ts);
        $t=strtotime("$a[v] $a[u] ago", $a['ts']);
        return (object) self::_getDateDetails($t);
    }


    //General units converstion
    public static function convertUnitsToLong($u)
    {
        $u=self::sanitizeUnits($u, 'convertUnitsToShort');
        if(!in_array($u, self::$unitMap)) {
            throw new Exception("Invalid units '$u' for convertUnitsToLong");
        }
        $arr=array_flip(self::$unitMap);
        return $arr[$u];
    }
    public static function convertUnitsToShort($u)
    {
        $u=self::sanitizeUnits($u, 'convertUnitsToShort');
        if(!isset(self::$unitMap[$u])) {
            throw new Exception("Invalid units '$u' for convertUnitsToLong");
        }
        return self::$unitMap[$u];
    }


    /* Private properties and methods */
    private static $unitMap=['days'=>'d','weeks'=>'w','months'=>'m','quarters'=>'v','years'=>'y','day'=>'d','week'=>'w','month'=>'m','quarter'=>'v','year'=>'y'];


    private static function sanitizeUnits($u, $function) {
        if(!is_string($u)) {
            throw new Exception("Invalid date units '$u' for $function");
        }
        return strtolower($u);
    }
    private static function validateTimestamp($ts) {
        if($ts && ( (!ctype_digit($ts) && !is_int($ts)) || $ts < ~PHP_INT_MAX || $ts > PHP_INT_MAX ) ) {
            //Doesn't validate whether out of range after applying offsets
            throw new Exception("Invalid timestamp '$ts' for ".debug_backtrace()[1]['function']);
        }
    }
    private static function sanitizeBoundaries($u, $v, $ts) {
        $u=self::sanitizeUnits($u, 'one of the getBoundaries methods');
        if(!ctype_digit($v) && !is_int($v)) {
            throw new Exception("Invalid date value '$v' for ".debug_backtrace()[1]['function']);
        }
        self::validateTimestamp($ts);
        if(in_array($u,['quarter','quarters'])) {
            $u='month';
            $v=3*$v;
        }
        return ['u'=>$u, 'v'=>$v, 'ts'=>$ts?$ts:time()];
    }


    private static function _getDateDetails($t)
    {
        $m=date("n", $t);
        $a=[
            'dayOfWeek' => date("N", $t),   // 1 to 7 where 1 is Monday
            'dayOfMonth'=> date("j", $t),   // 1 to 31
            'day'       => date("z", $t)+1, // 1 to 366
            'week'      => date("W", $t),   // 1 to 52 starting on Monday (ISO-8601)
            'month'     => $m,              // 1 to 12
            'quarter'   => ceil($m/3),      // 1 to 4
            'year'      => date("o", $t),   // 2017 (ISO-8601 for week to fall in year.  Use Y for non-ISO-8601)
        ];
        return $a;
    }


}
Link to comment
Share on other sites

You find the most senseless stuff to post your thoughts about.  I can't believe how many 'things' you have your hands into as evidenced by the number of concurrent posts you initiate.  Do you ever solve one of these riddles you create for yourself?

Link to comment
Share on other sites

You find the most senseless stuff to post your thoughts about.  I can't believe how many 'things' you have your hands into as evidenced by the number of concurrent posts you initiate.  Do you ever solve one of these riddles you create for yourself?

 

And you provide the most useless responses.  I looked back on some of your recent replies and they were zero value.  What part is senseless?  Asking how to best determine the Unix time of a time period's boundaries or whether a static class should or shouldn't be used for this application?   And I obviously solved some of these riddles as the second post on this thread is me posting a solution.

Link to comment
Share on other sites

I think that people are responding to your original question:

 

 


Creating a class to find the start and end time stamps of various periods.  

 

Very hard to understand what you mean here.  Programming has lots of jargon, and what I have found over the years is that the precision of your terminology within the specific domain you are investigating is important.  For example here, you have "start and end ... time stamps".  Then you add "various periods."  It's unclear where you are going.  

 

A "timestamp" has specific meaning within mysql, for example, as it's a column datatype you can use.  Like many things that seem simple on the surface, you can really dig into an implementation and find complexity.

 

Then of course there are unix timestamps.  Depending on what you are trying to do, working with a unix timestamp might be good.   For the most part, I find myself using php Datetime objects if I have the need to deal with datetime like values.  With that said a "time period" can be defined to be within a pair of timestamps.  You could also start with either a begin or end timestamp, and given a "time period"  which would be more precisely understood if you called it a "duration", "interval" or "Date Interval"  which is the specifc class that you use with the php Datetime class to do datetime arithmetic. 

 

The point here is that what you are trying to do -- not to mention "the why" is entirely missing.   

 


strtotime() seems so amazing some of the time but quirky others.  Likely it is mostly user operator but I've read of some gotchas. 

 

Highly vague statement.  If you read something, a link to what you read would help, don't you think?  Otherwise what use is this aside?

 


Think I should use mktime() as I do so in my year boundary class?  Or some other strategy?  Thanks

 

Now we get to your actual question, such as it is.  Then you follow up with a bunch of uncommented code that we are I guess supposed to read and ponder on your behalf.

 


PS.  Should I be using an instance class?  If so, why?

 

What is an "instance class".  Can you provide a definition or link to what you are talking about?  This is not standard terminology for OOP, PHP or any language really.  There are classes which are essentially blueprints for objects, and then there are objects which are sometimes referred to imprecisely as a "class instance", but in no way is "instance class" a phrase that will be clear to anyone or relevant.  It is not an alternative or a direction or a specific implementation.  Are you talking about a "singleton" which is an object oriented design pattern that tends to be used to solve certain problems.  I'm not sure what you are actually asking about, and I doubt anyone else is either.

 

I think it's great that you are intellectually stimulated by programming, and find this a community that is likewise interested in the same topics you are.  There are plenty of people who come here just to try and weasel out a free fix for some php software they can't be bothered to understand.  This community was built with people like you in mind, but what I've noticed in your interactions with people here, is that you would benefit greatly from taking some more time to think about and articulate your questions, and to be a lot more precise with the terminology you employ. 

 

It tends to be that, you might understand PHP OOP classes very well, but in your mind think of them as "cogs" because that works for you.  The problem is, that you can't drop a question here and start asking:  "So my cogs are not swishy the way I like them to be, do I make them more or less swishy"  and not expect to find annoyance, frustration or outright hostility from the people who spend lots of their free time trying to help people like you out.  If you would work on making sure you have clearly articulated and well focused questions, illustrated with self contained code snippets, along with an improved use of appropriate technical terminology, I think you'll find this more effective, and any responses you receive will reflect an appreciation of your efforts and a respect for you as a community member.

Link to comment
Share on other sites

Thank you gizmola for your response.  Looks like my use of the term "period" is not correct and probably should have used "time unit" (or at least according to Wikipedia)
 

The point here is that what you are trying to do -- not to mention "the why" is entirely missing.   

I am trying to retrieve the unix time on the boundaries of several traditional time units (day, week, month, quarter, and year) a user provided duration in the past.  For example, what was the unix time for the start and end of the week 1 year ago?  Of course, will need definition of a boundary, and will use the most commonly used. The values are needed to provide to an API.

 

 

Highly vague statement.  If you read something, a link to what you read would help, don't you think?  Otherwise what use is this aside?

I agree it was vague.  Most what I read were non-authoritative comments in various posts.  There is http://www.hashbangcode.com/blog/how-i-learned-stop-using-strtotime-and-love-php-datetime, but I can't say I really read it.  When I was using strtotime(), I sometimes received results other than desired.  As said, likely operator (i.e. my) error.  I think I will use datetime as you described. 

 

What is an "instance class".  Can you provide a definition or link to what you are talking about?

I was referring to a normal class, but calling it a "normal" class didn't seem right.  https://en.wikipedia.org/wiki/Instance_(computer_science)
 
I do very much appreciate your response, and will take steps to more clearly describe the question.  I won't, however, completely abandon hypothetical questions :)
Link to comment
Share on other sites

The problem with unix time is that it is missing a very important component:  timezone.  Timestamps are based on UTC.  This is another of the many reasons that the DateTime class exists and already solves all the problems you are encountering.

 

It already has the ability to convert a datetime value between timezones, as well as providing support for derivation of dates based on a wide variety of interval strings.  In essence, while not perfect, it already does 95% I would guess of everything you are trying to do, and would be a good basis for whatever the other 5% might be.

Link to comment
Share on other sites

Archived

This topic is now archived and is closed to further replies.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.