1 /* java.util.VMTimeZone
2 Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2007
3 Free Software Foundation, Inc.
5 This file is part of GNU Classpath.
7 GNU Classpath is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2, or (at your option)
12 GNU Classpath is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with GNU Classpath; see the file COPYING. If not, write to the
19 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 Linking this library statically or dynamically with other modules is
23 making a combined work based on this library. Thus, the terms and
24 conditions of the GNU General Public License cover the whole
27 As a special exception, the copyright holders of this library give you
28 permission to link this library with independent modules to produce an
29 executable, regardless of the license terms of these independent
30 modules, and to copy and distribute the resulting executable under
31 terms of your choice, provided that you also meet, for each linked
32 independent module, the terms and conditions of the license of that
33 module. An independent module is a module which is not derived from
34 or based on this library. If you modify this library, you may extend
35 this exception to your version of the library, but you are not
36 obligated to do so. If you do not wish to do so, delete this
37 exception statement from your version. */
42 import gnu.classpath.Configuration;
43 import java.util.TimeZone;
44 import java.util.Calendar;
45 import java.util.GregorianCalendar;
52 final class VMTimeZone
56 if (Configuration.INIT_LOAD_LIBRARY)
58 System.loadLibrary("javautil");
63 * This method returns a time zone id string which is in the form
64 * (standard zone name) or (standard zone name)(GMT offset) or
65 * (standard zone name)(GMT offset)(daylight time zone name). The
66 * GMT offset can be in seconds, or where it is evenly divisible by
67 * 3600, then it can be in hours. The offset must be the time to
68 * add to the local time to get GMT. If a offset is given and the
69 * time zone observes daylight saving then the (daylight time zone
70 * name) must also be given (otherwise it is assumed the time zone
71 * does not observe any daylight savings).
73 * The result of this method is given to the method
74 * TimeZone.getDefaultTimeZone(String) which tries to map the time
75 * zone id to a known TimeZone. See that method on how the returned
76 * String is mapped to a real TimeZone object.
78 * The reference implementation which is made for GNU/Posix like
79 * systems calls <code>System.getenv("TZ")</code>,
80 * <code>readTimeZoneFile("/etc/timezone")</code>,
81 * <code>readtzFile("/etc/localtime")</code> and finally
82 * <code>getSystemTimeZoneId()</code> till a supported TimeZone is
83 * found through <code>TimeZone.getDefaultTimeZone(String)</code>.
84 * If every method fails <code>null</code> is returned (which means
85 * the TimeZone code will fall back on GMT as default time zone).
87 * Note that this method is called inside a
88 * <code>AccessController.doPrivileged()</code> block and runs with
89 * the priviliges of the java.util system classes. It will only be
90 * called when the default time zone is not yet set, the system
91 * property user.timezone isn't set and it is requested for the
94 static TimeZone getDefaultTimeZoneId()
98 // See if TZ environment variable is set and accessible.
99 String tzid = System.getenv("TZ");
100 if (tzid != null && !tzid.equals(""))
101 zone = TimeZone.getDefaultTimeZone(tzid);
103 // Try to parse /etc/timezone.
106 tzid = readTimeZoneFile("/etc/timezone");
107 if (tzid != null && !tzid.equals(""))
108 zone = TimeZone.getDefaultTimeZone(tzid);
111 // Try to parse /etc/localtime
114 tzid = readtzFile("/etc/localtime");
115 if (tzid != null && !tzid.equals(""))
116 zone = TimeZone.getDefaultTimeZone(tzid);
119 // Try some system specific way
122 tzid = getSystemTimeZoneId();
123 if (tzid != null && !tzid.equals(""))
124 zone = TimeZone.getDefaultTimeZone(tzid);
131 * Tries to read the time zone name from a file. Only the first
132 * consecutive letters, digits, slashes, dashes and underscores are
133 * read from the file. If the file cannot be read or an IOException
134 * occurs null is returned.
136 * The /etc/timezone file is not standard, but a lot of systems have
137 * it. If it exist the first line always contains a string
138 * describing the timezone of the host of domain. Some systems
139 * contain a /etc/TIMEZONE file which is used to set the TZ
140 * environment variable (which is checked before /etc/timezone is
143 private static String readTimeZoneFile(String file)
145 File f = new File(file);
149 InputStreamReader isr = null;
152 FileInputStream fis = new FileInputStream(f);
153 BufferedInputStream bis = new BufferedInputStream(fis);
154 isr = new InputStreamReader(bis);
156 StringBuffer sb = new StringBuffer();
161 if (Character.isLetter(c) || Character.isDigit(c)
162 || c == '/' || c == '-' || c == '_')
170 return sb.toString();
172 catch (IOException ioe)
174 // Parse error, not a proper tzfile.
184 catch (IOException ioe)
186 // Error while close, nothing we can do.
192 * Tries to read a file as a "standard" tzfile and return a time
193 * zone id string as expected by <code>getDefaultTimeZone(String)</code>.
194 * If the file doesn't exist, an IOException occurs or it isn't a tzfile
195 * that can be parsed null is returned.
197 * The tzfile structure (as also used by glibc) is described in the Olson
198 * tz database archive as can be found at
199 * <code>ftp://elsie.nci.nih.gov/pub/</code>.
201 * At least the following platforms support the tzdata file format
202 * and /etc/localtime (GNU/Linux, Darwin, Solaris and FreeBSD at
203 * least). Some systems (like Darwin) don't start the file with the
204 * required magic bytes 'TZif', this implementation can handle
207 private static String readtzFile(String file)
209 File f = new File(file);
213 DataInputStream dis = null;
216 FileInputStream fis = new FileInputStream(f);
217 BufferedInputStream bis = new BufferedInputStream(fis);
218 dis = new DataInputStream(bis);
220 // Make sure we are reading a tzfile.
221 byte[] tzif = new byte[5];
224 if (tzif[0] == 'T' && tzif[1] == 'Z'
225 && tzif[2] == 'i' && tzif[3] == 'f')
230 skipFully(dis, 16 - 1);
233 // Darwin has tzdata files that don't start with the TZif marker
234 skipFully(dis, 16 - 5);
237 int ttisgmtcnt = dis.readInt();
238 int ttisstdcnt = dis.readInt();
239 int leapcnt = dis.readInt();
240 int timecnt = dis.readInt();
241 int typecnt = dis.readInt();
242 int charcnt = dis.readInt();
245 skipFully(dis, timecnt * (4 + 1) + typecnt * (4 + 1 + 1) + charcnt
246 + leapcnt * (4 + 4) + ttisgmtcnt + ttisstdcnt);
249 if (tzif[0] != 'T' || tzif[1] != 'Z' || tzif[2] != 'i'
250 || tzif[3] != 'f' || tzif[4] < '2')
254 skipFully(dis, 16 - 1);
255 ttisgmtcnt = dis.readInt();
256 ttisstdcnt = dis.readInt();
257 leapcnt = dis.readInt();
258 timecnt = dis.readInt();
259 typecnt = dis.readInt();
260 charcnt = dis.readInt();
264 int seltimecnt = timecnt;
268 long[] times = new long[seltimecnt];
269 int[] types = new int[seltimecnt];
272 skipFully(dis, (timecnt - seltimecnt) * tzif2);
274 for (int i = 0; i < seltimecnt; i++)
276 times[i] = dis.readLong();
278 times[i] = (long) dis.readInt();
281 skipFully(dis, timecnt - seltimecnt);
282 for (int i = 0; i < seltimecnt; i++)
284 types[i] = dis.readByte();
289 // Get std/dst_offset and dst/non-dst time zone names.
290 int std_abbrind = -1;
291 int dst_abbrind = -1;
298 if (seltimecnt >= 4 && types[0] != types[1]
299 && types[0] < typecnt && types[1] < typecnt)
301 // Verify only two types are involved
302 // in the transitions and they alternate.
304 for (int i = 2; i < seltimecnt; i++)
305 if (types[i] != types[i % 2])
309 // If a timezone previously used DST, but no longer does
310 // (or no longer will in the near future, say 5 years),
311 // then always pick only the std zone type corresponding
312 // to latest applicable transition.
314 && times[seltimecnt - 1]
315 < System.currentTimeMillis() / 1000 + 5 * 365 * 86400)
318 for (int i = 0; i < typecnt; i++)
321 int offset = dis.readInt();
322 int dst = dis.readByte();
323 int abbrind = dis.readByte();
328 && (i == types[0] || i == types[1]))
329 || (alternation == -1 && i == types[seltimecnt - 1]))
331 std_abbrind = abbrind;
332 std_offset = offset * -1;
336 else if (alternation >= 0)
338 if (alternation == 0 || i == types[0] || i == types[1])
340 dst_abbrind = abbrind;
341 dst_offset = offset * -1;
347 if (std_abbrind >= 0)
349 byte[] names = new byte[charcnt];
350 dis.readFully(names);
352 while (j < charcnt && names[j] != 0)
355 String zonename = new String(names, std_abbrind,
356 j - std_abbrind, "ASCII");
359 if (dst_abbrind >= 0)
362 while (j < charcnt && names[j] != 0)
364 dst_zonename = new String(names, dst_abbrind,
365 j - dst_abbrind, "ASCII");
370 String[] change_spec = { null, null };
371 if (dst_abbrind >= 0 && alternation > 0)
373 // Guess rules for the std->dst and dst->std transitions
374 // from the transition times since Epoch.
375 // tzdata actually uses only 3 forms of rules:
376 // fixed date within a month, e.g. change on April, 5th
377 // 1st weekday on or after Nth: change on Sun>=15 in April
378 // last weekday in a month: change on lastSun in April
379 GregorianCalendar cal
380 = new GregorianCalendar (TimeZone.getTimeZone("GMT"));
382 int[] values = new int[2 * 11];
384 for (i = seltimecnt - 1; i >= 0; i--)
386 int base = (i % 2) * 11;
387 int offset = types[i] == dst_ind ? std_offset : dst_offset;
388 cal.setTimeInMillis((times[i] - offset) * 1000);
389 if (i >= seltimecnt - 2)
391 values[base + 0] = cal.get(Calendar.YEAR);
392 values[base + 1] = cal.get(Calendar.MONTH);
393 values[base + 2] = cal.get(Calendar.DAY_OF_MONTH);
395 = cal.getActualMaximum(Calendar.DAY_OF_MONTH);
396 values[base + 4] = cal.get(Calendar.DAY_OF_WEEK);
397 values[base + 5] = cal.get(Calendar.HOUR_OF_DAY);
398 values[base + 6] = cal.get(Calendar.MINUTE);
399 values[base + 7] = cal.get(Calendar.SECOND);
400 values[base + 8] = values[base + 2]; // Range start
401 values[base + 9] = values[base + 2]; // Range end
402 values[base + 10] = 0; // Determined type
406 int year = cal.get(Calendar.YEAR);
407 int month = cal.get(Calendar.MONTH);
408 int day_of_month = cal.get(Calendar.DAY_OF_MONTH);
410 = cal.getActualMaximum(Calendar.DAY_OF_MONTH);
411 int day_of_week = cal.get(Calendar.DAY_OF_WEEK);
412 int hour = cal.get(Calendar.HOUR_OF_DAY);
413 int minute = cal.get(Calendar.MINUTE);
414 int second = cal.get(Calendar.SECOND);
415 if (year != values[base + 0] - 1
416 || month != values[base + 1]
417 || hour != values[base + 5]
418 || minute != values[base + 6]
419 || second != values[base + 7])
421 if (day_of_week == values[base + 4])
423 // Either a Sun>=8 or lastSun rule.
424 if (day_of_month < values[base + 8])
425 values[base + 8] = day_of_month;
426 if (day_of_month > values[base + 9])
427 values[base + 9] = day_of_month;
428 if (values[base + 10] < 0)
430 if (values[base + 10] == 0)
432 values[base + 10] = 1;
433 // If day of month > 28, this is
434 // certainly lastSun rule.
435 if (values[base + 2] > 28)
436 values[base + 2] = 3;
437 // If day of month isn't in the last
438 // week, it can't be lastSun rule.
439 else if (values[base + 2]
440 <= values[base + 3] - 7)
441 values[base + 3] = 2;
443 if (values[base + 10] == 1)
445 // If day of month is > 28, this is
446 // certainly lastSun rule.
447 if (day_of_month > 28)
448 values[base + 10] = 3;
449 // If day of month isn't in the last
450 // week, it can't be lastSun rule.
451 else if (day_of_month <= month_days - 7)
452 values[base + 10] = 2;
454 else if ((values[base + 10] == 2
455 && day_of_month > 28)
456 || (values[base + 10] == 3
463 // Must be fixed day in month rule.
464 if (day_of_month != values[base + 2]
465 || values[base + 10] > 0)
467 values[base + 4] = day_of_week;
468 values[base + 10] = -1;
470 values[base + 0] -= 1;
475 for (i = 0; i < 2; i++)
478 if (values[base + 10] == 0)
480 if (values[base + 10] == -1)
483 = { 0, 31, 59, 90, 120, 151,
484 181, 212, 243, 273, 304, 334 };
485 int d = dayCount[values[base + 1]
487 d += values[base + 2];
488 change_spec[i] = ",J" + Integer.toString(d);
490 else if (values[base + 10] == 2)
492 // If we haven't seen all days of the week,
493 // we can't be sure what the rule really is.
494 if (values[base + 8] + 6 != values[base + 9])
497 // FIXME: Sun >= 5 is representable in
498 // SimpleTimeZone, but not in POSIX TZ env
499 // strings. Should we change readtzFile
500 // to actually return a SimpleTimeZone
501 // rather than POSIX TZ string?
502 if ((values[base + 8] % 7) != 1)
506 d = values[base + 1] - Calendar.JANUARY + 1;
507 change_spec[i] = ",M" + Integer.toString(d);
508 d = (values[base + 8] + 6) / 7;
509 change_spec[i] += "." + Integer.toString(d);
510 d = values[base + 4] - Calendar.SUNDAY;
511 change_spec[i] += "." + Integer.toString(d);
515 // If we don't know whether this is lastSun or
516 // Sun >= 22 rule. That can be either because
517 // there was insufficient number of
518 // transitions, or February, where it is quite
519 // probable we haven't seen any 29th dates.
520 // For February, assume lastSun rule, otherwise
522 if (values[base + 10] == 1
523 && values[base + 1] != Calendar.FEBRUARY)
527 d = values[base + 1] - Calendar.JANUARY + 1;
528 change_spec[i] = ",M" + Integer.toString(d);
529 d = values[base + 4] - Calendar.SUNDAY;
530 change_spec[i] += ".5." + Integer.toString(d);
533 // Don't add time specification if time is
535 if (values[base + 5] != 2
536 || values[base + 6] != 0
537 || values[base + 7] != 0)
539 int d = values[base + 5];
540 change_spec[i] += "/" + Integer.toString(d);
541 if (values[base + 6] != 0
542 || values[base + 7] != 0)
544 d = values[base + 6];
547 += ":0" + Integer.toString(d);
550 += ":" + Integer.toString(d);
551 d = values[base + 7];
554 += ":" + Integer.toString(d);
557 += ":0" + Integer.toString(d);
561 if (types[0] == std_ind)
563 String tmp = change_spec[0];
564 change_spec[0] = change_spec[1];
565 change_spec[1] = tmp;
570 // Only use gmt offset when necessary.
571 // Also special case GMT+/- timezones.
572 String offset_string, dst_offset_string = "";
575 || zonename.startsWith("GMT+")
576 || zonename.startsWith("GMT-")))
580 offset_string = Integer.toString(std_offset / 3600);
581 int seconds = std_offset % 3600;
588 += ":0" + Integer.toString(seconds / 60);
591 += ":" + Integer.toString(seconds / 60);
592 seconds = seconds % 60;
595 += ":" + Integer.toString(seconds);
596 else if (seconds > 0)
598 += ":0" + Integer.toString(seconds);
601 && dst_offset != std_offset - 3600)
604 = Integer.toString(dst_offset / 3600);
605 seconds = dst_offset % 3600;
612 += ":0" + Integer.toString(seconds / 60);
615 += ":" + Integer.toString(seconds / 60);
616 seconds = seconds % 60;
619 += ":" + Integer.toString(seconds);
620 else if (seconds > 0)
622 += ":0" + Integer.toString(seconds);
628 id = zonename + offset_string;
629 else if (change_spec[0] != null && change_spec[1] != null)
630 id = zonename + offset_string + dst_zonename
631 + dst_offset_string + change_spec[0] + change_spec[1];
634 skipFully(dis, charcnt);
637 skipFully(dis, timecnt * (8 + 1) + typecnt * (4 + 1 + 1) + charcnt);
641 // Skip over the rest of 64-bit data
642 skipFully(dis, leapcnt * (8 + 4) + ttisgmtcnt + ttisstdcnt);
643 if (dis.readByte() == '\n')
645 String posixtz = dis.readLine();
646 if (posixtz.length() > 0)
653 catch (IOException ioe)
655 // Parse error, not a proper tzfile.
665 catch(IOException ioe)
667 // Error while close, nothing we can do.
673 * Skips the requested number of bytes in the given InputStream.
674 * Throws EOFException if not enough bytes could be skipped.
675 * Negative numbers of bytes to skip are ignored.
677 private static void skipFully(InputStream is, long l) throws IOException
683 throw new EOFException();
689 * Tries to get the system time zone id through native code.
691 private static native String getSystemTimeZoneId();