8
\$\begingroup\$

My PPCG holiday is over :D

Intro

Fractional time is the year + (the value (minute of year) / number of minutes in the year).

Example calculation

You should assume that February always has 28 days and the year is always 365 days long.

Let's say we want to convert the time: 17:34 3rd March 2013 to fractional time. Firstly, you find how many minutes were in 2013: 525600 minutes. Let's call this x.

Next, you want to find out how many minutes there have been since the start of 2013. A few quick calculations will tell you that the answer is 88894 minutes:

There have been 61 days since the start of the year, which times 1440 (number of minutes in a day) equals 87840 minutes. In 17 hours are 1020 minutes (17*60). Now, we can add 87840, 1020 and 34 minutes to equal 88894 minutes.

Let's call this y.

Finally, you divide y by x and add the year, resulting in 2013.16912 (to 5 decimal places).

Input

The date and time will be given as a single string. The string will be in the following format:

YYYY-MM-DD hh:mm 

The time will always be in 24 hour format and the year will always be in the range 1900-2050 inclusive.

Examples

Input:  2016-08-06 23:48
Output: 2016.59723

Input:  2013-03-03 17:34
Output: 2013.16912

Input:  1914-11-11 11:11
Output: 1914.86155

If you are going for the bounty, either ping me in the comments or in The Nineteenth Byte.

Challenge

Calculate the given date and time as a fractional year.

Give all output to five decimal places (you may round in anyway you wish: flooring, ceiling, or true rounding). Shortest code wins.

Bounty

I am offering a 100 rep bounty for the shortest program which also accepts the date in fractional year format and returns the time in YYYY-MM-DD hh:mm format. In essence, your program (or function) has to act in the following way:

f('1914-11-11 11:11') => 1914.86155
f('1914.86155')       => 1914-11-11 11:11
  1. t-clausen.dk - 154 bytes
  2. Neil - 183 bytes

Leaderboard

