I want to count "hours into a month" in php - php

The following code helps me project data from a database onto our internal adminsite for the current day.
Since we're in multiple time zones, we use UTC time for basic time function, but since our office is in California, we want the date to actually be = ($date)'hours' - 8, because PST is 8 hours behind.
We use the following logic to show the "previous day" if it's "a day ahead" UTC time but the same day our time, which works great, however, on the last day of the month at 4 PM, all of our data is hidden.
<?php
$date = getDate();
if ($date['hours'] < 8) {
$dateDay = $date['mday']-1;
} else {
$dateDay = $date['mday'];
}
$dateMonth = $date['mon'];
// last day of month
// $dateMonth = $date['mon'] - 1;
$dateYear = $date['year'];
$dateTime = $dateYear . "-" . $dateMonth . '-' . $dateDay . ' 08:00:00';
So, this 'if' function works great to show the true "day." What it says is, if the UTC hour < 8, then it's actually yesterday, as 3 AM UTC time is actually 7 PM the day before PST.
What I was wondering is if there's a way to track "month hours", so I could use the same 'if' function that reads "if we're less than 8 UTC hours into the month, it's actually still last month."
That way it'll work regardless of whether the month has 28, 30 , or 31 days. Does such logic exist?

I think its the best way to deal with it :
<?php
$date = new DateTime('2016-08-08', new DateTimeZone('UTC'));
echo $date->format('Y-m-d H:i:sP') . "\n";
$date->setTimezone(new DateTimeZone('America/Los_Angeles'));
echo $date->format('Y-m-d H:i:sP') . "\n";
?>

You can use DateTime::sub or DateTime::modify method which will keep correct month for you:
$now = new DateTime(getDay()); // supposing getDate() returns something that can be parsed with DateTime constructor
// Unless using DateTimeImmutable, cloning is needed to prevent altering the original object
$min = clone ($now);
$min->modify('-8 hours');
// alternatively $min->sub(new DateInterval('PT8H'));
if ($now->format('m') !== $min->format('m')) {
// month differs
}

Related

PHP | Calculate remaining days but taking leap years into account

small problem to calculate the remaining days of the month starting today just I write:
$today = date('Y/m/d');
$timestamp = strtotime($today);
$daysRemaining = (int)date('t', $timestamp) - (int)date('j', $timestamp);
echo $daysRemaining;
and I get the remaining days
to do a test I entered a static date for the month of February
$timestamp = strtotime('2020-02-01');
$daysRemaining = (int)date('t', $timestamp) - (int)date('j', $timestamp);
echo $daysRemaining;
the question is here how do I calculate the remaining days in the month taking into account leap years, for example in February 2020 it will have 29 days and in this way I get out of it that remain 28
Stop using the functions and start using the DateTime class!!!
This code should explain itself.
<?php
$x = new DateTime('2020-02-17'); // create your date
$y = clone $x; // copy the date
$y->modify('last day of this month'); // alter the copy to the last day
echo $x->format('d') . "\n"; // show the day of the first date
echo $y->format('d') . "\n"; // show the day of the second date
echo $y->format('d') - $x->format('d'); // show the difference between the two
Output:
17
29
12
Check it here https://3v4l.org/8ZhOb
Check the DateTime class docs here https://www.php.net/manual/en/class.datetime.php

Getting first day of the month, is outputting the wrong first day of the month

I want to display the first day of the month in 'day of the week' format.
For example, The below code should select the month of August and say the day of the week 01 falls on which is Thursday, but for some reason, the below code outputs Friday which is wrong, on some months such as May it correctly says Wednesday.
$monthNumber = 3;
$base = strtotime(date('Y-m',time()) . '-01 00:00:01');
$dateTest = date("Ym" . 1, strtotime($monthNumber . " month", $base));
$unixTimestamp = strtotime($dateTest, $base);
echo date("l", $unixTimestamp);
Does anyone have any ideas to make it show the correct day?
$base fixes a bug with it not showing the correct month.
And why won't you use the dateTime?
echo (new \DateTime('first day of august 2019'))->format('l');
Read more about Relative formats.
You have to use date() at first:
$base = date('Y-m',time()) . '-01 00:00:00';
And dont have to set the first second :)
To get the day of the week falling on the first day of any month you can use the prefix first day of e.g.
echo (new \DateTime('first day of August'))->format('l');
This will show correctly Thursday for August 2019.
If you want to do it dynamically using numbers for months, you can create a dummy date like you were trying to do.
$timeString = sprintf('01.%02d.%04d', 8, 2019); // replace the numbers with your variables
echo (new \DateTime($timeString))->format('l');

