ÄúµÄλÖãºÑ°ÃÎÍøÊ×Ò³£¾±à³ÌÀÖÔ°£¾PHP ±à³Ì£¾PHP 5 Power programming
Team LiB
Previous Section Next Section

9.4. Date Handling

PHP has a range of functions that handle date and time. Some of these functions work with a so-called UNIX timestamp, which is the number of seconds since January 1, 1970 at 00:00:00 GMT, the beginning of the UNIX epoch. Because PHP only handles unsigned 32-bit integers and most operating systems don't support negative timestamps, the range in which most of the PHP date functions operate is January 1, 1970 to January 19, 2038. The PEAR::Date package handles dates outside this range and also in a platform-independent way.

9.4.1. Retrieving Date and Time Information

The easiest way of obtaining the current time is with the time() function. It accepts no parameters and simply returns the current timestamp:

<?php
     echo time(); // Outputs something similar to "1077913162"
?>

The resolution is 1 second. If you want some more accuracy, you have two options: microtime() and gettimeofday(). The microtime() function has one annoying peculiarity: The return value is a floating-point number containing the decimal part of the timestamp and the number of seconds since the epoch, concatenated with a space. This makes it, of course, a bit hard to use for a timestamp with sub-second resolution:

<?php
     // Outputs something similar to "0.87395100 1078006447"
     echo microtime();

     $time = preg_replace('@^(.*)\s+(.*)$@e', '\\2 + \\1', microtime());
     echo $time; // Outputs 1078006447.8741
?>

In putting the two parts back together, you lose some of the precision. The gettimeofday() function has a nicer interface. It returns an array with elements representing the timestamp and additional microseconds. Two more elements are included in this array, but you cannot really rely on them because the underlying system functionalityat least in Linuxis not working correctly:

<?php
     print_r(gettimeofday());
?>

returns
Array
(
     [sec] => 1078006910
     [usec] => 339699
     [minuteswest] => -60
     [dsttime] => 0
)

localtime() and getdate() both return an array. The elements contain information belonging to the (optional) timestamp passed to the function. The returned arrays are not exactly the same. Table 9.5 shows what the elements in the arrays mean.

Table 9.5. Elements in Arrays Returned by localtime() and getdate()

Meaning

Index (localtime())

Index (getdate())

Remarks

Seconds

tm_sec

seconds

 

Minutes

tm_min

minutes

 

Hours

tm_hour

hours

 

Day of month

tm_mday

mday

 

Month

tm_mon

mon

For localtime: January=0; for getdate: January=1

Year

tm_year

year

 

Day of week

tm_wday

wday

With 0 being Sunday and 6 being Saturday

Day of year

tm_yday

yday

With 0 being January 1st and 366 being December 32nd

DST in effect

tm_isdst

 

Set to TRue if Daylight Savings Time is in effect

Textual day of week

 

weekday

English name of the weekday

Textual month

 

month

English name of the month

Timestamp

 

0

Number of seconds since 01-01-1970


The tm_isdst element of localtime() is especially interesting. It's the only way in PHP to see whether the server is in DST. Also, note that the month number in the return array of localtime() starts with 0, not with 1, which makes December month 11. The first parameter for both functions is a time stamp, allowing the functions to return date information based on the time you pass them, rather than just on the current time. localtime() normally returns an array with numerical indices, rather than the indices as described in the previous table. To signal the function to return an associative array, you need to pass TRue as the second parameter. If you want to return this associative array with information about the current time, you need to pass the time() function as first parameter:

<?php
     print_r(localtime(time(), true));
?>


Two more date functions are available: gmmktime() and mktime(). Both functions create a timestamp based on parameters passed when the function is called. The difference between the two functions is that gmmktime() TReats the date/time parameters passed as a Greenwich Mean Time (GMT), while parameters passed to mktime() are treated as local time. The order of parameters is not very user friendly, as you can see in the prototype of the following function:

timestamp mktime ( [$hour [, $minute [, $second [, $month [, $day [,  $year [, $is_dst]]]]]]])

Note the particularly weird order of the parameters. All parameters are optional. If any parameter is not included, the "current" value is used, depending on the current date and time. The last parameter, is_dst, controls whether the date and time parameters that are passed to the function are DST-enabled or not. The default value for the parameter is -1, which signals PHP to determine for itself whether the date falls into the range when DST is observed. Here is an example:

<?php
  /* mktime with a date outside the DST range */
  echo date("Ymd H:i:s", mktime(15, 16, 17, 1, 17, 2004)). "\n";
  echo date("Ymd H:i:s", mktime(15, 16, 17, 1, 17, 2004, 0)). "\n";
  echo date("Ymd H:i:s", mktime(15, 16, 17, 1, 17, 2004, 1)). "\n";


  /* mktime with a date inside the DST range */
  echo date("Ymd H:i:s", mktime(15, 16, 17, 6, 17, 2004)). "\n";
  echo date("Ymd H:i:s", mktime(15, 16, 17, 6, 17, 2004, 0)). "\n";
  echo date("Ymd H:i:s", mktime(15, 16, 17, 6, 17, 2004, 1)). "\n\n";
?>

The first three calls "make" a timestamp for January 17, in which no DST is observed. Therefore, setting the $is_dst parameter to 0 has no effect on the returned timestamp. If it's set to 1, though, the timestamp will be one hour earlier, as the mktime() function converts the DST time (which is always one hour ahead of non-DST). For the second set of mktime() calls, we use June 17 in which DST is observed. Setting the $is_dst parameter to 0 now makes the function convert the time from non-DST to DST and, thus, the returned time-stamp will be one hour ahead of the result of the first and third calls. The output is

20040217 15:16:17
20040217 15:16:17
20040217 14:16:17

20040617 15:16:17
20040617 16:16:17
20040617 15:16:17

It's best not to touch the $is_dst parameter, because PHP usually interprets the date and time correctly.

If we replace all calls to mktime() by gmmktime(), the parameters passed to the function are treated as GMT time, with no time zones taken into account. With mktime(), the time zone that the server has configured is taken into account. For instance, if you are on Central European Time (CET), passing the same parameters as shown previously to gmmktime output times that are one hour "later." Because the date function does take into account time zones, the generated GMT timestamp is treated as a CET time zone, resulting in times that are one hour for non-DST times and two hours for DST times (CEST is CET+1).

9.4.2. Formatting Date and Time

Making a GMT date with gmmktime() and then showing it in the current time zone with the date() function doesn't make much sense. Thus, we also have two functions for formatting date/time: date() to format a local date/time, and gmdate() to format a GMT date/time.

Both functions accept exactly the same parameters. The first parameter is a format string (more about that in a bit), and the second is an optional timestamp. If the timestamp parameter is not included, the current time is used in formatting the output. gmdate() and date() always format the date in English, not in the current "locale" that is set on your system. Two functions are provided to format local time/date according to locale settings: strftime() for local time and gmstrftime() for GMT times. Table 9.6 describes formatting string characters for both functions. Note that the (gm)strftime() prefix to the formatting string options with a %.

Table 9.6. Date Formatting Modifiers

Description

date / gmdate

strftime / gmstrftime

Remarks

AM/PM

A

  

am/pm

a

%p

Either am or pm for the English locale. Other locales might have their replacements (for example, nl_NL has an empty string here).

Century, numeric two digits

 

%C

Returns the century number 20 for 2004, and so on.

Character, literal %

 

%%

Use this to place a literal character % inside the formatting string.

Character, newline

 

%n

Use this to place a newline character inside the formatting string.

Character, tab

 

%t

Use this to place a tab character inside the formatting string.

Day count in month

t

 

Number of days in the month defined by the timestamp.

Day of month, leading spaces

 

%e

Current day in this month defined by the timestamp. A space is prepended when the day number is less than 10.

Day of month, leading zeros

d

%D

Current day in this month defined by the timestamp. A zero is prepended when the day number is less than 10.

Day of month, without leading zeros

j

 

Current day in this month defined by the timestamp.

Day of week, full textual

l

%A

For strftime(), the day is shown according to the names of the current locale.

<?php
setlocale(LC_ALL, 'C');
echo strftime('%A ');
setlocale(LC_ALL, 'no_NO');
echo strftime('%A');
?>

shows

Monday mandag

Day of week, numeric (0 = Sunday)

w

%w

The range is 06 with 0 being Sunday and 6 being Saturday.

Day of week, numeric (1= Monday)

 

%u

The range is 17 with 1 being Monday and 7 being Sunday.

Day of week, short textual

D

%a