var QUESTION_ID=88924,OVERRIDE_USER=30525;function answersUrl(e){return"https://api.stackexchange.com/2.2/questions/"+QUESTION_ID+"/answers?page="+e+"&pagesize=100&order=desc&sort=creation&site=codegolf&filter="+ANSWER_FILTER}function commentUrl(e,s){return"https://api.stackexchange.com/2.2/answers/"+s.join(";")+"/comments?page="+e+"&pagesize=100&order=desc&sort=creation&site=codegolf&filter="+COMMENT_FILTER}function getAnswers(){jQuery.ajax({url:answersUrl(answer_page++),method:"get",dataType:"jsonp",crossDomain:!0,success:function(e){answers.push.apply(answers,e.items),answers_hash=[],answer_ids=[],e.items.forEach(function(e){e.comments=[];var s=+e.share_link.match(/\d+/);answer_ids.push(s),answers_hash[s]=e}),e.has_more||(more_answers=!1),comment_page=1,getComments()}})}function getComments(){jQuery.ajax({url:commentUrl(comment_page++,answer_ids),method:"get",dataType:"jsonp",crossDomain:!0,success:function(e){e.items.forEach(function(e){e.owner.user_id===OVERRIDE_USER&&answers_hash[e.post_id].comments.push(e)}),e.has_more?getComments():more_answers?getAnswers():process()}})}function getAuthorName(e){return e.owner.display_name}function process(){var e=[];answers.forEach(function(s){var r=s.body;s.comments.forEach(function(e){OVERRIDE_REG.test(e.body)&&(r="<h1>"+e.body.replace(OVERRIDE_REG,"")+"</h1>")});var a=r.match(SCORE_REG);a&&e.push({user:getAuthorName(s),size:+a[2],language:a[1],link:s.share_link})}),e.sort(function(e,s){var r=e.size,a=s.size;return r-a});var s={},r=1,a=null,n=1;e.forEach(function(e){e.size!=a&&(n=r),a=e.size,++r;var t=jQuery("#answer-template").html();t=t.replace("{{PLACE}}",n+".").replace("{{NAME}}",e.user).replace("{{LANGUAGE}}",e.language).replace("{{SIZE}}",e.size).replace("{{LINK}}",e.link),t=jQuery(t),jQuery("#answers").append(t);var o=e.language;/<a/.test(o)&&(o=jQuery(o).text()),s[o]=s[o]||{lang:e.language,user:e.user,size:e.size,link:e.link}});var t=[];for(var o in s)s.hasOwnProperty(o)&&t.push(s[o]);t.sort(function(e,s){return e.lang>s.lang?1:e.lang<s.lang?-1:0});for(var c=0;c<t.length;++c){var i=jQuery("#language-template").html(),o=t[c];i=i.replace("{{LANGUAGE}}",o.lang).replace("{{NAME}}",o.user).replace("{{SIZE}}",o.size).replace("{{LINK}}",o.link),i=jQuery(i),jQuery("#languages").append(i)}}var ANSWER_FILTER="!t)IWYnsLAZle2tQ3KqrVveCRJfxcRLe",COMMENT_FILTER="!)Q2B_A2kjfAiU78X(md6BoYk",answers=[],answers_hash,answer_ids,answer_page=1,more_answers=!0,comment_page;getAnswers();var SCORE_REG=/<h\d>\s*([^\n,]*[^\s,]),.*?(\d+)(?=[^\n\d<>]*(?:<(?:s>[^\n<>]*<\/s>|[^\n<>]+>)[^\n\d<>]*)*<\/h\d>)/,OVERRIDE_REG=/^Override\s*header:\s*/i;
body{text-align:left!important}#answer-list,#language-list{padding:10px;width:290px;float:left}table thead{font-weight:700}table td{padding:5px}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <link rel="stylesheet" type="text/css" href="//cdn.sstatic.net/codegolf/all.css?v=83c949450c8b"> <div id="answer-list"> <h2>Leaderboard</h2> <table class="answer-list"> <thead> <tr><td></td><td>Author</td><td>Language</td><td>Size</td></tr></thead> <tbody id="answers"> </tbody> </table> </div><div id="language-list"> <h2>Winners by Language</h2> <table class="language-list"> <thead> <tr><td>Language</td><td>User</td><td>Score</td></tr></thead> <tbody id="languages"> </tbody> </table> </div><table style="display: none"> <tbody id="answer-template"> <tr><td>{{PLACE}}</td><td>{{NAME}}</td><td>{{LANGUAGE}}</td><td>{{SIZE}}</td><td><a href="{{LINK}}">Link</a></td></tr></tbody> </table> <table style="display: none"> <tbody id="language-template"> <tr><td>{{LANGUAGE}}</td><td>{{NAME}}</td><td>{{SIZE}}</td><td><a href="{{LINK}}">Link</a></td></tr></tbody> </table>

\$\endgroup\$
9
  • \$\begingroup\$ Perhaps taking the date and time as an input would allow for test cases and easier checking? \$\endgroup\$
    – Luis Mendo
    Aug 5, 2016 at 23:36
  • \$\begingroup\$ I'm a bit confused as to whether this takes into account leap years given For this, assume February has 28 days and the year is 365 days long., but also ... using the Gregorian calendar. \$\endgroup\$ Aug 6, 2016 at 0:04
  • \$\begingroup\$ @DestructibleWatermelon No, it does not take into account leap years \$\endgroup\$
    – Beta Decay
    Aug 6, 2016 at 0:05
  • \$\begingroup\$ can we give more than five decimal places? \$\endgroup\$ Aug 6, 2016 at 0:51
  • \$\begingroup\$ @DestructibleWatermelon No \$\endgroup\$
    – Beta Decay
    Aug 6, 2016 at 9:26

6 Answers 6

5
\$\begingroup\$

