strptime 0.1.4 → 0.1.5
raw patch · 4 files changed
+1305/−7 lines, 4 files
Files
- Data/Time/Parse.hsc +11/−6
- cbits/hstrptime.c +1235/−0
- include/hstrptime.h +53/−0
- strptime.cabal +6/−1
Data/Time/Parse.hsc view
@@ -31,12 +31,14 @@ import qualified Data.ByteString.Lazy.Internal as LI #include <time.h>+#include "hstrptime.h" -- | The class of values from which time may be parsed class Strptime a where- -- | Given a format string in the format of C's strptime (see <http://linux.die.net/man/3/strptime>)+ -- | Given a format string in the format of strptime (see <http://linux.die.net/man/3/strptime>) -- and a data string, parse a date+time value from the data string and also return the remainder- -- of the data string.+ -- of the data string. We also support a "%OS" format specifier for fractional seconds, because+ -- we are using the strptime from r-project.org. strptime :: a -> a -> Maybe (LocalTime, a) instance Strptime [Char] where@@ -77,7 +79,9 @@ return $ \s -> unsafePerformIO $ S.useAsCString s $ \cs -> do allocaBytes (#const sizeof(struct tm)) $ \p_tm -> do- last <- strptime_c cs (castPtr ztf) p_tm+ alloca $ \p_fsecs -> do+ alloca $ \p_offset -> do+ last <- hstrptime_c cs (castPtr ztf) p_tm p_fsecs p_offset if last == nullPtr then return Nothing else do@@ -87,8 +91,9 @@ mday <- (#peek struct tm,tm_mday ) p_tm :: IO CInt month <- (#peek struct tm,tm_mon ) p_tm :: IO CInt year <- (#peek struct tm,tm_year ) p_tm :: IO CInt+ fsecs <- peek p_fsecs let day = fromGregorian (fromIntegral (year+1900)) (1+fromIntegral month) (fromIntegral mday)- let tod = TimeOfDay (fromIntegral hour) (fromIntegral min) (fromIntegral sec)+ let tod = TimeOfDay (fromIntegral hour) (fromIntegral min) (fromIntegral (round (fsecs*1000000)) / fromIntegral 1000000) touchForeignPtr fztf @@ -96,5 +101,5 @@ type CTm = () -- struct tm -foreign import ccall unsafe "time.h strptime"- strptime_c :: CString -> CString -> Ptr CTm -> IO CString+foreign import ccall unsafe "hstrptime.h hstrptime"+ hstrptime_c :: CString -> CString -> Ptr CTm -> Ptr Double -> Ptr Int -> IO CString
+ cbits/hstrptime.c view
@@ -0,0 +1,1235 @@+/*+ A modified version of code from R (www.r-project.org)+ repackaged as a stand-alone .c file, with a couple of + R-specific bits removed .++ We use this version because of its support for input+ of fractional seconds.++ Modified by Eugene Kirpichov <ekirpichov@gmail.com>, 2010.++ */++/* For inclusion by datetime.c. ++ A modified version of code from the GNU C library with locale+ support removed and wchar support added.+*/++/* Convert a string representation of time to a time value.+ Copyright (C) 1996, 1997, 1998, 1999, 2000 Free Software Foundation, Inc.+ This file is part of the GNU C Library.+ Contributed by Ulrich Drepper <drepper@cygnus.com>, 1996.++ The GNU C Library is free software; you can redistribute it and/or+ modify it under the terms of the GNU Library General Public License as+ published by the Free Software Foundation; either version 2 of the+ License, or (at your option) any later version.++ The GNU C Library is distributed in the hope that it will be useful,+ but WITHOUT ANY WARRANTY; without even the implied warranty of+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU+ Library General Public License for more details.++ You should have received a copy of the GNU Library General Public+ License along with the GNU C Library; see the file COPYING.LIB. If not,+ a copy is available at http://www.r-project.org/licenses/+*/+/* XXX This version of the implementation is not really complete.+ Some of the fields cannot add information alone. But if seeing+ some of them in the same format (such as year, week and weekday)+ this is enough information for determining the date. */++#include <hstrptime.h>++#define match_char(ch1, ch2) if (ch1 != ch2) return NULL++/* we guarantee to have strncasecmp in R */+#if defined __GNUC__ && __GNUC__ >= 2+# define match_string(cs1, s2) \+ (__extension__ ({ size_t len = strlen (cs1); \+ int result = strncasecmp ((cs1), (s2), len) == 0; \+ if (result) (s2) += len; \+ result; }))+#else+/* Oh come on. Get a reasonable compiler. */+# define match_string(cs1, s2) \+ (strncasecmp ((cs1), (s2), strlen (cs1)) ? 0 : ((s2) += strlen (cs1), 1))+#endif++/* We intentionally do not use isdigit() for testing because this will+ lead to problems with the wide character version. */+#define get_number(from, to, n) \+ do { \+ int __n = n; \+ val = 0; \+ while (*rp == ' ') \+ ++rp; \+ if (*rp < '0' || *rp > '9') \+ return NULL; \+ do { \+ val *= 10; \+ val += *rp++ - '0'; \+/* } while (--__n > 0 && val * 10 <= to && *rp >= '0' && *rp <= '9');*/ \+ } while (--__n > 0 && *rp >= '0' && *rp <= '9'); \+ if (val < from || val > to) \+ return NULL; \+ } while (0)+# define get_alt_number(from, to, n) \+ /* We don't have the alternate representation. */ \+ get_number(from, to, n)+#define recursive(new_fmt) \+ (*(new_fmt) != '\0' \+ && (rp = strptime_internal (rp, (new_fmt), tm, decided, psecs, poffset)) != NULL)++/* This version: may overwrite these with versions for the locale,+ * hence the extra length of the fields+ */+static char weekday_name[][20] =+{+ "Sunday", "Monday", "Tuesday", "Wednesday",+ "Thursday", "Friday", "Saturday"+};+static char ab_weekday_name[][10] =+{+ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"+};+static char month_name[][20] =+{+ "January", "February", "March", "April", "May", "June",+ "July", "August", "September", "October", "November", "December"+};+static char ab_month_name[][10] =+{+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"+};++static char am_pm[][4] = {"AM", "PM"};+++# define HERE_D_T_FMT "%a %b %e %H:%M:%S %Y"+# define HERE_D_FMT "%y/%m/%d"+# define HERE_T_FMT_AMPM "%I:%M:%S %p"+# define HERE_T_FMT "%H:%M:%S"++static const unsigned short int __mon_yday[2][13] =+{+ /* Normal years. */+ { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 },+ /* Leap years. */+ { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }+};+++/* Status of lookup: do we use the locale data or the raw data? */+enum locale_status { Not, loc, raw };++# define __isleap(year) \+ ((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0))++/* Compute the day of the week. */+static void+day_of_the_week (struct tm *tm)+{+ /* We know that January 1st 1970 was a Thursday (= 4). Compute the+ the difference between this data in the one on TM and so determine+ the weekday. */+ int corr_year, wday;++ corr_year = 1900 + tm->tm_year - (tm->tm_mon < 2);+ wday = (-473+ + (365 * (tm->tm_year - 70))+ + (corr_year / 4)+ - ((corr_year / 4) / 25) + ((corr_year / 4) % 25 < 0)+ + (((corr_year / 4) / 25) / 4)+ + __mon_yday[0][tm->tm_mon]+ + tm->tm_mday - 1);+ tm->tm_wday = ((wday % 7) + 7) % 7;+}++/* Compute the day of the year. */+static void+day_of_the_year (struct tm *tm)+{+ tm->tm_yday = (__mon_yday[__isleap (1900 + tm->tm_year)][tm->tm_mon]+ + (tm->tm_mday - 1));+}++#include <wchar.h>+#include <wctype.h>++static wchar_t w_weekday_name[][20] =+{+ L"Sunday", L"Monday", L"Tuesday", L"Wednesday",+ L"Thursday", L"Friday", L"Saturday"+};+static wchar_t w_ab_weekday_name[][10] =+{+ L"Sun", L"Mon", L"Tue", L"Wed", L"Thu", L"Fri", L"Sat"+};+static wchar_t w_month_name[][20] =+{+ L"January", L"February", L"March", L"April", L"May", L"June",+ L"July", L"August", L"September", L"October", L"November", L"December"+};+static wchar_t w_ab_month_name[][10] =+{+ L"Jan", L"Feb", L"Mar", L"Apr", L"May", L"Jun",+ L"Jul", L"Aug", L"Sep", L"Oct", L"Nov", L"Dec"+};++static wchar_t w_am_pm[][4] = {L"AM", L"PM"};++/* Need case-insensitive version */+static int Rwcsncasecmp(const wchar_t *cs1, const wchar_t *s2)+{+ size_t i, n = wcslen(cs1);+ const wchar_t *a = cs1, *b = s2;+ for(i = 0; i < n; i++, a++, b++) {+ if(*b == L'\0' || towlower(*a) != towlower(*b)) return 1;+ }+ return 0;+}++#define w_match_string(cs1, s2) \+ (Rwcsncasecmp ((cs1), (s2)) ? 0 : ((s2) += wcslen (cs1), 1))++#define w_recursive(new_fmt) \+ (*(new_fmt) != '\0' \+ && (rp = w_strptime_internal (rp, (new_fmt), tm, decided, psecs, poffset)) != NULL)++static wchar_t *+w_strptime_internal (wchar_t *rp, const wchar_t *fmt, struct tm *tm,+ enum locale_status *decided, double *psecs, + int *poffset)+{+ const wchar_t *rp_backup;+ int cnt;+ size_t val;+ int have_I, is_pm;+ int century, want_century;+ int have_wday, want_xday;+ int have_yday;+ int have_mon, have_mday;+ int have_uweek, have_wweek;+ int week_no = 0; /* -Wall */++ have_I = is_pm = 0;+ century = -1;+ want_century = 0;+ have_wday = want_xday = have_yday = have_mon = have_mday = 0;+ have_uweek = have_wweek = 0;++ while (*fmt != L'\0')+ {+ /* A white space in the format string matches 0 more or white+ space in the input string. */+ if (iswspace (*fmt))+ {+ while (iswspace (*rp))+ ++rp;+ ++fmt;+ continue;+ }++ /* Any character but `%' must be matched by the same character+ in the input string. */+ if (*fmt != L'%')+ {+ match_char (*fmt++, *rp++);+ continue;+ }++ ++fmt;++ /* We need this for handling the `E' modifier. */+ start_over:++ /* Make back up of current processing pointer. */+ rp_backup = rp;++ switch (*fmt++)+ {+ case L'%':+ /* Match the `%' character itself. */+ match_char (L'%', *rp++);+ break;+ case L'a':+ case L'A':+ /* Match day of week. */+ for (cnt = 0; cnt < 7; ++cnt)+ {+ if (*decided != loc+ && (w_match_string (w_weekday_name[cnt], rp)+ || w_match_string (w_ab_weekday_name[cnt], rp)))+ {+ *decided = raw;+ break;+ }+ }+ if (cnt == 7)+ /* Does not match a weekday name. */+ return NULL;+ tm->tm_wday = cnt;+ have_wday = 1;+ break;+ case L'b':+ case L'B':+ case L'h':+ /* Match month name. */+ for (cnt = 0; cnt < 12; ++cnt)+ {+ if (w_match_string (w_month_name[cnt], rp)+ || w_match_string (w_ab_month_name[cnt], rp))+ {+ *decided = raw;+ break;+ }+ }+ if (cnt == 12)+ /* Does not match a month name. */+ return NULL;+ tm->tm_mon = cnt;+ want_xday = 1;+ break;+ case L'c':+ /* Match locale's date and time format. */+ if (!w_recursive (L"%a %b %e %H:%M:%S %Y")) /* HERE_D_T_FMT */+ return NULL;+ break;+ case L'C':+ /* Match century number. */+ get_number (0, 99, 2);+ century = val;+ want_xday = 1;+ break;+ case L'd':+ case L'e':+ /* Match day of month. */+ get_number (1, 31, 2);+ tm->tm_mday = val;+ have_mday = 1;+ want_xday = 1;+ break;+ case L'F':+ if (!w_recursive (L"%Y-%m-%d"))+ return NULL;+ want_xday = 1;+ break;+ case L'x':+ /* Fall through. */+ case L'D':+ /* Match standard day format. */+ if (!w_recursive (L"%y/%m/%d")) /* HERE_D_FMT */+ return NULL;+ want_xday = 1;+ break;+ case L'k':+ case L'H':+ /* Match hour in 24-hour clock. */+ get_number (0, 23, 2);+ tm->tm_hour = val;+ have_I = 0;+ break;+ case L'l':+ /* Match hour in 12-hour clock. GNU extension. */+ case L'I':+ /* Match hour in 12-hour clock. */+ get_number (1, 12, 2);+ tm->tm_hour = val % 12;+ have_I = 1;+ break;+ case L'j':+ /* Match day number of year. */+ get_number (1, 366, 3);+ tm->tm_yday = val - 1;+ have_yday = 1;+ break;+ case L'm':+ /* Match number of month. */+ get_number (1, 12, 2);+ tm->tm_mon = val - 1;+ have_mon = 1;+ want_xday = 1;+ break;+ case L'M':+ /* Match minute. */+ get_number (0, 59, 2);+ tm->tm_min = val;+ break;+ case L'n':+ case L't':+ /* Match any white space. */+ while (iswspace (*rp))+ ++rp;+ break;+ case L'p':+ /* Match locale's equivalent of AM/PM. */+ if (!w_match_string (w_am_pm[0], rp)) {+ if (w_match_string (w_am_pm[1], rp))+ is_pm = 1;+ else+ return NULL;+ }+ break;+ case L'r':+ if (!w_recursive (L"%I:%M:%S %p")) /* HERE_T_FMT_AMPM */+ return NULL;+ break;+ case L'R':+ if (!w_recursive (L"%H:%M"))+ return NULL;+ break;+ case L's':+ {+ /* The number of seconds may be very high so we cannot use+ the `get_number' macro. Instead read the number+ character for character and construct the result while+ doing this. */+ time_t secs = 0;+ if (*rp < L'0' || *rp > L'9')+ /* We need at least one digit. */+ return NULL;++ do+ {+ secs *= 10;+ secs += *rp++ - L'0';+ }+ while (*rp >= L'0' && *rp <= L'9');++ if ((tm = localtime (&secs)) == NULL)+ /* Error in function. */+ return NULL;+ }+ break;+ case L'S':+ get_number (0, 61, 2);+ tm->tm_sec = val;+ break;+ case L'X':+ /* Fall through. */+ case L'T':+ if (!w_recursive (L"%H:%M:%S")) /* HERE_T_FMT */+ return NULL;+ break;+ case L'u':+ get_number (1, 7, 1);+ tm->tm_wday = val % 7;+ have_wday = 1;+ break;+ case L'g':+ get_number (0, 99, 2);+ /* XXX This cannot determine any field in TM. */+ break;+ case L'G':+ if (*rp < L'0' || *rp > L'9')+ return NULL;+ /* XXX Ignore the number since we would need some more+ information to compute a real date. */+ do+ ++rp;+ while (*rp >= L'0' && *rp <= L'9');+ break;+ case L'U':+ get_number (0, 53, 2);+ week_no = val;+ have_uweek = 1;+ break;+ case L'W':+ get_number (0, 53, 2);+ week_no = val;+ have_wweek = 1;+ break;+ case L'V':+ get_number (0, 53, 2);+ /* XXX This cannot determine any field in TM without some+ information. */+ break;+ case L'w':+ /* Match number of weekday. */+ get_number (0, 6, 1);+ tm->tm_wday = val;+ have_wday = 1;+ break;+ case L'y':+ /* Match year within century. */+ get_number (0, 99, 2);+ /* The "Year 2000: The Millennium Rollover" paper suggests that+ values in the range 69-99 refer to the twentieth century. */+ tm->tm_year = val >= 69 ? val : val + 100;+ /* Indicate that we want to use the century, if specified. */+ want_century = 1;+ want_xday = 1;+ break;+ case L'Y':+ /* Match year including century number. */+ get_number (0, 9999, 4);+ tm->tm_year = val - 1900;+ want_century = 0;+ want_xday = 1;+ break;+ case L'z':+ {+ int n = 0, neg, off = 0;+ val = 0;+ while (*rp == L' ') ++rp;+ if (*rp != L'+' && *rp != L'-') return NULL;+ neg = *rp++ == L'-';+ while (n < 4 && *rp >= L'0' && *rp <= L'9') {+ val = val * 10 + *rp++ - L'0';+ ++n;+ }+ if (n != 4) return NULL;+ else {+ /* We have to convert the minutes into decimal. */+ if (val % 100 >= 60) return NULL;+ val = (val / 100) * 100 + ((val % 100) * 50) / 30;+ }+ if (val > 1200) return NULL;+ off = (val * 3600) / 100;+ if (neg) off = -off;+ *poffset = off;+ }+ break;+ case L'Z':+ fprintf(stderr, "use of %s for input is not supported", "%Z");+ return NULL;+ break;+ case L'E':+ /* We have no information about the era format. Just use+ the normal format. */+ if (*fmt != L'c' && *fmt != L'C' && *fmt != L'y' && *fmt != L'Y'+ && *fmt != L'x' && *fmt != L'X')+ /* This is an illegal format. */+ return NULL;++ goto start_over;+ case L'O':+ switch (*fmt++)+ {+ case L'd':+ case L'e':+ /* Match day of month using alternate numeric symbols. */+ get_alt_number (1, 31, 2);+ tm->tm_mday = val;+ have_mday = 1;+ want_xday = 1;+ break;+ case L'H':+ /* Match hour in 24-hour clock using alternate numeric+ symbols. */+ get_alt_number (0, 23, 2);+ tm->tm_hour = val;+ have_I = 0;+ break;+ case L'I':+ /* Match hour in 12-hour clock using alternate numeric+ symbols. */+ get_alt_number (1, 12, 2);+ tm->tm_hour = val % 12;+ have_I = 1;+ break;+ case L'm':+ /* Match month using alternate numeric symbols. */+ get_alt_number (1, 12, 2);+ tm->tm_mon = val - 1;+ have_mon = 1;+ want_xday = 1;+ break;+ case L'M':+ /* Match minutes using alternate numeric symbols. */+ get_alt_number (0, 59, 2);+ tm->tm_min = val;+ break;+ case L'S':+ /* Match seconds using alternate numeric symbols.+ get_alt_number (0, 61, 2); */+ {+ double sval;+ wchar_t *end;+ sval = wcstod(rp, &end);+ if( sval >= 0.0 && sval <= 61.0) {+ tm->tm_sec = sval;+ *psecs = sval;+ }+ rp = end;+ }+ break;+ case L'U':+ get_alt_number (0, 53, 2);+ week_no = val;+ have_uweek = 1;+ break;+ case L'W':+ get_alt_number (0, 53, 2);+ week_no = val;+ have_wweek = 1;+ break;+ case L'V':+ get_alt_number (0, 53, 2);+ /* XXX This cannot determine any field in TM without+ further information. */+ break;+ case L'w':+ /* Match number of weekday using alternate numeric symbols. */+ get_alt_number (0, 6, 1);+ tm->tm_wday = val;+ have_wday = 1;+ break;+ case L'y':+ /* Match year within century using alternate numeric symbols. */+ get_alt_number (0, 99, 2);+ tm->tm_year = val >= 69 ? val : val + 100;+ want_xday = 1;+ break;+ default:+ return NULL;+ }+ break;+ default:+ return NULL;+ }+ }++ if (have_I && is_pm)+ tm->tm_hour += 12;++ if (century != -1)+ {+ if (want_century)+ tm->tm_year = tm->tm_year % 100 + (century - 19) * 100;+ else+ /* Only the century, but not the year. Strange, but so be it. */+ tm->tm_year = (century - 19) * 100;+ }++ if (want_xday && !have_wday) {+ if ( !(have_mon && have_mday) && have_yday) {+ /* We don't have tm_mon and/or tm_mday, compute them. */+ int t_mon = 0;+ while (__mon_yday[__isleap(1900 + tm->tm_year)][t_mon] <= tm->tm_yday)+ t_mon++;+ if (!have_mon)+ tm->tm_mon = t_mon - 1;+ if (!have_mday)+ tm->tm_mday = (tm->tm_yday - __mon_yday[__isleap(1900 + tm->tm_year)][t_mon - 1] + 1);+ }+ day_of_the_week (tm);+ }++ if (want_xday && !have_yday)+ day_of_the_year (tm);++ if ((have_uweek || have_wweek) && have_wday) {+ int save_wday = tm->tm_wday;+ int save_mday = tm->tm_mday;+ int save_mon = tm->tm_mon;+ int w_offset = have_uweek ? 0 : 1;++ tm->tm_mday = 1;+ tm->tm_mon = 0;+ day_of_the_week (tm);+ if (have_mday)+ tm->tm_mday = save_mday;+ if (have_mon)+ tm->tm_mon = save_mon;++ if (!have_yday)+ tm->tm_yday = ((7 - (tm->tm_wday - w_offset)) % 7+ + (week_no - 1) *7+ + save_wday - w_offset);++ if (!have_mday || !have_mon)+ {+ int t_mon = 0;+ while (__mon_yday[__isleap(1900 + tm->tm_year)][t_mon]+ <= tm->tm_yday)+ t_mon++;+ if (!have_mon)+ tm->tm_mon = t_mon - 1;+ if (!have_mday)+ tm->tm_mday =+ (tm->tm_yday+ - __mon_yday[__isleap(1900 + tm->tm_year)][t_mon - 1] + 1);+ }++ tm->tm_wday = save_wday;+ }++ return rp;+}+++static char *+strptime_internal (const char *rp, const char *fmt, struct tm *tm,+ enum locale_status *decided, double *psecs,+ int *poffset)+{+ const char *rp_backup;+ int cnt;+ size_t val;+ int have_I, is_pm;+ int century, want_century;+ int have_wday, want_xday;+ int have_yday;+ int have_mon, have_mday;+ int have_uweek, have_wweek;+ int week_no = 0; /* -Wall */++ have_I = is_pm = 0;+ century = -1;+ want_century = 0;+ have_wday = want_xday = have_yday = have_mon = have_mday = 0;+ have_uweek = have_wweek = 0;++ while (*fmt != '\0')+ {+ /* A white space in the format string matches 0 more or white+ space in the input string. */+ if (isspace ((int)*fmt))+ {+ while (isspace ((int)*rp))+ ++rp;+ ++fmt;+ continue;+ }++ /* Any character but `%' must be matched by the same character+ in the input string. */+ if (*fmt != '%')+ {+ match_char (*fmt++, *rp++);+ continue;+ }++ ++fmt;++ /* We need this for handling the `E' modifier. */+ start_over:++ /* Make back up of current processing pointer. */+ rp_backup = rp;++ switch (*fmt++)+ {+ case '%':+ /* Match the `%' character itself. */+ match_char ('%', *rp++);+ break;+ case 'a':+ case 'A':+ /* Match day of week. */+ for (cnt = 0; cnt < 7; ++cnt)+ {+ if (*decided != loc+ && (match_string (weekday_name[cnt], rp)+ || match_string (ab_weekday_name[cnt], rp)))+ {+ *decided = raw;+ break;+ }+ }+ if (cnt == 7)+ /* Does not match a weekday name. */+ return NULL;+ tm->tm_wday = cnt;+ have_wday = 1;+ break;+ case 'b':+ case 'B':+ case 'h':+ /* Match month name. */+ for (cnt = 0; cnt < 12; ++cnt)+ {+ if (match_string (month_name[cnt], rp)+ || match_string (ab_month_name[cnt], rp))+ {+ *decided = raw;+ break;+ }+ }+ if (cnt == 12)+ /* Does not match a month name. */+ return NULL;+ tm->tm_mon = cnt;+ want_xday = 1;+ break;+ case 'c':+ /* Match locale's date and time format. */+ if (!recursive (HERE_D_T_FMT))+ return NULL;+ break;+ case 'C':+ /* Match century number. */+ get_number (0, 99, 2);+ century = val;+ want_xday = 1;+ break;+ case 'd':+ case 'e':+ /* Match day of month. */+ get_number (1, 31, 2);+ tm->tm_mday = val;+ have_mday = 1;+ want_xday = 1;+ break;+ case 'F':+ if (!recursive ("%Y-%m-%d"))+ return NULL;+ want_xday = 1;+ break;+ case 'x':+ /* Fall through. */+ case 'D':+ /* Match standard day format. */+ if (!recursive (HERE_D_FMT))+ return NULL;+ want_xday = 1;+ break;+ case 'k':+ case 'H':+ /* Match hour in 24-hour clock. */+ get_number (0, 23, 2);+ tm->tm_hour = val;+ have_I = 0;+ break;+ case 'l':+ /* Match hour in 12-hour clock. GNU extension. */+ case 'I':+ /* Match hour in 12-hour clock. */+ get_number (1, 12, 2);+ tm->tm_hour = val % 12;+ have_I = 1;+ break;+ case 'j':+ /* Match day number of year. */+ get_number (1, 366, 3);+ tm->tm_yday = val - 1;+ have_yday = 1;+ break;+ case 'm':+ /* Match number of month. */+ get_number (1, 12, 2);+ tm->tm_mon = val - 1;+ have_mon = 1;+ want_xday = 1;+ break;+ case 'M':+ /* Match minute. */+ get_number (0, 59, 2);+ tm->tm_min = val;+ break;+ case 'n':+ case 't':+ /* Match any white space. */+ while (isspace ((int)*rp))+ ++rp;+ break;+ case 'p':+ /* Match locale's equivalent of AM/PM. */+ if (!match_string (am_pm[0], rp)) {+ if (match_string (am_pm[1], rp))+ is_pm = 1;+ else+ return NULL;+ }+ break;+ case 'r':+ if (!recursive (HERE_T_FMT_AMPM))+ return NULL;+ break;+ case 'R':+ if (!recursive ("%H:%M"))+ return NULL;+ break;+ case 's':+ {+ /* The number of seconds may be very high so we cannot use+ the `get_number' macro. Instead read the number+ character for character and construct the result while+ doing this. */+ time_t secs = 0;+ if (*rp < '0' || *rp > '9')+ /* We need at least one digit. */+ return NULL;++ do+ {+ secs *= 10;+ secs += *rp++ - '0';+ }+ while (*rp >= '0' && *rp <= '9');++ if ((tm = localtime (&secs)) == NULL)+ /* Error in function. */+ return NULL;+ }+ break;+ case 'S':+ get_number (0, 61, 2);+ tm->tm_sec = val;+ break;+ case 'X':+ /* Fall through. */+ case 'T':+ if (!recursive (HERE_T_FMT))+ return NULL;+ break;+ case 'u':+ get_number (1, 7, 1);+ tm->tm_wday = val % 7;+ have_wday = 1;+ break;+ case 'g':+ get_number (0, 99, 2);+ /* XXX This cannot determine any field in TM. */+ break;+ case 'G':+ if (*rp < '0' || *rp > '9')+ return NULL;+ /* XXX Ignore the number since we would need some more+ information to compute a real date. */+ do+ ++rp;+ while (*rp >= '0' && *rp <= '9');+ break;+ case 'U':+ get_number (0, 53, 2);+ week_no = val;+ have_uweek = 1;+ break;+ case 'W':+ get_number (0, 53, 2);+ week_no = val;+ have_wweek = 1;+ break;+ case 'V':+ get_number (0, 53, 2);+ /* XXX This cannot determine any field in TM without some+ information. */+ break;+ case 'w':+ /* Match number of weekday. */+ get_number (0, 6, 1);+ tm->tm_wday = val;+ have_wday = 1;+ break;+ case 'y':+ /* Match year within century. */+ get_number (0, 99, 2);+ /* The "Year 2000: The Millennium Rollover" paper suggests that+ values in the range 69-99 refer to the twentieth century.+ And this is mandated by the POSIX 2001 standard, with a+ caveat that it might change in future.+ */+ tm->tm_year = val >= 69 ? val : val + 100;+ /* Indicate that we want to use the century, if specified. */+ want_century = 1;+ want_xday = 1;+ break;+ case 'Y':+ /* Match year including century number. */+ get_number (0, 9999, 4);+ tm->tm_year = val - 1900;+ want_century = 0;+ want_xday = 1;+ break;+ case 'z':+ /* Only recognize RFC 822 form */+ {+ int n = 0, neg, off = 0;+ val = 0;+ while (*rp == ' ') ++rp;+ if (*rp != '+' && *rp != '-') return NULL;+ neg = *rp++ == '-';+ while (n < 4 && *rp >= '0' && *rp <= '9') {+ val = val * 10 + *rp++ - '0';+ ++n;+ }+ if (n != 4) return NULL;+ else {+ /* We have to convert the minutes into decimal. */+ if (val % 100 >= 60) return NULL;+ val = (val / 100) * 100 + ((val % 100) * 50) / 30;+ }+ if (val > 1200) return NULL;+ off = (val * 3600) / 100;+ if (neg) off = -off;+ *poffset = off;+ }+ break;+ case 'Z':+ fprintf(stderr, "use of %s for input is not supported", "%Z");+ return NULL;+ break;+ case 'E':+ /* We have no information about the era format. Just use+ the normal format. */+ if (*fmt != 'c' && *fmt != 'C' && *fmt != 'y' && *fmt != 'Y'+ && *fmt != 'x' && *fmt != 'X')+ /* This is an illegal format. */+ return NULL;++ goto start_over;+ case 'O':+ switch (*fmt++)+ {+ case 'd':+ case 'e':+ /* Match day of month using alternate numeric symbols. */+ get_alt_number (1, 31, 2);+ tm->tm_mday = val;+ have_mday = 1;+ want_xday = 1;+ break;+ case 'H':+ /* Match hour in 24-hour clock using alternate numeric+ symbols. */+ get_alt_number (0, 23, 2);+ tm->tm_hour = val;+ have_I = 0;+ break;+ case 'I':+ /* Match hour in 12-hour clock using alternate numeric+ symbols. */+ get_alt_number (1, 12, 2);+ tm->tm_hour = val % 12;+ have_I = 1;+ break;+ case 'm':+ /* Match month using alternate numeric symbols. */+ get_alt_number (1, 12, 2);+ tm->tm_mon = val - 1;+ have_mon = 1;+ want_xday = 1;+ break;+ case 'M':+ /* Match minutes using alternate numeric symbols. */+ get_alt_number (0, 59, 2);+ tm->tm_min = val;+ break;+ case 'S':+ /* Match seconds using alternate numeric symbols.+ get_alt_number (0, 61, 2); */+ {+ double sval;+ char *end;+ sval = strtod(rp, &end);+ if( sval >= 0.0 && sval <= 61.0) {+ tm->tm_sec = sval;+ *psecs = sval;+ }+ rp = end;+ }+ break;+ case 'U':+ get_alt_number (0, 53, 2);+ week_no = val;+ have_uweek = 1;+ break;+ case 'W':+ get_alt_number (0, 53, 2);+ week_no = val;+ have_wweek = 1;+ break;+ case 'V':+ get_alt_number (0, 53, 2);+ /* XXX This cannot determine any field in TM without+ further information. */+ break;+ case 'w':+ /* Match number of weekday using alternate numeric symbols. */+ get_alt_number (0, 6, 1);+ tm->tm_wday = val;+ have_wday = 1;+ break;+ case 'y':+ /* Match year within century using alternate numeric symbols. */+ get_alt_number (0, 99, 2);+ tm->tm_year = val >= 69 ? val : val + 100;+ want_xday = 1;+ break;+ default:+ return NULL;+ }+ break;+ default:+ return NULL;+ }+ }++ if (have_I && is_pm)+ tm->tm_hour += 12;++ if (century != -1)+ {+ if (want_century)+ tm->tm_year = tm->tm_year % 100 + (century - 19) * 100;+ else+ /* Only the century, but not the year. Strange, but so be it. */+ tm->tm_year = (century - 19) * 100;+ }++ if (want_xday && !have_wday) {+ if ( !(have_mon && have_mday) && have_yday) {+ /* We don't have tm_mon and/or tm_mday, compute them. */+ int t_mon = 0;+ while (__mon_yday[__isleap(1900 + tm->tm_year)][t_mon] <= tm->tm_yday)+ t_mon++;+ if (!have_mon)+ tm->tm_mon = t_mon - 1;+ if (!have_mday)+ tm->tm_mday = (tm->tm_yday - __mon_yday[__isleap(1900 + tm->tm_year)][t_mon - 1] + 1);+ }+ day_of_the_week (tm);+ }++ if (want_xday && !have_yday)+ day_of_the_year (tm);++ if ((have_uweek || have_wweek) && have_wday) {+ int save_wday = tm->tm_wday;+ int save_mday = tm->tm_mday;+ int save_mon = tm->tm_mon;+ int w_offset = have_uweek ? 0 : 1;++ tm->tm_mday = 1;+ tm->tm_mon = 0;+ day_of_the_week (tm);+ if (have_mday)+ tm->tm_mday = save_mday;+ if (have_mon)+ tm->tm_mon = save_mon;++ if (!have_yday)+ tm->tm_yday = ((7 - (tm->tm_wday - w_offset)) % 7+ + (week_no - 1) *7+ + save_wday - w_offset);++ if (!have_mday || !have_mon)+ {+ int t_mon = 0;+ while (__mon_yday[__isleap(1900 + tm->tm_year)][t_mon]+ <= tm->tm_yday)+ t_mon++;+ if (!have_mon)+ tm->tm_mon = t_mon - 1;+ if (!have_mday)+ tm->tm_mday =+ (tm->tm_yday+ - __mon_yday[__isleap(1900 + tm->tm_year)][t_mon - 1] + 1);+ }++ tm->tm_wday = save_wday;+ }++ return (char *) rp;+}+++#ifdef HAVE_LOCALE_H+# include <locale.h>++/* We check for a changed locale here, as setting the locale strings is+ on some systems slow compared to the conversions. */++static void get_locale_strings(void)+{+ int i;+ struct tm tm;+ char buff[4];++ tm.tm_sec = tm.tm_min = tm.tm_hour = tm.tm_mday = tm.tm_mon+ = tm.tm_isdst = 0;+ tm.tm_year = 30;+ for(i = 0; i < 12; i++) {+ tm.tm_mon = i;+ strftime(ab_month_name[i], 10, "%b", &tm);+ strftime(month_name[i], 20, "%B", &tm);+ }+ tm.tm_mon = 0;+ for(i = 0; i < 7; i++) {+ tm.tm_mday = tm.tm_yday = i+1; /* 2000-1-2 was a Sunday */+ tm.tm_wday = i;+ strftime(ab_weekday_name[i], 10, "%a", &tm);+ strftime(weekday_name[i], 20, "%A", &tm);+ }+ tm.tm_hour = 1;+ /* in locales where these are unused, they may be empty: better+ not to reset them then */+ strftime(buff, 4, "%p", &tm);+ if(strlen(buff)) strcpy(am_pm[0], buff);+ tm.tm_hour = 13;+ strftime(buff, 4, "%p", &tm);+ if(strlen(buff)) strcpy(am_pm[1], buff);+}++#if defined(HAVE_WCSTOD) && defined(HAVE_WCSFTIME)+static void get_locale_w_strings(void)+{+ int i;+ struct tm tm;+ wchar_t buff[4];++ tm.tm_sec = tm.tm_min = tm.tm_hour = tm.tm_mday = tm.tm_mon+ = tm.tm_isdst = 0;+ tm.tm_year = 30;+ for(i = 0; i < 12; i++) {+ tm.tm_mon = i;+ wcsftime(w_ab_month_name[i], 10, L"%b", &tm);+ wcsftime(w_month_name[i], 20, L"%B", &tm);+ }+ tm.tm_mon = 0;+ for(i = 0; i < 7; i++) {+ tm.tm_mday = tm.tm_yday = i+1; /* 2000-1-2 was a Sunday */+ tm.tm_wday = i;+ wcsftime(w_ab_weekday_name[i], 10, L"%a", &tm);+ wcsftime(w_weekday_name[i], 20, L"%A", &tm);+ }+ tm.tm_hour = 1;+ /* in locales where these are unused, they may be empty: better+ not to reset them then */+ wcsftime(buff, 4, L"%p", &tm);+ if(wcslen(buff)) wcscpy(w_am_pm[0], buff);+ tm.tm_hour = 13;+ wcsftime(buff, 4, L"%p", &tm);+ if(wcslen(buff)) wcscpy(w_am_pm[1], buff);+}+#endif+#endif /* HAVE_LOCALE_H */+++/* We only care if the result is null or not */+/* non-static */ char *+hstrptime (const char *buf, const char *format, struct tm *tm, + double *psecs, int *poffset)+{+ enum locale_status decided;+ decided = raw;+#if defined(HAVE_WCSTOD)+ if(mbcslocale) {+ wchar_t wbuf[1001], wfmt[1001]; size_t n;+#if defined(HAVE_LOCALE_H) && defined(HAVE_WCSFTIME)+ get_locale_w_strings();+#endif+ n = mbstowcs(NULL, buf, 1000);+ if(n > 1000) error(_("input string is too long"));+ n = mbstowcs(wbuf, buf, 1000);+ if(n == -1) error(_("invalid multibyte input string"));++ n = mbstowcs(NULL, format, 1000);+ if(n > 1000) error(_("format string is too long"));+ n = mbstowcs(wfmt, format, 1000);+ if(n == -1) error(_("invalid multibyte format string"));+ return (char *) w_strptime_internal (wbuf, wfmt, tm, &decided, psecs, poffset);+ } else+#endif+ {+#ifdef HAVE_LOCALE_H+ get_locale_strings();+#endif+ return strptime_internal (buf, format, tm, &decided, psecs, poffset);+ }+}
+ include/hstrptime.h view
@@ -0,0 +1,53 @@+/*+ A modified version of code from R (www.r-project.org)+ repackaged as a stand-alone .c file, with a couple of + R-specific bits removed .++ We use this version because of its support for input+ of fractional seconds.++ Modified by Eugene Kirpichov <ekirpichov@gmail.com>, 2010.++ */++/* For inclusion by datetime.c. ++ A modified version of code from the GNU C library with locale+ support removed and wchar support added.+*/++/* Convert a string representation of time to a time value.+ Copyright (C) 1996, 1997, 1998, 1999, 2000 Free Software Foundation, Inc.+ This file is part of the GNU C Library.+ Contributed by Ulrich Drepper <drepper@cygnus.com>, 1996.++ The GNU C Library is free software; you can redistribute it and/or+ modify it under the terms of the GNU Library General Public License as+ published by the Free Software Foundation; either version 2 of the+ License, or (at your option) any later version.++ The GNU C Library is distributed in the hope that it will be useful,+ but WITHOUT ANY WARRANTY; without even the implied warranty of+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU+ Library General Public License for more details.++ You should have received a copy of the GNU Library General Public+ License along with the GNU C Library; see the file COPYING.LIB. If not,+ a copy is available at http://www.r-project.org/licenses/+*/+/* XXX This version of the implementation is not really complete.+ Some of the fields cannot add information alone. But if seeing+ some of them in the same format (such as year, week and weekday)+ this is enough information for determining the date. */++#include <ctype.h>+#include <limits.h>+#include <string.h>+#include <time.h>+#include <stdlib.h>+#include <stdio.h>++#ifdef HAVE_STRINGS_H+#include <strings.h> /* for strncasecmp */+#endif+
strptime.cabal view
@@ -1,6 +1,6 @@ Name: strptime Category: System, Data, Parsing-Version: 0.1.4+Version: 0.1.5 Cabal-version: >= 1.2 Build-type: Simple Copyright: Eugene Kirpichov@@ -14,6 +14,7 @@ dates and times from strings and strict/lazy bytestrings. The library creates LocalTime values. Cabal-Version: >= 1.6+Extra-Source-Files: include/hstrptime.h Source-repository head type: darcs@@ -23,8 +24,12 @@ Library Build-Depends: time, bytestring+ C-sources: cbits/hstrptime.c+ Include-dirs: include+ if flag(split-base) Build-depends: base >= 3.0 && < 5.0 else Build-depends: base < 3.0 Exposed-modules: Data.Time.Parse+