OSDN Git Service

2007-02-09 Jakub Jelinek <jakub@redhat.com>
[pf3gnuchains/gcc-fork.git] / libjava / java / util / VMTimeZone.java
1 /* java.util.VMTimeZone
2    Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2007
3    Free Software Foundation, Inc.
4
5 This file is part of GNU Classpath.
6
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)
10 any later version.
11
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.
16
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
20 02110-1301 USA.
21
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
25 combination.
26
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. */
38
39
40 package java.util;
41
42 import gnu.classpath.Configuration;
43 import java.util.TimeZone;
44 import java.util.Calendar;
45 import java.util.GregorianCalendar;
46
47 import java.io.*;
48
49 /**
50  *
51  */
52 final class VMTimeZone
53 {
54   static
55   {
56     if (Configuration.INIT_LOAD_LIBRARY)
57       {
58         System.loadLibrary("javautil");
59       }
60   }
61                 
62   /**
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).
72    * <p>
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.
77    * <p>
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).
86    * <p>
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
92    * first time.
93    */
94   static TimeZone getDefaultTimeZoneId()
95   {
96     TimeZone zone = null;
97
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);
102
103     // Try to parse /etc/timezone.
104     if (zone == null)
105       {
106         tzid = readTimeZoneFile("/etc/timezone");
107         if (tzid != null && !tzid.equals(""))
108           zone = TimeZone.getDefaultTimeZone(tzid);
109       }
110     
111     // Try to parse /etc/localtime
112     if (zone == null)
113       {
114         tzid = readtzFile("/etc/localtime");
115         if (tzid != null && !tzid.equals(""))
116           zone = TimeZone.getDefaultTimeZone(tzid);
117       }
118
119     // Try some system specific way
120     if (zone == null)
121       {
122         tzid = getSystemTimeZoneId();
123         if (tzid != null && !tzid.equals(""))
124           zone = TimeZone.getDefaultTimeZone(tzid);
125       }
126
127     return zone;
128   }
129
130   /**
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.
135    * <p>
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
141    * read).
142    */
143   private static String readTimeZoneFile(String file)
144   {
145     File f = new File(file);
146     if (!f.exists())
147       return null;
148
149     InputStreamReader isr = null;
150     try
151       {
152         FileInputStream fis = new FileInputStream(f);
153         BufferedInputStream bis = new BufferedInputStream(fis);
154         isr = new InputStreamReader(bis);
155         
156         StringBuffer sb = new StringBuffer();
157         int i = isr.read();
158         while (i != -1)
159           {
160             char c = (char) i;
161             if (Character.isLetter(c) || Character.isDigit(c)
162                 || c == '/' || c == '-' || c == '_')
163               {
164                 sb.append(c);
165                 i = isr.read();
166               }
167             else
168               break;
169           }
170         return sb.toString();
171       }
172     catch (IOException ioe)
173       {
174         // Parse error, not a proper tzfile.
175         return null;
176       }
177     finally
178       {
179         try
180           {
181             if (isr != null)
182               isr.close();
183           }
184         catch (IOException ioe)
185           {
186             // Error while close, nothing we can do.
187           }
188       }
189   }
190
191   /**
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.
196    * <p>
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>.
200    * <p>
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
205    * that).
206    */
207   private static String readtzFile(String file)
208   {
209     File f = new File(file);
210     if (!f.exists())
211       return null;
212
213     DataInputStream dis = null;
214     try
215       {
216         FileInputStream fis = new FileInputStream(f);
217         BufferedInputStream bis = new BufferedInputStream(fis);
218         dis = new DataInputStream(bis);
219
220         // Make sure we are reading a tzfile.
221         byte[] tzif = new byte[5];
222         dis.readFully(tzif);
223         int tzif2 = 4;
224         if (tzif[0] == 'T' && tzif[1] == 'Z'
225             && tzif[2] == 'i' && tzif[3] == 'f')
226           {
227             if (tzif[4] >= '2')
228               tzif2 = 8;
229             // Reserved bytes
230             skipFully(dis, 16 - 1);
231           }
232         else
233           // Darwin has tzdata files that don't start with the TZif marker
234           skipFully(dis, 16 - 5);
235
236         String id = null;
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();
243         if (tzif2 == 8)
244           {
245             skipFully(dis, timecnt * (4 + 1) + typecnt * (4 + 1 + 1) + charcnt
246                            + leapcnt * (4 + 4) + ttisgmtcnt + ttisstdcnt);
247
248             dis.readFully(tzif);
249             if (tzif[0] != 'T' || tzif[1] != 'Z' || tzif[2] != 'i'
250                 || tzif[3] != 'f' || tzif[4] < '2')
251               return null;
252
253             // Reserved bytes
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();
261           }
262         if (typecnt > 0)
263           {
264             int seltimecnt = timecnt;
265             if (seltimecnt > 16)
266               seltimecnt = 16;
267
268             long[] times = new long[seltimecnt];
269             int[] types = new int[seltimecnt];
270
271             // Transition times
272             skipFully(dis, (timecnt - seltimecnt) * tzif2);
273
274             for (int i = 0; i < seltimecnt; i++)
275               if (tzif2 == 8)
276                 times[i] = dis.readLong();
277               else
278                 times[i] = (long) dis.readInt();
279
280             // Transition types
281             skipFully(dis, timecnt - seltimecnt);
282             for (int i = 0; i < seltimecnt; i++)
283               {
284                 types[i] = dis.readByte();
285                 if (types[i] < 0)
286                   types[i] += 256;
287               }
288
289             // Get std/dst_offset and dst/non-dst time zone names.
290             int std_abbrind = -1;
291             int dst_abbrind = -1;
292             int std_offset = 0;
293             int dst_offset = 0;
294             int std_ind = -1;
295             int dst_ind = -1;
296
297             int alternation = 0;
298             if (seltimecnt >= 4 && types[0] != types[1]
299                 && types[0] < typecnt && types[1] < typecnt)
300               {
301                 // Verify only two types are involved
302                 // in the transitions and they alternate.
303                 alternation = 1;
304                 for (int i = 2; i < seltimecnt; i++)
305                   if (types[i] != types[i % 2])
306                     alternation = 0;
307               }
308
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.
313             if (seltimecnt > 0
314                 && times[seltimecnt - 1]
315                    < System.currentTimeMillis() / 1000 + 5 * 365 * 86400)
316               alternation = -1;
317
318             for (int i = 0; i < typecnt; i++)
319               {
320                 // gmtoff
321                 int offset = dis.readInt();
322                 int dst = dis.readByte();
323                 int abbrind = dis.readByte();
324                 if (dst == 0)
325                   {
326                     if (alternation == 0
327                         || (alternation == 1
328                             && (i == types[0] || i == types[1]))
329                         || (alternation == -1 && i == types[seltimecnt - 1]))
330                       {
331                         std_abbrind = abbrind;
332                         std_offset = offset * -1;
333                         std_ind = i;
334                       }
335                   }
336                 else if (alternation >= 0)
337                   {
338                     if (alternation == 0 || i == types[0] || i == types[1])
339                       {
340                         dst_abbrind = abbrind;
341                         dst_offset = offset * -1;
342                         dst_ind = i;
343                       }
344                   }
345               }
346
347             if (std_abbrind >= 0)
348               {
349                 byte[] names = new byte[charcnt];
350                 dis.readFully(names);
351                 int j = std_abbrind;
352                 while (j < charcnt && names[j] != 0)
353                   j++;
354
355                 String zonename = new String(names, std_abbrind,
356                                              j - std_abbrind, "ASCII");
357
358                 String dst_zonename;
359                 if (dst_abbrind >= 0)
360                   {
361                     j = dst_abbrind;
362                     while (j < charcnt && names[j] != 0)
363                       j++;
364                     dst_zonename = new String(names, dst_abbrind,
365                                               j - dst_abbrind, "ASCII");
366                   }
367                 else
368                   dst_zonename = "";
369
370                 String[] change_spec = { null, null };
371                 if (dst_abbrind >= 0 && alternation > 0)
372                   {
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"));
381
382                     int[] values = new int[2 * 11];
383                     int i;
384                     for (i = seltimecnt - 1; i >= 0; i--)
385                       {
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)
390                           {
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);
394                             values[base + 3]
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
403                           }
404                         else
405                           {
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);
409                             int month_days
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])
420                               break;
421                             if (day_of_week == values[base + 4])
422                               {
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)
429                                   break;
430                                 if (values[base + 10] == 0)
431                                   {
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;
442                                   }
443                                 if (values[base + 10] == 1)
444                                   {
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;
453                                   }
454                                 else if ((values[base + 10] == 2
455                                           && day_of_month > 28)
456                                          || (values[base + 10] == 3
457                                              && day_of_month
458                                                 <= month_days - 7))
459                                   break;
460                               }
461                             else
462                               {
463                                 // Must be fixed day in month rule.
464                                 if (day_of_month != values[base + 2]
465                                     || values[base + 10] > 0)
466                                   break;
467                                 values[base + 4] = day_of_week;
468                                 values[base + 10] = -1;
469                               }
470                             values[base + 0] -= 1;
471                           }
472                       }
473                     if (i < 0)
474                       {
475                         for (i = 0; i < 2; i++)
476                           {
477                             int base = 11 * i;
478                             if (values[base + 10] == 0)
479                               continue;
480                             if (values[base + 10] == -1)
481                               {
482                                 int[] dayCount
483                                   = { 0, 31, 59, 90, 120, 151,
484                                       181, 212, 243, 273, 304, 334 };
485                                 int d = dayCount[values[base + 1]
486                                                  - Calendar.JANUARY];
487                                 d += values[base + 2];
488                                 change_spec[i] = ",J" + Integer.toString(d);
489                               }
490                             else if (values[base + 10] == 2)
491                               {
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])
495                                   continue;
496
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)
503                                   continue;
504
505                                 int d;
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);
512                               }
513                             else
514                               {
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
521                                 // punt.
522                                 if (values[base + 10] == 1
523                                     && values[base + 1] != Calendar.FEBRUARY)
524                                   continue;
525
526                                 int d;
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);
531                               }
532
533                             // Don't add time specification if time is
534                             // 02:00:00.
535                             if (values[base + 5] != 2
536                                 || values[base + 6] != 0
537                                 || values[base + 7] != 0)
538                               {
539                                 int d = values[base + 5];
540                                 change_spec[i] += "/" + Integer.toString(d);
541                                 if (values[base + 6] != 0
542                                     || values[base + 7] != 0)
543                                   {
544                                     d = values[base + 6];
545                                     if (d < 10)
546                                       change_spec[i]
547                                         += ":0" + Integer.toString(d);
548                                     else
549                                       change_spec[i]
550                                         += ":" + Integer.toString(d);
551                                     d = values[base + 7];
552                                     if (d >= 10)
553                                       change_spec[i]
554                                         += ":" + Integer.toString(d);
555                                     else if (d > 0)
556                                       change_spec[i]
557                                         += ":0" + Integer.toString(d);
558                                   }
559                               }
560                           }
561                         if (types[0] == std_ind)
562                           {
563                             String tmp = change_spec[0];
564                             change_spec[0] = change_spec[1];
565                             change_spec[1] = tmp;
566                           }
567                       }
568                   }
569
570                 // Only use gmt offset when necessary.
571                 // Also special case GMT+/- timezones.
572                 String offset_string, dst_offset_string = "";
573                 if (dst_abbrind < 0
574                     && (std_offset == 0
575                         || zonename.startsWith("GMT+")
576                         || zonename.startsWith("GMT-")))
577                   offset_string = "";
578                 else
579                   {
580                     offset_string = Integer.toString(std_offset / 3600);
581                     int seconds = std_offset % 3600;
582                     if (seconds != 0)
583                       {
584                         if (seconds < 0)
585                           seconds *= -1;
586                         if (seconds < 600)
587                           offset_string
588                             += ":0" + Integer.toString(seconds / 60);
589                         else
590                           offset_string
591                             += ":" + Integer.toString(seconds / 60);
592                         seconds = seconds % 60;
593                         if (seconds >= 10)
594                           offset_string
595                             += ":" + Integer.toString(seconds);
596                         else if (seconds > 0)
597                           offset_string
598                             += ":0" + Integer.toString(seconds);
599                       }
600                     if (dst_abbrind >= 0
601                         && dst_offset != std_offset - 3600)
602                       {
603                         dst_offset_string
604                           = Integer.toString(dst_offset / 3600);
605                         seconds = dst_offset % 3600;
606                         if (seconds != 0)
607                           {
608                             if (seconds < 0)
609                               seconds *= -1;
610                             if (seconds < 600)
611                               dst_offset_string
612                                 += ":0" + Integer.toString(seconds / 60);
613                             else
614                               dst_offset_string
615                                 += ":" + Integer.toString(seconds / 60);
616                             seconds = seconds % 60;
617                             if (seconds >= 10)
618                               dst_offset_string
619                                 += ":" + Integer.toString(seconds);
620                             else if (seconds > 0)
621                               dst_offset_string
622                                 += ":0" + Integer.toString(seconds);
623                           }
624                       }
625                   }
626
627                 if (dst_abbrind < 0)
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];
632               }
633             else if (tzif2 == 8)
634               skipFully(dis, charcnt);
635           }
636         else if (tzif2 == 8)
637           skipFully(dis, timecnt * (8 + 1) + typecnt * (4 + 1 + 1) + charcnt);
638
639         if (tzif2 == 8)
640           {
641             // Skip over the rest of 64-bit data
642             skipFully(dis, leapcnt * (8 + 4) + ttisgmtcnt + ttisstdcnt);
643             if (dis.readByte() == '\n')
644               {
645                 String posixtz = dis.readLine();
646                 if (posixtz.length() > 0)
647                   id = posixtz;
648               }
649           }
650
651         return id;
652       }
653     catch (IOException ioe)
654       {
655         // Parse error, not a proper tzfile.
656         return null;
657       }
658     finally
659       {
660         try
661           {
662             if (dis != null)
663               dis.close();
664           }
665         catch(IOException ioe)
666           {
667             // Error while close, nothing we can do.
668           }
669       }
670   }
671   
672   /**
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.
676    */
677   private static void skipFully(InputStream is, long l) throws IOException
678   {
679     while (l > 0)
680       {
681         long k = is.skip(l);
682         if (k <= 0)
683           throw new EOFException();
684         l -= k;
685       }
686   }
687
688   /**
689    * Tries to get the system time zone id through native code.
690    */
691   private static native String getSystemTimeZoneId();
692 }