JavaScript (ES6), 84 bytes

f=
s=>(Date.parse(1970+s.slice(4).replace(/ /,'T')+'Z')/31536e6+parseInt(s)).toFixed(5)
;
<input placeholder=Input oninput=o.value=f(this.value)><input placeholder=Output id=o>

Assuming no leap years or DST are required, I replace the year with 1970 which makes Date.parse directly return the time offset in milliseconds, of which there were 31536000000 in 1970 (or indeed any other non-leap year). All I need do then is to add back the year and round to 5 decimal places. Edit: Bidirectional conversion in 183 178 bytes:

f=
s=>/:/.test(s,m=31536e6)?(Date.parse(1970+s.slice(4).replace(/ /,'T')+'Z')/m+parseInt(s)).toFixed(5):s.slice(0,4)+new Date(s.slice(4)*m+3e4).toJSON().slice(4,16).replace(/T/,' ')
;
<input placeholder=Input oninput=o.value=f(this.value)><input placeholder=Output id=o>

Assumes strings containing a : are time format, handled as above, otherwise strips off the year, converts to milliseconds, adds half a minute for rounding purposes, converts to a date in 1970, extracts the month, day, hour and minute, and prefixes the original year.

\$\endgroup\$
6
  • \$\begingroup\$ @βετѧΛєҫαγ Yes, s is the argument to the function. \$\endgroup\$
    – Neil
    Aug 6, 2016 at 0:09
  • \$\begingroup\$ Doesn't this give invalid output, because the challenge explicitly asks for Feb to only have 28 days, and 365 days per year total? \$\endgroup\$ Aug 8, 2016 at 14:04
  • \$\begingroup\$ @TimmyD sometimes february has 29 days. Let's assume doesn't mean it is dissallowed to handle leap years correctly \$\endgroup\$ Aug 8, 2016 at 14:51
  • \$\begingroup\$ @t-clausen.dk Sorry, I meant that leap years should not be handled \$\endgroup\$
    – Beta Decay
    Aug 8, 2016 at 15:54
  • \$\begingroup\$ @βετѧΛєҫαγ Now using neither leap years nor DST, and also has a reversible version. Is that satisfactory? \$\endgroup\$
    – Neil
    Aug 8, 2016 at 17:28
2
\$\begingroup\$

Matlab, 132 115 bytes

 d=input('');m=cumsum([-1 '# #"#"##"#"'-4]);year(d)+fix((((m(month(d))+day(d))*24+hour(d))*60+minute(d))/5.256)/10^5

Rounds towards 0.

Hmmm... That's quite long.
That's using some builtins, but not the really helpful ones - because of leap years.

Parsing the string directly gives 2 more byte solution:

d=sscanf(input(''),'%d%[- :]');m=cumsum([-1 '# #"#"##"#"'-4]);d(1)+fix((((m(d(3))+d(5))*24+d(7))*60+d(9))/5.256)/10^5

Newer Matlab versions probably have some better split function, but I'm running on 2012a.

Some bytes saved thanks to @Luis Mendo

\$\endgroup\$
4
  • \$\begingroup\$ You can define the array with [-1 '# #"#"##"#"'-4] to save a few bytes. Are year etc standard functions? If you are using a toolbox you should indicate it in the title, e.g. "Matlab with Financial Toolbox" \$\endgroup\$
    – Luis Mendo
    Aug 9, 2016 at 11:40
  • \$\begingroup\$ @LuisMendo I'm not familiar with the #-notation and never even encountered it. Where can I read more about it (quick Google search didn't help)? I think the year etc. are standard functions, but I don't know if they behave the same outside the toolbox (docs). \$\endgroup\$
    – pajonk
    Aug 9, 2016 at 14:56
  • \$\begingroup\$ It's not special notation. It's just a string with characters that have the appropriate ASCII codes to produce the required numbers. As for the functions, I didn't know, thanks for clarifying. Matlab 15b doesn't seem to have them \$\endgroup\$
    – Luis Mendo
    Aug 9, 2016 at 16:49
  • \$\begingroup\$ Oh, that's clever. \$\endgroup\$
    – pajonk
    Aug 9, 2016 at 17:55