Take current datetime and make 2 datetime variables? (6AM - 5:59AM)

I'm trying to make an events page and I want to create 2 datetime variables. They would take the current time, and create one variable at 06:00 am, and one at 05:59 am.
The issue I'm having though is with the calculations.
If a person is visiting the page on March 17, 11PM - then var1 would be March 17 06:00AM, and var 2 March 18 05:59AM.
However if a person is viewing the page on March 18 01:00 AM, then var 1 would still be March 17 06:00AM, the same goes for var2.
How would I take the below $date variable, and do the calculations for the other 2 variables?
date_default_timezone_set('America/New_York');
$date = date('Y-m-d H:i:s', time());
You can simply query the current hour to see if it's less than 6; if it is, then the start of the current logical day (based on your rules) was yesterday, 6am; otherwise it was today, 6am. Given this, strtotime can trivially get you the "start" time and adding a day to that gives you the "end" time.
date_default_timezone_set('America/New_York');
$currentHour = date('H');
if ($currentHour < 6) {
// logical day started yesterday
$start = strtotime('yesterday 06:00');
$end = strtotime('today 05:59:59');
}
else {
// logical day started today
$start = strtotime('today 06:00');
$end = strtotime('tomorrow 05:59:59');
}
echo "The current logical day started on ".date('Y-m-d H:i:s', $start);
echo " and it ends on ".date('Y-m-d H:i:s', $end);
The method for chopping the date up could be improved, but the principle works...
<?php
date_default_timezone_set('America/New_York');
$date = date('Y-m-d H:i:s', time());
// get adjusted date which subtracts 6 hours
$date_adjusted = date('Y-m-d H:i:s', time() - 60 * 60 * 6);
// chop off the time (so we are always left with the correct date now)
$date_adjusted_date = preg_split("/ /",$date_adjusted);
// Add the time element (in this case 6 AM)
$correct_date = date('Y-m-d H:i:s', strtotime($date_adjusted_date[0]."T06:00:00"));
// check the result
echo $correct_date;
?>

PHP DateTime::modify adding and subtracting months

