packages feed

git-date (empty) → 0.1

raw patch · 6 files changed

+1545/−0 lines, 6 filesdep +basedep +bytestringdep +timesetup-changed

Dependencies added: base, bytestring, time, utf8-string

Files

+ COPYING view
@@ -0,0 +1,365 @@+The date.c which forms the bulk of this library is ripped directly from+git, and so I have chosen to license the whole library the same as git,+with the contents of git's COPYING file reproduced below.++----------------------------------------++ Note that the only valid version of the GPL as far as this project+ is concerned is _this_ particular version of the license (ie v2, not+ v2.2 or v3.x or whatever), unless explicitly otherwise stated.++ HOWEVER, in order to allow a migration to GPLv3 if that seems like+ a good idea, I also ask that people involved with the project make+ their preferences known. In particular, if you trust me to make that+ decision, you might note so in your copyright message, ie something+ like++	This file is licensed under the GPL v2, or a later version+	at the discretion of Linus.++  might avoid issues. But we can also just decide to synchronize and+  contact all copyright holders on record if/when the occasion arises.++			Linus Torvalds++----------------------------------------++		    GNU GENERAL PUBLIC LICENSE+		       Version 2, June 1991++ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA+ Everyone is permitted to copy and distribute verbatim copies+ of this license document, but changing it is not allowed.++			    Preamble++  The licenses for most software are designed to take away your+freedom to share and change it.  By contrast, the GNU General Public+License is intended to guarantee your freedom to share and change free+software--to make sure the software is free for all its users.  This+General Public License applies to most of the Free Software+Foundation's software and to any other program whose authors commit to+using it.  (Some other Free Software Foundation software is covered by+the GNU Lesser General Public License instead.)  You can apply it to+your programs, too.++  When we speak of free software, we are referring to freedom, not+price.  Our General Public Licenses are designed to make sure that you+have the freedom to distribute copies of free software (and charge for+this service if you wish), that you receive source code or can get it+if you want it, that you can change the software or use pieces of it+in new free programs; and that you know you can do these things.++  To protect your rights, we need to make restrictions that forbid+anyone to deny you these rights or to ask you to surrender the rights.+These restrictions translate to certain responsibilities for you if you+distribute copies of the software, or if you modify it.++  For example, if you distribute copies of such a program, whether+gratis or for a fee, you must give the recipients all the rights that+you have.  You must make sure that they, too, receive or can get the+source code.  And you must show them these terms so they know their+rights.++  We protect your rights with two steps: (1) copyright the software, and+(2) offer you this license which gives you legal permission to copy,+distribute and/or modify the software.++  Also, for each author's protection and ours, we want to make certain+that everyone understands that there is no warranty for this free+software.  If the software is modified by someone else and passed on, we+want its recipients to know that what they have is not the original, so+that any problems introduced by others will not reflect on the original+authors' reputations.++  Finally, any free program is threatened constantly by software+patents.  We wish to avoid the danger that redistributors of a free+program will individually obtain patent licenses, in effect making the+program proprietary.  To prevent this, we have made it clear that any+patent must be licensed for everyone's free use or not licensed at all.++  The precise terms and conditions for copying, distribution and+modification follow.++		    GNU GENERAL PUBLIC LICENSE+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION++  0. This License applies to any program or other work which contains+a notice placed by the copyright holder saying it may be distributed+under the terms of this General Public License.  The "Program", below,+refers to any such program or work, and a "work based on the Program"+means either the Program or any derivative work under copyright law:+that is to say, a work containing the Program or a portion of it,+either verbatim or with modifications and/or translated into another+language.  (Hereinafter, translation is included without limitation in+the term "modification".)  Each licensee is addressed as "you".++Activities other than copying, distribution and modification are not+covered by this License; they are outside its scope.  The act of+running the Program is not restricted, and the output from the Program+is covered only if its contents constitute a work based on the+Program (independent of having been made by running the Program).+Whether that is true depends on what the Program does.++  1. You may copy and distribute verbatim copies of the Program's+source code as you receive it, in any medium, provided that you+conspicuously and appropriately publish on each copy an appropriate+copyright notice and disclaimer of warranty; keep intact all the+notices that refer to this License and to the absence of any warranty;+and give any other recipients of the Program a copy of this License+along with the Program.++You may charge a fee for the physical act of transferring a copy, and+you may at your option offer warranty protection in exchange for a fee.++  2. You may modify your copy or copies of the Program or any portion+of it, thus forming a work based on the Program, and copy and+distribute such modifications or work under the terms of Section 1+above, provided that you also meet all of these conditions:++    a) You must cause the modified files to carry prominent notices+    stating that you changed the files and the date of any change.++    b) You must cause any work that you distribute or publish, that in+    whole or in part contains or is derived from the Program or any+    part thereof, to be licensed as a whole at no charge to all third+    parties under the terms of this License.++    c) If the modified program normally reads commands interactively+    when run, you must cause it, when started running for such+    interactive use in the most ordinary way, to print or display an+    announcement including an appropriate copyright notice and a+    notice that there is no warranty (or else, saying that you provide+    a warranty) and that users may redistribute the program under+    these conditions, and telling the user how to view a copy of this+    License.  (Exception: if the Program itself is interactive but+    does not normally print such an announcement, your work based on+    the Program is not required to print an announcement.)++These requirements apply to the modified work as a whole.  If+identifiable sections of that work are not derived from the Program,+and can be reasonably considered independent and separate works in+themselves, then this License, and its terms, do not apply to those+sections when you distribute them as separate works.  But when you+distribute the same sections as part of a whole which is a work based+on the Program, the distribution of the whole must be on the terms of+this License, whose permissions for other licensees extend to the+entire whole, and thus to each and every part regardless of who wrote it.++Thus, it is not the intent of this section to claim rights or contest+your rights to work written entirely by you; rather, the intent is to+exercise the right to control the distribution of derivative or+collective works based on the Program.++In addition, mere aggregation of another work not based on the Program+with the Program (or with a work based on the Program) on a volume of+a storage or distribution medium does not bring the other work under+the scope of this License.++  3. You may copy and distribute the Program (or a work based on it,+under Section 2) in object code or executable form under the terms of+Sections 1 and 2 above provided that you also do one of the following:++    a) Accompany it with the complete corresponding machine-readable+    source code, which must be distributed under the terms of Sections+    1 and 2 above on a medium customarily used for software interchange; or,++    b) Accompany it with a written offer, valid for at least three+    years, to give any third party, for a charge no more than your+    cost of physically performing source distribution, a complete+    machine-readable copy of the corresponding source code, to be+    distributed under the terms of Sections 1 and 2 above on a medium+    customarily used for software interchange; or,++    c) Accompany it with the information you received as to the offer+    to distribute corresponding source code.  (This alternative is+    allowed only for noncommercial distribution and only if you+    received the program in object code or executable form with such+    an offer, in accord with Subsection b above.)++The source code for a work means the preferred form of the work for+making modifications to it.  For an executable work, complete source+code means all the source code for all modules it contains, plus any+associated interface definition files, plus the scripts used to+control compilation and installation of the executable.  However, as a+special exception, the source code distributed need not include+anything that is normally distributed (in either source or binary+form) with the major components (compiler, kernel, and so on) of the+operating system on which the executable runs, unless that component+itself accompanies the executable.++If distribution of executable or object code is made by offering+access to copy from a designated place, then offering equivalent+access to copy the source code from the same place counts as+distribution of the source code, even though third parties are not+compelled to copy the source along with the object code.++  4. You may not copy, modify, sublicense, or distribute the Program+except as expressly provided under this License.  Any attempt+otherwise to copy, modify, sublicense or distribute the Program is+void, and will automatically terminate your rights under this License.+However, parties who have received copies, or rights, from you under+this License will not have their licenses terminated so long as such+parties remain in full compliance.++  5. You are not required to accept this License, since you have not+signed it.  However, nothing else grants you permission to modify or+distribute the Program or its derivative works.  These actions are+prohibited by law if you do not accept this License.  Therefore, by+modifying or distributing the Program (or any work based on the+Program), you indicate your acceptance of this License to do so, and+all its terms and conditions for copying, distributing or modifying+the Program or works based on it.++  6. Each time you redistribute the Program (or any work based on the+Program), the recipient automatically receives a license from the+original licensor to copy, distribute or modify the Program subject to+these terms and conditions.  You may not impose any further+restrictions on the recipients' exercise of the rights granted herein.+You are not responsible for enforcing compliance by third parties to+this License.++  7. If, as a consequence of a court judgment or allegation of patent+infringement or for any other reason (not limited to patent issues),+conditions are imposed on you (whether by court order, agreement or+otherwise) that contradict the conditions of this License, they do not+excuse you from the conditions of this License.  If you cannot+distribute so as to satisfy simultaneously your obligations under this+License and any other pertinent obligations, then as a consequence you+may not distribute the Program at all.  For example, if a patent+license would not permit royalty-free redistribution of the Program by+all those who receive copies directly or indirectly through you, then+the only way you could satisfy both it and this License would be to+refrain entirely from distribution of the Program.++If any portion of this section is held invalid or unenforceable under+any particular circumstance, the balance of the section is intended to+apply and the section as a whole is intended to apply in other+circumstances.++It is not the purpose of this section to induce you to infringe any+patents or other property right claims or to contest validity of any+such claims; this section has the sole purpose of protecting the+integrity of the free software distribution system, which is+implemented by public license practices.  Many people have made+generous contributions to the wide range of software distributed+through that system in reliance on consistent application of that+system; it is up to the author/donor to decide if he or she is willing+to distribute software through any other system and a licensee cannot+impose that choice.++This section is intended to make thoroughly clear what is believed to+be a consequence of the rest of this License.++  8. If the distribution and/or use of the Program is restricted in+certain countries either by patents or by copyrighted interfaces, the+original copyright holder who places the Program under this License+may add an explicit geographical distribution limitation excluding+those countries, so that distribution is permitted only in or among+countries not thus excluded.  In such case, this License incorporates+the limitation as if written in the body of this License.++  9. The Free Software Foundation may publish revised and/or new versions+of the General Public License from time to time.  Such new versions will+be similar in spirit to the present version, but may differ in detail to+address new problems or concerns.++Each version is given a distinguishing version number.  If the Program+specifies a version number of this License which applies to it and "any+later version", you have the option of following the terms and conditions+either of that version or of any later version published by the Free+Software Foundation.  If the Program does not specify a version number of+this License, you may choose any version ever published by the Free Software+Foundation.++  10. If you wish to incorporate parts of the Program into other free+programs whose distribution conditions are different, write to the author+to ask for permission.  For software which is copyrighted by the Free+Software Foundation, write to the Free Software Foundation; we sometimes+make exceptions for this.  Our decision will be guided by the two goals+of preserving the free status of all derivatives of our free software and+of promoting the sharing and reuse of software generally.++			    NO WARRANTY++  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,+REPAIR OR CORRECTION.++  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE+POSSIBILITY OF SUCH DAMAGES.++		     END OF TERMS AND CONDITIONS++	    How to Apply These Terms to Your New Programs++  If you develop a new program, and you want it to be of the greatest+possible use to the public, the best way to achieve this is to make it+free software which everyone can redistribute and change under these terms.++  To do so, attach the following notices to the program.  It is safest+to attach them to the start of each source file to most effectively+convey the exclusion of warranty; and each file should have at least+the "copyright" line and a pointer to where the full notice is found.++    <one line to give the program's name and a brief idea of what it does.>+    Copyright (C) <year>  <name of author>++    This program is free software; you can redistribute it and/or modify+    it under the terms of the GNU General Public License as published by+    the Free Software Foundation; either version 2 of the License, or+    (at your option) any later version.++    This program 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 General Public License for more details.++    You should have received a copy of the GNU General Public License along+    with this program; if not, write to the Free Software Foundation, Inc.,+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.++Also add information on how to contact you by electronic and paper mail.++If the program is interactive, make it output a short notice like this+when it starts in an interactive mode:++    Gnomovision version 69, Copyright (C) year name of author+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.+    This is free software, and you are welcome to redistribute it+    under certain conditions; type `show c' for details.++The hypothetical commands `show w' and `show c' should show the appropriate+parts of the General Public License.  Of course, the commands you use may+be called something other than `show w' and `show c'; they could even be+mouse-clicks or menu items--whatever suits your program.++You should also get your employer (if you work as a programmer) or your+school, if any, to sign a "copyright disclaimer" for the program, if+necessary.  Here is a sample; alter the names:++  Yoyodyne, Inc., hereby disclaims all copyright interest in the program+  `Gnomovision' (which makes passes at compilers) written by James Hacker.++  <signature of Ty Coon>, 1 April 1989+  Ty Coon, President of Vice++This General Public License does not permit incorporating your program into+proprietary programs.  If your program is a subroutine library, you may+consider it more useful to permit linking proprietary applications with the+library.  If this is what you want to do, use the GNU Lesser General+Public License instead of this License.
+ Data/Time/Git.hs view
@@ -0,0 +1,42 @@+{-# LANGUAGE ForeignFunctionInterface #-}++module Data.Time.Git (approxidate, approxidateUTC) where++import Foreign+import Foreign.C.Types+import Foreign.C.String++import Data.Time+import Data.Time.Clock.POSIX++import qualified Data.ByteString as BS+import qualified Data.ByteString.UTF8 as BS (fromString)++-- | Parse a date/time string and return Just a Unix timestamp.+--   Return Nothing if the string could not be interpreted.+--   If no timezone is in the string, the local timezone is used.+approxidate :: String -> Maybe Integer+approxidate = unsafePerformIO . io_approxidate_careful++-- | The same as approxidate, but returns a UTCTime instead of a raw+--   timestamp.+approxidateUTC :: String -> Maybe UTCTime+approxidateUTC input =+	fmap (posixSecondsToUTCTime . realToFrac) (approxidate input)++foreign import ccall unsafe "date.c approxidate_careful"+	c_approxidate_careful ::+	CString -> Ptr CInt ->+	IO CULong++io_approxidate_careful :: String -> IO (Maybe Integer)+io_approxidate_careful input =+	BS.useAsCString (BS.fromString input) (\c_input ->+		alloca (\c_error_ret -> do+			poke c_error_ret 0+			time <- c_approxidate_careful c_input c_error_ret+			err <- peek c_error_ret+			if err == 0 then return $ Just (toInteger time) else+				return Nothing+		)+	)
+ README view
@@ -0,0 +1,3 @@+This library implements a simple, pure binding onto the date parsing+code from git.  Why?  Because it was the only reasonable implementation+of a generic date parser I could find.
+ Setup.hs view
@@ -0,0 +1,3 @@+import Distribution.Simple++main = defaultMain
+ ext/date.c view
@@ -0,0 +1,1092 @@+/*+ * GIT - The information manager from hell+ *+ * Copyright (C) Linus Torvalds, 2005+ */++/* Add includes and some stuff from git to make this code independent+ * -- Stephen Paul Weber */++#include <time.h>+#include <sys/time.h>+#include <stdio.h>+#include <string.h>+#include <limits.h>+#include <ctype.h>+#include <stdlib.h>++#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))++enum date_mode {+	DATE_NORMAL = 0,+	DATE_RELATIVE,+	DATE_SHORT,+	DATE_LOCAL,+	DATE_ISO8601,+	DATE_RFC2822,+	DATE_RAW,+	DATE_UNKNOWN+};++/*+ * This is like mktime, but without normalization of tm_wday and tm_yday.+ */+static time_t tm_to_time_t(const struct tm *tm)+{+	static const int mdays[] = {+	    0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334+	};+	int year = tm->tm_year - 70;+	int month = tm->tm_mon;+	int day = tm->tm_mday;++	if (year < 0 || year > 129) /* algo only works for 1970-2099 */+		return -1;+	if (month < 0 || month > 11) /* array bounds */+		return -1;+	if (month < 2 || (year + 2) % 4)+		day--;+	if (tm->tm_hour < 0 || tm->tm_min < 0 || tm->tm_sec < 0)+		return -1;+	return (year * 365 + (year + 1) / 4 + mdays[month] + day) * 24*60*60UL ++		tm->tm_hour * 60*60 + tm->tm_min * 60 + tm->tm_sec;+}++static const char *month_names[] = {+	"January", "February", "March", "April", "May", "June",+	"July", "August", "September", "October", "November", "December"+};++static const char *weekday_names[] = {+	"Sundays", "Mondays", "Tuesdays", "Wednesdays", "Thursdays", "Fridays", "Saturdays"+};++static time_t gm_time_t(unsigned long time, int tz)+{+	int minutes;++	minutes = tz < 0 ? -tz : tz;+	minutes = (minutes / 100)*60 + (minutes % 100);+	minutes = tz < 0 ? -minutes : minutes;+	return time + minutes * 60;+}++/*+ * The "tz" thing is passed in as this strange "decimal parse of tz"+ * thing, which means that tz -0100 is passed in as the integer -100,+ * even though it means "sixty minutes off"+ */+static struct tm *time_to_tm(unsigned long time, int tz)+{+	time_t t = gm_time_t(time, tz);+	return gmtime(&t);+}++/*+ * What value of "tz" was in effect back then at "time" in the+ * local timezone?+ */+static int local_tzoffset(unsigned long time)+{+	time_t t, t_local;+	struct tm tm;+	int offset, eastwest;++	t = time;+	localtime_r(&t, &tm);+	t_local = tm_to_time_t(&tm);++	if (t_local < t) {+		eastwest = -1;+		offset = t - t_local;+	} else {+		eastwest = 1;+		offset = t_local - t;+	}+	offset /= 60; /* in minutes */+	offset = (offset % 60) + ((offset / 60) * 100);+	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.+ *+ * In my world, it's always summer, and things are probably a bit off+ * in other ways too.+ */+static const struct {+	const char *name;+	int offset;+	int dst;+} timezone_names[] = {+	{ "IDLW", -12, 0, },	/* International Date Line West */+	{ "NT",   -11, 0, },	/* Nome */+	{ "CAT",  -10, 0, },	/* Central Alaska */+	{ "HST",  -10, 0, },	/* Hawaii Standard */+	{ "HDT",  -10, 1, },	/* Hawaii Daylight */+	{ "YST",   -9, 0, },	/* Yukon Standard */+	{ "YDT",   -9, 1, },	/* Yukon Daylight */+	{ "PST",   -8, 0, },	/* Pacific Standard */+	{ "PDT",   -8, 1, },	/* Pacific Daylight */+	{ "MST",   -7, 0, },	/* Mountain Standard */+	{ "MDT",   -7, 1, },	/* Mountain Daylight */+	{ "CST",   -6, 0, },	/* Central Standard */+	{ "CDT",   -6, 1, },	/* Central Daylight */+	{ "EST",   -5, 0, },	/* Eastern Standard */+	{ "EDT",   -5, 1, },	/* Eastern Daylight */+	{ "AST",   -3, 0, },	/* Atlantic Standard */+	{ "ADT",   -3, 1, },	/* Atlantic Daylight */+	{ "WAT",   -1, 0, },	/* West Africa */++	{ "GMT",    0, 0, },	/* Greenwich Mean */+	{ "UTC",    0, 0, },	/* Universal (Coordinated) */+	{ "Z",      0, 0, },    /* Zulu, alias for UTC */++	{ "WET",    0, 0, },	/* Western European */+	{ "BST",    0, 1, },	/* British Summer */+	{ "CET",   +1, 0, },	/* Central European */+	{ "MET",   +1, 0, },	/* Middle European */+	{ "MEWT",  +1, 0, },	/* Middle European Winter */+	{ "MEST",  +1, 1, },	/* Middle European Summer */+	{ "CEST",  +1, 1, },	/* Central European Summer */+	{ "MESZ",  +1, 1, },	/* Middle European Summer */+	{ "FWT",   +1, 0, },	/* French Winter */+	{ "FST",   +1, 1, },	/* French Summer */+	{ "EET",   +2, 0, },	/* Eastern Europe, USSR Zone 1 */+	{ "EEST",  +2, 1, },	/* Eastern European Daylight */+	{ "WAST",  +7, 0, },	/* West Australian Standard */+	{ "WADT",  +7, 1, },	/* West Australian Daylight */+	{ "CCT",   +8, 0, },	/* China Coast, USSR Zone 7 */+	{ "JST",   +9, 0, },	/* Japan Standard, USSR Zone 8 */+	{ "EAST", +10, 0, },	/* Eastern Australian Standard */+	{ "EADT", +10, 1, },	/* Eastern Australian Daylight */+	{ "GST",  +10, 0, },	/* Guam Standard, USSR Zone 9 */+	{ "NZT",  +12, 0, },	/* New Zealand */+	{ "NZST", +12, 0, },	/* New Zealand Standard */+	{ "NZDT", +12, 1, },	/* New Zealand Daylight */+	{ "IDLE", +12, 0, },	/* International Date Line East */+};++static int match_string(const char *date, const char *str)+{+	int i = 0;++	for (i = 0; *date; date++, str++, i++) {+		if (*date == *str)+			continue;+		if (toupper(*date) == toupper(*str))+			continue;+		if (!isalnum(*date))+			break;+		return 0;+	}+	return i;+}++static int skip_alpha(const char *date)+{+	int i = 0;+	do {+		i++;+	} while (isalpha(date[i]));+	return i;+}++/*+* Parse month, weekday, or timezone name+*/+static int match_alpha(const char *date, struct tm *tm, int *offset)+{+	int i;++	for (i = 0; i < 12; i++) {+		int match = match_string(date, month_names[i]);+		if (match >= 3) {+			tm->tm_mon = i;+			return match;+		}+	}++	for (i = 0; i < 7; i++) {+		int match = match_string(date, weekday_names[i]);+		if (match >= 3) {+			tm->tm_wday = i;+			return match;+		}+	}++	for (i = 0; i < ARRAY_SIZE(timezone_names); i++) {+		int match = match_string(date, timezone_names[i].name);+		if (match >= 3 || match == strlen(timezone_names[i].name)) {+			int off = timezone_names[i].offset;++			/* This is bogus, but we like summer */+			off += timezone_names[i].dst;++			/* Only use the tz name offset if we don't have anything better */+			if (*offset == -1)+				*offset = 60*off;++			return match;+		}+	}++	if (match_string(date, "PM") == 2) {+		tm->tm_hour = (tm->tm_hour % 12) + 12;+		return 2;+	}++	if (match_string(date, "AM") == 2) {+		tm->tm_hour = (tm->tm_hour % 12) + 0;+		return 2;+	}++	/* BAD CRAP */+	return skip_alpha(date);+}++static int is_date(int year, int month, int day, struct tm *now_tm, time_t now, struct tm *tm)+{+	if (month > 0 && month < 13 && day > 0 && day < 32) {+		struct tm check = *tm;+		struct tm *r = (now_tm ? &check : tm);+		time_t specified;++		r->tm_mon = month - 1;+		r->tm_mday = day;+		if (year == -1) {+			if (!now_tm)+				return 1;+			r->tm_year = now_tm->tm_year;+		}+		else if (year >= 1970 && year < 2100)+			r->tm_year = year - 1900;+		else if (year > 70 && year < 100)+			r->tm_year = year;+		else if (year < 38)+			r->tm_year = year + 100;+		else+			return 0;+		if (!now_tm)+			return 1;++		specified = tm_to_time_t(r);++		/* Be it commit time or author time, it does not make+		 * 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)+			return 0;+		tm->tm_mon = r->tm_mon;+		tm->tm_mday = r->tm_mday;+		if (year != -1)+			tm->tm_year = r->tm_year;+		return 1;+	}+	return 0;+}++static int match_multi_number(unsigned long num, char c, const char *date, char *end, struct tm *tm)+{+	time_t now;+	struct tm now_tm;+	struct tm *refuse_future;+	long num2, num3;++	num2 = strtol(end+1, &end, 10);+	num3 = -1;+	if (*end == c && isdigit(end[1]))+		num3 = strtol(end+1, &end, 10);++	/* Time? Date? */+	switch (c) {+	case ':':+		if (num3 < 0)+			num3 = 0;+		if (num < 25 && num2 >= 0 && num2 < 60 && num3 >= 0 && num3 <= 60) {+			tm->tm_hour = num;+			tm->tm_min = num2;+			tm->tm_sec = num3;+			break;+		}+		return 0;++	case '-':+	case '/':+	case '.':+		now = time(NULL);+		refuse_future = NULL;+		if (gmtime_r(&now, &now_tm))+			refuse_future = &now_tm;++		if (num > 70) {+			/* yyyy-mm-dd? */+			if (is_date(num, num2, num3, refuse_future, now, tm))+				break;+			/* yyyy-dd-mm? */+			if (is_date(num, num3, num2, refuse_future, now, tm))+				break;+		}+		/* Our eastern European friends say dd.mm.yy[yy]+		 * is the norm there, so giving precedence to+		 * mm/dd/yy[yy] form only when separator is not '.'+		 */+		if (c != '.' &&+		    is_date(num3, num, num2, refuse_future, now, tm))+			break;+		/* European dd.mm.yy[yy] or funny US dd/mm/yy[yy] */+		if (is_date(num3, num2, num, refuse_future, now, tm))+			break;+		/* Funny European mm.dd.yy */+		if (c == '.' &&+		    is_date(num3, num, num2, refuse_future, now, tm))+			break;+		return 0;+	}+	return end - date;+}++/*+ * Have we filled in any part of the time/date yet?+ * We just do a binary 'and' to see if the sign bit+ * is set in all the values.+ */+static inline int nodate(struct tm *tm)+{+	return (tm->tm_year &+		tm->tm_mon &+		tm->tm_mday &+		tm->tm_hour &+		tm->tm_min &+		tm->tm_sec) < 0;+}++/*+ * We've seen a digit. Time? Year? Date?+ */+static int match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt)+{+	int n;+	char *end;+	unsigned long num;++	num = strtoul(date, &end, 10);++	/*+	 * Seconds since 1970? We trigger on that for any numbers with+	 * more than 8 digits. This is because we don't want to rule out+	 * numbers like 20070606 as a YYYYMMDD date.+	 */+	if (num >= 100000000 && nodate(tm)) {+		time_t time = num;+		if (gmtime_r(&time, tm)) {+			*tm_gmt = 1;+			return end - date;+		}+	}++	/*+	 * Check for special formats: num[-.:/]num[same]num+	 */+	switch (*end) {+	case ':':+	case '.':+	case '/':+	case '-':+		if (isdigit(end[1])) {+			int match = match_multi_number(num, *end, date, end, tm);+			if (match)+				return match;+		}+	}++	/*+	 * None of the special formats? Try to guess what+	 * the number meant. We use the number of digits+	 * to make a more educated guess..+	 */+	n = 0;+	do {+		n++;+	} while (isdigit(date[n]));++	/* Four-digit year or a timezone? */+	if (n == 4) {+		if (num <= 1400 && *offset == -1) {+			unsigned int minutes = num % 100;+			unsigned int hours = num / 100;+			*offset = hours*60 + minutes;+		} else if (num > 1900 && num < 2100)+			tm->tm_year = num - 1900;+		return n;+	}++	/*+	 * Ignore lots of numerals. We took care of 4-digit years above.+	 * Days or months must be one or two digits.+	 */+	if (n > 2)+		return n;++	/*+	 * NOTE! We will give precedence to day-of-month over month or+	 * year numbers in the 1-12 range. So 05 is always "mday 5",+	 * unless we already have a mday..+	 *+	 * IOW, 01 Apr 05 parses as "April 1st, 2005".+	 */+	if (num > 0 && num < 32 && tm->tm_mday < 0) {+		tm->tm_mday = num;+		return n;+	}++	/* Two-digit year? */+	if (n == 2 && tm->tm_year < 0) {+		if (num < 10 && tm->tm_mday >= 0) {+			tm->tm_year = num + 100;+			return n;+		}+		if (num >= 70) {+			tm->tm_year = num;+			return n;+		}+	}++	if (num > 0 && num < 13 && tm->tm_mon < 0)+		tm->tm_mon = num-1;++	return n;+}++static int match_tz(const char *date, int *offp)+{+	char *end;+	int hour = strtoul(date + 1, &end, 10);+	int n = end - (date + 1);+	int min = 0;++	if (n == 4) {+		/* hhmm */+		min = hour % 100;+		hour = hour / 100;+	} else if (n != 2) {+		min = 99; /* random crap */+	} else if (*end == ':') {+		/* hh:mm? */+		min = strtoul(end + 1, &end, 10);+		if (end - (date + 1) != 5)+			min = 99; /* random crap */+	} /* otherwise we parsed "hh" */++	/*+	 * Don't accept any random crap. Even though some places have+	 * offset larger than 12 hours (e.g. Pacific/Kiritimati is at+	 * UTC+14), there is something wrong if hour part is much+	 * larger than that. We might also want to check that the+	 * minutes are divisible by 15 or something too. (Offset of+	 * Kathmandu, Nepal is UTC+5:45)+	 */+	if (min < 60 && hour < 24) {+		int offset = hour * 60 + min;+		if (*date == '-')+			offset = -offset;+		*offp = offset;+	}+	return end - date;+}++static int date_string(unsigned long date, int offset, char *buf, int len)+{+	int sign = '+';++	if (offset < 0) {+		offset = -offset;+		sign = '-';+	}+	return snprintf(buf, len, "%lu %c%02d%02d", date, sign, offset / 60, offset % 60);+}++/*+ * Parse a string like "0 +0000" as ancient timestamp near epoch, but+ * only when it appears not as part of any other string.+ */+static int match_object_header_date(const char *date, unsigned long *timestamp, int *offset)+{+	char *end;+	unsigned long stamp;+	int ofs;++	if (*date < '0' || '9' <= *date)+		return -1;+	stamp = strtoul(date, &end, 10);+	if (*end != ' ' || stamp == ULONG_MAX || (end[1] != '+' && end[1] != '-'))+		return -1;+	date = end + 2;+	ofs = strtol(date, &end, 10);+	if ((*end != '\0' && (*end != '\n')) || end != date + 4)+		return -1;+	ofs = (ofs / 100) * 60 + (ofs % 100);+	if (date[-1] == '-')+		ofs = -ofs;+	*timestamp = stamp;+	*offset = ofs;+	return 0;+}++/* Gr. strptime is crap for this; it doesn't have a way to require RFC2822+   (i.e. English) day/month names, and it doesn't work correctly with %z. */+int parse_date_basic(const char *date, unsigned long *timestamp, int *offset)+{+	struct tm tm;+	int tm_gmt;+	unsigned long dummy_timestamp;+	int dummy_offset;++	if (!timestamp)+		timestamp = &dummy_timestamp;+	if (!offset)+		offset = &dummy_offset;++	memset(&tm, 0, sizeof(tm));+	tm.tm_year = -1;+	tm.tm_mon = -1;+	tm.tm_mday = -1;+	tm.tm_isdst = -1;+	tm.tm_hour = -1;+	tm.tm_min = -1;+	tm.tm_sec = -1;+	*offset = -1;+	tm_gmt = 0;++	if (*date == '@' &&+	    !match_object_header_date(date + 1, timestamp, offset))+		return 0; /* success */+	for (;;) {+		int match = 0;+		unsigned char c = *date;++		/* Stop at end of string or newline */+		if (!c || c == '\n')+			break;++		if (isalpha(c))+			match = match_alpha(date, &tm, offset);+		else if (isdigit(c))+			match = match_digit(date, &tm, offset, &tm_gmt);+		else if ((c == '-' || c == '+') && isdigit(date[1]))+			match = match_tz(date, offset);++		if (!match) {+			/* BAD CRAP */+			match = 1;+		}++		date += match;+	}++	/* mktime uses local timezone */+	*timestamp = tm_to_time_t(&tm);+	if (*offset == -1)+		*offset = ((time_t)*timestamp - mktime(&tm)) / 60;++	if (*timestamp == -1)+		return -1;++	if (!tm_gmt)+		*timestamp -= *offset * 60;+	return 0; /* success */+}++int parse_date(const char *date, char *result, int maxlen)+{+	unsigned long timestamp;+	int offset;+	if (parse_date_basic(date, &timestamp, &offset))+		return -1;+	return date_string(timestamp, offset, result, maxlen);+}++enum date_mode parse_date_format(const char *format)+{+	if (!strcmp(format, "relative"))+		return DATE_RELATIVE;+	else if (!strcmp(format, "iso8601") ||+		 !strcmp(format, "iso"))+		return DATE_ISO8601;+	else if (!strcmp(format, "rfc2822") ||+		 !strcmp(format, "rfc"))+		return DATE_RFC2822;+	else if (!strcmp(format, "short"))+		return DATE_SHORT;+	else if (!strcmp(format, "local"))+		return DATE_LOCAL;+	else if (!strcmp(format, "default"))+		return DATE_NORMAL;+	else if (!strcmp(format, "raw"))+		return DATE_RAW;+	else+		return DATE_UNKNOWN;+}++void datestamp(char *buf, int bufsize)+{+	time_t now;+	int offset;++	time(&now);++	offset = tm_to_time_t(localtime(&now)) - now;+	offset /= 60;++	date_string(now, offset, buf, bufsize);+}++/*+ * Relative time update (eg "2 days ago").  If we haven't set the time+ * yet, we need to set it from current time.+ */+static unsigned long update_tm(struct tm *tm, struct tm *now, unsigned long sec)+{+	time_t n;++	if (tm->tm_mday < 0)+		tm->tm_mday = now->tm_mday;+	if (tm->tm_mon < 0)+		tm->tm_mon = now->tm_mon;+	if (tm->tm_year < 0) {+		tm->tm_year = now->tm_year;+		if (tm->tm_mon > now->tm_mon)+			tm->tm_year--;+	}++	n = mktime(tm) - sec;+	localtime_r(&n, tm);+	return n;+}++static void date_now(struct tm *tm, struct tm *now, int *num)+{+	update_tm(tm, now, 0);+}++static void date_yesterday(struct tm *tm, struct tm *now, int *num)+{+	update_tm(tm, now, 24*60*60);+}++static void date_time(struct tm *tm, struct tm *now, int hour)+{+	if (tm->tm_hour < hour)+		date_yesterday(tm, now, NULL);+	tm->tm_hour = hour;+	tm->tm_min = 0;+	tm->tm_sec = 0;+}++static void date_midnight(struct tm *tm, struct tm *now, int *num)+{+	date_time(tm, now, 0);+}++static void date_noon(struct tm *tm, struct tm *now, int *num)+{+	date_time(tm, now, 12);+}++static void date_tea(struct tm *tm, struct tm *now, int *num)+{+	date_time(tm, now, 17);+}++static void date_pm(struct tm *tm, struct tm *now, int *num)+{+	int hour, n = *num;+	*num = 0;++	hour = tm->tm_hour;+	if (n) {+		hour = n;+		tm->tm_min = 0;+		tm->tm_sec = 0;+	}+	tm->tm_hour = (hour % 12) + 12;+}++static void date_am(struct tm *tm, struct tm *now, int *num)+{+	int hour, n = *num;+	*num = 0;++	hour = tm->tm_hour;+	if (n) {+		hour = n;+		tm->tm_min = 0;+		tm->tm_sec = 0;+	}+	tm->tm_hour = (hour % 12);+}++static void date_never(struct tm *tm, struct tm *now, int *num)+{+	time_t n = 0;+	localtime_r(&n, tm);+}++static const struct special {+	const char *name;+	void (*fn)(struct tm *, struct tm *, int *);+} special[] = {+	{ "yesterday", date_yesterday },+	{ "noon", date_noon },+	{ "midnight", date_midnight },+	{ "tea", date_tea },+	{ "PM", date_pm },+	{ "AM", date_am },+	{ "never", date_never },+	{ "now", date_now },+	{ NULL }+};++static const char *number_name[] = {+	"zero", "one", "two", "three", "four",+	"five", "six", "seven", "eight", "nine", "ten",+};++static const struct typelen {+	const char *type;+	int length;+} typelen[] = {+	{ "seconds", 1 },+	{ "minutes", 60 },+	{ "hours", 60*60 },+	{ "days", 24*60*60 },+	{ "weeks", 7*24*60*60 },+	{ NULL }+};++static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm *now, int *num, int *touched)+{+	const struct typelen *tl;+	const struct special *s;+	const char *end = date;+	int i;++	while (isalpha(*++end));+		;++	for (i = 0; i < 12; i++) {+		int match = match_string(date, month_names[i]);+		if (match >= 3) {+			tm->tm_mon = i;+			*touched = 1;+			return end;+		}+	}++	for (s = special; s->name; s++) {+		int len = strlen(s->name);+		if (match_string(date, s->name) == len) {+			s->fn(tm, now, num);+			*touched = 1;+			return end;+		}+	}++	if (!*num) {+		for (i = 1; i < 11; i++) {+			int len = strlen(number_name[i]);+			if (match_string(date, number_name[i]) == len) {+				*num = i;+				*touched = 1;+				return end;+			}+		}+		if (match_string(date, "last") == 4) {+			*num = 1;+			*touched = 1;+		}+		return end;+	}++	tl = typelen;+	while (tl->type) {+		int len = strlen(tl->type);+		if (match_string(date, tl->type) >= len-1) {+			update_tm(tm, now, tl->length * *num);+			*num = 0;+			*touched = 1;+			return end;+		}+		tl++;+	}++	for (i = 0; i < 7; i++) {+		int match = match_string(date, weekday_names[i]);+		if (match >= 3) {+			int diff, n = *num -1;+			*num = 0;++			diff = tm->tm_wday - i;+			if (diff <= 0)+				n++;+			diff += 7*n;++			update_tm(tm, now, diff * 24 * 60 * 60);+			*touched = 1;+			return end;+		}+	}++	if (match_string(date, "months") >= 5) {+		int n;+		update_tm(tm, now, 0); /* fill in date fields if needed */+		n = tm->tm_mon - *num;+		*num = 0;+		while (n < 0) {+			n += 12;+			tm->tm_year--;+		}+		tm->tm_mon = n;+		*touched = 1;+		return end;+	}++	if (match_string(date, "years") >= 4) {+		update_tm(tm, now, 0); /* fill in date fields if needed */+		tm->tm_year -= *num;+		*num = 0;+		*touched = 1;+		return end;+	}++	return end;+}++static const char *approxidate_digit(const char *date, struct tm *tm, int *num)+{+	char *end;+	unsigned long number = strtoul(date, &end, 10);++	switch (*end) {+	case ':':+	case '.':+	case '/':+	case '-':+		if (isdigit(end[1])) {+			int match = match_multi_number(number, *end, date, end, tm);+			if (match)+				return date + match;+		}+	}++	/* Accept zero-padding only for small numbers ("Dec 02", never "Dec 0002") */+	if (date[0] != '0' || end - date <= 2)+		*num = number;+	return end;+}++/*+ * Do we have a pending number at the end, or when+ * we see a new one? Let's assume it's a month day,+ * as in "Dec 6, 1992"+ */+static void pending_number(struct tm *tm, int *num)+{+	int number = *num;++	if (number) {+		*num = 0;+		if (tm->tm_mday < 0 && number < 32)+			tm->tm_mday = number;+		else if (tm->tm_mon < 0 && number < 13)+			tm->tm_mon = number-1;+		else if (tm->tm_year < 0) {+			if (number > 1969 && number < 2100)+				tm->tm_year = number - 1900;+			else if (number > 69 && number < 100)+				tm->tm_year = number;+			else if (number < 38)+				tm->tm_year = 100 + number;+			/* We screw up for number = 00 ? */+		}+	}+}++static unsigned long approxidate_str(const char *date,+				     const struct timeval *tv,+				     int *error_ret)+{+	int number = 0;+	int touched = 0;+	struct tm tm, now;+	time_t time_sec;++	time_sec = tv->tv_sec;+	localtime_r(&time_sec, &tm);+	now = tm;++	tm.tm_year = -1;+	tm.tm_mon = -1;+	tm.tm_mday = -1;++	for (;;) {+		unsigned char c = *date;+		if (!c)+			break;+		date++;+		if (isdigit(c)) {+			pending_number(&tm, &number);+			date = approxidate_digit(date-1, &tm, &number);+			touched = 1;+			continue;+		}+		if (isalpha(c))+			date = approxidate_alpha(date-1, &tm, &now, &number, &touched);+	}+	pending_number(&tm, &number);+	if (!touched)+		*error_ret = 1;+	return update_tm(&tm, &now, 0);+}++unsigned long approxidate_relative(const char *date, const struct timeval *tv)+{+	unsigned long timestamp;+	int offset;+	int errors = 0;++	if (!parse_date_basic(date, &timestamp, &offset))+		return timestamp;+	return approxidate_str(date, tv, &errors);+}++unsigned long approxidate_careful(const char *date, int *error_ret)+{+	struct timeval tv;+	unsigned long timestamp;+	int offset;+	int dummy = 0;+	if (!error_ret)+		error_ret = &dummy;++	if (!parse_date_basic(date, &timestamp, &offset)) {+		*error_ret = 0;+		return timestamp;+	}++	gettimeofday(&tv, NULL);+	return approxidate_str(date, &tv, error_ret);+}
+ git-date.cabal view
@@ -0,0 +1,40 @@+name:            git-date+version:         0.1+cabal-version:   >= 1.8+license:         GPL-2+license-file:    COPYING+category:        Data+copyright:       © 2012 Stephen Paul Weber+author:          Stephen Paul Weber <singpolyma@singpolyma.net>+maintainer:      Stephen Paul Weber <singpolyma@singpolyma.net>+stability:       experimental+tested-with:     GHC == 7.0.3+synopsis:        Bindings to the date parsing from Git.+homepage:        https://github.com/singpolyma/git-date-haskell+bug-reports:     https://github.com/singpolyma/git-date-haskell/issues+build-type:      Simple+description:+        This library implements a simple, pure binding onto the date parsing+        code from git.  Why?  Because it was the only reasonable implementation+        of a generic date parser I could find.++extra-source-files:+        README,+        ext/date.c++library+        c-sources: ext/date.c+        include-dirs: ext++        exposed-modules:+                Data.Time.Git++        build-depends:+                base == 4.*,+                bytestring,+                utf8-string,+                time++source-repository head+        type:     git+        location: git://github.com/singpolyma/git-date-haskell.git