With PHP, how to retrieve items that match created month and year - php

I'm using this code to retrieve news items from my MongoDB:
foreach (collection("news")->find(["created"=>$time]) as $news)
Now I would like to find only those news articles with "created" unix timestamp that matches specific month of the specific year, like April 2014.
Any help would be appreciated.
Cheers.
EDIT: Thank you all for your effort, and yes, it's unix timestamp.

How did you create the Unix Timestamp? How is it represented in the document?
Are you using the MongoDate type, or are you using an integer?
If you are using the MongoDate type then you need to construct MongoDate objects and use them for your $gte and $lt conditions
$article = array(
"title" => "My first article",
"content" => "This is good news",
"published" => new MongoDate(),
"author" => "Random guy on the internet",
);
$collection->insert($article);
$start = new MongoDate(strtotime("yesterday"));
$end = new MongoDate(strtotime("tomorrow"));
$cursor = $collection->find(array("ts" => array('$gt' => $start, '$lte' => $end)));
foreach($cursor as $article) {
var_dump($article);
}
See http://www.php.net/manual/en/class.mongodate.php for more info

you'll have build your own query:
SELECT * FROM news WHERE created >= $start_date AND created <= $end_date
where:
assuming created is unix timestamp, otherwise you should skip strtotime function
// first day of april 2014
$start_date = strtotime(date('2014-04-01'));
// last day of april 2014
$end_date = strtotime(date('2014-04-t'));

You could use DateTime::createFromFormat to create your timestamps.
$begin = DateTime::createFromFormat('!Y-m', '2014-04');
$end = clone $begin;
$end->modify('next month');
echo $begin->format('Y-m-d H:i:s'). PHP_EOL; // 2014-04-01 00:00:00
echo $end->format('Y-m-d H:i:s'); // 2014-05-01 00:00:00
And build more complex condition.
collection("news")
->find(["created" => array(
'$gte' => $begin->getTimestamp(),
'$lt' => $end->getTimestamp())]);

Related

How can I get the MONTH name in a DQL query?

I have a query to get the number of insertions grouped by months. I have beberlei Doctrine extensions already installed and working, but the array return have the month number as key. How will be used in a chart line, I want to show the month name instead of month number.
What is the best way to do this?
This the query:
//get and format the current year
$date = new \DateTime();
$year = $date->format('Y');
//create query
$qb = $this->_em->createQueryBuilder();
$qb->select(['MONTH(p.createdAt) as month', 'count(ccp) as insertions'])
->from(CareConnectPatient::class, 'ccp')
->join('ccp.person', 'p')
->where('YEAR(p.createdAt) = :year')->setParameter('year', $year);
$qb->groupBy('month');
return $qb->getQuery()->getScalarResult();
And I do that with that result:
`$countMonths = count($cCPatientGroupedByMonth);
for ($count = 0; $count<$countMonths; $count++){
$arrayKeyAsMonth[$cCPatientGroupedByMonth[$count]['month']] = $cCPatientGroupedByMonth[$count]['insertions'];
}`
What gives me something like this:
array:3 [
4 => "1",
8 => "2",
9 => "2"
]
I what to know the best way to make this appear as:
array:3 [
april => "1",
august => "2",
september => "2"
]
Sorry for bad english and thanks!
have you tried using MONTHNAME() function?
instead of:
$qb->select(['MONTH(p.createdAt) as month', 'count(ccp) as insertions'])
try:
$qb->select(['MONTHNAME(p.createdAt) as month', 'count(ccp) as insertions'])

PHP Determine when multiple(n) datetime ranges overlap each other

