001: // Copyright (c) Corporation for National Research Initiatives
002:
003: // An implementation of the Python standard time module. Currently
004: // unimplemented:
005: //
006: // accept2dyear
007: // strptime()
008: //
009: // There may also be some incompatibilities in strftime(), because the Java
010: // tools for creating those formats don't always map to C's strftime()
011: // function.
012: //
013: // NOTE: This file is prepared for the JDK 1.2 APIs, however it is
014: // currently set up to compile cleanly under 1.1.
015: //
016: // If you would like to enable the JDK 1.2 behavior (perhaps because you
017: // are running under JDK 1.2 and would like to actually have stuff like
018: // time.tzname or time.altzone work correctly, just search for the string
019: // "XXXAPI" and stick a couple of double slashes at the beginning of each
020: // matching line.
021:
022: // see org/python/modules/time.java for previous history.
023: package org.python.modules.time;
024:
025: import java.lang.reflect.Method;
026: import java.text.DateFormatSymbols;
027: import java.util.Arrays;
028: import java.util.Calendar;
029: import java.util.Date;
030: import java.util.GregorianCalendar;
031: import java.util.Locale;
032: import java.util.TimeZone;
033:
034: import org.python.core.ClassDictInit;
035: import org.python.core.Py;
036: import org.python.core.PyBuiltinFunctionSet;
037: import org.python.core.PyException;
038: import org.python.core.PyInteger;
039: import org.python.core.PyObject;
040: import org.python.core.PyString;
041: import org.python.core.PyTuple;
042: import org.python.core.PyType;
043:
044: class TimeFunctions extends PyBuiltinFunctionSet {
045: public TimeFunctions(String name, int index, int argcount) {
046: super (name, index, argcount);
047: }
048:
049: public PyObject __call__() {
050: switch (index) {
051: case 0:
052: return Py.newFloat(Time.time());
053: case 1:
054: return Py.newFloat(Time.clock());
055: default:
056: throw info.unexpectedCall(0, false);
057: }
058: }
059: }
060:
061: public class Time implements ClassDictInit {
062: public static PyString __doc__ = new PyString(
063: "This module provides various functions to manipulate time values.\n"
064: + "\n"
065: + "There are two standard representations of time. One is the "
066: + "number\n"
067: + "of seconds since the Epoch, in UTC (a.k.a. GMT). It may be an "
068: + "integer\n"
069: + "or a floating point number (to represent fractions of seconds).\n"
070: + "The Epoch is system-defined; on Unix, it is generally "
071: + "January 1st, 1970.\n"
072: + "The actual value can be retrieved by calling gmtime(0).\n"
073: + "\n"
074: + "The other representation is a tuple of 9 integers giving "
075: + "local time.\n"
076: + "The tuple items are:\n"
077: + " year (four digits, e.g. 1998)\n"
078: + " month (1-12)\n"
079: + " day (1-31)\n"
080: + " hours (0-23)\n"
081: + " minutes (0-59)\n"
082: + " seconds (0-59)\n"
083: + " weekday (0-6, Monday is 0)\n"
084: + " Julian day (day in the year, 1-366)\n"
085: + " DST (Daylight Savings Time) flag (-1, 0 or 1)\n"
086: + "If the DST flag is 0, the time is given in the regular time zone;\n"
087: + "if it is 1, the time is given in the DST time zone;\n"
088: + "if it is -1, mktime() should guess based on the date and time.\n"
089: + "\n"
090: + "Variables:\n"
091: + "\n"
092: + "timezone -- difference in seconds between UTC and local "
093: + "standard time\n"
094: + "altzone -- difference in seconds between UTC and local DST time\n"
095: + "daylight -- whether local time should reflect DST\n"
096: + "tzname -- tuple of (standard time zone name, DST time zone name)\n"
097: + "\n"
098: + "Functions:\n"
099: + "\n"
100: + "time() -- return current time in seconds since the Epoch "
101: + "as a float\n"
102: + "clock() -- return CPU time since process start as a float\n"
103: + "sleep() -- delay for a number of seconds given as a float\n"
104: + "gmtime() -- convert seconds since Epoch to UTC tuple\n"
105: + "localtime() -- convert seconds since Epoch to local time tuple\n"
106: + "asctime() -- convert time tuple to string\n"
107: + "ctime() -- convert time in seconds to string\n"
108: + "mktime() -- convert local time tuple to seconds since Epoch\n"
109: + "strftime() -- convert time tuple to string according to "
110: + "format specification\n"
111: + "strptime() -- parse string to time tuple according to "
112: + "format specification\n");
113:
114: public static void classDictInit(PyObject dict) {
115: dict.__setitem__("time", new TimeFunctions("time", 0, 0));
116: dict.__setitem__("clock", new TimeFunctions("clock", 1, 0));
117: dict.__setitem__("struct_time", PyType
118: .fromClass(PyTimeTuple.class));
119:
120: // calculate the static variables tzname, timezone, altzone, daylight
121: TimeZone tz = TimeZone.getDefault();
122:
123: tzname = new PyTuple(new PyObject[] {
124: new PyString(getDisplayName(tz, false, 0)),
125: new PyString(getDisplayName(tz, true, 0)) });
126:
127: daylight = tz.useDaylightTime() ? 1 : 0;
128: timezone = -tz.getRawOffset() / 1000;
129: altzone = timezone - getDSTSavings(tz) / 1000;
130: }
131:
132: public static double time() {
133: return System.currentTimeMillis() / 1000.0;
134: }
135:
136: private static double __initialclock__ = 0.0;
137:
138: public static double clock() {
139: if (__initialclock__ == 0.0) {
140: // set on the first call
141: __initialclock__ = time();
142: }
143: return time() - __initialclock__;
144: }
145:
146: private static void throwValueError(String msg) {
147: throw new PyException(Py.ValueError, new PyString(msg));
148: }
149:
150: private static int item(PyTuple tup, int i) {
151: // knows about and asserts format on tuple items. See
152: // documentation for Python's time module for details.
153: int val = ((PyInteger) tup.__getitem__(i).__int__()).getValue();
154: boolean valid = true;
155: switch (i) {
156: case 0:
157: break; // year
158: case 1:
159: valid = (1 <= val && val <= 12);
160: break; // month 1-12
161: case 2:
162: valid = (1 <= val && val <= 31);
163: break; // day 1 - 31
164: case 3:
165: valid = (0 <= val && val <= 23);
166: break; // hour 0 - 23
167: case 4:
168: valid = (0 <= val && val <= 59);
169: break; // minute 0 - 59
170: case 5:
171: valid = (0 <= val && val <= 59);
172: break; // second 0 - 59
173: case 6:
174: valid = (0 <= val && val <= 6);
175: break; // weekday 0 - 6
176: case 7:
177: valid = (1 <= val && val < 367);
178: break; // julian day 1 - 366
179: case 8:
180: valid = (-1 <= val && val <= 1);
181: break; // d.s. flag, -1,0,1
182: }
183: // raise a ValueError if not within range
184: if (!valid) {
185: String msg;
186: switch (i) {
187: case 1:
188: msg = "month out of range (1-12)";
189: break;
190: case 2:
191: msg = "day out of range (1-31)";
192: break;
193: case 3:
194: msg = "hour out of range (0-23)";
195: break;
196: case 4:
197: msg = "minute out of range (0-59)";
198: break;
199: case 5:
200: msg = "second out of range (0-59)";
201: break;
202: case 6:
203: msg = "day of week out of range (0-6)";
204: break;
205: case 7:
206: msg = "day of year out of range (1-366)";
207: break;
208: case 8:
209: msg = "daylight savings flag out of range (-1,0,1)";
210: break;
211: default:
212: // make compiler happy
213: msg = "ignore";
214: break;
215: }
216: throwValueError(msg);
217: }
218: // Java's months are usually 0-11
219: if (i == 1)
220: val--;
221: return val;
222: }
223:
224: private static GregorianCalendar _tupletocal(PyTuple tup) {
225: return new GregorianCalendar(item(tup, 0), item(tup, 1), item(
226: tup, 2), item(tup, 3), item(tup, 4), item(tup, 5));
227: }
228:
229: public static double mktime(PyTuple tup) {
230: GregorianCalendar cal;
231: try {
232: cal = _tupletocal(tup);
233: } catch (PyException e) {
234: // CPython's mktime raises OverflowErrors... yuck!
235: e.type = Py.OverflowError;
236: throw e;
237: }
238: int dst = item(tup, 8);
239: if (dst == 0 || dst == 1) {
240: cal.set(Calendar.DST_OFFSET, dst
241: * getDSTSavings(cal.getTimeZone()));
242: }
243: return (double) cal.getTime().getTime() / 1000.0;
244: }
245:
246: protected static PyTimeTuple _timefields(double secs, TimeZone tz) {
247: GregorianCalendar cal = new GregorianCalendar(tz);
248: cal.clear();
249: cal.setTime(new Date((long) (secs * 1000)));
250: // This call used to be needed to work around JVM bugs.
251: // It appears to break jdk1.2, so it's not removed.
252: // cal.clear();
253: int dow = cal.get(Calendar.DAY_OF_WEEK) - 2;
254: if (dow < 0)
255: dow = dow + 7;
256: // TBD: is this date dst?
257: boolean isdst = tz.inDaylightTime(cal.getTime());
258: return new PyTimeTuple(new PyObject[] {
259: new PyInteger(cal.get(Calendar.YEAR)),
260: new PyInteger(cal.get(Calendar.MONTH) + 1),
261: new PyInteger(cal.get(Calendar.DAY_OF_MONTH)),
262: new PyInteger(cal.get(Calendar.HOUR) + 12
263: * cal.get(Calendar.AM_PM)),
264: new PyInteger(cal.get(Calendar.MINUTE)),
265: new PyInteger(cal.get(Calendar.SECOND)),
266: new PyInteger(dow),
267: new PyInteger(cal.get(Calendar.DAY_OF_YEAR)),
268: new PyInteger(isdst ? 1 : 0) });
269: }
270:
271: public static PyTuple localtime() {
272: return localtime(time());
273: }
274:
275: public static PyTuple localtime(double secs) {
276: return _timefields(secs, TimeZone.getDefault());
277: }
278:
279: public static PyTuple gmtime() {
280: return gmtime(time());
281: }
282:
283: public static PyTuple gmtime(double secs) {
284: return _timefields(secs, TimeZone.getTimeZone("GMT"));
285: }
286:
287: public static String ctime() {
288: return ctime(time());
289: }
290:
291: public static String ctime(double secs) {
292: return asctime(localtime(secs));
293: }
294:
295: // Python's time module specifies use of current locale
296: protected static Locale currentLocale = null;
297: protected static DateFormatSymbols datesyms = new DateFormatSymbols();
298: protected static String[] shortdays = null;
299: protected static String[] shortmonths = null;
300:
301: private static String[] enshortdays = new String[] { "Mon", "Tue",
302: "Wed", "Thu", "Fri", "Sat", "Sun" };
303:
304: private static String[] enshortmonths = new String[] { "Jan",
305: "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep",
306: "Oct", "Nov", "Dec" };
307:
308: private static String _shortday(int dow) {
309: // we need to hand craft shortdays[] because Java and Python have
310: // different specifications. Java (undocumented) appears to be
311: // first element "", followed by 0=Sun. Python says 0=Mon
312: try {
313: if (shortdays == null) {
314: shortdays = new String[7];
315: String[] names = datesyms.getShortWeekdays();
316: for (int i = 0; i < 6; i++)
317: shortdays[i] = names[i + 2];
318: shortdays[6] = names[1];
319: }
320: } catch (ArrayIndexOutOfBoundsException e) {
321: throwValueError("day of week out of range (0-6)");
322: }
323: return shortdays[dow];
324: }
325:
326: private static String _shortmonth(int month0to11) {
327: // getShortWeekdays() returns a 13 element array with the last item
328: // being the empty string. This is also undocumented ;-/
329: try {
330: if (shortmonths == null) {
331: shortmonths = new String[12];
332: String[] names = datesyms.getShortMonths();
333: for (int i = 0; i < 12; i++)
334: shortmonths[i] = names[i];
335: }
336: } catch (ArrayIndexOutOfBoundsException e) {
337: throwValueError("month out of range (1-12)");
338: }
339: return shortmonths[month0to11];
340: }
341:
342: private static String _padint(int i, int target) {
343: String s = Integer.toString(i);
344: int sz = s.length();
345: if (target <= sz)
346: // no truncation
347: return s;
348: if (target == sz + 1)
349: return "0" + s;
350: if (target == sz + 2)
351: return "00" + s;
352: else {
353: char[] c = new char[target - sz];
354: Arrays.fill(c, '0');
355: return new String(c) + s;
356: }
357: }
358:
359: private static String _twodigit(int i) {
360: return _padint(i, 2);
361: }
362:
363: private static String _truncyear(int year) {
364: String yearstr = _padint(year, 4);
365: return yearstr
366: .substring(yearstr.length() - 2, yearstr.length());
367: }
368:
369: public static String asctime() {
370: return asctime(localtime());
371: }
372:
373: public static String asctime(PyTuple tup) {
374: StringBuffer buf = new StringBuffer(25);
375: buf.append(enshortdays[item(tup, 6)]).append(' ');
376: buf.append(enshortmonths[item(tup, 1)]).append(' ');
377: int dayOfMonth = item(tup, 2);
378: if (dayOfMonth < 10) {
379: buf.append(' ');
380: }
381: buf.append(dayOfMonth).append(' ');
382: buf.append(_twodigit(item(tup, 3))).append(':');
383: buf.append(_twodigit(item(tup, 4))).append(':');
384: buf.append(_twodigit(item(tup, 5))).append(' ');
385: return buf.append(item(tup, 0)).toString();
386: }
387:
388: public static String locale_asctime(PyTuple tup) {
389: checkLocale();
390: int day = item(tup, 6);
391: int mon = item(tup, 1);
392: return _shortday(day) + " " + _shortmonth(mon) + " "
393: + _twodigit(item(tup, 2)) + " "
394: + _twodigit(item(tup, 3)) + ":"
395: + _twodigit(item(tup, 4)) + ":"
396: + _twodigit(item(tup, 5)) + " " + item(tup, 0);
397: }
398:
399: public static void sleep(double secs) {
400: try {
401: java.lang.Thread.sleep((long) (secs * 1000));
402: } catch (java.lang.InterruptedException e) {
403: throw new PyException(Py.KeyboardInterrupt,
404: "interrupted sleep");
405: }
406: }
407:
408: // set by classDictInit()
409: public static int timezone;
410: public static int altzone = -1;
411: public static int daylight;
412: public static PyTuple tzname = null;
413: // TBD: should we accept 2 digit years? should we make this attribute
414: // writable but ignore its value?
415: public static final int accept2dyear = 0;
416:
417: public static String strftime(String format) {
418: return strftime(format, localtime());
419: }
420:
421: public static String strftime(String format, PyTuple tup) {
422: checkLocale();
423:
424: String s = "";
425: int lastc = 0;
426: int j;
427: String[] syms;
428: GregorianCalendar cal = null;
429: while (lastc < format.length()) {
430: int i = format.indexOf("%", lastc);
431: if (i < 0) {
432: // the end of the format string
433: s = s + format.substring(lastc);
434: break;
435: }
436: if (i == format.length() - 1) {
437: // there's a bare % at the end of the string. Python lets
438: // this go by just sticking a % at the end of the result
439: // string
440: s = s + "%";
441: break;
442: }
443: s = s + format.substring(lastc, i);
444: i++;
445: switch (format.charAt(i)) {
446: case 'a':
447: // abbrev weekday
448: j = item(tup, 6);
449: s = s + _shortday(j);
450: break;
451: case 'A':
452: // full weekday
453: // see _shortday()
454: syms = datesyms.getWeekdays();
455: j = item(tup, 6);
456: if (0 <= j && j < 6)
457: s = s + syms[j + 2];
458: else if (j == 6)
459: s = s + syms[1];
460: else
461: throwValueError("day of week out of range (0 - 6)");
462: break;
463: case 'b':
464: // abbrev month
465: j = item(tup, 1);
466: s = s + _shortmonth(j);
467: break;
468: case 'B':
469: // full month
470: syms = datesyms.getMonths();
471: j = item(tup, 1);
472: s = s + syms[j];
473: break;
474: case 'c':
475: s = s + locale_asctime(tup);
476: break;
477: case 'd':
478: // day of month (01-31)
479: s = s + _twodigit(item(tup, 2));
480: break;
481: case 'H':
482: // hour (00-23)
483: s = s + _twodigit(item(tup, 3));
484: break;
485: case 'I':
486: // hour (01-12)
487: j = item(tup, 3) % 12;
488: if (j == 0)
489: j = 12; // midnight or noon
490: s = s + _twodigit(j);
491: break;
492: case 'j':
493: // day of year (001-366)
494: s = s + _padint(item(tup, 7), 3);
495: break;
496: case 'm':
497: // month (01-12)
498: s = s + _twodigit(item(tup, 1) + 1);
499: break;
500: case 'M':
501: // minute (00-59)
502: s = s + _twodigit(item(tup, 4));
503: break;
504: case 'p':
505: // AM/PM
506: j = item(tup, 3);
507: syms = datesyms.getAmPmStrings();
508: if (0 <= j && j < 12)
509: s = s + syms[0];
510: else if (12 <= j && j < 24)
511: s = s + syms[1];
512: else
513: throwValueError("hour out of range (0-23)");
514: break;
515: case 'S':
516: // seconds (00-61)
517: s = s + _twodigit(item(tup, 5));
518: break;
519: case 'U':
520: // week of year (sunday is first day) (00-53). all days in
521: // new year preceding first sunday are considered to be in
522: // week 0
523: if (cal == null)
524: cal = _tupletocal(tup);
525: cal.setFirstDayOfWeek(cal.SUNDAY);
526: cal.setMinimalDaysInFirstWeek(7);
527: j = cal.get(cal.WEEK_OF_YEAR);
528: if (cal.get(cal.MONTH) == cal.JANUARY && j >= 52)
529: j = 0;
530: s = s + _twodigit(j);
531: break;
532: case 'w':
533: // weekday as decimal (0=Sunday-6)
534: // tuple format has monday=0
535: j = (item(tup, 6) + 1) % 7;
536: s = s + _twodigit(j);
537: break;
538: case 'W':
539: // week of year (monday is first day) (00-53). all days in
540: // new year preceding first sunday are considered to be in
541: // week 0
542: if (cal == null)
543: cal = _tupletocal(tup);
544: cal.setFirstDayOfWeek(cal.MONDAY);
545: cal.setMinimalDaysInFirstWeek(7);
546: j = cal.get(cal.WEEK_OF_YEAR);
547:
548: if (cal.get(cal.MONTH) == cal.JANUARY && j >= 52)
549: j = 0;
550: s = s + _twodigit(j);
551: break;
552: case 'x':
553: // TBD: A note about %x and %X. Python's time.strftime()
554: // by default uses the "C" locale, which is changed by
555: // using the setlocale() function. In Java, the default
556: // locale is set by user.language and user.region
557: // properties and is "en_US" by default, at least around
558: // here! Locale "en_US" differs from locale "C" in the way
559: // it represents dates and times. Eventually we might want
560: // to craft a "C" locale for Java and set JPython to use
561: // this by default, but that's too much work right now.
562: //
563: // For now, we hard code %x and %X to return values
564: // formatted in the "C" locale, i.e. the default way
565: // CPython does it. E.g.:
566: // %x == mm/dd/yy
567: // %X == HH:mm:SS
568: //
569: s = s + _twodigit(item(tup, 1) + 1) + "/"
570: + _twodigit(item(tup, 2)) + "/"
571: + _truncyear(item(tup, 0));
572: break;
573: case 'X':
574: // See comment for %x above
575: s = s + _twodigit(item(tup, 3)) + ":"
576: + _twodigit(item(tup, 4)) + ":"
577: + _twodigit(item(tup, 5));
578: break;
579: case 'Y':
580: // year w/ century
581: s = s + _padint(item(tup, 0), 4);
582: break;
583: case 'y':
584: // year w/o century (00-99)
585: s = s + _truncyear(item(tup, 0));
586: break;
587: case 'Z':
588: // timezone name
589: if (cal == null)
590: cal = _tupletocal(tup);
591: s = s + getDisplayName(cal.getTimeZone(),
592: // in daylight savings time? true if == 1 -1
593: // means the information was not available;
594: // treat this as if not in dst
595: item(tup, 8) > 0, 0);
596: break;
597: case '%':
598: // %
599: s = s + "%";
600: break;
601: default:
602: // TBD: should this raise a ValueError?
603: s = s + "%" + format.charAt(i);
604: i++;
605: break;
606: }
607: lastc = i + 1;
608: i++;
609: }
610: return s;
611: }
612:
613: private static void checkLocale() {
614: if (!Locale.getDefault().equals(currentLocale)) {
615: currentLocale = Locale.getDefault();
616: datesyms = new DateFormatSymbols(currentLocale);
617: shortdays = null;
618: shortmonths = null;
619: }
620: }
621:
622: private static String getDisplayName(TimeZone tz, boolean dst,
623: int style) {
624: String version = System.getProperty("java.version");
625: if (version.compareTo("1.2") >= 0) {
626: try {
627: Method m = tz.getClass().getMethod("getDisplayName",
628: new Class[] { Boolean.TYPE, Integer.TYPE });
629: return (String) m.invoke(tz, new Object[] {
630: new Boolean(dst), new Integer(style) });
631: } catch (Exception exc) {
632: }
633: }
634: return tz.getID();
635: }
636:
637: private static int getDSTSavings(TimeZone tz) {
638: String version = System.getProperty("java.version");
639: if (version.compareTo("1.2") >= 0) {
640: try {
641: Method m = tz.getClass().getMethod("getDSTSavings",
642: (Class[]) null);
643: return ((Integer) m.invoke(tz, (Object[]) null))
644: .intValue();
645: } catch (Exception exc) {
646: }
647: }
648: return 0;
649: }
650:
651: }
|