For the (gm)strftime() function, the name is shown according to the locale; for (gm)date() it is the normal three letter abbreviation: Sun, Sat, Wed, and so on.

Day of year, numeric with leading zeros

 

%j

The day number in a year, starting with 001 for January 1 to 365 or 366.

Day of year, numeric without leading zeros

z

 

The day number in a year, starting with 0 for January 1 to 364 or 365.

DST active

I

 

Returns 1 if DST is active and 0 if DST is not active for the given timestamp.

Formatted, %d/%m/%y

 

%D

Gives the same result as using %d/%m/%y.

Formatted, %H:%M:%S

 

%T

Gives the same result as using %H:%M:%S.

Formatted, in 24-hour notation

 

%R

The time in 24-hour notation without seconds.

<?php
echo strftime("%R\n"); //

shows

23:53
?>

Formatted, in a.m./p.m. notation

 

%r

The time in 12-hour notation including seconds.

<?php
echo strftime("%r\n"); //

shows

11:53:47
?>

Formatted, locale preferred date

 

%x

The date in preferred locale format.

<?php
setlocale(LC_ALL, 'iw_IL');
echo strftime("%x\n"); // shows 29/02/04
?>

Formatted, locale preferred date and time

 

%c

The date and time in preferred locale format.

<?php
setlocale(LC_ALL, 'nl_NL');
// shows zo 29 feb 2004 23:56:12 CET
echo strftime("%c\n");
?>

Formatted, locale preferred time

 

%X

The date in preferred locale format.

<?php
setlocale(LC_ALL, 'nl_NL');
echo strftime("%x\n"); // shows 29-02-04
?>

Hour, 12-hour format, leading zeros

h

%I

 

Hour, 12-hour format, no leading zeros

g

  

Hour,24-hour format, leading zeros

H

%H

 

Hour, 24-hour format, no leading zeros

G

  

Internet time

B

 

The swatch Internet time in which a day is divided into 1,000 units:

<?php
echo date('B'). "\n"; // shows 005
?>

ISO 8601

c

 

Shows the date in ISO 8601 format: 2004-03-01T00:08:37+01:00

Leap year

L

 

Returns 1 if the year represented by the timestamp is a leap year, or 0 otherwise.

Minutes, leading zeros

i

%M

 

Month, full textual

F

%B

For (gm)strftime(), the month name is the name in the language of the current locale.

<?php
setlocale(LC_ALL, 'iw_IL');
echo strftime("%B\n"); // shows
?>

Month, numeric with leading zeros

M

%m

 

Month, numeric without leading zeros

N

  

Month, short textual

M

%b, %h

 

RFC 2822

R

 

Returns a RFC 2822 (mail) formatted text (Mon, 1 Mar 2004 00:13:34 +0100).

Seconds since UNIX epoch

U

  

Seconds, numeric with leading zeros

s

%S

 

Suffix for day of month, English ordinal

S

 

Returns an English ordinal suffix for use with the j formatting option.

<?php
echo date("jS\n"); // returns 1st
?>

Time zone, numeric (in seconds)

Z

 

Returns the offset to GMT in seconds. For CET, this is 3600; for EST, this is 18000, for example.

Time zone, numeric formatted

O

 

Returns a formatted offset to GMT. For CET, this is +0100; for EST, this is -0500, for example.

Time zone, textual

T

%Z

Returns the current time zone name: CET, EST, and so on.

Week number, ISO 8601

W

%V

In ISO 8601, week #1 is the first week in the year having four or more days. The range is 01 to 53, and you can use this in combination with %g or %G for the accompanying year.

Week number, the first Monday in a year is the start of week 1

 

%W

<?php
// shows 01
echo strftime("%W",
    strtotime("2001-01-01")),"\n";// shows 53
echo strftime("%W",
    strtotime("2001-12-31")),"\n";
?>

Week number, the first Sunday in a year is the start of week 1

 

%U

<?php
// shows 00
echo strftime("%U",
    strtotime("2001-01- 01")),"\n";
// shows 52
echo strftime("%U",
    strtotime("2001-12- 31")),"\n";
?>

Year, numeric two digits with leading zeroes

y

%y

 

Year, numeric two digits; year component for %W

 

%g

This number might differ from the "real year," as in ISO 8601; January 1 might still belong to week 53 of the year before. In that case, the year returned with this formatting option will be the one of the previous year, too.