1
\$\begingroup\$

TSQL, 63 bytes

This is a shorter version than my original answer, because my initial answer included calculation for leap year.

DECLARE @ VARCHAR(16) ='1914-11-11 11:11'
    
PRINT LEFT(YEAR(@)+DATEDIFF(mi,0,STUFF(@,1,4,1900))/525600.,10)

Fiddle

The following sql can handle both input types

TSQL, 154 bytes

DECLARE @ VARCHAR(16) ='1914-11-11 11:11'--can be replaced with '1914.86155'

PRINT IIF(isdate(@)=1,LEFT(YEAR(@)+DATEDIFF(mi,0,STUFF(@,1,4,1900))/525600.,10),LEFT(@,4)+RIGHT(CONVERT(CHAR(16),DATEADD(mi,1*RIGHT(@,5)*5.256,0),20),12))

Combined Fiddle 1

Combined Fiddle 2

\$\endgroup\$
1
\$\begingroup\$

PowerShell v3+, 131 bytes

$t=date $args[0];$a=1440*!!(date "2-29-$($t.year)");"{0:F5}"-f((($t.DayOfYear-1)*1440+$t.TimeOfDay.TotalMinutes-$a)/525600+$t.Year)

Takes input $args[0] and converts it with Get-Date to store into .NET [datetime] format, stores into $t. Calculates whether February 29th exists, and sets $a so we subtract out that date's worth of minutes (if present).

Then we use the -format operator with a fixed-string specification of 5 decimal digits to run through the calculation. Uses built-ins to calculate the .DayOfYear and the .TotalMinutes of the given input, subtract $a if it's a leap year, divide by 525600 minutes total, and add on the input .Year.

Will complain (verbosely) to STDERR if February 29th doesn't exist (see examples below), but since STDERR is ignored by default, we don't worry about that. Also, note that PowerShell does banker's rounding by default.

Examples

PS C:\Tools\Scripts\golfing> .\fractional-year.ps1 '2016-08-06 23:48'
2016.59724

