title besides title

 
Showing posts with label PHP - Dates and Times. Show all posts
Showing posts with label PHP - Dates and Times. Show all posts

Saturday, November 24, 2012

PHP : Dates and Times - [3.1] Introduction

Displaying and manipulating dates and times seems simple at first but gets more difficult depending on how diverse and complicated your users are. Do your users span more than one time zone? Probably so, unless you are building an intranet or a site with a very specific geographical audience. Is your audience frightened away by timestamps that look like "2002-07-20 14:56:34 EDT" or do they need to be calmed with familiar representations like "Saturday July 20, 2000 (2:56 P.M.)." Calculating the number of hours between today at 10 A.M. and today at 7 P.M. is pretty easy. How about between today at 3 A.M. and noon on the first day of next month? Finding the difference between dates is discussed in Section 3.6 and Section 3.7.
These calculations and manipulations are made even more hectic by daylight saving (or summer) time (DST). Thanks to DST, there are times that don't exist (in most of the United States, 2 A.M. to 3 A.M. on the first Sunday in April) and times that exist twice (in most of the United States, 1 A.M. to 2 A.M. on the last Sunday in October). Some of your users may live in places that observe DST, some may not. Section 3.12 and Section 3.13 provide ways to work with time zones and DST.
Programmatic time handling is made much easier by two conventions. First, treat time internally as Coordinated Universal Time (abbreviated UTC and also known as GMT, Greenwich Mean Time), the patriarch of the time-zone family with no DST or summer time observance. This is the time zone at 0 degrees longitude, and all other time zones are expressed as offsets (either positive or negative) from it. Second, treat time not as an array of different values for month, day, year, minute, second, etc., but as seconds elapsed since the Unix epoch: midnight on January 1, 1970 (UTC, of course). This makes calculating intervals much easier, and PHP has plenty of functions to help you move easily between epoch timestamps and human-readable time representations.
The function mktime( ) produces epoch timestamps from a given set of time parts, while date( ) , given an epoch timestamp, returns a formatted time string. You can use these functions, for example, to find on what day of the week New Year's Day 1986 occurred:
$stamp = mktime(0,0,0,1,1,1986);
print date('l',$stamp);
Wednesday
This use of mktime( ) returns the epoch timestamp at midnight on January 1, 1986. The l format character to date( ) tells it to return the full name of the day of the week that corresponds to the given epoch timestamp. Section 3.5 details the many format characters available to date( ).
In this book, the phrase epoch timestamp refers to a count of seconds since the Unix epoch. Time parts (or date parts or time and date parts) means an array or group of time and date components such as day, month, year, hour, minute, and second. Formatted time string (or formatted date string, etc.) means a string that contains some particular grouping of time and date parts, for example "2002-03-12," "Wednesday, 11:23 A.M.," or "February 25."
If you used epoch timestamps as your internal time representation, you avoided any Y2K issues, because the difference between 946702799 (1999-12-31 23:59:59 UTC) and 946702800 (2000-01-01 00:00:00 UTC) is treated just like the difference between any other two timestamps. You may, however, run into a Y2038 problem. January 19, 2038 at 3:14:07 A.M. (UTC) is 2147483647 seconds after midnight January 1, 1970. What's special about 2147483647? It's 231 - 1, which is the largest integer expressible when 32 bits represent a signed integer. (The 32nd bit is used for the sign.)
The solution? At some point before January 19, 2038, make sure you trade up to hardware that uses, say, a 64-bit quantity for time storage. This buys you about another 292 billion years. (Just 39 bits would be enough to last you until about 10680, well after the impact of the Y10K bug has leveled the Earth's cold fusion factories and faster-than-light travel stations.) 2038 might seem far off right now, but so did 2000 to COBOL programmers in the 1950s and 1960s. Don't repeat their mistake!

PHP : Dates and Times - [3.2] Finding the Current Date and Time

3.2.1 Problem

You want to know what the time or date is.

3.2.2 Solution

Use strftime( ) or date( ) for a formatted time string:
print strftime('%c');
print date('r');
Mon Aug 12 18:23:45 2002
Mon, 12 Aug 2002 18:23:45 -0400
Use getdate( ) or localtime( ) if you want time parts:
$now_1 = getdate( );
$now_2 = localtime( );
print "$now_1[hours]:$now_1[minutes]:$now_1[seconds]";
print "$now_2[2]:$now_2[1]:$now_2[0]";
18:23:45
18:23:45

3.2.3 Discussion

The functions strftime( ) and date( ) can produce a variety of formatted time and date strings. They are discussed in more detail in Section 3.5. Both localtime( ) and getdate( ), on the other hand, return arrays whose elements are the different pieces of the specified date and time.
The associative array getdate( ) returns has the key/value pairs listed in Table 3-1.

Table 3-1. Return array from getdate( )
Key
Value
seconds
Seconds
minutes
Minutes
hours
Hours
mday
Day of the month
wday
Day of the week, numeric (Sunday is 0, Saturday is 6)
mon
Month, numeric
year
Year, numeric
yday
Day of the year, numeric (e.g., 299)
weekday
Day of the week, textual, full (e.g., "Friday")
month
Month, textual, full (e.g., "January")
For example, here's how to use getdate( ) to print out the month, day, and year:
$a = getdate( );
printf('%s %d, %d',$a['month'],$a['mday'],$a['year']);
August 7, 2002
Pass getdate( ) an epoch timestamp as an argument to make the returned array the appropriate values for local time at that timestamp. For example, the month, day, and year at epoch timestamp 163727100 is:
$a = getdate(163727100);
printf('%s %d, %d',$a['month'],$a['mday'],$a['year']);
March 10, 1975
The function localtime( ) returns an array of time and date parts. It also takes an epoch timestamp as an optional first argument, as well as a boolean as an optional second argument. If that second argument is true, localtime( ) returns an associative array instead of a numerically indexed array. The keys of that array are the same as the members of the tm_struct structure that the C function localtime( ) returns, as shown in Table 3-2.

Table 3-2. Return array from localtime( )
Numeric position
Key
Value
0
tm_sec
Second
1
tm_min
Minutes
2
tm_hour
Hour
3
tm_mday
Day of the month
4
tm_mon
Month of the year (January is 0)
5
tm_year
Years since 1900
6
tm_wday
Day of the week
7
tm_yday
Day of the year
8
tm_isdst
Is daylight saving time in effect?
For example, here's how to use localtime( ) to print out today's date in month/day/year format:
$a = localtime( );
$a[4] += 1;
$a[5] += 1900;
print "$a[4]/$a[3]/$a[5]";
8/7/2002
The month is incremented by 1 before printing since localtime( ) starts counting months with 0 for January, but we want to display 1 if the current month is January. Similarly, the year is incremented by 1900 because localtime( ) starts counting years with 0 for 1900.
Like getdate( ), localtime( ) accepts an epoch timestamp as an optional first argument to produce time parts for that timestamp:
$a = localtime(163727100);
$a[4] += 1;
$a[5] += 1900;
print "$a[4]/$a[3]/$a[5]";
3/10/1975

PHP : Dates and Times - [3.3] Converting Time and Date Parts to an Epoch Timestamp

3.3.1 Problem

You want to know what epoch timestamp corresponds to a set of time and date parts.

3.3.2 Solution

Use mktime( ) if your time and date parts are in the local time zone:
// 7:45:03 PM on March 10, 1975, local time
$then = mktime(19,45,3,3,10,1975);
Use gmmktime( ) if your time and date parts are in GMT:
// 7:45:03 PM on March 10, 1975, in GMT
$then = gmmktime(19,45,3,3,10,1975);
Pass no arguments to get the current date and time in the local or UTC time zone:
$now = mktime();
$now_utc = gmmktime();

3.3.3 Discussion

The functions mktime( ) and gmmktime( ) each take a date and time's parts (hour, minute, second, month, day, year, DST flag) and return the appropriate Unix epoch timestamp. The components are treated as local time by mktime( ), while gmmktime( ) treats them as a date and time in UTC. For both functions, a seventh argument, the DST flag (1 if DST is being observed, 0 if not), is optional. These functions return sensible results only for times within the epoch. Most systems store epoch timestamps in a 32-bit signed integer, so "within the epoch" means between 8:45:51 P.M. December 13, 1901 UTC and 3:14:07 A.M. January 19, 2038 UTC.
In the following example, $stamp_now is the epoch timestamp when mktime( ) is called and $stamp_future is the epoch timestamp for 3:25 P.M. on June 4, 2012:
$stamp_now = mktime( );
$stamp_future = mktime(15,25,0,6,4,2012);

print $stamp_now;
print $stamp_future;
1028782421
1338837900
Both epoch timestamps can be fed back to strftime( ) to produce formatted time strings:
print strftime('%c',$stamp_now);
print strftime('%c',$stamp_future);
Thu Aug  8 00:53:41 2002
Mon Jun 4 15:25:00 2012
Because the previous calls to mktime( ) were made on a computer set to EDT (which is four hours behind GMT), using gmmktime( ) instead produces epoch timestamps that are 14400 seconds (four hours) smaller:
$stamp_now = gmmktime( );
$stamp_future = gmmktime(15,25,0,6,4,2012);

print $stamp_now;
print $stamp_future;
1028768021
1338823500
Feeding these gmmktime( )-generated epoch timestamps back to strftime( ) produces formatting time strings that are also four hours earlier:
print strftime('%c',$stamp_now);
print strftime('%c',$stamp_future);
Wed Aug  7 20:53:41 2002
Mon Jun 4 11:25:00 2012

PHP : Dates and Times - [3.4] Converting an Epoch Timestamp to Time and Date Parts

3.4.1 Problem

You want the set of time and date parts that corresponds to an epoch timestamp.

3.4.2 Solution

Pass an epoch timestamp to getdate( ):
$time_parts = getdate(163727100);

3.4.3 Discussion

The time parts returned by getdate( ) are detailed in Table 3-1. These time parts are in local time. If you want time parts in another time zone corresponding to a particular epoch timestamp, see Section 3.12.

PHP : Dates and Times - [3.5] Printing a Date or Time in a Specified Format

3.5.1 Problem

You need to print out a date or time formatted in a particular way.

3.5.2 Solution

Use date( ) or strftime( ):
print strftime('%c');
print date('m/d/Y');
Tue Jul 30 11:31:08 2002
07/30/2002

3.5.3 Discussion

Both date( ) and strftime( ) are flexible functions that can produce a formatted time string with a variety of components. The formatting characters for these functions are listed in Table 3-3. The Windows column indicates whether the formatting character is supported by strftime( ) on Windows systems.

Table 3-3. strftime( ) and date( ) format characters
Type
strftime( )
date( )
Description
Range
Windows
Hour
%H
H
Hour, numeric, 24-hour clock
00-23
Yes
Hour
%I
h
Hour, numeric, 12-hour clock
01-12
Yes
Hour
%k
Hour, numeric, 24-hour clock, leading zero as space
0-23
No
Hour
%l
Hour, numeric, 12-hour clock, leading zero as space
1-12
No
Hour
%p
A
AM or PM designation for current locale
Yes
Hour
%P
a
am/pm designation for current locale
No
Hour
G
Hour, numeric, 24-hour clock, leading zero trimmed
0-23
No
Hour
g
Hour, numeric, 12-hour clock, leading zero trimmed
0-1
No
Minute
%M
I
Minute, numeric
00-59
Yes
Second
%S
s
Second, numeric
00-61[1]
Yes
Day
%d
d
Day of the month, numeric
01-31
Yes
Day
%e
Day of the month, numeric, leading zero as space
1-31
No
Day
%j
z
Day of the year, numeric
001-366 for strftime( ); 0-365 for date( )
Yes
Day
%u
Day of the week, numeric (Monday is 1)
1-7
No
Day
%w
w
Day of the week, numeric (Sunday is 0)
0-6
Yes
Day
j
Day of the month, numeric, leading zero trimmed
1-31
No
Day
S
English ordinal suffix for day of the month, textual
"st," "th," "nd," "rd"
No
Week
%a
D
Abbreviated weekday name, text for current locale
Yes
Week
%A
l
Full weekday name, text for current locale
Yes
Week
%U
Week number in the year; numeric; first Sunday is the first day of the first week
00-53
Yes
Week
%V
W
ISO 8601:1988 week number in the year; numeric; week 1 is the first week that has at least 4 days in the current year; Monday is the first day of the week
01-53
No
Week
%W
Week number in the year; numeric; first Monday is the first day of the first week
00-53
Yes
Month
%B
F
Full month name, text for current locale
Yes
Month
%b
M
Abbreviated month name, text for current locale
Yes
Month
%h
Same as %b
No
Month
%m
m
Month, numeric
01-12
Yes
Month
n
Month, numeric, leading zero trimmed
1-12
No
Month
t
Month length in days, numeric
28, 29, 30, 31
No
Year
%C
Century, numeric
00-99
No
Year
%g
Like %G, but without the century
00-99
No
Year
%G
ISO 8601 year with century; numeric; the four-digit year corresponding to the ISO week number; same as %y except if the ISO week number belongs to the previous or next year, that year is used instead
No
Year
%y
y
Year without century, numeric
00-99
Yes
Year
%Y
Y
Year, numeric, including century
Yes
Year
L
Leap year flag (yes is 1)
0, 1
No
Timezone
%z
O
Hour offset from GMT, +/-HHMM (e.g., -0400, +0230)
-1200-+1200
Yes, but acts like %Z
Timezone
%Z
T
Time zone, name, or abbreviation; textual
Yes
Timezone
I
Daylight saving time flag (yes is 1)
0, 1
No
Timezone
Z
Seconds offset from GMT; west of GMT is negative, east of GMT is positive
-43200-43200
No
Compound
%c
Standard date and time format for current locale
Yes
Compound
%D
Same as %m/%d/%y
No
Compound
%F
Same as %Y-%m-%d
No
Compound
%r
Time in AM or PM notation for current locale
No
Compound
%R
Time in 24-hour notation for current locale
No
Compound
%T
Time in 24-hour notation (same as %H:%M:%S)
No
Compound
%x
Standard date format for current locale(without time)
Yes
Compound
%X
Standard time format for current locale(without date)
Yes
Compound
r
RFC 822 formatted date (e.g., "Thu, 22 Aug 2002 16:01:07 +0200")
No
Other
%s
U
Seconds since the epoch
No
Other
B
Swatch Internet time
No
Formatting
%%
Literal % character
Yes
Formatting
%n
Newline character
No
Formatting
%t
Tab character
No
[1] The range for seconds extends to 61 to account for leap seconds.
The first argument to each function is a format string, and the second argument is an epoch timestamp. If you leave out the second argument, both functions default to the current date and time. While date( ) and strftime( ) operate over local time, they each have UTC-centric counterparts (gmdate( ) and gmstrftime( )).
The formatting characters for date( ) are PHP-specific, but strftime( ) uses the C-library strftime( ) function. This may make strftime( ) more understandable to someone coming to PHP from another language, but it also makes its behavior slightly different on various platforms. Windows doesn't support as many strftime( ) formatting commands as most Unix-based systems. Also, strftime( ) expects its formatting characters to each be preceded by a % (think printf( )), so it's easier to produce strings with lots of interpolated time and date values in them.
For example, at 12:49 P.M. on July 15, 2002, the code to print out:
It's after 12 pm on July 15, 2002
with strftime( ) looks like:
print strftime("It's after %I %P on %B %d, %Y");
With date( ) it looks like:
print "It's after ".date('h a').' on '.date('F d, Y');
Non-date-related characters in a format string are fine for strftime( ), because it looks for the % character to decide where to interpolate the appropriate time information. However, date( ) doesn't have such a delimiter, so about the only extras you can tuck into the formatting string are spaces and punctuation. If you pass strftime( )'s formatting string to date( ):
print date("It's after %I %P on %B%d, %Y");
you'd almost certainly not want what you'd get:
131'44 pmf31eMon, 15 Jul 2002 12:49:44 -0400 %1 %P o7 %742%15, %2002
To generate time parts with date( ) that are easy to interpolate, group all time and date parts from date( ) into one string, separating the different components with a delimiter that date( ) won't translate into anything and that isn't itself part of one of your substrings. Then, using explode( ) with that delimiter character, put each piece of the return value from date( ) in an array, which is easily interpolated in your output string:
$ar = explode(':',date("h a:F d, Y"));
print "It's after $ar[0] on $ar[1]";

Friday, November 23, 2012

PHP : Dates and Times - [3.6] Finding the Difference of Two Dates

3.6.1 Problem

You want to find the elapsed time between two dates. For example, you want to tell a user how long it's been since she last logged onto your site.

3.6.2 Solution

Convert both dates to epoch timestamps and subtract one from the other. Use this code to separate the difference into weeks, days, hours, minutes, and seconds:
// 7:32:56 pm on May 10, 1965
$epoch_1 = mktime(19,32,56,5,10,1965);
// 4:29:11 am on November 20, 1962
$epoch_2 = mktime(4,29,11,11,20,1962);

$diff_seconds  = $epoch_1 - $epoch_2;
$diff_weeks    = floor($diff_seconds/604800);
$diff_seconds -= $diff_weeks   * 604800;
$diff_days     = floor($diff_seconds/86400);
$diff_seconds -= $diff_days    * 86400;
$diff_hours    = floor($diff_seconds/3600);
$diff_seconds -= $diff_hours   * 3600;
$diff_minutes  = floor($diff_seconds/60);
$diff_seconds -= $diff_minutes * 60;

print "The two dates have $diff_weeks weeks, $diff_days days, ";
print "$diff_hours hours, $diff_minutes minutes, and $diff_seconds ";
print "seconds elapsed between them.";
The two dates have 128 weeks, 6 days, 14 hours, 3 minutes, 
and 45 seconds elapsed between them.
Note that the difference isn't divided into larger chunks than weeks (i.e., months or years) because those chunks have variable length and wouldn't give an accurate count of the time difference calculated.

3.6.3 Discussion

There are a few strange things going on here that you should be aware of. First of all, 1962 and 1965 precede the beginning of the epoch. Fortunately, mktime( ) fails gracefully here and produces negative epoch timestamps for each. This is okay because the absolute time value of either of these questionable timestamps isn't necessary, just the difference between the two. As long as epoch timestamps for the dates fall within the range of a signed integer, their difference is calculated correctly.
Next, a wall clock (or calendar) reflects a slightly different amount of time change between these two dates, because they are on different sides of a DST switch. The result subtracting epoch timestamps gives is the correct amount of elapsed time, but the perceived human time change is an hour off. For example, on the Sunday morning in April when DST is activated, what's the difference between 1:30 A.M. and 4:30 A.M.? It seems like three hours, but the epoch timestamps for these two times are only 7,200 seconds apart — two hours. When a local clock springs forward an hour (or falls back an hour in October), the steady march of epoch timestamps takes no notice. Truly, only two hours have passed, although our clock manipulations make it seem like three.
If you want to measure actual elapsed time (and you usually do), this method is fine. If you're more concerned with the difference in what a clock says at two points in time, use Julian days to compute the interval, as discussed in Section 3.7.
To tell a user the elapsed time since her last login, you need to find the difference between the login time and her last login time:
$epoch_1 = time();
$r = mysql_query("SELECT UNIX_TIMESTAMP(last_login) AS login 
                 FROM user WHERE id = $id") or die();
$ob = mysql_fetch_object($r);
$epoch_2 = $ob->login;

$diff_seconds  = $epoch_1 - $epoch_2;
$diff_weeks    = floor($diff_seconds/604800);
$diff_seconds -= $diff_weeks   * 604800;
$diff_days     = floor($diff_seconds/86400);
$diff_seconds -= $diff_days    * 86400;
$diff_hours    = floor($diff_seconds/3600);
$diff_seconds -= $diff_hours   * 3600;
$diff_minutes  = floor($diff_seconds/60);
$diff_seconds -= $diff_minutes * 60;

print "You last logged in $diff_weeks weeks, $diff_days days, ";
print "$diff_hours hours, $diff_minutes minutes, and $diff_seconds ago.";

Thursday, November 22, 2012

PHP : Dates and Times - [3.7] Finding the Difference of Two Dates with Julian Days

3.7.1 Problem

You want to find the difference of two dates measured by what a clock would say, not the actual elapsed time.

3.7.2 Solution

Use gregoriantojd( ) to get the Julian day for a set of date parts, then subtract one Julian day from the other to find the date difference. Then convert the time parts to seconds and subtract one from the other to find the time difference. If the time difference is less than 0, decrease the date difference by one and adjust the time difference to apply to the previous day. Here's the code:
$diff_date = gregoriantojd($date_1_mo, $date_1_dy, $date_1_yr) -
             gregoriantojd($date_2_mo, $date_2_dy, $date_2_yr);
$diff_time = $date_1_hr * 3600 + $date_1_mn * 60 + $date_1_sc -
             $date_2_hr * 3600 - $date_2_mn * 60 - $date_2_sc;
if ($diff_time < 0) {
   $diff_date--; 
   $diff_time = 86400 - $diff_time;
}

3.7.3 Discussion

Finding differences with Julian days lets you operate outside the range of epoch seconds and also accounts for DST differences.
If you have the components of your two days in arrays:
// 7:32:56 pm on May 10, 1965
list($date_1_yr, $date_1_mo, $date_1_dy, $date_1_hr, $date_1_mn, $date_1_sc)=
    array(1965, 5, 10, 19, 32, 56);
// 4:29:11 am on November 20, 1962
list($date_2_yr, $date_2_mo, $date_2_dy, $date_2_hr, $date_2_mn, $date_2_sc)=
    array(1962, 11, 20, 4, 29, 11);

$diff_date = gregoriantojd($date_1_mo, $date_1_dy, $date_1_yr) -
             gregoriantojd($date_2_mo, $date_2_dy, $date_2_yr);
$diff_time = $date_1_hr * 3600 + $date_1_mn * 60 + $date_1_sc -
             $date_2_hr * 3600 - $date_2_mn * 60 - $date_2_sc;
if ($diff_time < 0) {
   $diff_date--; 
   $diff_time = 86400 - $diff_time;
}
$diff_weeks = floor($diff_date/7); $diff_date -= $diff_weeks * 7;
$diff_hours = floor($diff_time/3600); $diff_time -= $diff_hours * 3600;
$diff_minutes = floor($diff_time/60); $diff_time -= $diff_minutes * 60;

print "The two dates have $diff_weeks weeks, $diff_date days, ";
print "$diff_hours hours, $diff_minutes minutes, and $diff_time ";
print "seconds between them.";
The two dates have 128 weeks, 6 days, 15 hours, 3 minutes,
and 45 seconds between them.
This method produces a time difference based on clock time, which is why the result shows an hour more of difference than in Section 3.6. May 10 is during DST, and November 11 is during standard time.
The function gregoriantojd( ) is part of PHP's calendar extension, and so is available only if that extension is loaded.

Wednesday, November 21, 2012

PHP : Dates and Times - [3.8] Finding the Day in a Week, Month, Year, or the Week Number in a Year

3.8.1 Problem

You want to know the day or week of the year, the day of the week, or the day of the month. For example, you want to print a special message every Monday, or on the first of every month.

3.8.2 Solution

Use the appropriate arguments to date( ) or strftime( ):
print strftime("Today is day %d of the month and %j of the year.");
print 'Today is day '.date('d').' of the month and '.date('z').' of the year.';

3.8.3 Discussion

The two functions date( ) and strftime( ) don't behave identically. Days of the year start with 0 for date( ), but with 1 for strftime( ). Table 3-4 contains all the day and week number format characters date( ) and strftime( ) understand.

Table 3-4. Day and week number format characters
Type
strftime( )
date( )
Description
Range
Day
%d
d
Day of the month, numeric
01-31
Day
%j
z
Day of the year, numeric
001-366 for strftime( ); 0-365 for date( )
Day
%u
Day of the week, numeric (Monday is 1)
1-7
Day
%w
w
Day of the week, numeric (Sunday is 0)
0-6
Day
%W
ISO 8601 day of the week, numeric (Monday is the first day of the week)
0-6
Week
%U
Week number in the year; numeric; first Sunday is the first day of the first week
00-53
Week
%V
W
ISO 8601:1988 week number in the year; numeric; week 1 is the first week that has at least four days in the current year; Monday is the first day of the week
01-53
To print out something only on Mondays, use the w formatting character to date( ) or the %w string with strftime( ):
if (1 == date('w')) {
    print "Welcome to the beginning of your work week.";
}

if (1 == strftime('%w')) {
    print "Only 4 more days until the weekend!";
}
There are different ways to calculate week numbers and days in a week, so be careful to choose the appropriate one. The ISO standard (ISO 8601), says that weeks begin on Mondays and that the days in the week are numbered 1 (Monday) through 7 (Sunday). Week 1 in a year is the first week in a year with a Thursday in that year. This means the first week in a year is the first week with a majority of its days in that year. These week numbers range from 01 to 53.
Other week number standards range from 00 to 53, with days in a year's week 53 potentially overlapping with days in the following year's week 00.
As long as you're consistent within your programs, you shouldn't run into any trouble, but be careful when interfacing with other PHP programs or your database. For example, MySQL's DAYOFWEEK( ) function treats Sunday as the first day of the week, but numbers the days 1 to 7, which is the ODBC standard. Its WEEKDAY( ) function, however, treats Monday as the first day of the week and numbers the days from 0 to 6. Its WEEK( ) function lets you choose whether weeks should start on Sunday or Monday, but it's incompatible with the ISO standard.

Tuesday, November 20, 2012

PHP : Dates and Times - [3.9] Validating a Date

3.9.1 Problem

You want to check if a date is valid. For example, you want to make sure a user hasn't provided a birthdate such as February 30, 1962.

3.9.2 Solution

Use checkdate( ):
$valid = checkdate($month,$day,$year);

3.9.3 Discussion

The function checkdate( ) returns true if $month is between 1 and 12, $year is between 1 and 32767, and $day is between 1 and the correct maximum number of days for $month and $year. Leap years are correctly handled by checkdate( ), and dates are rendered using the Gregorian calendar.
Because checkdate( ) has such a broad range of valid years, you should do additional validation on user input if, for example, you're expecting a valid birthdate. The Guinness Book of World Records says the oldest person ever reached 122. To check that a birthdate indicates a user between 18 and 122 years old, use the pc_checkbirthdate( ) function shown in Example 3-1.
Example 3-1. pc_checkbirthdate( )
function pc_checkbirthdate($month,$day,$year) {
    $min_age = 18;
    $max_age = 122;

    if (! checkdate($month,$day,$year)) {
        return false;
    }

    list($this_year,$this_month,$this_day) = explode(',',date('Y,m,d'));

    $min_year = $this_year - $max_age;
    $max_year = $this_year - $min_age;

    print "$min_year,$max_year,$month,$day,$year\n";
    
    if (($year > $min_year) && ($year < $max_year)) {
        return true;
    } elseif (($year == $max_year) && 
              (($month < $this_month) ||
               (($month == $this_month) && ($day <= $this_day)))) {
        return true;
    } elseif (($year == $min_year) &&
              (($month > $this_month) ||
               (($month == $this_month && ($day > $this_day))))) {
        return true;
    } else {
        return false;
    }
}
Here is some sample usage:
// check December 3, 1974
if (pc_checkbirthdate(12,3,1974)) {
    print "You may use this web site.";
} else {
    print "You are too young to proceed.";
    exit( );
}
This function first uses checkdate( ) to make sure that $month, $day, and $year represent a valid date. Various comparisons then make sure that the supplied date is in the range set by $min_age and $max_age.
If $year is noninclusively between $min_year and $max_year, the date is definitely within the range, and the function returns true. If not, some additional checks are required. If $year equals $max_year (e.g., in 2002, $year is 1984), $month must be before the current month. If $month equals the current month, $day must be before or equal to the current day. If $year equals $min_year (e.g., in 2002, $year is 1880), $month must be after the current month. If $month equals the current month, $day must be after the current day. If none of these conditions are met, the supplied date is outside the appropriate range, and the function returns false.
The function returns true if the supplied date is exactly $min_age years before the current date, but false if the supplied date is exactly $max_age years after the current date. That is, it would let you through on your 18th birthday, but not on your 123rd.

Monday, November 19, 2012

PHP : Dates and Times - [3.10] Parsing Dates and Times from Strings

3.10.1 Problem

You need to get a date or time in a string into a format you can use in calculations. For example, you want to convert date expressions such as "last Thursday" into an epoch timestamp.

3.10.2 Solution

The simplest way to parse a date or time string is with strtotime( ) , which turns a variety of human-readable date and time strings into epoch timestamps:
$a = strtotime('march 10'); // defaults to the current year

3.10.3 Discussion

The grammar strtotime( ) uses is both complicated and comprehensive so the best way to get comfortable with it is to try out lots of different time expressions. If you're curious about its nuts and bolts, check out ext/standard/parsedate.y in the PHP source distribution.
The function strtotime( ) understands words about the current time:
$a = strtotime('now');
print strftime('%c',$a);
$a = strtotime('today');
print strftime('%c',$a);
Mon Aug 12 20:35:10 2002
Mon Aug 12 20:35:10 2002
It understands different ways to identify a time and date:
$a = strtotime('5/12/1994');
print strftime('%c',$a);
$a = strtotime('12 may 1994');
print strftime('%c',$a);
Thu May 12 00:00:00 1994
Thu May 12 00:00:00 1994
It understands relative times and dates:
$a = strtotime('last thursday');   // On August 12, 2002
print strftime('%c',$a);
$a = strtotime('2001-07-12 2pm + 1 month');
print strftime('%c',$a);
Thu Aug  8 00:00:00 2002
Mon Aug 12 14:00:00 2002
It understands time zones. When the following is run from a computer in EDT, it prints out the same time:
$a = strtotime('2002-07-12 2pm edt + 1 month');
print strftime('%c',$a);
Mon Aug 12 14:00:00 2002
However, when the following is run from a computer in EDT, it prints out the time in EDT when it is 2 P.M. in MDT (two hours before EDT):
$a = strtotime('2002-07-12 2pm mdt + 1 month');
print strftime('%c',$a);
Mon Aug 12 16:00:00 2002
If the date and time you want to parse out of a string are in a format you know in advance, instead of calling strtotime( ), you can build a regular expression that grabs the different date and time parts you need. For example, here's how to parse "YYYY-MM-DD HH:MM:SS" dates, such as a MySQL DATETIME field:
$date = '1974-12-03 05:12:56';
preg_match('/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/',$date,$date_parts);
This puts the year, month, day, hour, minute, and second into $date_parts[1] through $date_parts[6]. (preg_match( ) puts the entire matched expression into $date_parts[0].)
You can use regular expressions to pull the date and time out of a larger string that might also contain other information (from user input, or a file you're reading), but if you're sure about the position of the date in the string you're parsing, you can use substr( ) to make it even faster:
$date_parts[0] = substr($date,0,4);
$date_parts[1] = substr($date,5,2);
$date_parts[2] = substr($date,8,2);
$date_parts[3] = substr($date,11,2);
$date_parts[4] = substr($date,14,2);
$date_parts[5] = substr($date,17,2);
You can also use split( );
$ar = split('[- :]',$date);
print_r($ar);
Array
(
    [0] => 1974
    [1] => 12
    [2] => 03
    [3] => 05
    [4] => 12
    [5] => 56
)
Be careful: PHP converts between numbers and strings without any prompting, but numbers beginning with a 0 are considered to be in octal (base 8). So, 03 and 05 are 3 and 5; but, 08 and 09 are not 8 and 9.
preg_match( ) and strtotime( ) are equally efficient in parsing a date format such as "YYYY-MM-DD HH:MM:SS", but ereg( ) is about four times slower than either. If you need the individual parts of the date string, preg_match( ) is more convenient, but strtotime( ) is obviously much more flexible.

Sunday, November 18, 2012

PHP : Dates and Times - [3.11] Adding to or Subtracting from a Date

3.11.1 Problem

You need to add or subtract an interval from a date.

3.11.2 Solution

Depending on how your date and interval are represented, use strtotime( ) or some simple arithmetic.
If you have your date and interval in appropriate formats, the easiest thing to do is use strtotime( ):
$birthday = 'March 10, 1975';
$whoopee_made = strtotime("$birthday - 9 months ago");
If your date in an epoch timestamp and you can express your interval in seconds, subtract the interval from the timestamp:
$birthday = 163727100;
$gestation = 36 * 7 * 86400; // 36 weeks
$whoopee_made = $birthday - $gestation;

3.11.3 Discussion

Using strtotime( ) is good for intervals that are of varying lengths, like months. If you can't use strtotime( ), you can convert your date to an epoch timestamp and add or subtract the appropriate interval in seconds. This is mostly useful for intervals of a fixed time, such as days or weeks:
$now = time( );
$next_week = $now + 7 * 86400;
Using this method, however, you can run into problems if the endpoints of your interval are on different sides of a DST switch. In this case, one of your fixed length days isn't 86,400 seconds long; it's either 82,800 or 90,000 seconds long, depending on the season. If you use UTC exclusively in your application, you don't have to worry about this. But if you have to use local time, you can count days without worrying about this hiccup with Julian days. You can convert between epoch timestamps and Julian days with unixtojd( ) and jdtounix( ):
$now = time( );
$today = unixtojd($now);
$next_week = jdtounix($today + 7);
// don't forget to add back hours, minutes, and seconds
$next_week += 3600 * date('H',$now) + 60 * date('i',$now) + date('s',$now);
The functions unixtojd( ) and jdtounix( ) are part of PHP's calendar extension, so they are only available if that extension is loaded.

Saturday, November 17, 2012

PHP : Dates and Times - [3.12] Calculating Time with Time Zones

3.12.1 Problem

You need to calculate times in different time zones. For example, you want to give users information adjusted to their local time, not the local time of your server.

3.12.2 Solution

For simple calculations, you can explicitly add or subtract the offsets between two time zones:
// If local time is EST
$time_parts = localtime();
// California (PST) is three hours earlier
$california_time_parts = localtime(time() - 3 * 3600);
On Unix-based systems, if you don't know the offsets between time zones, just set the TZ environment variable to your target time zone:
putenv('TZ=PST8PDT');
$california_time_parts = localtime();

3.12.3 Discussion

Before we sink too deeply into the ins and outs of time zones, we want to pass along the disclaimer that the U.S. Naval Observatory offers at http://tycho.usno.navy.mil/tzones.html. Namely, official worldwide time-zone information is somewhat fragile "because nations are sovereign powers that can and do change their timekeeping systems as they see fit." So, remembering that we are at the mercy of the vagaries of international relations, here are some ways to cope with Earth's many time zones.
For a relatively simple treatment of offsets between time zones, use an array in your program that has the various time zones' offsets from UTC. Once you determine what time zone your user is in, just add that offset to the appropriate UTC time and the functions that print out UTC time (e.g., gmdate( ), gmstrftime( )) can print out the correct adjusted time.
// Find the current time 
$now = time();

// California is 8 hours behind UTC
$now += $pc_timezones['PST'];

Friday, November 16, 2012

PHP : Dates and Times - [3.13] Accounting for Daylight Saving Time

3.13.1 Problem

You need to make sure your time calculations properly consider DST.

3.13.2 Solution

The zoneinfo library calculates the effects of DST properly. If you are using a Unix-based system, take advantage of zoneinfo with putenv( ):
putenv('TZ=MST7MDT');
print strftime('%c');
If you can't use zoneinfo, you can modify hardcoded time-zone offsets based on whether the local time zone is currently observing DST. Use localtime( ) to determine the current DST observance status:
// Find the current UTC time 
$now = time();

// California is 8 hours behind UTC
$now -= 8 * 3600;

// Is it DST? 
$ar = localtime($now,true);
if ($ar['tm_isdst']) { $now += 3600; }

// Use gmdate() or gmstrftime() to print California-appropriate time
print gmstrftime('%c',$now);

3.13.3 Discussion

Altering an epoch timestamp by the amount of a time zone's offset from UTC and then using gmdate( ) or gmstrftime( ) to print out time zone-appropriate functions is flexible — it works from any time zone — but the DST calculations are slightly inaccurate. For the brief intervals when the server's DST status is different from the target time zone's, the results are incorrect. For example, at 3:30 A.M. EDT on the first Sunday in April (after the switch to DST), it's still before the switch (11:30 P.M.) in the Pacific time zone. A server in Eastern time using this method calculates California time to be seven hours behind UTC, whereas it's actually eight hours. At 6:00 A.M. EDT (3:00 A.M. PDT), both Pacific and Eastern time are observing DST, and the calculation is correct again (putting California at seven hours behind UTC).

Thursday, November 15, 2012

PHP : Dates and Times - [3.14] Generating a High-Precision Time

3.14.1 Problem

You need to measure time with finer than one-second resolution, for example to generate a unique ID.

3.14.2 Solution

Use microtime( ) :
list($microseconds,$seconds) = explode(' ',microtime());

3.14.3 Discussion

The function microtime( ) returns a string that contains the microseconds part of elapsed time since the epoch, a space, and seconds since the epoch. For example, a return value of 0.41644100 1026683258 means that 1026683258.41644100 seconds have elapsed since the epoch. A string is returned instead of a double because the double doesn't have enough capacity to hold the entire value with microsecond precision.
Time including microseconds is useful for generating unique IDs. When combined with the current process ID, it guarantees a unique ID, as long as a process doesn't generate more than one ID per microsecond:
list($microseconds,$seconds) = explode(' ',microtime());
$id = $seconds.$microseconds.getmypid();
However, this method is not as foolproof on multithreaded systems, where there is a nonzero (but very tiny) chance that two threads of the same process could call microtime( ) simultaneously.

Wednesday, November 14, 2012

PHP : Dates and Times - [3.15] Generating Time Ranges

3.15.1 Problem

You need to know all the days in a week or a month. For example, you want to print out a list of appointments for a week.

3.15.2 Solution

Identify your start date using time( ) and strftime( ). If your interval has a fixed length, you can loop through that many days. If not, you need to test each subsequent day for membership in your desired range.
For example, a week has seven days, so you can use a fixed loop to generate all the days in the current week:
// generate a time range for this week
$now = time();

// If it's before 3 AM, increment $now, so we don't get caught by DST
// when moving back to the beginning of the week
if (3 < strftime('%H', $now)) { $now += 7200; }

// What day of the week is today?
$today = strftime('%w', $now);

// How many days ago was the start of the week?
$start_day = $now - (86400 * $today);

// Print out each day of the week
for ($i = 0; $i < 7; $i++) {
  print strftime('%c',$start_day + 86400 * $i);
}

3.15.3 Discussion

A particular month or year could have a variable number of days, so you need to compute the end of the time range based on the specifics of that month or year. To loop through every day in a month, find the epoch timestamps for the first day of the month and the first day of the next month. The loop variable, $day is incremented a day at a time (86400 seconds) until it's no longer less than the epoch timestamp at the beginning of the next month:
// Generate a time range for this month
$now = time();

// If it's before 3 AM, increment $now, so we don't get caught by DST
// when moving back to the beginning of the month
if (3 < strftime('%H', $now)) { $now += 7200; }

// What month is this?
$this_month = strftime('%m',$now);

// Epoch timestamp for midnight on the first day of this month
$day = mktime(0,0,0,$this_month,1);
// Epoch timestamp for midnight on the first day of next month
$month_end = mktime(0,0,0,$this_month+1,1);

while ($day < $month_end) {
  print strftime('%c',$day); 
  $day += 86400;
}

Tuesday, November 13, 2012

PHP : Dates and Times - [3.16] Using Non-Gregorian Calendars

3.16.1 Problem

You want to use a non-Gregorian calendar, such as a Julian, Jewish, or French Republican calendar.

3.16.2 Solution

PHP's calendar extension provides conversion functions for working with the Julian calendar, as well as the French Republican and Jewish calendars. To use these functions, the calendar extension must be loaded.
These functions use the Julian day count (which is different than the Julian calendar) as their intermediate format to move information between them.
The two functions jdtogregorian( ) and gregoriantojd( ) convert between Julian days and the familiar Gregorian calendar:
$jd = gregoriantojd(3,9,1876);      // March 9, 1876; $jd = 2406323

$gregorian = jdtogregorian($jd);    // $gregorian = 3/9/1876
The valid range for the Gregorian calendar is 4714 BCE to 9999 CE.

3.16.3 Discussion

To convert between Julian days and the Julian calendar, use jdtojulian( ) and juliantojd( ):
// February 29, 1900 (not a Gregorian leap year)
$jd = juliantojd(2,29,1900);      // $jd = 2415092
$julian = jdtojulian($jd);        // $julian = 2/29/1900
$gregorian = jdtogregorian($jd);  // $gregorian = 3/13/1900
The valid range for the Julian calendar is 4713 BCE to 9999 CE, but since it was created in 46 BCE, you run the risk of annoying Julian calendar purists if you use it for dates before that.
To convert between Julian days and the French Republican calendar, use jdtofrench( ) and frenchtojd( ):
$jd = frenchtojd(8,13,11);       // 13 floréal XI; $jd = 2379714
$french = jdtofrench($jd);       // $french = 8/13/11
$gregorian = jdtofregorian($jd); // $gregorian = 5/3/1803; sale of Louisiana to U.S.
The valid range for the French Republican calendar is September 1792 to September 1806, which is small, but since the calendar was only in use from October 1793 to January 1806, it's comprehensive enough.
To convert between Julian days and the Jewish calendar, use jdtojewish( ) and jewishtojd( ):
$jd = JewishToJD(6,14,5761);      // Adar 14, 5761; $jd = 2451978
$jewish = JDToJewish($jd);        // $jewish = 6/14/5761
$gregorian = JDToGregorian($jd);  // $gregorian = 3/9/2001
The valid range for the Jewish calendar starts with 3761 BCE (year 1 on the Jewish calendar).

Monday, November 12, 2012

PHP : Dates and Times - [3.17] Calendar Program

The pc_calendar() function shown in Example 3-4 prints out a month's calendar, similar to the Unix cal program. Here's how you can use the function:
// print the calendar for the current month
list($month,$year) = explode(',',date('m,Y'));
pc_calendar($month,$year);
The pc_calendar( ) function prints out a table with a month's calendar in it. It provides links to the previous and next month and highlights the current day.
Example 3-4. pc_calendar( )
<?php
function pc_calendar($month,$year,$opts = '') {
    // set default options //
    if (! is_array($opts)) { $opts = array(); }
    if (! isset($opts['today_color'])) { $opts['today_color'] = '#FFFF00'; }
    if (! isset($opts['month_link'])) {
        $opts['month_link'] = 
            '<a href="'.$_SERVER['PHP_SELF'].'?month=%d&year=%d">%s</a>';
    }
    
    list($this_month,$this_year,$this_day) = split(',',strftime('%m,%Y,%d'));
    $day_highlight = (($this_month == $month) && ($this_year == $year));
    
    list($prev_month,$prev_year) = 
        split(',',strftime('%m,%Y',mktime(0,0,0,$month-1,1,$year)));
    $prev_month_link = sprintf($opts['month_link'],$prev_month,$prev_year,'&lt;');
    
    list($next_month,$next_year) = 
        split(',',strftime('%m,%Y',mktime(0,0,0,$month+1,1,$year)));
    $next_month_link = sprintf($opts['month_link'],$next_month,$next_year,'&gt;');
    
?>
<table border="0" cellspacing="0" cellpadding="2" align="center">
        <tr>
                <td align="left">
                        <?php print $prev_month_link ?>
                </td>
                <td colspan="5" align="center">
                <?php print strftime('%B %Y',mktime(0,0,0,$month,1,$year)); ?>
                </td>
                <td align="right">
                        <?php print $next_month_link ?>
                </td>
        </tr>
<?php
    $totaldays = date('t',mktime(0,0,0,$month,1,$year));
 
    // print out days of the week
    print '<tr>';
    $weekdays = array('Su','Mo','Tu','We','Th','Fr','Sa');
    while (list($k,$v) = each($weekdays)) {
        print '<td align="center">'.$v.'</td>';
    }
    print '</tr><tr>';