Skip Navigation

[Resolved] Calculating dates

This support ticket is created 6 years, 2 months ago. There's a good chance that you are reading advice that it now obsolete.

This is the technical support forum for Toolset - a suite of plugins for developing WordPress sites without writing PHP.

Everyone can read this forum, but only Toolset clients can post in it. Toolset support works 6 days per week, 19 hours per day.

Sun Mon Tue Wed Thu Fri Sat
- 7:00 – 14:00 7:00 – 14:00 7:00 – 14:00 7:00 – 14:00 7:00 – 14:00 -
- 15:00 – 16:00 15:00 – 16:00 15:00 – 16:00 15:00 – 16:00 15:00 – 16:00 -

Supporter timezone: Europe/London (GMT+00:00)

This topic contains 14 replies, has 2 voices.

Last updated by tanjaN 6 years, 1 month ago.

Assisted by: Nigel.

Author
Posts
#1113853

Hi there!

First of all, I just started out using Toolset and already love the possiblilites. However, I got stumped and hope you can help me.

I am trying to develop a list view. It's supposed to show a list of events (a custom post type in my Toolset), sorted by date. So far, so good, now there's the catch: All those events lie in the past (mostly birthdates), and I would like to show in a table cell how many years ago they happened.

I saw on various support threads that issues like mine were solveable and tried multiple solutions, but none really worked. The one closest to mine was this: https://toolset.com/forums/topic/calculate-age-from-date-of-birth-custom-filed/

So I added the following code to my functions.php:
add_shortcode( 'time_ago', 'time_ago_func');
function time_ago_func($atts){
global $wpdb;
extract( shortcode_atts( array(
'birthdate' => '',
), $atts ) );

$age = floor((time() - $birthdate)/(365*24*60*60));
return $age;
}

Then I went to the Settings and registered time_ago (you only have to add time_ago as a snippet slug and let the system create a time_ago.php, right?), the I activated it.