I've been working a lot with the DateTime class and recently ran into what I thought was a bug when adding months. After a bit of research, it appears that it wasn't a bug, but instead working as intended. According to the documentation found here:
Example #2 Beware when adding or
subtracting months
<?php
$date = new DateTime('2000-12-31');
$date->modify('+1 month');
echo $date->format('Y-m-d') . "\n";
$date->modify('+1 month');
echo $date->format('Y-m-d') . "\n";
?>
The above example will output:
2001-01-31
2001-03-03
Can anyone justify why this isn't considered a bug?
Furthermore does anyone have any elegant solutions to correct the issue and make it so +1 month will work as expected instead of as intended?
Why it's not a bug:
The current behavior is correct. The following happens internally:
+1 month increases the month number (originally 1) by one. This makes the date 2010-02-31.
The second month (February) only has 28 days in 2010, so PHP auto-corrects this by just continuing to count days from February 1st. You then end up at March 3rd.
How to get what you want:
To get what you want is by: manually checking the next month. Then add the number of days next month has.
I hope you can yourself code this. I am just giving what-to-do.
PHP 5.3 way:
To obtain the correct behavior, you can use one of the PHP 5.3's new functionality that introduces the relative time stanza first day of. This stanza can be used in combination with next month, fifth month or +8 months to go to the first day of the specified month. Instead of +1 month from what you're doing, you can use this code to get the first day of next month like this:
<?php
$d = new DateTime( '2010-01-31' );
$d->modify( 'first day of next month' );
echo $d->format( 'F' ), "\n";
?>
This script will correctly output February. The following things happen when PHP processes this first day of next month stanza:
next month increases the month number (originally 1) by one. This makes the date 2010-02-31.
first day of sets the day number to 1, resulting in the date 2010-02-01.
Here is another compact solution entirely using DateTime methods, modifying the object in-place without creating clones.
$dt = new DateTime('2012-01-31');
echo $dt->format('Y-m-d'), PHP_EOL;
$day = $dt->format('j');
$dt->modify('first day of +1 month');
$dt->modify('+' . (min($day, $dt->format('t')) - 1) . ' days');
echo $dt->format('Y-m-d'), PHP_EOL;
It outputs:
2012-01-31
2012-02-29
This may be useful:
echo Date("Y-m-d", strtotime("2013-01-01 +1 Month -1 Day"));
// 2013-01-31
echo Date("Y-m-d", strtotime("2013-02-01 +1 Month -1 Day"));
// 2013-02-28
echo Date("Y-m-d", strtotime("2013-03-01 +1 Month -1 Day"));
// 2013-03-31
echo Date("Y-m-d", strtotime("2013-04-01 +1 Month -1 Day"));
// 2013-04-30
echo Date("Y-m-d", strtotime("2013-05-01 +1 Month -1 Day"));
// 2013-05-31
echo Date("Y-m-d", strtotime("2013-06-01 +1 Month -1 Day"));
// 2013-06-30
echo Date("Y-m-d", strtotime("2013-07-01 +1 Month -1 Day"));
// 2013-07-31
echo Date("Y-m-d", strtotime("2013-08-01 +1 Month -1 Day"));
// 2013-08-31
echo Date("Y-m-d", strtotime("2013-09-01 +1 Month -1 Day"));
// 2013-09-30
echo Date("Y-m-d", strtotime("2013-10-01 +1 Month -1 Day"));
// 2013-10-31
echo Date("Y-m-d", strtotime("2013-11-01 +1 Month -1 Day"));
// 2013-11-30
echo Date("Y-m-d", strtotime("2013-12-01 +1 Month -1 Day"));
// 2013-12-31
My solution to the problem:
$startDate = new \DateTime( '2015-08-30' );
$endDate = clone $startDate;
$billing_count = '6';
$billing_unit = 'm';
$endDate->add( new \DateInterval( 'P' . $billing_count . strtoupper( $billing_unit ) ) );
if ( intval( $endDate->format( 'n' ) ) > ( intval( $startDate->format( 'n' ) ) + intval( $billing_count ) ) % 12 )
{
if ( intval( $startDate->format( 'n' ) ) + intval( $billing_count ) != 12 )
{
$endDate->modify( 'last day of -1 month' );
}
}
I agree with the sentiment of the OP that this is counter-intuitive and frustrating, but so is determining what +1 month means in the scenarios where this occurs. Consider these examples:
You start with 2015-01-31 and want to add a month 6 times to get a scheduling cycle for sending an email newsletter. With the OP's initial expectations in mind, this would return:
2015-01-31
2015-02-28
2015-03-31
2015-04-30
2015-05-31
2015-06-30
Right away, notice that we are expecting +1 month to mean last day of month or, alternatively, to add 1 month per iteration but always in reference to the start point. Instead of interpreting this as "last day of month" we could read it as "31st day of next month or last available within that month". This means that we jump from April 30th to May 31st instead of to May 30th. Note that this is not because it is "last day of month" but because we want "closest available to date of start month."
So suppose one of our users subscribes to another newsletter to start on 2015-01-30. What is the intuitive date for +1 month? One interpretation would be "30th day of next month or closest available" which would return:
2015-01-30
2015-02-28
2015-03-30
2015-04-30
2015-05-30
2015-06-30
This would be fine except when our user gets both newsletters on the same day. Let's assume that this is a supply-side issue instead of demand-side We're not worried that the user will be annoyed with getting 2 newsletters in the same day but instead that our mail servers can't afford the bandwidth for sending twice as many newsletters. With that in mind, we return to the other interpretation of "+1 month" as "send on the second to last day of each month" which would return:
2015-01-30
2015-02-27
2015-03-30
2015-04-29
2015-05-30
2015-06-29
Now we've avoided any overlap with the first set, but we also end up with April and June 29th, which certainly does match our original intuitions that +1 month simply should return m/$d/Y or the attractive and simple m/30/Y for all possible months. So now let's consider a third interpretation of +1 month using both dates:
Jan. 31st
2015-01-31
2015-03-03
2015-03-31
2015-05-01
2015-05-31
2015-07-01
Jan. 30th
2015-01-30
2015-03-02
2015-03-30
2015-04-30
2015-05-30
2015-06-30
The above has some issues. February is skipped, which could be a problem both supply-end (say if there is a monthly bandwidth allocation and Feb goes to waste and March gets doubled up on) and demand-end (users feel cheated out of Feb and perceive the extra March as attempt to correct mistake). On the other hand, notice that the two date sets:
never overlap
are always on the same date when that month has the date (so the Jan. 30 set looks pretty clean)
are all within 3 days (1 day in most cases) of what might be considered the "correct" date.
are all at least 28 days (a lunar month) from their successor and predecessor, so very evenly distributed.
Given the last two sets, it would not be difficult to simply roll back one of the dates if it falls outside of the actual following month (so roll back to Feb 28th and April 30th in the first set) and not lose any sleep over the occasional overlap and divergence from the "last day of month" vs "second to last day of month" pattern. But expecting the library to choose between "most pretty/natural", "mathematical interpretation of 02/31 and other month overflows", and "relative to first of month or last month" is always going to end with someone's expectations not being met and some schedule needing to adjust the "wrong" date to avoid the real-world problem that the "wrong" interpretation introduces.
So again, while I also would expect +1 month to return a date that actually is in the following month, it is not as simple as intuition and given the choices, going with math over the expectations of web developers is probably the safe choice.
Here's an alternative solution that is still as clunky as any but I think has nice results:
foreach(range(0,5) as $count) {
$new_date = clone $date;
$new_date->modify("+$count month");
$expected_month = $count + 1;
$actual_month = $new_date->format("m");
if($expected_month != $actual_month) {
$new_date = clone $date;
$new_date->modify("+". ($count - 1) . " month");
$new_date->modify("+4 weeks");
}
echo "* " . nl2br($new_date->format("Y-m-d") . PHP_EOL);
}
It's not optimal but the underlying logic is : If adding 1 month results in a date other than the expected next month, scrap that date and add 4 weeks instead. Here are the results with the two test dates:
Jan. 31st
2015-01-31
2015-02-28
2015-03-31
2015-04-28
2015-05-31
2015-06-28
Jan. 30th
2015-01-30
2015-02-27
2015-03-30
2015-04-30
2015-05-30
2015-06-30
(My code is a mess and wouldn't work in a multi-year scenario. I welcome anyone to rewrite the solution with more elegant code so long as the underlying premise is kept intact, i.e. if +1 month returns a funky date, use +4 weeks instead.)
In conjunction with shamittomar's answer, it could then be this for adding months "safely":
/**
* Adds months without jumping over last days of months
*
* #param \DateTime $date
* #param int $monthsToAdd
* #return \DateTime
*/
public function addMonths($date, $monthsToAdd) {
$tmpDate = clone $date;
$tmpDate->modify('first day of +'.(int) $monthsToAdd.' month');
if($date->format('j') > $tmpDate->format('t')) {
$daysToAdd = $tmpDate->format('t') - 1;
}else{
$daysToAdd = $date->format('j') - 1;
}
$tmpDate->modify('+ '. $daysToAdd .' days');
return $tmpDate;
}
I made a function that returns a DateInterval to make sure that adding a month shows the next month, and removes the days into the after that.
$time = new DateTime('2014-01-31');
echo $time->format('d-m-Y H:i') . '<br/>';
$time->add( add_months(1, $time));
echo $time->format('d-m-Y H:i') . '<br/>';
function add_months( $months, \DateTime $object ) {
$next = new DateTime($object->format('d-m-Y H:i:s'));
$next->modify('last day of +'.$months.' month');
if( $object->format('d') > $next->format('d') ) {
return $object->diff($next);
} else {
return new DateInterval('P'.$months.'M');
}
}
This is an improved version of Kasihasi's answer in a related question. This will correctly add or subtract an arbitrary number of months to a date.
public static function addMonths($monthToAdd, $date) {
$d1 = new DateTime($date);
$year = $d1->format('Y');
$month = $d1->format('n');
$day = $d1->format('d');
if ($monthToAdd > 0) {
$year += floor($monthToAdd/12);
} else {
$year += ceil($monthToAdd/12);
}
$monthToAdd = $monthToAdd%12;
$month += $monthToAdd;
if($month > 12) {
$year ++;
$month -= 12;
} elseif ($month < 1 ) {
$year --;
$month += 12;
}
if(!checkdate($month, $day, $year)) {
$d2 = DateTime::createFromFormat('Y-n-j', $year.'-'.$month.'-1');
$d2->modify('last day of');
}else {
$d2 = DateTime::createFromFormat('Y-n-d', $year.'-'.$month.'-'.$day);
}
return $d2->format('Y-m-d');
}
For example:
addMonths(-25, '2017-03-31')
will output:
'2015-02-28'
I found a shorter way around it using the following code:
$datetime = new DateTime("2014-01-31");
$month = $datetime->format('n'); //without zeroes
$day = $datetime->format('j'); //without zeroes
if($day == 31){
$datetime->modify('last day of next month');
}else if($day == 29 || $day == 30){
if($month == 1){
$datetime->modify('last day of next month');
}else{
$datetime->modify('+1 month');
}
}else{
$datetime->modify('+1 month');
}
echo $datetime->format('Y-m-d H:i:s');
Here is an implementation of an improved version of Juhana's answer in a related question:
<?php
function sameDateNextMonth(DateTime $createdDate, DateTime $currentDate) {
$addMon = clone $currentDate;
$addMon->add(new DateInterval("P1M"));
$nextMon = clone $currentDate;
$nextMon->modify("last day of next month");
if ($addMon->format("n") == $nextMon->format("n")) {
$recurDay = $createdDate->format("j");
$daysInMon = $addMon->format("t");
$currentDay = $currentDate->format("j");
if ($recurDay > $currentDay && $recurDay <= $daysInMon) {
$addMon->setDate($addMon->format("Y"), $addMon->format("n"), $recurDay);
}
return $addMon;
} else {
return $nextMon;
}
}
This version takes $createdDate under the presumption that you are dealing with a recurring monthly period, such as a subscription, that started on a specific date, such as the 31st. It always takes $createdDate so late "recurs on" dates won't shift to lower values as they are pushed forward thru lesser-valued months (e.g., so all 29th, 30th or 31st recur dates won't eventually get stuck on the 28th after passing thru a non-leap-year February).
Here is some driver code to test the algorithm:
$createdDate = new DateTime("2015-03-31");
echo "created date = " . $createdDate->format("Y-m-d") . PHP_EOL;
$next = sameDateNextMonth($createdDate, $createdDate);
echo " next date = " . $next->format("Y-m-d") . PHP_EOL;
foreach(range(1, 12) as $i) {
$next = sameDateNextMonth($createdDate, $next);
echo " next date = " . $next->format("Y-m-d") . PHP_EOL;
}
Which outputs:
created date = 2015-03-31
next date = 2015-04-30
next date = 2015-05-31
next date = 2015-06-30
next date = 2015-07-31
next date = 2015-08-31
next date = 2015-09-30
next date = 2015-10-31
next date = 2015-11-30
next date = 2015-12-31
next date = 2016-01-31
next date = 2016-02-29
next date = 2016-03-31
next date = 2016-04-30
$ds = new DateTime();
$ds->modify('+1 month');
$ds->modify('first day of this month');
If you just want to avoid skipping a month you can perform something like this to get the date out and run a loop on the next month reducing the date by one and rechecking until a valid date where $starting_calculated is a valid string for strtotime (i.e. mysql datetime or "now"). This finds the very end of the month at 1 minute to midnight instead of skipping the month.
$start_dt = $starting_calculated;
$next_month = date("m",strtotime("+1 month",strtotime($start_dt)));
$next_month_year = date("Y",strtotime("+1 month",strtotime($start_dt)));
$date_of_month = date("d",$starting_calculated);
if($date_of_month>28){
$check_date = false;
while(!$check_date){
$check_date = checkdate($next_month,$date_of_month,$next_month_year);
$date_of_month--;
}
$date_of_month++;
$next_d = $date_of_month;
}else{
$next_d = "d";
}
$end_dt = date("Y-m-$next_d 23:59:59",strtotime("+1 month"));
Extension for DateTime class which solves problem of adding or subtracting months
https://gist.github.com/66Ton99/60571ee49bf1906aaa1c
If using strtotime() just use $date = strtotime('first day of +1 month');
I needed to get a date for 'this month last year' and it becomes unpleasant quite quickly when this month is February in a leap year. However, I believe this works... :-/ The trick seems to be to base your change on the 1st day of the month.
$this_month_last_year_end = new \DateTime();
$this_month_last_year_end->modify('first day of this month');
$this_month_last_year_end->modify('-1 year');
$this_month_last_year_end->modify('last day of this month');
$this_month_last_year_end->setTime(23, 59, 59);
$month = 1; $year = 2017;
echo date('n', mktime(0, 0, 0, $month + 2, -1, $year));
will output 2 (february). will work for other months too.
$current_date = new DateTime('now');
$after_3_months = $current_date->add(\DateInterval::createFromDateString('+3 months'));
For days:
$after_3_days = $current_date->add(\DateInterval::createFromDateString('+3 days'));
Important:
The method add() of DateTime class modify the object value so after calling add() on a DateTime Object it returns the new date object and also it modify the object it self.
you can actually do it with just date() and strtotime() as well. For example to add 1 month to todays date:
date("Y-m-d",strtotime("+1 month",time()));
if you are wanting to use the datetime class thats fine too but this is just as easy. more details here
$date = date('Y-m-d', strtotime("+1 month"));
echo $date;

