I am kind of stuck on figuring out how to do this.
In my array that I am looping over, it has multiple arrays with the following fields year and quarter. Year is in numeric form like 2012 and quarter is in text form like Q1 or Q2
Now I am given a range for example
from: Q1 2013 to: Q3 2014
I have access to the numeric form of the quarters in the range.
So this is the variables I have from the example above: $quarter_from, $quarter_to, $year_from, and $year_to
I need to figure out if the current array in the loop that has the fields year and quarter is in the range of the range given.
How do I go about checking? Sure I can do a simple numeric check for years but quarters is a whole another story. For example the current array could have Q4 and 2013 and that's obviously in the range of the example, and the first case would pass by doing a year check. But for quarter, how do I check that?
I think you can do a concatenate of the quarter and year like this, assuming that the range that is selected logically makes sense (to comes later than from with respect to time).
from: 20122 => year 2012 and quarter 2
to: 20141 => year 2014 and quarter 1
So even though your quarter's value is in to is less than the quarter's value in from you still get a higher value in to because of the year.
This way you can now do a range check like this, by concatenating your year and quarter fields (do a substr() to get the second character of the string and convert to number then concatenate).
For example if 2013, Q4 shows up.
That looks like 20134 then do a range check like this
20122 <= 20134 <= 20141 which should work.
Wrote this in a hurry, it's more of a phsudo code but should give you some idea:
From: Q2 2013
Middle: Q1 2014 (checking if in range of From and To)
To: Q1 2014
Now we need to compare the years of from and middle and middle and to, and if the year is the same in either case you can split the quarter to get the number of the quarter and compare the quarters to see if the middle part is actually in the range.
fromQuarter = from.quarter.split("")[1] // Q2 => 2
middleQuarter = middle.quarter.split("")[1] // Q1 => 1
toQuarter = to.quarter.split("")[1] // Q1 => 1
If to.year >= middle.year && middle.year >= from.year
// Year range legit.
if(to.year == middle.year || from.year == middle.year)
// We need to check the quarters.
if(to.year == middle.year)
// Need to compare the quarter of the to year and the middle year.
if(toQuarter >= middleQuarter )
// In range.
else
// Out of range.
if(from.year == middle.year)
// Need to compare the quarter of the from year and the middle year.
if(fromQuarter <= middleQuarter)
// In range.
else
// Out of range.
else
// Between two distinct years, in range.
else
// Not in range.
I'll check this again soon to see if I made an logic error.
Try this:
function expand($quarter, $year) {
return $year * 4 + $quarter;
}
$quarter_from = 1; $year_from = 2013;
$quarter_to = 3; $year_to = 2014;
$quarter = 3; $year = 2014;
$n1 = expand($quarter_from, $year_from);
$n2 = expand($quarter_to, $year_to);
$n = expand($quarter, $year);
$is_in_range = $n >= $n1 && $n <= $n2;
Related
CONTEXT: My client, a local movie theater, runs a Sunday Matinee Special every other Sunday starting with the SECOND SUNDAY every year. So for this year the dates are 1/11, 1/18, 2/8, 2/22, .... [The only exception is the SUNDAY after their film festival which runs the the THIRD FULL WEEK OF OCTOBER, but automating this single exception is a "would-be-nice" item, not essential.]
MY SKILL LEVEL: BEGINNER (I need your help!) I believe I need to use a combination of mktime() and date() but I don't know how to put it together.
WHAT I'VE TRIED: I suspect the answer is a combination of what I see on these three posts:
(1) a do-while loop to get a specific day of the week from a date range
(2) there may be a shortcut for referencing the second sunday in the ACCEPTED ANSWER here, but I'm not sure this helps
(3) MOST RELEVANT(?): Get the First Sunday of Every Month
END RESULT: I want to display the [Month] and [Day] of the next Sunday Matinee (so I want to find and display the first item in the array AFTER the current date). The text will appear like this: "Next: [Month] [Day]"
Make sense? Let me know if I've forgotten anything.
If it's not too much to ask, please explain your code so I (and others?) can learn from this; but I'd be more than grateful for "merely" a straight-up solution.
Many thanks.
Debra
UPDATE/PROGRESS: This code will get me the array of Sundays:
$startDate = strtotime("second Sunday of ".date('Y')."");
for ($i=0; $i < 27; $i++){
$sundays = date('F j', ($startDate + (($i*14) * 24 * 3600))) . '<br>';
print $sundays;
}
NEXT TO FIGURE OUT: write a statement to find in the array of Sundays the first date after the current date.
This is a pretty manual, procedural solution, but it should work.
<?php
$SECS_PER_DAY = 86400;
# Find the first Sunday on or after a given timestamp
function firstSundayOnOrAfter($time) {
global $SECS_PER_DAY;
# What day of the week is the given date?
$wday = date('w', $time);
if ($wday == 0) {
# it's already a Sunday
return $time;
}
return $time + (7 - $wday) * $SECS_PER_DAY;
}
# return an array of timestamps representing
# (noon on) the special matinee Sundays for the given year
function specialMatineeSundays($year) {
global $SECS_PER_DAY;
# When's the film festival?
$oct1 = mktime(12,0,0,10,1,$year);
$festivalStart = firstSundayOnOrAfter($oct1);
$festivalSkip = $festivalStart + 7 * $SECS_PER_DAY;
# find the first Sunday of the year
$jan1 = mktime(12,0,0,1,1,$year);
$sunday = firstSundayOnOrAfter($jan1);
# Add a week to start with the second Sunday
$sunday += 7 * $SECS_PER_DAY;
# Build up our result list
$result = [];
# As long as the Sunday under examination is still the same year,
# add it to the list (unless it's the post-festival skip date)
# and then add two weeks
while (date('Y',$sunday) == $year) {
if ($sunday != $festivalSkip) {
$result[] = $sunday;
}
$sunday += 14 * $SECS_PER_DAY;
}
return $result;
}
I am trying to come up with the most efficient and best way to accomplish this somewhat of a complex situation. I know that I could build this solution using probably around 5 if else statements, maybe more - however there must be a better way to accomplish what I want to.
So here's what I am trying to do. I have an events page on my website, and what I want to do is display the dates in a minimalistic way when possible. What I mean is the following:
Say I have 3 dates: May 5, May 6, May 7. I want to display it as: May 5 - 7.
However, there will be situations where the dates may be: May 5, May 7. In this case I would like to display it as: May 5 & 7.
However, there may also be situations where the dates may be: May 25, June 2. In this case I would like to display it as: May 25 & June 2.
However! There also may be situations where the dates may be: May 25, May 26, June 2. In this case it should display as: May 25 - 26 & June 2
Of course, there could just be a single date as well. But one other thing, it could be possible that there could be more than 3 dates as well, so it would be nice if it could work regardless of how many dates there are (IE loop through an array).
I know that we are suppose to make an attempt and show some code to debug, however I don't even know where to start with this, if this is too much for someone to put together - just giving me an idea of how to do something like this efficiently would be a huge help.
Thanks
//input data: sorted list of dates
$dates = array('May 5','May 6','May 7','May 30','Jun 2','Jun 3','Dec 11','Dec 12','Dec 14');
array_push($dates,false); //add an extra value so the last range gets printed
//initialize min & previous date as first date
$min_date = array_shift($dates);
$prev_date = $min_date;
$counter = 0; //keep count of # of days between min and max
$formatted_dates = array();
foreach($dates as $date) {
//if the difference in number of days between current date and min date
//is greater than counted number of days then we capture the current range
//and start a new one by resetting $min_date to $date and $counter to 0
if(!$date || ($counter + 1) < diff_in_days($min_date,$date)) {
if($counter == 0) { //format for 1 date
$formatted_dates[] = $min_date;
}
elseif($counter == 1) { //format for 2 dates
$formatted_dates[] = "$min_date & $prev_date";
}
elseif($counter > 1) { //format for > 2 dates
$formatted_dates[] = "$min_date - $prev_date";
}
$counter = 0;
$min_date = $date;
}
else {
$counter++;
}
$prev_date = $date;
}
//may also want to verify that neither formatted date contains an '&'
//so you don't end up with "May 11 & May 12 & June 1 & June 2" which may be confusing
if(count($formatted_dates) == 2) {
print implode(' & ',$formatted_dates);
}
else {
print implode("\n",$formatted_dates);
}
function diff_in_days($day1,$day2) {
$datetime1 = new DateTime($day1);
$datetime2 = new DateTime($day2);
$interval = $datetime1->diff($datetime2);
$ret = (int) $interval->format('%a');
return $ret;
}
Output
May 5 - May 7
May 30
Jun 2 & Jun 3
Dec 11 & Dec 12
Dec 14
My dilemma is that if I request more than 6 months or so ( I do not know the approximate number ) from my webservices ( which gets called via JS ), I get nothing back. In other words, I have to limit it to 6 months.
So let's consider this scenario:
$a = strtotime('June 3, 2011');
$b = strtotime('June 3, 2012');
I need to split this up by 6 months in order to make 2 distinct web servicerequests so that each call requests 6 months.
So with $a and $b, I need to split these up into as many date ranges as possible when taking the amount of months total divided by 6.
The first date range I need is from June 1, 2011 to November 31, 2011. The second date range I need is from December 1, 2011 to July 1, 2012.
The idea I had in mind was finding the number of months, then if it was greater than the limit variable 6, do a loop and increment the initial date by 6 months per loop.
Pseudo-code ( I actually initially wrote it in JS but figured it'd be easier to do in PHP because I wouldn't have to deal with multiple asynchronous request behaviour ):
var numMonths = monthDiff ( a, b ), ret = [], limit = 6, loopLimit = Math.ceil( numMonths / limit ), ranges = [];
if ( numMonths > limit ) {
for ( var i = 0; i<loopLimit; i++ ) {
var start = new Date(b);
var end = new Date ( b.setMonth( b.getMonth() + limit ) );
ranges.push( start, end );
}
}
Does anyone know of a succinct way of doing this? Can anyone spot any programmatic flaws in this?
Try:
$a = strtotime("June 3, 2011 00:00:00Z");
$b = strtotime("June 3, 2012 00:00:00Z");
fetchAll($a,$b);
function fetchAll($a,$b) {
$fetchLimit = "6 months"; // or, say, "180 days"; a string
if ($b <= strtotime(gmdate("Y-m-d H:i:s\Z",$a)." +".$fetchLimit)) {
// it fits in one chunk
fetchChunk($a,$b);
}
else { // chunkify it!
$lowerBound = $a;
$upperBound = strtotime(gmdate("Y-m-d H:i:s\Z",$a)." +".$fetchLimit);
while ($upperBound < $b) { // fetch full chunks while there're some left
fetchChunk($lowerBound,$upperBound);
$lowerBound = $upperBound;
$upperBound = strtotime(gmdate("Y-m-d H:i:s\Z",$lowerBound)." +".$fetchLimit);
}
fetchChunk($lowerBound,$b); // get last (likely) partial chunk
}
}
function fetchChunk($a,$b) {
/* insert your function that actually grabs the partial data */
//
// for test, just display the chunk range:
echo gmdate("Y-m-d H:i:s\Z",$a)." to ".gmdate("Y-m-d H:i:s\Z",$b)."<br>";
}
...where $fetchLimit in fetchAll() is any duration string parseable by strtotime(). You could then keep appending the output of each fetchChunk() to an initially blank variable which is later returned by fetchAll().
This example fetches two six-month "chunks", as expected. Changing $b to one day later adds a third chunk containing only that extra day:
2011-06-03 00:00:00Z to 2011-12-03 00:00:00Z
2011-12-03 00:00:00Z to 2012-06-03 00:00:00Z
2012-06-03 00:00:00Z to 2012-06-04 00:00:00Z
Of course, PHP 5.3 has somewhat more elegant time functions like DateTime::diff, but the code above should work fine in PHP 5.2.x.
The only way to do this accurately if a month means X days is to have a method that can perform day calculation.
Unless by 6 months you mean 6 literal months.... Do this in a loop:
Subtract 6 from the month portion, and decrement year and add 12 to month if your month number is negative. Then determine which is later, the start date or your decremented date.
If your start date is later, then use that date. If your decremented date is later, then pack that date range and loop up and start the check over.
Another method is:
$a = strtotime("2018-12-01");
$b = strtotime("2020-12-01");
$makeArray = array();
function addDate($date){
return strtotime(gmdate("Y-m-d", $date)." +6 months");
}
array_push($makeArray, array("start" => date("Y-m-d", $a), "end" => date("Y-m-d", addDate($a))));
while ($c < $b) {
$a = addDate($a);
$c = addDate($a);
if ($c >= $b) {
$c = $b;
}
array_push($makeArray, array("start" => date("Y-m-d", $a), "end" => date("Y-m-d", $c)));
}
print_r($makeArray);
How would you go about calculating the amount of months between two arbitrary dates? Given that even if just one day falls on a month, it is considered a full month.
Examples:
2010-01-01 - 2010-03-31 = three months
2010-06-15 - 2010-09-01 = four months
Et cetera. I thought of just dividing the difference of timestamps with 2592000 (average number of seconds in a month) but that seems hacky and prone to errors. And I'd like to keep it as fast as possible (needs to run thousands of times quick), so I guess using strtotime isn't optimal either?
If I am reading your question correctly, you would want to return "2" for January 31st and February 1st, because it spans both January and February, even though they are only 1 day apart.
You could work out (psuedocode):
monthno1 = (date1_year * 12) + date1_month;
monthno2 = (date2_year * 12) + date2_month;
return (monthno2 - monthno1) + 1;
This assumes that the second date is the later date.
Assuming the dates are in a known format:
function getMonths($start, $end) {
$startParsed = date_parse_from_format('Y-m-d', $start);
$startMonth = $startParsed['month'];
$startYear = $startParsed['year'];
$endParsed = date_parse_from_format('Y-m-d', $end);
$endMonth = $endParsed['month'];
$endYear = $endParsed['year'];
return ($endYear - $startYear) * 12 + ($endMonth - $startMonth) + 1;
}
This gives:
print(getMonths('2010-01-01', '2010-03-31')); // 3
print(getMonths('2010-06-15', '2010-09-01')); // 4
I've been trying to count the age of something in weekdays. I've tried the method detailed in this question, Given a date range how to calculate the number of weekends partially or wholly within that range? but it doesn't seem to fit my usecase.
An item has a created DATETIME in the database, and I need to mark it as old if the created date is over 2 days old. However, the client has requested that age only count week days (Mon to Fri) and exclude Sat+Sun.
So far, my pseudo code looks like the following,
now - created_datetime = number_of_days
for(i number_of_days)
if(created_datetime - i)
is a weekday, then age++
There must be a cleaner way of achieving this? As if an item were to get very old, looping through each day of it's age, looking for a weekend day would impact speed quite a bit.
Any ideas would be great! Thanks
You only have to check the last 7 days to know the exact age in weekdays.
Here's the intuition:
Subtract from the total age of the object, the number of weekends in its lifetime. Note that every 7 days there are exactly 2 weekends. Add to this the number of weekend days in the remainder days (the ones that aren't in a full week) and you have to total number of weekend days in its lifetime. Subtract from the real age to get the weekday age.
int weekendCount = 0;
// Check the remainder days that are not in a full week.
for (int i = totalAge % 7; i < 7; i++) {
if (created_datetime - i is weekend) {
weekendCount++;
}
}
weekdayAge = totalNumberOfDays - (((totalNumberOfDays / 7) * 2) + weekendCount);
Note that the division is an integer division.
I'm sure you can do this with some math and a bit of careful thought. You need to check what day of the week the item was created and what day of the week it currently is. First, you calculate the number of days old it is, as I'm sure you were doing at first.
// if today is saturday, subtract 1 from the day. if its sunday, subtract 2
if (now() is sunday) $now = now() - 2 days;
if (now() is saturday) $now = now() - 1 day;
// same thing for $date posted. but adding 1 or 2 days
if ( $date is saturday) $date = $date + 2;
if ( $date is sunday) $date = $date + 1;
// get days difference
$days = $today - $date;
// have to subtract 2 days for every 7
$days = $days - floor($days/7)*2
You'd have to check if that works. Maybe you can do the calculation without moving your date to before/after the weekend. It may not be perfect, but its the right idea. No need to iterate.