I'm having a hell of a time trying to solve the following problem:
It's a calendar program where given a set of available datetime sets from multiple people, I need to figure out what datetime ranges everyone is available in PHP
Availability Sets:
p1: start: "2016-04-30 12:00", end: "2016-05-01 03:00"
p2: start: "2016-04-30 03:00", end: "2016-05-01 03:00"
p3: start: "2016-04-30 03:00", end: "2016-04-30 13:31"
start: "2016-04-30 15:26", end: "2016-05-01 03:00"
I'm looking for a function that I can call that will tell me what datetime ranges all (p) people are available at the same time.
In the above example the answer should be:
2016-04-30 12:00 -> 2016-04-30 13:31
2016-04-30 15:26 -> 2016-05-01 03:00
I did find this similar question and answer
Datetime -Determine whether multiple(n) datetime ranges overlap each other in R
But I have no idea what language that is, and have to unable to translate the logic in the answer.
Well that was fun. There's probably a more elegant way of doing this than looping over every minute, but I don't know if PHP is the language for it. Note that this currently needs to manage the start and end times to search separately, although it would be fairly trivial to calculate them based on the available shifts.
<?php
$availability = [
'Alex' => [
[
'start' => new DateTime('2016-04-30 12:00'),
'end' => new DateTime('2016-05-01 03:00'),
],
],
'Ben' => [
[
'start' => new DateTime('2016-04-30 03:00'),
'end' => new DateTime('2016-05-01 03:00'),
],
],
'Chris' => [
[
'start' => new DateTime('2016-04-30 03:00'),
'end' => new DateTime('2016-04-30 13:31')
],
[
'start' => new DateTime('2016-04-30 15:26'),
'end' => new DateTime('2016-05-01 03:00')
],
],
];
$start = new DateTime('2016-04-30 00:00');
$end = new DateTime('2016-05-01 23:59');
$tick = DateInterval::createFromDateString('1 minute');
$period = new DatePeriod($start, $tick, $end);
$overlaps = [];
$overlapStart = $overlapUntil = null;
foreach ($period as $minute)
{
$peopleAvailable = 0;
// Find out how many people are available for the current minute
foreach ($availability as $name => $shifts)
{
foreach ($shifts as $shift)
{
if ($shift['start'] <= $minute && $shift['end'] >= $minute)
{
// If any shift matches, this person is available
$peopleAvailable++;
break;
}
}
}
// If everyone is available...
if ($peopleAvailable == count($availability))
{
// ... either start a new period...
if (!$overlapStart)
{
$overlapStart = $minute;
}
// ... or track an existing one
else
{
$overlapUntil = $minute;
}
}
// If not and we were previously in a period of overlap, end it
elseif ($overlapStart)
{
$overlaps[] = [
'start' => $overlapStart,
'end' => $overlapUntil,
];
$overlapStart = null;
}
}
foreach ($overlaps as $overlap)
{
echo $overlap['start']->format('Y-m-d H:i:s'), ' -> ', $overlap['end']->format('Y-m-d H:i:s'), PHP_EOL;
}
There are some bugs with this implementation, see the comments. I'm unable to delete it as it's the accepted answer. Please use iainn or fusion3k's very good answers until I get around to fixing it.
There's actually no need to use any date/time handling to solve this
problem. You can exploit the fact that dates in this format are in alphabetical as well as chronological order.
I'm not sure this makes the solution any less complex. It's probably less
readable this way. But it's considerably faster than iterating over every minute so you might choose it if performance is a concern.
You also get to use
every
single
array
function
out there, which is nice.
Of course, because I haven't used any date/time functions, it might not work if Daylight Savings Time or users in different time zones need dealing with.
$availability = [
[
["2016-04-30 12:00", "2016-05-01 03:00"]
],
[
["2016-04-30 03:00", "2016-05-01 03:00"]
],
[
["2016-04-30 03:00", "2016-04-30 13:31"],
["2016-04-30 15:26", "2016-05-01 03:00"]
]
];
// Placeholder array to contain the periods when everyone is available.
$periods = [];
// Loop until one of the people has no periods left.
while (count($availability) &&
count(array_filter($availability)) == count($availability)) {
// Select every person's earliest date, then choose the latest of these
// dates.
$start = array_reduce($availability, function($carry, $ranges) {
$start = array_reduce($ranges, function($carry, $range) {
// This person's earliest start date.
return !$carry ? $range[0] : min($range[0], $carry);
});
// The latest of all the start dates.
return !$carry ? $start : max($start, $carry);
});
// Select each person's range which contains this date.
$matching_ranges = array_filter(array_map(function($ranges) use($start) {
return current(array_filter($ranges, function($range) use($start) {
// The range starts before and ends after the start date.
return $range[0] <= $start && $range[1] >= $start;
}));
}, $availability));
// Find the earliest of the ranges' end dates, and this completes our
// first period that everyone can attend.
$end = array_reduce($matching_ranges, function($carry, $range) {
return !$carry ? $range[1] : min($range[1], $carry);
});
// Add it to our list of periods.
$periods[] = [$start, $end];
// Remove any availability periods which finish before the end of this
// new period.
array_walk($availability, function(&$ranges) use ($end) {
$ranges = array_filter($ranges, function($range) use($end) {
return $range[1] > $end;
});
});
}
// Output the answer in the specified format.
foreach ($periods as $period) {
echo "$period[0] -> $period[1]\n";
}
/**
* Output:
*
* 2016-04-30 12:00 -> 2016-04-30 13:31
* 2016-04-30 15:26 -> 2016-05-01 03:00
*/
A different approach to your question is to use bitwise operators. The benefits of this solution are memory usage, speed and short code. The handicap is that — in your case — we can not use php integer, because we work with large numbers (1 day in minutes is 224*60), so we have to use GMP Extension, that is not available by default in most php distribution. However, if you use apt-get or any other packages manager, the installation is very simple.
To better understand my approach, I will use an array with a total period of 30 minutes to simplify binary representation:
$calendar =
[
'p1' => [
['start' => '2016-04-30 12:00', 'end' => '2016-04-30 12:28']
],
'p2' => [
['start' => '2016-04-30 12:10', 'end' => '2016-04-30 12:16'],
['start' => '2016-04-30 12:22', 'end' => '2016-05-01 12:30']
]
];
First of all, we find min and max dates of all array elements, then we init the free (time) variable with the difference in minutes between max and min. In above example (30 minutes), we obtain 230-20=1,073,741,823, that is a binary with 30 ‘1’ (or with 30 bits set):
111111111111111111111111111111
Now, for each person, we create the corresponding free-time variable with the same method. For the first person is easy (we have only one time interval): the difference between start and min is 0, the difference between end and min is 28, so we have 228-20=268435455, that is:
001111111111111111111111111111
At this point, we update global free time with a AND bitwise operation between global free time itself and person free time. The OR operator set bits if they are set in both compared values:
111111111111111111111111111111 global free time
001111111111111111111111111111 person free time
==============================
001111111111111111111111111111 new global free time
For the second person, we have two time intervals: we calculate each time interval with know method, then we compone global person free time using OR operator, that set bits if they are set in either first or second value:
000000000000001111110000000000 12:10 - 12:16
111111110000000000000000000000 12:22 - 12:30
==============================
111111110000001111110000000000 person total free time
Now we update global free time with the same method used for first person (AND operator):
001111111111111111111111111111 previous global free time
111111110000001111110000000000 person total free time
==============================
001111110000001111110000000000 new global free time
└────┘ └────┘
:28-:22 :16-:10
As you can see, at the end we have an integer with bits set only in minutes when everyone is available (you have to count starting from right). Now, you can convert back this integer to datetimes. Fortunately, GMP extension has a method to find 1/0 offset, so we can avoid to perform a for/foreach loop through all digits (that in real case are many more than 30).
Let's see the complete code to apply this concept to your array:
$calendar =
[
'p1' => [
['start' => '2016-04-30 12:00', 'end' => '2016-05-01 03:00']
],
'p2' => [
['start' => '2016-04-30 03:00', 'end' => '2016-05-01 03:00']
],
'p3' => [
['start' => '2016-04-30 03:00', 'end' => '2016-04-30 13:31'],
['start' => '2016-04-30 15:26', 'end' => '2016-05-01 03:00']
]
];
/* Get active TimeZone, then calculate min and max dates in minutes: */
$tz = new DateTimeZone( date_default_timezone_get() );
$flat = call_user_func_array( 'array_merge', $calendar );
$min = date_create( min( array_column( $flat, 'start' ) ) )->getTimestamp()/60;
$max = date_create( max( array_column( $flat, 'end' ) ) )->getTimestamp()/60;
/* Init global free time (initially all-free): */
$free = gmp_sub( gmp_pow( 2, $max-$min ), gmp_pow( 2, 0 ) );
/* Process free time(s) for each person: */
foreach( $calendar as $p )
{
$pf = gmp_init( 0 );
foreach( $p as $time )
{
$start = date_create( $time['start'] )->getTimestamp()/60;
$end = date_create( $time['end'] )->getTimestamp()/60;
$pf = gmp_or( $pf, gmp_sub( gmp_pow( 2, $end-$min ), gmp_pow( 2, $start-$min ) ) );
}
$free = gmp_and( $free, $pf );
}
$result = [];
$start = $end = 0;
/* Create resulting array: */
while( ($start = gmp_scan1( $free, $end )) >= 0 )
{
$end = gmp_scan0( $free, $start );
if( $end === False) $end = strlen( gmp_strval( $free, 2 ) )-1;
$result[] =
[
'start' => date_create( '#'.($start+$min)*60 )->setTimezone( $tz )->format( 'Y-m-d H:i:s' ),
'end' => date_create( '#'.($end+$min)*60 )->setTimezone( $tz )->format( 'Y-m-d H:i:s' )
];
}
print_r( $result );
Output:
Array
(
[0] => Array
(
[start] => 2016-04-30 12:00:00
[end] => 2016-04-30 13:31:00
)
[1] => Array
(
[start] => 2016-04-30 15:26:00
[end] => 2016-05-01 03:00:00
)
)
3v4l.org demo
Some additional notes:
At the start, we set $tz to current timezone: we will use it later, at the end, when we create final dates from timestamps. Dates created from timestamps are in UTC, so we have to set correct timezone.
To retrieve initial $min and $max values in minutes, firstly we flat original array, then we retrieve min and max date using array_column.
gmp_sub subtract second argument from first argument, gmp_pow raise number (arg 1) into power (arg 2).
In the final while loop, we use gmp_scan1 and gmp_scan0 to retrieve each ‘111....’ interval, then we create returning array elements using gmp_scan1 position for start key and gmp_scan0 position for end key.

