There is a bug that exists between current calendar module and current Date module - essentially the calendar module is calling this "date_time" function to get the current date and then applying some corrections for users local timezone - BUT the date_time module has already done this - so what happens is that you get a "today" date that is twice the timeoffset ahead of time, which for more users probably looks like a day ahead.

from date module

// $Id: date.module,v 1.32.2.40 2007/04/19 20:35:25 karens Exp $

/**
 * Implementation of time() adjusted for the current site and user.
 *
 * @param $offset - optional method to force time to a specific offset
 * @return integer timestamp
 */
function date_time($offset = NULL) {
  global $user;

  if ($offset) {
        return (time() - date("Z")) + offset;
  }
  elseif (variable_get('configurable_timezones', 1) && $user->uid && strlen($user->timezone)) {
    return (time() - date("Z")) + $user->timezone;
  }
  else {
    return (time() - date("Z")) + variable_get('date_default_timezone', 0);
  }
}

and from calendar_api.module

// $Id: calendar_api.inc,v 1.15.2.16 2007/03/30 16:57:38 karens Exp $

/**
 * Returns a local timestamp (as defined by the user or site's timezone) for
 * midnight GMT.
 * @return integer timestamp
 */
function calendar_user_date($part = 'timestamp') {
  global $user;
  static $date;
  calendar_load_date_api();
  if (!$date) {
    $now = date_time();

    if (variable_get('configurable_timezones', 1) && $user->uid && strlen($user->timezone)) {
      $user_offset = $user->timezone;
    }
    else {
      $user_offset = variable_get('date_default_timezone', 0);
    }

    $now += $user_offset;
    $date = date_gmmktime(array(
      'mon' => date_gmdate('m', $now),
      'mday' => date_gmdate('j', $now),
      'year' => date_gmdate('Y', $now),
      ));
  }

so i think that $now += $user_offset; line can disappear from the calendar module?

is anyone mainting calendar module still?

CommentFileSizeAuthor
#1 date-calender-today.patch580 bytesdgtlmoon
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

dgtlmoon’s picture

Status: Active » Needs review
FileSize
580 bytes

here is my patch to remove the duplication of this functionality that leads to the extra erroneous time being added to the user offset.

this probably affects every calendar installation where it is required to show the current day?

dgtlmoon’s picture

maybe also related to http://drupal.org/node/147392

KarenS’s picture

Status: Needs review » Fixed

Good detective work. Fix made in latest commit. Thanks!

Anonymous’s picture

Status: Fixed » Closed (fixed)
dabro’s picture

Status: Closed (fixed) » Active

I found this thread and it describes the problem I'm having with the current day being ahead. I'm in GMT-5 timezone and I have the site set up for configurable timezones. The events displaying in the calendar are correct and displaying on the right day with the right time. It's just the current day gets ahead in the late evening, I'm not sure but probably at the 5 hour before midnight mark. I tried the latest dev release (Calendar 5.1-dev with the patch?) and the problem still exists. I'm also using Date 5.x-1.6, MySQL ver 5.0.27, PHP ver 5.1.6. Thanks for the great work! Dave

Prodigy’s picture

I turned off timezones and still get this problem. Anything you did to fix, please advise.

vortical’s picture

I second what dabro said... I was about to post details, but it would have been a duplication of his post... the latest dev module didn't work for me either.

I am at GMT-5 and it switched on me tonight at 7PM local time, so 'today' seems to be tracking GMT.

vortical’s picture

Got bored and decided to play with the code... turns out that all I had to do was add back the following code to calendar_api.inc (some of what dgtlmoon removed above, and some that must have been removed in the release since then)

    if (variable_get('configurable_timezones', 1) && $user->uid && strlen($user->timezone)) {
      $user_offset = $user->timezone;
    }
    else {
      $user_offset = variable_get('date_default_timezone', 0);
    }

    $now += $user_offset;

I guess what fixed it some number of months ago is what broke it for me today... a related change to the Date module perhaps?

I'm using the latest dev version of the Calendar module and the latest released version (1.6) of the Date module.

I hope that helps... I don't know how to create/use .patch files or I would post one.

dabro’s picture

Vortical, can you clarify where your code is placed in calendar_api.inc? I'm not savvy enough to tell where it should go, I'd like to try your fix to see if that corrects my five hours ahead calendar. Thanks for the posting, Dave

sbloewen’s picture

In my investigation of this last week I found that the server timezone adjustment is applied twice. See code from 5.x-1.x-dev calendar_api.inc below:

/**
 * Returns a local timestamp (as defined by the user or site's timezone) for
 * midnight GMT.
 * @return integer timestamp
 */
function calendar_user_date($part = 'timestamp') {
  global $user;
  static $date;
  calendar_load_date_api();
  if (!$date) {
    $now = date_time();
    $date = date_gmmktime(array(
      'mon' => date_format_date('m', $now),
      'mday' => date_format_date('j', $now),
      'year' => date_format_date('Y', $now),
      ));
  }
  switch ($part) {
  ...

The date_time() function applies both drupal (default or user) and server timezone correction. The date_gmmktime() applies server correction as well, I believe. I changed $now = date_time(); to $now = date_time() + date("Z"); as a quick-n-dirty hack to negate the first correction. My testing indicated things then worked as I would expect them to. (I tried using date_mktime() once instead, but as things worked much worse, I left it like this.) I hope there is a cleaner way of doing this. Ideas?

Vortical, you are pretty much doing the same thing I did. You are subtracting 5 hours from the incorrect $now time, which, in your case, is 5 hours ahead. This will only (appear to) work if your website timezone is the same as your server timezone.

By the way, I'm GMT +8 and my server -6.

Prodigy’s picture

Fantastic discussion, as a lot of people are suffering from the "1 day ahead" problem.

I never made any changes at all to my files, they are all the latest stable releases.

My server is in the Eastern Time Zone, as is my site. What would I need to do to get my CCK Dates to appear in the right Views? Where should the code be placed? Thanks guys! I've been on this for 3 days trying work arounds!

The only thing that work is using hour/minute granularity with timezones, and I only want the year-month-day.

vortical’s picture

Ok, well I dove into this a little further and found a few things out...

it seems that date_time() was incorrectly calculating the local time

date_gmmktime() does no date/time alteration, it just returns the time given by the input parameter into a "GMT formatted" time that allows date_format_date() to extract all of the needed information (in the switch statement).

I changed the following function in date.inc on line 219 from

/**
 * Implementation of time() adjusted for the current site and user.
 *
 * @param $offset - optional method to force time to a specific offset
 * @return integer timestamp
 */
function date_time($offset = NULL) {
  global $user;

  if ($offset) {
  	return (time() - date("Z")) + offset;
  }
  elseif (variable_get('configurable_timezones', 1) && $user->uid && strlen($user->timezone)) {
    return (time() - date("Z")) + $user->timezone;
  }
  else {
    return (time() - date("Z")) + variable_get('date_default_timezone', 0);
  }
}

to

/**
 * Implementation of time() adjusted for the current site and user.
 *
 * @param $offset - optional method to force time to a specific offset
 * @return integer timestamp
 */
function date_time($offset = NULL) {
  global $user;

  if ($offset) {
  	return (time() - date("Z")) + offset;
  }
  elseif (variable_get('configurable_timezones', 1) && $user->uid && strlen($user->timezone)) {
    return time() + $user->timezone;
  }
  else {
    return time() + variable_get('date_default_timezone', 0);
  }
}

Calls to time() returns the current GMT, so you don't need to offset with - date("Z") before you offset based on the user's timezone or the default server timezone.

@sbloewen (fancy meeting you here ;-) I suspect you know who I am, but look at my profile if you can't figure it out) -- using mktime() returns the timezone corrected time... which would have just "corrected" the time once again... so it makes sense that it screwed things up even more.

Again, I don't know how to use/make .patch files, so you're on your own to edit date.inc

Cheers!

vortical’s picture

wow the php highlighting tags didn't do what I thought they would.... sbloewen, how did you highlight code within a paragraph like that?

sbloewen’s picture

I'm confused. Based on documentation and testing I don't see why this doesn't work. Maybe the problem is elsewhere? I've been using the pink "today" box on the calendar view as my debug tool. If the day is correct, could the reference be off (i.e. is the grid correct)?

I'm going to have to work on this later as I'm pressed for time, have many other issues in other modules, and currently have a workaround. Hopefully you'll find the problem soon.

Vortical, I'm pretty sure time() returns local (server) value, not GMT. I'm very hesitant to change anything in the date api as I don't know what else uses those functions. And yes, I know who you are. That's why I knew your server was in the same timezone as you. Don't use the code tags when you do php (I think that's what you were doing, anyway).

vortical’s picture

Status: Active » Needs review

Sbloewen, you're right... what I did will definitely not work, and the date_time() function was correct as it was before I touched it. Calls to time() definitely return the server local time.

After hours of reading the documentation on php.net and testing a whole lot of different functions and such, I have FINALLY narrowed down the real cause of this problem. It does indeed lie in the date_api module, in the date.inc file in fact, but it has nothing to do with the date_time() function. The problem lies it the date_format_date() function, as explained below:

/**
 * Format date
 *
 * Translate month and day text in date formats.
 * Using gmdate so php won't try to adjust value for server timezone.
 * Needed because date object has already made necessary timezone adjustments.
 *
 * Similar to core format_date function but will work on old, pre-1970 dates
 * if adodb library is available.
 */
function date_format_date($format, $timestamp) {
  $max = strlen($format);
  $date = '';
  for ($i = 0; $i < $max; $i++) {
    $c = $format[$i];
    if (strpos('AaDFlM', $c) !== FALSE) {
      $date .= t(date_gmdate($c, $timestamp));
    }
    else if (strpos('BdgGhHiIjLmnsStTUwWYyz', $c) !== FALSE) {
      $date .= date_gmdate($c, $timestamp);
    }
    else if ($c == 'r') {
      $date .= date_format_date($timestamp, 'D, d M Y H:i:s');
    }
    else if ($c == '\\') {
      $date .= $format[++$i];
    }
    else {
      $date .= $c;
    }
  }
  return $date;
}

function date_gmdate($format, $timestamp = FALSE) {
  if (!$timestamp) return '';
  date_load_library();
  switch (DATE_LIBRARY) {
    case('ADODB'):
    return @adodb_gmdate($format, $timestamp);
  default:
    return @gmdate($format, $timestamp);
  }
}

A successful call to date_format_date() will in turn call date_gmdate(), which will call the php native gmdate() function for all current timestamps (not pre-1970). Through testing I have determined that the gmdate() function takes the timestamp passed into it, and then reverses the local server default timezone offset to derive GMT date information. The issue is, and it is made clear in the original coder's comments above, that the original coder assumed that calls to gmdate() vs. calls to date() were needed to prevent this timezone offset, however the opposite is actually true (try it yourself or look at the example code for gmdate() on php.net if you don' t believe me!). There is one gotcha to just fixing this in the date_api module though -->

Drupal's problems with dates/timezones are threaded in through hundreds of different places, so messing with the date_api module files is probably not the way to go... in fact, when I 'fixed' this in the date.inc file, it threw off a bunch of other calendar formatting things that were dependent upon date_format_date to be returning GMT date info... but the current date was right :-P So I ended up leaving the date module as it was -- flawed.

So I edited calendar_user_date() in the calendar_api.inc file, from

/**
 * Returns a local timestamp (as defined by the user or site's timezone) for
 * midnight GMT.
 * @return integer timestamp
 */
function calendar_user_date($part = 'timestamp') {
  global $user;
  static $date;
  calendar_load_date_api();
  if (!$date) {
    $now = date_time();
    $date = date_gmmktime(array(
      'mon' => date_format_date('m', $now),
      'mday' => date_format_date('j', $now),
      'year' => date_format_date('Y', $now),
      ));

  }
  switch ($part) {
  case ('year'):
    return date_format_date('Y', $date);
  case ('month'):
    return date_format_date('m', $date);
  case ('day'):
    return date_format_date('j', $date);
  case ('hour'):
    return date_format_date('H', $date);
  case ('minute'):
    return date_format_date('i', $date);
  case ('week'):
    return date_format_date('W', $date);
  default:
    return $date;
  }
}

to

/**
 * Returns a local timestamp (as defined by the user or site's timezone) for
 * midnight GMT.
 * @return integer timestamp
 */
function calendar_user_date($part = 'timestamp') {
  global $user;
  static $date;
  calendar_load_date_api();
  if (!$date) {
    $now = date_time();  /*returns user (or server default, if user timezones are off) timezone corrected time*/
    $date = date_gmmktime(array(
      'mon' => date_date('m', $now),
      'mday' => date_date('j', $now),
      'year' => date_date('Y', $now),
      ));

  }
  switch ($part) {
  case ('year'):
    return date_date('Y', $date);
  case ('month'):
    return date_date('m', $date);
  case ('day'):
    return date_date('j', $date);
  case ('hour'):
    return date_date('H', $date);
  case ('minute'):
    return date_date('i', $date);
  case ('week'):
    return date_date('W', $date);
  default:
    return $date;
  }
}

This changes the call from date_format_date() (and therefore never calls date_gmdate()) to date_date() instead. date_date() then calls the native php date() function, which is what we want in this case because we want no further timezone corrections when we call for formatting. By doing this, we lose some text formatting that date_format_date() would have done for us if the passed in options were requesting text in return, but none of the calls in ths fuction require such formatting, so we are ok!

It feels good to finally get to the bottom of this particular issue, even if it just exposed more issues...

Again, I don't know how to create a .patch file, or use one for that matter, so you're on your own to edit calendar_user_date() in calendar_api.inc

Cheers!

dabro’s picture

Sounded promising but all I get with this patch code is a white screen of death. Probably an error on my part. Thanks for your efforts, Dave

dabro’s picture

I'd like to report that with a Vorticals' latest patch applied, it did work to correct my day ahead problem. If anyone else is experiencing this symptom, give it a try. Thanks for the help, Dave

jpsalter’s picture

Ditto - this change worked for me. Thank you very much!

therainmakor’s picture

I also found that lines 620 & 621 of calendar.module should be changed to this as well.

            $node->start_time_format = date_date(variable_get('calendar_time_format_'. $view->name, 'H:i'), intval($node->calendar_start + $node->start_offset));
            if ($node->calendar_end) $node->end_time_format   = date_date(variable_get('calendar_time_format_'. $view->name, 'H:i'), intval($node->calendar_end + $node->end_offset));

KarenS’s picture

The original issue was fixed and this was re-opened with a different issue, and the new issue (one day off in calendar) is a duplicate of http://drupal.org/node/99223. There is some interesting research into reasons why in this thread, and I may be able to incorporate some of it.

The 'one day off' issue has been a problem in every Drupal module that does calendars, including the Event module. My ultimate fix is the new Date API which I'm working on in HEAD that will quit using date() and gmmktime() and all those functions in favor of the much more robust date_create(), date_timezone_set(), and date_modify() functions added in PHP 5.

KarenS’s picture

Status: Needs review » Closed (duplicate)

Meant to change the status...