Year, numeric four digits

Y

%Y

 

Year, numeric four digits; year component for %W

 

%G

This number might differ from the "real year," as in ISO 8601; January 1 might still belong to week 53 of the year before. In that case, the year returned with this formatting option will be the one of the previous year, too.


9.4.2.1 Example 1: ISO 8601 Week Numbers

This example shows that the ISO 8601 year format option (%V) might differ from the normal year format option (%Y) if a year has less than four days:

<?php
    for ($i = 27; $i <= 31; $i++) {
        echo gmstrftime(
                 "%Y-%m-%d (%V %G, %A)\n",
                 gmmktime(0, 0, 0, 12, $i, 2004)
            );
    }
    for ($i = 1; $i <= 6; $i++) {
        echo gmstrftime(
                 "%Y-%m-%d (%V %G, %A)\n",
                 gmmktime(0, 0, 0, 1, $i, 2005)
            );
    }
?>

The script outputs
2004-12-27 (53 2004, Monday)
2004-12-28 (53 2004, Tuesday)
2004-12-29 (53 2004, Wednesday)
2004-12-30 (53 2004, Thursday)
2004-12-31 (53 2004, Friday)
2005-01-01 (53 2004, Saturday)
2005-01-02 (53 2004, Sunday)
2005-01-03 (01 2005, Monday)
2005-01-04 (01 2005, Tuesday)
2005-01-05 (01 2005, Wednesday)
2005-01-06 (01 2005, Thursday)

As you can see, the ISO year is different for January 1 and 2, 2005, because the first week (Monday to Sunday) only has two days.

9.4.2.2 Example 2: DST Issues

Every year around October, at least 1025 bugs are reported when a day is listed twice in somebody's overview. Actually, the day listed twice is the date on which DST ends, as you can see in this example:

<?php
    /* Start date for the loop is October 31th, 2004 */
    $ts = mktime(0, 0, 0, 10, 31, 2004);

    /* We loop for 4 days */
    for ($i = 0; $i < 4; $i++) {
        echo date ("Y-m-d (H:i:s)\n", $ts);
        $ts += (24 * 60 * 60); /* 24 hours */
    }
?>

When this script is run, you see the following output:
2004-10-31 (00:00:00)
2004-10-31 (23:00:00)
2004-11-01 (23:00:00)
2004-11-02 (23:00:00)

The 31st is listed twice because there are actually 25 hours between midnight, October 31 and November 1, not the 24 hours that were added in our loop. You can solve the problem in one of two ways. If you pick a different time of day, such as noon, the script will always have the correct date:

<?php
    /* Start date for the loop is October 29th, 2004 */
    $ts = mktime(12, 0, 0, 10, 29, 2004);

    /* We loop for 4 days */
    for ($i = 0; $i < 4; $i++) {
        echo date ("Y-m-d (H:i:s)\n", $ts);
        $ts += (24 * 60 * 60);
    }
?>

Its output is
2004-10-29 (12:00:00)
2004-10-30 (12:00:00)
2004-10-31 (11:00:00)
2004-11-01 (11:00:00)

However, there is still a difference in the time. A better solution is to abuse the mktime() function a little:

<?php
    /* We loop for 6 days */
    for ($i = 0; $i < 6; $i++) {
        $ts = mktime(0, 0, 0, 10, 30 + $i, 2004);
        echo date ("Y-m-d (H:i:s) T\n", $ts);
    }
?>

Its output is
2004-10-30 (00:00:00) CEST
2004-10-31 (00:00:00) CEST
2004-11-01 (00:00:00) CET
2004-11-02 (00:00:00) CET
2004-11-03 (00:00:00) CET
2004-11-04 (00:00:00) CET

We add the day offset to the mktime() parameter that describes the day of month. mktime() then correctly wraps into the next months and years and takes care of the DST hours, as you can see in the previous output.

9.4.2.3 Example 3: Showing the Local Time in Other Time Zones

Sometimes, you want to show a formatted time in the current time zone and in other time zones as well. The following script shows a full textual date representation for the U.S., Norway, the Netherlands, and Israel:

<?php
    echo strftime("%c\n");

    echo "\nEST in en_US:\n";
    setlocale(LC_ALL, "en_US");
    putenv("TZ=EST");
    echo strftime("%c\n");

    echo "\nMET in nl_NL:\n";
    setlocale(LC_ALL, "nl_NL");
    putenv("TZ=MET");
    echo strftime("%c\n");

    echo "\nMET in no_NO:\n";
    setlocale(LC_ALL, "no_NO");
    putenv("TZ=MET");
    echo strftime("%c\n");

    echo "\nIST in iw_IL:\n";
    setlocale(LC_ALL, "iw_IL");
    putenv("TZ=IST");
    echo strftime("%c\n");
?>

Figure 9.4 shows its output.

Figure 9.4. March 1 in different locales.


Note

You need to have the locales and time-zone settings installed on your system before this will work. It is a system-dependent setting and not everything is always available on your system. If you're a Mac OS X user, have a look at http://www.macmax.org/locales/index_en.html to install locales.


9.4.3. Parsing Date Formats

The opposite of formatting text is parsing a textual description of a date into a timestamp. The strtotime() function handles a many different formats. In addition to the formats listed at http://www.gnu.org/software/tar/manual/html_chapter/tar_7.html, PHP also supports some extra ISO 8601 formats (http://www.w3.org/TR/NOTE-datetime). Table 9.7 contains a list of the most useful formats.

Table 9.7. Date/Time Formats as Understood by strtotime()

Date String

GMT Formatted Date

Remarks

1970-09-17

1970-09-16 23:00:00

ISO 8601 preferred date.

9/17/72

1972-09-16 23:00:00

Common U.S. way (d/m/yy).

24 September 1972

1972-09-23 23:00:00

Without any specified time, 0:00 is used. Because the time zone is set to MET (GMT+1), the GMT formatted date is in the previous day.

24 Sep 1972

1972-09-23 23:00:00

Sep 24, 1972

1972-09-23 23:00:00

20:02:00

2004-03-01 19:02:00

Without any date specified, the current date is used.

20:02

2004-03-01 19:02:00

8:02pm

2004-03-01 19:02:00

20:02-0500

2004-03-02 01:02:00

-0500 is the time zone (EST).

20:02 EST

2004-03-02 01:02:00

 

Thursday 1 Thursday this Thursday

2004-03-03 23:00:00

A day name advances to the first available day with this name. In case the current day has this name, the current day is used.

2 Thursday 19:00

2004-03-11 18:00:00

2 is the second Thursday from now.

next Thursday 7pm

2004-03-11 18:00:00

Next means the next available day with this name after the first available day, and thus is the same as 2.

last Thursday 19:34

2004-02-26 18:34:00

The Thursday before the current day. If the name of the day is the same as the current day, the time-stamp of the previous day is used.

1 year 2 days ago

2003-02-27 21:25:44

The current time is used to calculate the relative displacement with. The sign is needed before every displacement unit; if it's not used, + is assumed. If "ago" is postfixed, the meaning of + and - is reversed. Other possible units are second, minute, hour, week, Month, and fortnight (14 days).

-1 year -2 days

2003-02-27 21:25:44

-1 year 2 days

2003-03-03 21:25:44

1 year -2 days

2005-02-27 21:25:44

tomorrow

2004-03-02 21:25:44

yesterday

2004-02-29 21:25:44

20040301T00:00:00+1900

2004-02-29 05:00:00

Used for WDDX parsing.

2004W021

2004-01-04 23:00:00

Midnight of the first day of ISO week 21 in 2004.

2004122 0915

2004-12-22 08:15:00

Only numbers in the form yyyymmdd hhmm.


Using the strtotime() function is easy. It accepts two parameters: the string to parse to a timestamp and an optional timestamp. If the timestamp is included, the time is converted relative to the timestamp; if it's not included, the current time is used. The relative calculations are only written with yesterday, tomorrow, and the 1 year 2 days (ago) format strings.

strtotime() parsing is always done with the current time zone, unless a different time zone is specified in the string that is parsed:

<?php
echo date("H:i T\n", strtotime("09:22"));       // shows 09:22 CET
echo date("H:i T\n\n", strtotime("09:22 GMT")); // shows 10:22 CET

echo gmdate("H:i T\n", strtotime("09:22"));     // shows 08:22 GMT
echo gmdate("H:i T\n", strtotime("09:22 GMT")); // shows 09:22 GMT
?>

For more information on time zones, times, and calendars, see the excellent web site at http://www.timeanddate.com/.

    Team LiB
    Previous Section Next Section