how to get the date of last week's (tuesday or any other day) in php?

I think its possible but i cant come up with the right algorithm for it.
What i wanted to do was:
If today is monday feb 2 2009, how would i know the date of last week's tuesday? Using that same code 2 days after, i would find the same date of last week's tuesday with the current date being wednesday, feb 4 2009.
Most of these answers are either too much, or technically incorrect because "last Tuesday" doesn't necessarily mean the Tuesday from last week, it just means the previous Tuesday, which could be within the same week of "now".
The correct answer is:
strtotime('tuesday last week')
I know there is an accepted answer already, but imho it does not meet the second requirement that was asked for. In the above case, strtotime would yield yesterday if used on a wednesday. So, just to be exact you would still need to check for this:
$tuesday = strtotime('last Tuesday');
// check if we need to go back in time one more week
$tuesday = date('W', $tuesday)==date('W') ? $tuesday-7*86400 : $tuesday;
As davil pointed out in his comment, this was kind of a quick-shot of mine. The above calculation will be off by one once a year due to daylight saving time. The good-enough solution would be:
$tuesday = date('W', $tuesday)==date('W') ? $tuesday-7*86400+7200 : $tuesday;
If you need the time to be 0:00h, you'll need some extra effort of course.
PHP actually makes this really easy:
echo strtotime('last Tuesday');
See the strtotime documentation.
Working solution:
$z = date("Y-m-d", strtotime("last Saturday"));
$z = (date('W', strtotime($z)) == date('W')) ? (strtotime($z)-7*86400+7200) : strtotime($z);
print date("Y-m-d", $z);
you forgot strtotime for second argument of date('W', $tuesday)
hmm.
convert $tuesday to timestamp before "$tuesday-7*86400+7200"
mde.
// test: find last date for each day of the week
foreach (array('Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun') as $day) {
print $day . " => " . date('m/d/Y', last_dayofweek($day)) . "\n";
}
function last_dayofweek($day)
{
// return timestamp of last Monday...Friday
// will return today if today is the requested weekday
$day = strtolower(substr($day, 0, 3));
if (strtolower(date('D')) == $day)
return strtotime("today");
else
return strtotime("last {$day}");
}
<?php
$currentDay = date('D');
echo "Today-".$today = date("Y-m-d");
echo "Yesterday-".$yesterday = date("Y-m-d",strtotime('yesterday'));
echo "Same day last week-".$same_day_last_week = date("Y-m-d",strtotime('last '.$currentDay));
?>
Do not use manual calculation, use DateTime object instead. It has proper implementation, takes into account leap years, yeap seconds, etc.
$today = new \DateTime();
$today->modify('tuesday last week');
The modify method modifies the date relative to it's state, so if you set the date to a different date, it calculates it relative to it.
$date = new \DateTime('2020-01-01');
echo $date->format('Y-m-d'); // 2020-01-01
$date->modify('tuesday last week');
echo $date->format('Y-m-d'); // 2019-12-24

Categories