Converting birthday in format DDMMMYY to date value in PHP

I've got bunch of birthdays which are stored in format DDMMMYY. I need to convert those to date values, so i can store those in database.
Is there any easy way of telling strtotime function that date must be in the past?
<?php
$datestring = '22SEP41';
echo date('Y-m-d',strtotime($datestring)); //outputs 2041-09-22, should be 1941-09-22
?>
<?php
$datestring = '22SEP41';
$matches = [];
preg_match('/([0-9]{2})([A-Z]{3})([0-9]{2})/', $datestring, $matches);
$prefix = ($matches[3] <= date('y') ? '20' : '19');
$matches[3] = "{$prefix}{$matches[3]}";
$ts = strtotime("{$matches[1]} {$matches[2]} {$matches[3]}");
// date ('Y-m-d', $ts) == 1941-09-22
This assumes that 22SEP06 should be interpreted as 2006 rather than 1906 - basically it gives the output a range of 1917 -> 2016.
This method create a date of past century only if standard evaluated date is after today:
$date = date_create( $datestring );
if( $date->diff( date_create() )->invert )
{
$date->modify( '-100 years' );
}
echo $date->format( 'Y-m-d' );
For
$datestring = '22SEP41';
the output is:
1941-09-22
For
$datestring = '22SEP01';
the output is:
2001-09-22
eval.in demo
Basically, we create a DateTime based on given string, then we calculate difference with current day; if the difference is negative (->invert), we subtract 1 century from the date.
You can personalize the condition using ->format('%R%Y') instead of ->invert. In this example:
if( $date->diff( date_create() )->format('%R%Y') < 10 )
Dates from 00 through 05 as evaluated as 2000-2005.
You could try something like:
$ds = '22SEP41';
$day = $ds[0].$ds[1];
// Getting the month.
$mon = array(
'JAN' => 1,
'FEB' => 2,
'MAR' => 3,
'APR' => 4,
'MAY' => 5,
'JUN' => 6,
'JUL' => 7,
'AUG' => 8,
'SEP' => 9,
'OCT' => 10,
'NOV' => 11,
'DEC' => 12
);
$mont = $ds[2].$ds[3].$ds[4];
$month = $mon[$mont]; // Gets the month real.
$year = '19'.$ds[5].$ds[6];
$final = $day.'-'.$month.'-'.$year;
I tested it on my local machine and it worked. Hope it works and is what you're looking for :)