Next, I used the Loop Editor (not the Divi editor because I wasn't sure how to add the custom part), it currently looks like this:
[wpv-layout-start]
[wpv-items-found]
<!-- wpv-loop-start -->
<ul class="wpv-loop js-wpv-loop">
<wpv-loop>

  • <tr>
    <td>[types field="datum"][/types]</td>
    <td>[time_ago birthdate='[types field="ereignistag" raw="true" id=""][/types]']</td>
    <td>[wpv-post-link]</td>
    <td>[types field="beschreibung"][/types]</td>
  • </wpv-loop>

    <!-- wpv-loop-end -->
    [/wpv-items-found]
    [wpv-no-items-found]
    [wpml-string context="wpv-views"]No items found[/wpml-string]
    [/wpv-no-items-found]
    [wpv-layout-end]

    ("ereignistag" is the custom field where I enter when the event happened.)

    Now, I expected to see a list. Instead, I got "No items found", although I know there are events matching the query.

    Can you tell me where I went wrong? Can it be a problem with the date format?

    Thanks a lot and kind regards,
    Tanja

    #1114234

    Nigel
    Supporter

    Languages: English (English ) Spanish (Español )

    Timezone: Europe/London (GMT+00:00)

    Hi Tanja

    Firstly, I don't think the code you are using would give accurate results as it doesn't allow for leap years, try the following instead:

    /**
     *  Calculate age (in years) from date field
     *  specified as shortcode attribute 'slug'
     */
    add_shortcode( 'age', function( $atts = [], $content = null ){
    
    	// provide defaults
    	$atts = shortcode_atts( 
    		array(
    			'slug'		=>	null
    		), 
    		$atts 
    	);
    
    	$age = '';
    
    	if ( isset( $atts['slug'] ) ) {
    
    		global $post;
    		$timestamp = get_post_meta( $post->ID, 'wpcf-' . $atts['slug'], true );
    
    		$date = DateTime::createFromFormat( "U", $timestamp );
    
    		$now = new DateTime();
    
    		$diff = $now->diff( $date );
    
    		$age = $diff->y;
    	}
    
    	return $age;
    });
    

    You would use it like so to output the age in years calculated from a Types date field with a slug specified by the slug shortcode attribute:

    <p>Age: [age slug='date-of-birth']</p>
    

    You can either add this code to your theme's functions.php file, *or* use the Code Snippets feature added with Types 3.1 by adding the code in a new snippet you create at Toolset > Settings > Custom Code. Not both.

    No need to register this shortcode at Toolset > Settings > Front-end Content, unless you want to use the output of this shortcode as an attribute in Views shortcodes (e.g. wpv-conditional).

    Now, the question of why your View is displaying No results found is unrelated to the question of how to output a date field as a calculated age.

    You presumably have added a Query Filter to your View and none of the posts match this filter, or you are ordering the results by some custom field, and none of the posts have a value for this custom field (the default WordPress behaviour when ordering by a non-existing field is to exclude the posts from the results).

    #1114297

    Hi Nigel!

    Thank you for responding! I made some progress, but still get an error. I added the code you suggested to my functions.php and then inserted the shortcode in my view, changing the "slug: 'date-of-birth'" to the slug of my custom field.

    Now, I get a correct output when I delete all filters, but also this: Warning: DateTime::diff() expects parameter 1 to be DateTimeInterface, boolean given in /www/htdocs/w012c213/jubilaen.museumsmag.de/wp-content/themes/Divi Child/functions.php on line 32

    For reference, line 32 is this: $diff = $now->diff( $date );

    Plus, I would like to filter by the calculated age. I registered the shortcode "age" in the Front-end Content section, but I don't see it in the filters assistant, do I have to add it manually?

    Thank you!
    Tanja

    #1114462

    Nigel
    Supporter

    Languages: English (English ) Spanish (Español )

    Timezone: Europe/London (GMT+00:00)

    Hi Tanja

    immediately after the line

    $date = DateTime::createFromFormat( "U", $timestamp );
    

    can you add the following:

    error_log('date: ' . print_r($date, true));
    

    If you haven't already, turn on the debug log by editing your wp-config.php file and change the line with WP_DEBUG like so:

    define('WP_DEBUG', true);
    define('WP_DEBUG_LOG', true);
    define('WP_DEBUG_DISPLAY', false);
    

    That will create a file debug.log in your /wp-content directory which that error_log message will be sent to and which you can examine in any text editor. Try visiting the same page where you use the shortcode, then inspect the log. Let me know what you find.

    You can't use a shortcode as filter in the way you are hoping. You will need to modify the query arguments to effectively do the reverse of the shortcode, which is to start with an age filter, and calculate the date value to compare against the date custom field which is actually stored alongside the posts.

    I can help you with that, but first report back what you see in the debug.log

    #1114498

    Hi Nigel!

    I did that, now the output is what I hoped it would be, and the error log states the following:

    [24-Sep-2018 18:39:50 UTC] date: 
    [24-Sep-2018 18:39:50 UTC] PHP Warning:  DateTime::diff() expects parameter 1 to be DateTimeInterface, boolean given in /www/htdocs/w012c213/jubilaen.museumsmag.de/wp-content/themes/Divi Child/functions.php on line 33
    [24-Sep-2018 18:39:50 UTC] PHP Notice:  Trying to get property of non-object in /www/htdocs/w012c213/jubilaen.museumsmag.de/wp-content/themes/Divi Child/functions.php on line 35
    [24-Sep-2018 18:39:50 UTC] date: 
    [24-Sep-2018 18:39:50 UTC] PHP Warning:  DateTime::diff() expects parameter 1 to be DateTimeInterface, boolean given in /www/htdocs/w012c213/jubilaen.museumsmag.de/wp-content/themes/Divi Child/functions.php on line 33
    [24-Sep-2018 18:39:50 UTC] PHP Notice:  Trying to get property of non-object in /www/htdocs/w012c213/jubilaen.museumsmag.de/wp-content/themes/Divi Child/functions.php on line 35

    Line 33 in my functions.php is

    $diff = $now->diff( $date );

    Line 35 is

    $age = $diff->y;

    Best,
    Tanja

    #1115001

    Nigel
    Supporter

    Languages: English (English ) Spanish (Español )

    Timezone: Europe/London (GMT+00:00)

    Hi Tanja

    The date object isn't being created, meaning there must be a problem getting the date field value from the post meta, but I can't see enough from what you've described to see why not.

    The code is fine, I have it working on a local test site, and I'll need to see it on your own site to understand why it isn't for you.

    I will mark your next reply as private so that I can get log-in credentials from you—you may want to create a temporary admin user for me to use that you can later delete. And be sure to have a current backup of your site, even though I don't intend to make any changes.

    Can you please remove the code from where you added it to your child theme, I'll add it as a Types code snippet so I can easily edit if required.

    Also please confirm the URL where you have inserted the shortcode.

    #1115735

    Nigel
    Supporter

    Languages: English (English ) Spanish (Español )

    Timezone: Europe/London (GMT+00:00)

    Screen Shot 2018-09-26 at 15.28.57.png

    Hi Tanja

    I added the code to register the age shortcode as a code snippet in the Toolset > Settings.

    From the log messages I could see when visiting the page that displayed the View which uses the age shortcode I could see that the PHP warnings occurred for posts that have no date field set, something I hadn't allowed for.

    So I added a test for that in the code and updated it accordingly, like so:

    <?php
    /**
     *  Calculate age (in years) from date field
     *  specified as shortcode attribute 'slug'
     */
    add_shortcode( 'age', function( $atts = [], $content = null ){
     
        // provide defaults
        $atts = shortcode_atts( 
            array(
                'slug'      =>   null
            ), 
            $atts
        );
     
        $age = '';
        global $post;
        $timestamp = get_post_meta( $post->ID, 'wpcf-' . $atts['slug'], true );
     
        if ( isset( $atts['slug'] ) && !empty( $timestamp ) ) {
     
     
            $date = DateTime::createFromFormat( "U", $timestamp );
     
            $now = new DateTime();
     
            $diff = $now->diff( $date );
     
            $age = $diff->y;
        }
     
        return $age;
    });
    

    The age shortcode now works, calculating the age in years from the value of the date field, as you can see in the screenshot.

    #1116341
    Jubiläendatenbank.jpg

    Hi Nigel!

    Sorry, I should have gotten that... Thank you very much for helping me! I went ahead and started changing the layout and adding filtering options, as you can see in the image.

    Now, I still have a filter / query problem. First of all, I tried showing events that took place from today on but not minding the year. First, it seemed to work, but the more events I add the less it does. I added a query with the field "ereignistag" being "greater than or equal to" TODAY(). Whether I use a string, a number or a date, the results are different - string and number come closer to what I want than date -, but it's never entirely right. What can I do?

    Also, you mentionend above that you could help me filter by age and I suppose this is related to my issue. "You can't use a shortcode as filter in the way you are hoping. You will need to modify the query arguments to effectively do the reverse of the shortcode, which is to start with an age filter, and calculate the date value to compare against the date custom field which is actually stored alongside the posts."

    Ideally, I would only output events with an age that can be divided by five. Can that be done?

    Thank you very much
    Tanja

    #1116533

    Nigel
    Supporter

    Languages: English (English ) Spanish (Español )

    Timezone: Europe/London (GMT+00:00)

    Hi Tanja

    Let me give you a short answer before I finish for today, and if needs be I can expand on the subject tomorrow.

    Types date fields are stored as timestamps (hidden link), and they refer to a particular moment in time (measured in seconds).

    Although you can convert a timestamp into a date so that you know what the month or day of the week etc. are, when it comes to performing comparisons in a post query you are basically limited to before, same as, or after the point in time.

    So you might effectively have a filter to show posts before 24 June 2008 19:00, for example. You would actually be comparing NUMBERs, because the field in the database is stored as a timestamp (a number), and if you use something like TODAY() that is converted into a timestamp for today, i.e. another number.

    Which means it is not amenable to creating queries such as "show posts that occur after June 30, regardless of what year".

    You would need an alternative approach for something like that, which stored dates in a different format, which means a custom solution.

    #1116617

    Hi Nigel,

    I see. So I would need to pull the timestamp value from the custom field and convert it to a datetime object, right? Something like this?

    $now = new DateTime();
    
    $datetime = get_post_meta($post->ID, 'wpcf-ereignistag', true);
    
    $datetime = new DateTime('Y-m-d', new DateTimeZone('Europe/Berlin'));

    In case that works, I should be able to do something like this...

    $interval = $now->diff($datetime);
    
    if ( ($interval->m >= 0) && ($interval->d >= 0) && (is_int($age / 5)))
    	
    { display the event };
    

    Am I thinking in the right direction?

    Best,
    Tanja

    #1117131

    Nigel
    Supporter

    Languages: English (English ) Spanish (Español )

    Timezone: Europe/London (GMT+00:00)

    If you were writing custom SQL queries you could transform the value stored in the database for a field before comparing it.

    If you already had the age in years saved as a custom field, for example, you could check the modulo (i.e. field % 5 == 0), but when you are using WP_Query (which Toolset is built on) to query the database then there is no option to transform the field values before any comparisons are done.

    A hybrid is possible, where you modify the SQL that is generated by WP_Query which may open up your options here (see, for example, here how to modify the WHERE clause: https://codex.wordpress.org/Plugin_API/Filter_Reference/posts_where). I haven't got anything to add about this, it is well outside normal Toolset usage.

    So, aside from a custom solution, you cannot query posts on the basis you are describing.

    What you can do, though, is modify the results of a View before they are output, using the wpv_filter_query_post_process hook (hidden link).

    In that case you would set up a View to display the event posts, and then you would use that hook to loop over the results. Then for each post you can get the date field and perform whatever test you want, and discard any results which don't pass the test.

    When discarding results you need to be careful to also update the results count and then re-index the results.

    A boilerplate of how this would work (you can add your own tests for which posts should be discarded) would look something like this:

    <?php
    function tssupp_query_post_process( $query, $view_settings, $view_id ){
      
      	// Edit for View id(s)
        if ( in_array( $view_id, array( 99 ) ) && !empty( $query->posts ) ){
     
        	// Loop over the posts
            foreach ($query->posts as $key => $post) {
     
     			// Perform some test for posts to be discarded
                if ( true ) {
     
                    unset( $query->posts[$key] );
                    $query->found_posts = $query->found_posts - 1;
                    $query->post_count = $query->post_count - 1;
                }
     
            }
     
            $query->posts = array_values( $query->posts );
        }
     
        return $query;
    }
    add_filter( 'wpv_filter_query_post_process', 'tssupp_query_post_process', 1001, 3 );
    
    #1117888

    Hi Nigel!

    I see, thank you so much for your patience. I found a compromise that is not quite as sophisticated, but works for me: I added two custom fields (numbers) for month and day to my CPT. Now I set up a view for every month that checks if "month" is, for the September view, a string equal to the constant 9, and I have it sorted by the "day" field in ascending order. That way, I get a calendar-like view that doesn't consider the year, which is what I wanted.

    The option you mentioned would be interesting to check if $age can be divided by 5, but I assume that is not so easy because at the moment, this value is not stored, right?

    Best,
    Tanja

    #1118218

    Nigel
    Supporter

    Languages: English (English ) Spanish (Español )

    Timezone: Europe/London (GMT+00:00)

    Well if you used the method I described above—testing each post after they have been returned by the query but before being output by the View with the wpv_query_filter_post_process hook—then you can calculate the age on the fly, using the same methodology as the custom shortcut I described.

    But that does mean running the calculation for each post every time the page with this View is visited, and it might make sense to store the age with the post to avoid that.

    If you publish the posts using a Toolset Form you could use the cred_save_data hook (https://toolset.com/documentation/programmer-reference/cred-api/#cred_save_data) to calculate the age and save it as a post field.

    If you are entering the posts directly in the back end there is no Toolset hook to use, but you could use the standard WP save_post hook: https://developer.wordpress.org/reference/hooks/save_post/

    #1118270

    Hi Nigel,

    okay, I'll look into that! Thank you so much. 🙂

    #1118271

    My issue is resolved now. Thank you!