// Copyright (c) Corporation for National Research Initiatives // An implementation of the Python standard time module. Currently // unimplemented: // // accept2dyear // strptime() // // There may also be some incompatibilities in strftime(), because the Java // tools for creating those formats don't always map to C's strftime() // function. // // NOTE: This file is prepared for the JDK 1.2 APIs, however it is // currently set up to compile cleanly under 1.1. // // If you would like to enable the JDK 1.2 behavior (perhaps because you // are running under JDK 1.2 and would like to actually have stuff like // time.tzname or time.altzone work correctly, just search for the string // "XXXAPI" and stick a couple of double slashes at the beginning of each // matching line. // see org/python/modules/time.java for previous history. package org.python.modules.time; import java.lang.reflect.Method; import java.text.DateFormatSymbols; import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.util.Locale; import java.util.TimeZone; import org.python.core.ClassDictInit; import org.python.core.Py; import org.python.core.PyBuiltinFunctionSet; import org.python.core.PyException; import org.python.core.PyInteger; import org.python.core.PyObject; import org.python.core.PyString; import org.python.core.PyTuple; import org.python.core.PyType; class TimeFunctions extends PyBuiltinFunctionSet { public TimeFunctions(String name, int index, int argcount) { super(name, index, argcount); } public PyObject __call__() { switch (index) { case 0: return Py.newFloat(Time.time()); case 1: return Py.newFloat(Time.clock()); default: throw info.unexpectedCall(0, false); } } } public class Time implements ClassDictInit { public static PyString __doc__ = new PyString("This module provides various functions to manipulate time values.\n" + "\n" + "There are two standard representations of time. One is the " + "number\n" + "of seconds since the Epoch, in UTC (a.k.a. GMT). It may be an " + "integer\n" + "or a floating point number (to represent fractions of seconds).\n" + "The Epoch is system-defined; on Unix, it is generally " + "January 1st, 1970.\n" + "The actual value can be retrieved by calling gmtime(0).\n" + "\n" + "The other representation is a tuple of 9 integers giving " + "local time.\n" + "The tuple items are:\n" + " year (four digits, e.g. 1998)\n" + " month (1-12)\n" + " day (1-31)\n" + " hours (0-23)\n" + " minutes (0-59)\n" + " seconds (0-59)\n" + " weekday (0-6, Monday is 0)\n" + " Julian day (day in the year, 1-366)\n" + " DST (Daylight Savings Time) flag (-1, 0 or 1)\n" + "If the DST flag is 0, the time is given in the regular time zone;\n" + "if it is 1, the time is given in the DST time zone;\n" + "if it is -1, mktime() should guess based on the date and time.\n" + "\n" + "Variables:\n" + "\n" + "timezone -- difference in seconds between UTC and local " + "standard time\n" + "altzone -- difference in seconds between UTC and local DST time\n" + "daylight -- whether local time should reflect DST\n" + "tzname -- tuple of (standard time zone name, DST time zone name)\n" + "\n" + "Functions:\n" + "\n" + "time() -- return current time in seconds since the Epoch " + "as a float\n" + "clock() -- return CPU time since process start as a float\n" + "sleep() -- delay for a number of seconds given as a float\n" + "gmtime() -- convert seconds since Epoch to UTC tuple\n" + "localtime() -- convert seconds since Epoch to local time tuple\n" + "asctime() -- convert time tuple to string\n" + "ctime() -- convert time in seconds to string\n" + "mktime() -- convert local time tuple to seconds since Epoch\n" + "strftime() -- convert time tuple to string according to " + "format specification\n" + "strptime() -- parse string to time tuple according to " + "format specification\n"); public static void classDictInit(PyObject dict) { dict.__setitem__("time", new TimeFunctions("time", 0, 0)); dict.__setitem__("clock", new TimeFunctions("clock", 1, 0)); dict.__setitem__("struct_time", PyType.fromClass(PyTimeTuple.class)); // calculate the static variables tzname, timezone, altzone, daylight TimeZone tz = TimeZone.getDefault(); tzname = new PyTuple(new PyObject[] { new PyString(getDisplayName(tz, false, 0)), new PyString(getDisplayName(tz, true, 0)) }); daylight = tz.useDaylightTime() ? 1 : 0; timezone = -tz.getRawOffset() / 1000; altzone = timezone - getDSTSavings(tz) / 1000; } public static double time() { return System.currentTimeMillis() / 1000.0; } private static double __initialclock__ = 0.0; public static double clock() { if (__initialclock__ == 0.0) { // set on the first call __initialclock__ = time(); } return time() - __initialclock__; } private static void throwValueError(String msg) { throw new PyException(Py.ValueError, new PyString(msg)); } private static int item(PyTuple tup, int i) { // knows about and asserts format on tuple items. See // documentation for Python's time module for details. int val = ((PyInteger) tup.__getitem__(i).__int__()).getValue(); boolean valid = true; switch (i) { case 0: break; // year case 1: valid = (1 <= val && val <= 12); break; // month 1-12 case 2: valid = (1 <= val && val <= 31); break; // day 1 - 31 case 3: valid = (0 <= val && val <= 23); break; // hour 0 - 23 case 4: valid = (0 <= val && val <= 59); break; // minute 0 - 59 case 5: valid = (0 <= val && val <= 59); break; // second 0 - 59 case 6: valid = (0 <= val && val <= 6); break; // weekday 0 - 6 case 7: valid = (1 <= val && val < 367); break; // julian day 1 - 366 case 8: valid = (-1 <= val && val <= 1); break; // d.s. flag, -1,0,1 } // raise a ValueError if not within range if (!valid) { String msg; switch (i) { case 1: msg = "month out of range (1-12)"; break; case 2: msg = "day out of range (1-31)"; break; case 3: msg = "hour out of range (0-23)"; break; case 4: msg = "minute out of range (0-59)"; break; case 5: msg = "second out of range (0-59)"; break; case 6: msg = "day of week out of range (0-6)"; break; case 7: msg = "day of year out of range (1-366)"; break; case 8: msg = "daylight savings flag out of range (-1,0,1)"; break; default: // make compiler happy msg = "ignore"; break; } throwValueError(msg); } // Java's months are usually 0-11 if (i == 1) val--; return val; } private static GregorianCalendar _tupletocal(PyTuple tup) { return new GregorianCalendar(item(tup, 0), item(tup, 1), item(tup, 2), item(tup, 3), item(tup, 4), item(tup, 5)); } public static double mktime(PyTuple tup) { GregorianCalendar cal; try { cal = _tupletocal(tup); } catch (PyException e) { // CPython's mktime raises OverflowErrors... yuck! e.type = Py.OverflowError; throw e; } int dst = item(tup, 8); if (dst == 0 || dst == 1) { cal.set(Calendar.DST_OFFSET, dst * getDSTSavings(cal.getTimeZone())); } return (double) cal.getTime().getTime() / 1000.0; } protected static PyTimeTuple _timefields(double secs, TimeZone tz) { GregorianCalendar cal = new GregorianCalendar(tz); cal.clear(); cal.setTime(new Date((long) (secs * 1000))); // This call used to be needed to work around JVM bugs. // It appears to break jdk1.2, so it's not removed. // cal.clear(); int dow = cal.get(Calendar.DAY_OF_WEEK) - 2; if (dow < 0) dow = dow + 7; // TBD: is this date dst? boolean isdst = tz.inDaylightTime(cal.getTime()); return new PyTimeTuple(new PyObject[] { new PyInteger(cal.get(Calendar.YEAR)), new PyInteger(cal.get(Calendar.MONTH) + 1), new PyInteger(cal.get(Calendar.DAY_OF_MONTH)), new PyInteger(cal.get(Calendar.HOUR) + 12 * cal.get(Calendar.AM_PM)), new PyInteger(cal.get(Calendar.MINUTE)), new PyInteger(cal.get(Calendar.SECOND)), new PyInteger(dow), new PyInteger(cal.get(Calendar.DAY_OF_YEAR)), new PyInteger(isdst ? 1 : 0) }); } public static PyTuple localtime() { return localtime(time()); } public static PyTuple localtime(double secs) { return _timefields(secs, TimeZone.getDefault()); } public static PyTuple gmtime() { return gmtime(time()); } public static PyTuple gmtime(double secs) { return _timefields(secs, TimeZone.getTimeZone("GMT")); } public static String ctime() { return ctime(time()); } public static String ctime(double secs) { return asctime(localtime(secs)); } // Python's time module specifies use of current locale protected static Locale currentLocale = null; protected static DateFormatSymbols datesyms = new DateFormatSymbols(); protected static String[] shortdays = null; protected static String[] shortmonths = null; private static String[] enshortdays = new String[] { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" }; private static String[] enshortmonths = new String[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; private static String _shortday(int dow) { // we need to hand craft shortdays[] because Java and Python have // different specifications. Java (undocumented) appears to be // first element "", followed by 0=Sun. Python says 0=Mon try { if (shortdays == null) { shortdays = new String[7]; String[] names = datesyms.getShortWeekdays(); for (int i = 0; i < 6; i++) shortdays[i] = names[i + 2]; shortdays[6] = names[1]; } } catch (ArrayIndexOutOfBoundsException e) { throwValueError("day of week out of range (0-6)"); } return shortdays[dow]; } private static String _shortmonth(int month0to11) { // getShortWeekdays() returns a 13 element array with the last item // being the empty string. This is also undocumented ;-/ try { if (shortmonths == null) { shortmonths = new String[12]; String[] names = datesyms.getShortMonths(); for (int i = 0; i < 12; i++) shortmonths[i] = names[i]; } } catch (ArrayIndexOutOfBoundsException e) { throwValueError("month out of range (1-12)"); } return shortmonths[month0to11]; } private static String _padint(int i, int target) { String s = Integer.toString(i); int sz = s.length(); if (target <= sz) // no truncation return s; if (target == sz + 1) return "0" + s; if (target == sz + 2) return "00" + s; else { char[] c = new char[target - sz]; Arrays.fill(c, '0'); return new String(c) + s; } } private static String _twodigit(int i) { return _padint(i, 2); } private static String _truncyear(int year) { String yearstr = _padint(year, 4); return yearstr.substring(yearstr.length() - 2, yearstr.length()); } public static String asctime() { return asctime(localtime()); } public static String asctime(PyTuple tup) { StringBuffer buf = new StringBuffer(25); buf.append(enshortdays[item(tup, 6)]).append(' '); buf.append(enshortmonths[item(tup, 1)]).append(' '); int dayOfMonth = item(tup, 2); if (dayOfMonth < 10) { buf.append(' '); } buf.append(dayOfMonth).append(' '); buf.append(_twodigit(item(tup, 3))).append(':'); buf.append(_twodigit(item(tup, 4))).append(':'); buf.append(_twodigit(item(tup, 5))).append(' '); return buf.append(item(tup, 0)).toString(); } public static String locale_asctime(PyTuple tup) { checkLocale(); int day = item(tup, 6); int mon = item(tup, 1); return _shortday(day) + " " + _shortmonth(mon) + " " + _twodigit(item(tup, 2)) + " " + _twodigit(item(tup, 3)) + ":" + _twodigit(item(tup, 4)) + ":" + _twodigit(item(tup, 5)) + " " + item(tup, 0); } public static void sleep(double secs) { try { java.lang.Thread.sleep((long) (secs * 1000)); } catch (java.lang.InterruptedException e) { throw new PyException(Py.KeyboardInterrupt, "interrupted sleep"); } } // set by classDictInit() public static int timezone; public static int altzone = -1; public static int daylight; public static PyTuple tzname = null; // TBD: should we accept 2 digit years? should we make this attribute // writable but ignore its value? public static final int accept2dyear = 0; public static String strftime(String format) { return strftime(format, localtime()); } public static String strftime(String format, PyTuple tup) { checkLocale(); String s = ""; int lastc = 0; int j; String[] syms; GregorianCalendar cal = null; while (lastc < format.length()) { int i = format.indexOf("%", lastc); if (i < 0) { // the end of the format string s = s + format.substring(lastc); break; } if (i == format.length() - 1) { // there's a bare % at the end of the string. Python lets // this go by just sticking a % at the end of the result // string s = s + "%"; break; } s = s + format.substring(lastc, i); i++; switch (format.charAt(i)) { case 'a': // abbrev weekday j = item(tup, 6); s = s + _shortday(j); break; case 'A': // full weekday // see _shortday() syms = datesyms.getWeekdays(); j = item(tup, 6); if (0 <= j && j < 6) s = s + syms[j + 2]; else if (j == 6) s = s + syms[1]; else throwValueError("day of week out of range (0 - 6)"); break; case 'b': // abbrev month j = item(tup, 1); s = s + _shortmonth(j); break; case 'B': // full month syms = datesyms.getMonths(); j = item(tup, 1); s = s + syms[j]; break; case 'c': s = s + locale_asctime(tup); break; case 'd': // day of month (01-31) s = s + _twodigit(item(tup, 2)); break; case 'H': // hour (00-23) s = s + _twodigit(item(tup, 3)); break; case 'I': // hour (01-12) j = item(tup, 3) % 12; if (j == 0) j = 12; // midnight or noon s = s + _twodigit(j); break; case 'j': // day of year (001-366) s = s + _padint(item(tup, 7), 3); break; case 'm': // month (01-12) s = s + _twodigit(item(tup, 1) + 1); break; case 'M': // minute (00-59) s = s + _twodigit(item(tup, 4)); break; case 'p': // AM/PM j = item(tup, 3); syms = datesyms.getAmPmStrings(); if (0 <= j && j < 12) s = s + syms[0]; else if (12 <= j && j < 24) s = s + syms[1]; else throwValueError("hour out of range (0-23)"); break; case 'S': // seconds (00-61) s = s + _twodigit(item(tup, 5)); break; case 'U': // week of year (sunday is first day) (00-53). all days in // new year preceding first sunday are considered to be in // week 0 if (cal == null) cal = _tupletocal(tup); cal.setFirstDayOfWeek(cal.SUNDAY); cal.setMinimalDaysInFirstWeek(7); j = cal.get(cal.WEEK_OF_YEAR); if (cal.get(cal.MONTH) == cal.JANUARY && j >= 52) j = 0; s = s + _twodigit(j); break; case 'w': // weekday as decimal (0=Sunday-6) // tuple format has monday=0 j = (item(tup, 6) + 1) % 7; s = s + _twodigit(j); break; case 'W': // week of year (monday is first day) (00-53). all days in // new year preceding first sunday are considered to be in // week 0 if (cal == null) cal = _tupletocal(tup); cal.setFirstDayOfWeek(cal.MONDAY); cal.setMinimalDaysInFirstWeek(7); j = cal.get(cal.WEEK_OF_YEAR); if (cal.get(cal.MONTH) == cal.JANUARY && j >= 52) j = 0; s = s + _twodigit(j); break; case 'x': // TBD: A note about %x and %X. Python's time.strftime() // by default uses the "C" locale, which is changed by // using the setlocale() function. In Java, the default // locale is set by user.language and user.region // properties and is "en_US" by default, at least around // here! Locale "en_US" differs from locale "C" in the way // it represents dates and times. Eventually we might want // to craft a "C" locale for Java and set Jython to use // this by default, but that's too much work right now. // // For now, we hard code %x and %X to return values // formatted in the "C" locale, i.e. the default way // CPython does it. E.g.: // %x == mm/dd/yy // %X == HH:mm:SS // s = s + _twodigit(item(tup, 1) + 1) + "/" + _twodigit(item(tup, 2)) + "/" + _truncyear(item(tup, 0)); break; case 'X': // See comment for %x above s = s + _twodigit(item(tup, 3)) + ":" + _twodigit(item(tup, 4)) + ":" + _twodigit(item(tup, 5)); break; case 'Y': // year w/ century s = s + _padint(item(tup, 0), 4); break; case 'y': // year w/o century (00-99) s = s + _truncyear(item(tup, 0)); break; case 'Z': // timezone name if (cal == null) cal = _tupletocal(tup); s = s + getDisplayName(cal.getTimeZone(), // in daylight savings time? true if == 1 -1 // means the information was not available; // treat this as if not in dst item(tup, 8) > 0, 0); break; case '%': // % s = s + "%"; break; default: // TBD: should this raise a ValueError? s = s + "%" + format.charAt(i); i++; break; } lastc = i + 1; i++; } return s; } private static void checkLocale() { if (!Locale.getDefault().equals(currentLocale)) { currentLocale = Locale.getDefault(); datesyms = new DateFormatSymbols(currentLocale); shortdays = null; shortmonths = null; } } private static String getDisplayName(TimeZone tz, boolean dst, int style) { String version = System.getProperty("java.version"); if (version.compareTo("1.2") >= 0) { try { Method m = tz.getClass().getMethod("getDisplayName", new Class[] { Boolean.TYPE, Integer.TYPE }); return (String) m.invoke(tz, new Object[] { new Boolean(dst), new Integer(style) }); } catch (Exception exc) { } } return tz.getID(); } private static int getDSTSavings(TimeZone tz) { String version = System.getProperty("java.version"); if (version.compareTo("1.2") >= 0) { try { Method m = tz.getClass().getMethod("getDSTSavings", (Class[]) null); return ((Integer) m.invoke(tz, (Object[]) null)).intValue(); } catch (Exception exc) { } } return 0; } }