How to get start date of child time frame within parent time frame

I know exact date of the beginning of the month but I need to know when first week of this month begins and this week can start outside of this month.
I can do something like - find weekday when "MONTH" starts and subtract number of days to the beginning of the "WEEK" :
$weekday = d('w', $monthStartTimeStamp);
$weekStart = $monthStartTimeStamp - strtotime('1 day', 0) * (7 - $weekday);
Question :
Is there some way to make this calculation more generic for time frames that I do not know in advance?
Possible use cases :
Given some "DATETIME" get datetime of the first "YEAR"
Given some "DATETIME" get datetime of the first "MONTH"
Given some "DATETIME" get datetime of the first "WEEK"
Given some "DATETIME" get datetime of the first "DAY"
Given some "DATETIME" get datetime of the first "HOUR"
Given some "DATETIME" get datetime of the first "MINUTE"
For date time 2016-01-10 10:30 results would be :
YEAR : 2016-01-01 00:00
MONTH : 2016-01-01 00:00
WEEK : 2016-01-04 00:00
DAY : 2016-01-10 00:00
HOUR : 2016-01-10 10:00
MINUTE : 2016-01-10 10:30
P. S. most of the questions here specify what exactly time frame is required, e.g. "How to get beginning of the year", they do not answer how to do this for ant time frame.
Here's an idea in pseudocode:
You already have the hardest case for the week
For the rest: Convert the date to a format like: YYYYMMDDHHmm, then depending on the $interval just change the right X characters to 0:
`
$int2char = array(
'YEAR' => 8,
'MONTH' => 6,
'DAY' => 4,
'HOUR' => 2,
'MINUTE' => 0
);
$interval = 'MONTH';
$str = date("YmdHi", $timestamp);
$zeros = $int2char[$interval];
$d = substring($str, 0, 12-$zeros) . substring("00000000", 0, $zeros);
$date = DateTime::createFromFormat('YmdHi', $d);
echo $date->format('Y-m-d H:i');
`