PS C:\Tools\Scripts\golfing> .\fractional-year.ps1 '2013-03-03 17:34'
Get-Date : Cannot bind parameter 'Date'. Cannot convert value "2-29-2013" to type "System.DateTime". Error: "String was not recognized as a valid DateTime."
At C:\Tools\Scripts\golfing\fractional-year.ps1:7 char:36
+   $t=date $args[0];$a=1440*!!(date "2-29-$($t.year)");"{0:F5}"-f((($t.DayOfYear- ...
+                                    ~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [Get-Date], ParameterBindingException
    + FullyQualifiedErrorId : CannotConvertArgumentNoMessage,Microsoft.PowerShell.Commands.GetDateCommand

2013.16913

PS C:\Tools\Scripts\golfing> .\fractional-year.ps1 '1914-11-11 11:11'
Get-Date : Cannot bind parameter 'Date'. Cannot convert value "2-29-1914" to type "System.DateTime". Error: "String was not recognized as a valid DateTime."
At C:\Tools\Scripts\golfing\fractional-year.ps1:7 char:36
+   $t=date $args[0];$a=1440*!!(date "2-29-$($t.year)");"{0:F5}"-f((($t.DayOfYear- ...
+                                    ~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [Get-Date], ParameterBindingException
    + FullyQualifiedErrorId : CannotConvertArgumentNoMessage,Microsoft.PowerShell.Commands.GetDateCommand

1914.86155

Alternately, with leap years, 119 bytes

"{0:F5}"-f((($t=date $args[0]).DayOfYear-1+$t.TimeOfDay.TotalMinutes/1440)/(date "12-31-$($t.year)").DayOfYear+$t.Year)

If we follow calendar settings, we can shave off a dozen bytes because we won't need to account for $a in the original code above. This allows us to move $t into the expression itself, and we'll explicitly calculate the .DayOfYear for December 31st. Otherwise, follows the same methodology.

\$\endgroup\$
0
\$\begingroup\$

Python 3, 163 173 bytes

more bytes because of output format

doesn't use any date builtins, like the java answer.

def s(x):z=x[5:10].split("-");v=x[10:].split(":");return int(x[:4])+(sum(([-1,31,28]+[31,30,31,30,31]*2)[:int(z[0])],int(z[1]))*1440+int(v[0])*60+int(v[1]))/525600

slightly more readable

def s(x):
    date=x[5:10].split("-");time=x[10:].split(":")
    return int(x[:4])+(sum(([-1,31,28]+[31,30,31,30,31]*2)[:int(date[0])],int(date[1]))*1440+int(time[0])*60+int(time[1]))/525600
\$\endgroup\$
0
\$\begingroup\$

Perl 6,  107 105  102 bytes

{$/=.comb(/\d+/);($0+(sum(|flat(-1,31,28,(31,30,31,30,31)xx 2)[^$1],$2)*1440+$3*60+$4)/525600).fmt: '%.5f'}
{$/=.comb(/\d+/);fmt $0+(sum(|flat(-1,31,28,(31,30,31,30,31)xx 2)[^$1],$2)*1440+$3*60+$4)/525600: '%.5f'}

{$/=.comb(/\d+/);fmt $0+(flat($2-1,31,28,(31,30,31,30,31)xx 2)[^$1].sum*1440+$3*60+$4)/525600: '%.5f'}
{$/=.comb(/\d+/);round $0+(flat($2-1,31,28,(31,30,31,30,31)xx 2)[^$1].sum*1440+$3*60+$4)/525600,.1⁵}

Explanation:

{
  $/ = .comb(/\d+/); # grab a list of only the digits
  # 「$0」 is short for 「$/[0]」

  fmt

    $0 # year
    +
    (
      flat(
        $2 -1,                     #    days
        31,28,(31,30,31,30,31)xx 2 # -+ months
      )[^$1].sum * 1440            # /
      + $3 * 60                    #    hours
      + $4                         #    minutes
    )
    /
    525600

  : '%.5f' # the 「:」 is used here as 「fmt」 is a method not a subroutine
}

Test:

#! /usr/bin/env perl6
use v6.c;
use Test;

# returns Str
my &fractional-year-a = {$/=.comb(/\d+/);fmt $0+(sum(flat($2-1,31,28,(31,30,31,30,31)xx 2)[^$1])*1440+$3*60+$4)/525600: '%.5f'}
# returns Rat
my &fractional-year-b = {$/=.comb(/\d+/);round $0+(sum(flat($2-1,31,28,(31,30,31,30,31)xx 2)[^$1])*1440+$3*60+$4)/525600,.1⁵}

my @tests = (
  '2016-08-06 23:48' => 2016.59723,
  '2013-03-03 17:34' => 2013.16912,
  '1914-11-11 11:11' => 1914.86155,
);

plan +@tests * 2;

for @tests -> $_ ( :key($input), :value($expected) ) {
  is-approx +fractional-year-a($input), +$expected, 1e-5, "a $input => $expected";
  is-approx +fractional-year-b($input), +$expected, 1e-5, "b $input => $expected";
}
1..6
ok 1 - a 2016-08-06 23:48 => 2016.59723
ok 2 - b 2016-08-06 23:48 => 2016.59723
ok 3 - a 2013-03-03 17:34 => 2013.16912
ok 4 - b 2013-03-03 17:34 => 2013.16912
ok 5 - a 1914-11-11 11:11 => 1914.86155
ok 6 - b 1914-11-11 11:11 => 1914.86155
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.