git-date 0.2 → 0.2.1
raw patch · 3 files changed
+108/−117 lines, 3 filesdep +QuickCheckdep +old-localedep +test-framework
Dependencies added: QuickCheck, old-locale, test-framework, test-framework-quickcheck2
Files
- ext/date.c +34/−116
- git-date.cabal +21/−1
- tests/suite.hs +53/−0
ext/date.c view
@@ -28,6 +28,8 @@ DATE_UNKNOWN }; +unsigned long approxidate_careful(const char *date, int *error_ret);+ /* * This is like mktime, but without normalization of tm_wday and tm_yday. */@@ -108,118 +110,6 @@ return offset * eastwest; } -const char *show_date_relative(unsigned long time, int tz,- const struct timeval *now,- char *timebuf,- size_t timebuf_size)-{- unsigned long diff;- if (now->tv_sec < time)- return "in the future";- diff = now->tv_sec - time;- if (diff < 90) {- snprintf(timebuf, timebuf_size, "%lu seconds ago", diff);- return timebuf;- }- /* Turn it into minutes */- diff = (diff + 30) / 60;- if (diff < 90) {- snprintf(timebuf, timebuf_size, "%lu minutes ago", diff);- return timebuf;- }- /* Turn it into hours */- diff = (diff + 30) / 60;- if (diff < 36) {- snprintf(timebuf, timebuf_size, "%lu hours ago", diff);- return timebuf;- }- /* We deal with number of days from here on */- diff = (diff + 12) / 24;- if (diff < 14) {- snprintf(timebuf, timebuf_size, "%lu days ago", diff);- return timebuf;- }- /* Say weeks for the past 10 weeks or so */- if (diff < 70) {- snprintf(timebuf, timebuf_size, "%lu weeks ago", (diff + 3) / 7);- return timebuf;- }- /* Say months for the past 12 months or so */- if (diff < 365) {- snprintf(timebuf, timebuf_size, "%lu months ago", (diff + 15) / 30);- return timebuf;- }- /* Give years and months for 5 years or so */- if (diff < 1825) {- unsigned long totalmonths = (diff * 12 * 2 + 365) / (365 * 2);- unsigned long years = totalmonths / 12;- unsigned long months = totalmonths % 12;- int n;- n = snprintf(timebuf, timebuf_size, "%lu year%s",- years, (years > 1 ? "s" : ""));- if (months)- snprintf(timebuf + n, timebuf_size - n,- ", %lu month%s ago",- months, (months > 1 ? "s" : ""));- else- snprintf(timebuf + n, timebuf_size - n, " ago");- return timebuf;- }- /* Otherwise, just years. Centuries is probably overkill. */- snprintf(timebuf, timebuf_size, "%lu years ago", (diff + 183) / 365);- return timebuf;-}--const char *show_date(unsigned long time, int tz, enum date_mode mode)-{- struct tm *tm;- static char timebuf[200];-- if (mode == DATE_RAW) {- snprintf(timebuf, sizeof(timebuf), "%lu %+05d", time, tz);- return timebuf;- }-- if (mode == DATE_RELATIVE) {- struct timeval now;- gettimeofday(&now, NULL);- return show_date_relative(time, tz, &now,- timebuf, sizeof(timebuf));- }-- if (mode == DATE_LOCAL)- tz = local_tzoffset(time);-- tm = time_to_tm(time, tz);- if (!tm)- return NULL;- if (mode == DATE_SHORT)- sprintf(timebuf, "%04d-%02d-%02d", tm->tm_year + 1900,- tm->tm_mon + 1, tm->tm_mday);- else if (mode == DATE_ISO8601)- sprintf(timebuf, "%04d-%02d-%02d %02d:%02d:%02d %+05d",- tm->tm_year + 1900,- tm->tm_mon + 1,- tm->tm_mday,- tm->tm_hour, tm->tm_min, tm->tm_sec,- tz);- else if (mode == DATE_RFC2822)- sprintf(timebuf, "%.3s, %d %.3s %d %02d:%02d:%02d %+05d",- weekday_names[tm->tm_wday], tm->tm_mday,- month_names[tm->tm_mon], tm->tm_year + 1900,- tm->tm_hour, tm->tm_min, tm->tm_sec, tz);- else- sprintf(timebuf, "%.3s %.3s %d %02d:%02d:%02d %d%c%+05d",- weekday_names[tm->tm_wday],- month_names[tm->tm_mon],- tm->tm_mday,- tm->tm_hour, tm->tm_min, tm->tm_sec,- tm->tm_year + 1900,- (mode == DATE_LOCAL) ? 0 : ' ',- tz);- return timebuf;-}- /* * Check these. And note how it doesn't do the summer-time conversion. *@@ -388,7 +278,7 @@ * sense to specify timestamp way into the future. Make * sure it is not later than ten days from now... */- if (now + 10*24*3600 < specified)+ if ((specified != -1) && (now + 10*24*3600 < specified)) return 0; tm->tm_mon = r->tm_mon; tm->tm_mday = r->tm_mday;@@ -629,7 +519,7 @@ unsigned long stamp; int ofs; - if (*date < '0' || '9' <= *date)+ if (*date < '0' || '9' < *date) return -1; stamp = strtoul(date, &end, 10); if (*end != ' ' || stamp == ULONG_MAX || (end[1] != '+' && end[1] != '-'))@@ -699,8 +589,14 @@ /* mktime uses local timezone */ *timestamp = tm_to_time_t(&tm);- if (*offset == -1)- *offset = ((time_t)*timestamp - mktime(&tm)) / 60;+ if (*offset == -1) {+ time_t temp_time = mktime(&tm);+ if ((time_t)*timestamp > temp_time) {+ *offset = ((time_t)*timestamp - temp_time) / 60;+ } else {+ *offset = -(int)((temp_time - (time_t)*timestamp) / 60);+ }+ } if (*timestamp == -1) return -1;@@ -708,6 +604,28 @@ if (!tm_gmt) *timestamp -= *offset * 60; return 0; /* success */+}++int parse_expiry_date(const char *date, unsigned long *timestamp)+{+ int errors = 0;++ if (!strcmp(date, "never") || !strcmp(date, "false"))+ *timestamp = 0;+ else if (!strcmp(date, "all") || !strcmp(date, "now"))+ /*+ * We take over "now" here, which usually translates+ * to the current timestamp. This is because the user+ * really means to expire everything she has done in+ * the past, and by definition reflogs are the record+ * of the past, and there is nothing from the future+ * to be kept.+ */+ *timestamp = ULONG_MAX;+ else+ *timestamp = approxidate_careful(date, &errors);++ return errors; } int parse_date(const char *date, char *result, int maxlen)
git-date.cabal view
@@ -1,5 +1,5 @@ name: git-date-version: 0.2+version: 0.2.1 cabal-version: >= 1.8 license: GPL-2 license-file: COPYING@@ -34,6 +34,26 @@ bytestring, utf8-string, time++test-suite tests+ type: exitcode-stdio-1.0+ main-is: tests/suite.hs++ c-sources: ext/date.c+ include-dirs: ext++ other-modules:+ Data.Time.Git++ build-depends:+ base == 4.*,+ bytestring,+ utf8-string,+ time,+ old-locale,+ QuickCheck >= 2.4.1.1,+ test-framework,+ test-framework-quickcheck2 source-repository head type: git
+ tests/suite.hs view
@@ -0,0 +1,53 @@+import Test.Framework (defaultMain, Test)+import Test.Framework.Providers.QuickCheck2+import Test.QuickCheck++import Data.Int+import Data.Time.Git+import System.Locale+import Data.Time+import Data.Time.Clock.POSIX+import System.IO.Unsafe++-- utcToLocalZonedTime is safeish because timezone won't change during testing+toLocal :: UTCTime -> ZonedTime+toLocal = unsafePerformIO . utcToLocalZonedTime++newtype PositiveTime = PositiveTime ZonedTime deriving (Show)++instance Arbitrary PositiveTime where+ arbitrary =+ fmap (PositiveTime . toLocal . posixSecondsToUTCTime . fromIntegral) $+ choose (0, maxBound::Int32) -- Should be able to use CULong, but that failed++newtype DateFormat = DateFormat String deriving (Show, Eq)++instance Arbitrary DateFormat where+ arbitrary = elements $ map DateFormat $ [+ "%c",+ "%Y-%m-%d %H:%M:%S%Q%z",+ "%B %d, %Y %l:%M:%S %P",+ "%B %d, %Y %l:%M:%S %p %Z",+ "%l:%M:%S %p %Y %B %d",+ rfc822DateFormat,+ iso8601DateFormat (Just "%H:%M:%S%Q"),+ iso8601DateFormat (Just "%H:%M:%S"),+ iso8601DateFormat (Just "%H:%M:%S%Q%z"),+ iso8601DateFormat (Just "%H:%M:%S%Q%Z")+ ]++prop_format :: DateFormat -> PositiveTime -> Bool+prop_format (DateFormat fmt) (PositiveTime time) =+ approxidate formatted == Just posix+ where+ posix = floor $ utcTimeToPOSIXSeconds $ zonedTimeToUTC time+ formatted = formatTime defaultTimeLocale fmt time++tests :: [Test]+tests =+ [+ testProperty "Arbitrary date formats work" prop_format+ ]++main :: IO ()+main = defaultMain tests