MySQL query for dates with CakePHP

I'm trying to select the values that fall between 2 dates, so I'll have to use <= and >=, however, my query is seemingly behaving as just less than or greater than and not considering values equal to the date.
I'm using CakePHP which saves dates in "Y-m-d h:i:a" format. I wanted to find dates on given week intervals (starting on Sundays), so I used the following.
$start_date = date('Y/m/d', strtotime('last Sunday', strtotime($timestamp)));
$formatted_start_date = str_replace("/","-",$start_date);
I tried to do find $start_date formatted as "Y-m-d" but then it wouldn't find the correct date, so I switched it to the way it is and used str_replace to format it to using "-" instead of "/".
$date_query = $this->Order->query("select * from orders where id = '$id' and created => '$formatted_start_date' and created <= '$current_timestamp' ");
Considering the time values in my database are in "Y-m-d h:i:a" format, can I use "Y-m-d" for date comparison? Is it possible to do a MySQL query that involves both LIKE and <= ?
No need to do a str_replace() - just get the Y-m-d:
$start_date = date('Y-m-d', strtotime('last Sunday', strtotime($timestamp)));
Then, instead of manually creating a query, use the CakePHP conventions (yes, you can use Y-m-d for date comparison even though the datetimes stored in the database are Y-m-d H:i:s)
$this->Order->find('all', array(
'conditions' => array(
'id' => $id,
'created >=' => $start_date,
'created <=' => $end_date . ' 23:59:59',
'my_field LIKE' => '%whatever%'
));
Though - this seems kind of strange - usually you're either looking for something by 'id' OR by a date range - not both. But - maybe you have a reason :)
And as you can see above, yes, you can add a 'LIKE' also if you need.
Above answer is totally correct, but you can take easier approach using cakePHP time helper, which has function daysAsSql, it transcribes and time-readable strings into database range.
http://book.cakephp.org/2.0/en/core-libraries/helpers/time.html#TimeHelper::daysAsSql
Just add condition's array like this.
$resl = $this->DBNAME->find('all',array(conditions=>array('date1>date','date1<date')));
Replace 'date' with your date.
This is worked for me.
Try this in your controller
$searchTutorQuery = $this->Tutordetails->find('all', array(
'conditions' => array(
"User.zip" => $zipcode1,
"Tutordetails.user_id"=>$uid,
"Tutordetails.subject_id" => $subjectName,
"Tutordetails.hourly_rate >=" => $hourly_rate
),
//"User.id =" => $userid,
'fields' => array('user_id', 'Tutordetails.subject_id', 'Tutordetails.hourly_rate',
'User.zip', 'Tutordetails.zip'),
'order' => array('Tutordetails.id DESC')
));

Categories