[NSSound isPlaying] fails to indicate sound termination in Lion

  • I have a problem with some code to monitor when a sound has finished playing; it works fine under Snow Leopard (MacOS 10.6.8) but not under Lion (MacOS 10.7.4).  Since Mountain Lion is still under nondisclosure I will not comment on its behavior there.

    This has been a problem in an existing app for quite a while, but sufficiently minor that I let is slide.  I have a Mac App, "Wraith Scheme", that was built under MacOS 10.6.8, using Xcode 3.2.6.  The app has a rather feline theme, and a cute feature of it is that if you push a button labeled "meow", an image of the face of a cat appears, opens its mouth, says "meow", and then disappears.  The problem is, the *same* binary for this works just fine under Snow Leopard but not under Lion.  Here is the relevant code, with a few lines of instrumentation put in (and I will tell you what the instrumentation produces in just a moment):

            [wraithIconView setImage:wraithIconImageOM]; // "OM" means "Open Mouth".
            [self showIconViewAbruptly:YES];
            [toPlay play];
            while( [toPlay isPlaying] ) {
                NSLog(@"Still playing...");    // Instrumentation for debugging.
                usleep( 100000 );              // Instrumentation for debugging.
                ;
                }
            [wraithIconView setImage:wraithIconImage];
            [self showIconViewAbruptly:YES];

    In this code, "toPlay" is an instance of NSSound initialized to an aif file that takes about half a second to play, and "showIconView abruptly" just sets the alpha of the image to 1 or 0 according to whether the argument is YES or NO.

    All this works like gangbusters under 10.6.8 -- the "NSLog" prints about six times, which is consistent with the length of the sound and the 0.1-second sleep.  But under 10.7.4, The "NSLog" prints forever, or so it seems, even though the sound has played to all intents and purposes normally; that is, the kitty has said "meow" and then stopped making any sound.  Notwithstanding, my app is hung in an infinite loop.

    So nominally, it looks as if possibly [NSSound isPlaying] is not correctly indicating sound termination under Lion, or perhaps the sound is actually not terminating under Lion, or who knows, maybe it is something else.

    [NSSound play] is supposed to be asynchronous, so there should be no problem trying to do other things while the sound is playing.

    I have several options, such as (1) rebuild the whole thing under a later XCode (I will spare you the usual litany of why I didn't do that sooner, but at the moment I am just lazy), (2) thrash randomly (try explicitly dispatching things asynchronously, or just put in something like usleep( 600000 ) with no while loop...), or (3) disable the silly button (but it's cute).  I thought I would first ask here, whether anyone had seen anything like this.  I would like to understand what is going on ...

    I haven't filed a bug report on this because the Apple folks will probably quite properly tell me to go try it under something newer than Xcode 3.2.6, but I am not quite prepared to abandon 3.2.6 just yet.

    I have googled and searched the forums but turned up no leads.

    If anyone is terminally curious, you can download a copy of the app that exhibits this system (works under Snow Leopard, breaks under Lion) -- but without the two lines of instrumentation -- from

        http://jayreynoldsfreeman.com/My/Wraith_Scheme_%2864-bit_version%29.html

    Thank you ...

    --  Jay Reynolds Freeman
    ---------------------
    <Jay_Reynolds_Freeman...>
    http://JayReynoldsFreeman.com (personal web site)
  • On 22 Jul 2012, at 6:12 PM, Jay Reynolds Freeman wrote:

    > [wraithIconView setImage:wraithIconImageOM]; // "OM" means "Open Mouth".
    > [self showIconViewAbruptly:YES];
    > [toPlay play];
    > while( [toPlay isPlaying] ) {
    > NSLog(@"Still playing...");    // Instrumentation for debugging.
    > usleep( 100000 );              // Instrumentation for debugging.
    > ;
    > }
    > [wraithIconView setImage:wraithIconImage];
    > [self showIconViewAbruptly:YES];
    >
    > In this code, "toPlay" is an instance of NSSound initialized to an aif file that takes about half a second to play, and "showIconView abruptly" just sets the alpha of the image to 1 or 0 according to whether the argument is YES or NO.
    >
    > All this works like gangbusters under 10.6.8 -- the "NSLog" prints about six times, which is consistent with the length of the sound and the 0.1-second sleep.  But under 10.7.4, The "NSLog" prints forever, or so it seems, even though the sound has played to all intents and purposes normally; that is, the kitty has said "meow" and then stopped making any sound.  Notwithstanding, my app is hung in an infinite loop.
    >
    > So nominally, it looks as if possibly [NSSound isPlaying] is not correctly indicating sound termination under Lion, or perhaps the sound is actually not terminating under Lion, or who knows, maybe it is something else.
    >
    > [NSSound play] is supposed to be asynchronous, so there should be no problem trying to do other things while the sound is playing.

    Except I don't think you _are_ doing other things while the sound is playing, at least not in the sense that Cocoa means it. Bringing the main thread to a dead halt and not returning to the run loop is asking for trouble. NSSound is within its rights to depend on the main run loop to manage its status events, and apparently that's what it does in Lion.

    The class provides a delegate method (-sound:didFinishPlaying:) to let you know when it's done, to a precision of less than 0.1 seconds, without halting the program. Put your wraith cleanup there.

    Calling any kind of sleep on the main thread means you're doing it wrong. On other threads, it means that you're _probably_ doing it wrong.

    — F

    --
    Fritz Anderson
    Xcode 4 Unleashed: Okay, let me be blunt — why buy just one?
    <http://x4u.manoverboard.org/>
  • The "sleep" was only for instrumentation; without the sleep and the NSLog, the code exhibits the problem described.  And [NSSound play] is supposed to run asynchronously ...

    --  Jay Reynolds Freeman
    ---------------------
    <Jay_Reynolds_Freeman...>
    http://JayReynoldsFreeman.com (personal web site)

    On Jul 22, 2012, at 5:27 PM, Fritz Anderson wrote:

    On 22 Jul 2012, at 6:12 PM, Jay Reynolds Freeman wrote:

    > [wraithIconView setImage:wraithIconImageOM]; // "OM" means "Open Mouth".
    > [self showIconViewAbruptly:YES];
    > [toPlay play];
    > while( [toPlay isPlaying] ) {
    > NSLog(@"Still playing...");    // Instrumentation for debugging.
    > usleep( 100000 );              // Instrumentation for debugging.
    > ;
    > }
    > [wraithIconView setImage:wraithIconImage];
    > [self showIconViewAbruptly:YES];
    >
    > In this code, "toPlay" is an instance of NSSound initialized to an aif file that takes about half a second to play, and "showIconView abruptly" just sets the alpha of the image to 1 or 0 according to whether the argument is YES or NO.
    >
    > All this works like gangbusters under 10.6.8 -- the "NSLog" prints about six times, which is consistent with the length of the sound and the 0.1-second sleep.  But under 10.7.4, The "NSLog" prints forever, or so it seems, even though the sound has played to all intents and purposes normally; that is, the kitty has said "meow" and then stopped making any sound.  Notwithstanding, my app is hung in an infinite loop.
    >
    > So nominally, it looks as if possibly [NSSound isPlaying] is not correctly indicating sound termination under Lion, or perhaps the sound is actually not terminating under Lion, or who knows, maybe it is something else.
    >
    > [NSSound play] is supposed to be asynchronous, so there should be no problem trying to do other things while the sound is playing.

    Except I don't think you _are_ doing other things while the sound is playing, at least not in the sense that Cocoa means it. Bringing the main thread to a dead halt and not returning to the run loop is asking for trouble. NSSound is within its rights to depend on the main run loop to manage its status events, and apparently that's what it does in Lion.

    The class provides a delegate method (-sound:didFinishPlaying:) to let you know when it's done, to a precision of less than 0.1 seconds, without halting the program. Put your wraith cleanup there.

    Calling any kind of sleep on the main thread means you're doing it wrong. On other threads, it means that you're _probably_ doing it wrong.

    — F

    --
    Fritz Anderson
    Xcode 4 Unleashed: Okay, let me be blunt — why buy just one?
    <http://x4u.manoverboard.org/>
  • On Jul 22, 2012, at 5:43 PM, Jay Reynolds Freeman <jay_reynolds_freeman...> wrote:

    > The "sleep" was only for instrumentation; without the sleep and the NSLog, the code exhibits the problem described.  And [NSSound play] is supposed to run asynchronously ...
    >
    > --  Jay Reynolds Freeman
    > ---------------------
    > <Jay_Reynolds_Freeman...>
    > http://JayReynoldsFreeman.com (personal web site)
    >
    >
    >
    > On Jul 22, 2012, at 5:27 PM, Fritz Anderson wrote:
    >
    > On 22 Jul 2012, at 6:12 PM, Jay Reynolds Freeman wrote:
    >
    >> [wraithIconView setImage:wraithIconImageOM]; // "OM" means "Open Mouth".
    >> [self showIconViewAbruptly:YES];
    >> [toPlay play];
    >> while( [toPlay isPlaying] ) {
    >> NSLog(@"Still playing...");    // Instrumentation for debugging.
    >> usleep( 100000 );              // Instrumentation for debugging.
    >> ;
    >> }
    >> [wraithIconView setImage:wraithIconImage];
    >> [self showIconViewAbruptly:YES];
    >>
    >> In this code, "toPlay" is an instance of NSSound initialized to an aif file that takes about half a second to play, and "showIconView abruptly" just sets the alpha of the image to 1 or 0 according to whether the argument is YES or NO.
    >>
    >> All this works like gangbusters under 10.6.8 -- the "NSLog" prints about six times, which is consistent with the length of the sound and the 0.1-second sleep.  But under 10.7.4, The "NSLog" prints forever, or so it seems, even though the sound has played to all intents and purposes normally; that is, the kitty has said "meow" and then stopped making any sound.  Notwithstanding, my app is hung in an infinite loop.
    >>
    >> So nominally, it looks as if possibly [NSSound isPlaying] is not correctly indicating sound termination under Lion, or perhaps the sound is actually not terminating under Lion, or who knows, maybe it is something else.
    >>
    >> [NSSound play] is supposed to be asynchronous, so there should be no problem trying to do other things while the sound is playing.
    >
    > Except I don't think you _are_ doing other things while the sound is playing, at least not in the sense that Cocoa means it. Bringing the main thread to a dead halt and not returning to the run loop is asking for trouble. NSSound is within its rights to depend on the main run loop to manage its status events, and apparently that's what it does in Lion.
    >
    > The class provides a delegate method (-sound:didFinishPlaying:) to let you know when it's done, to a precision of less than 0.1 seconds, without halting the program. Put your wraith cleanup there.
    >
    > Calling any kind of sleep on the main thread means you're doing it wrong. On other threads, it means that you're _probably_ doing it wrong.
    >
    > — F
    >
    > --
    > Fritz Anderson
    > Xcode 4 Unleashed: Okay, let me be blunt — why buy just one?
    > <http://x4u.manoverboard.org/
  • On Jul 22, 2012, at 5:43 PM, Jay Reynolds Freeman <jay_reynolds_freeman...> wrote:

    > The "sleep" was only for instrumentation; without the sleep and the NSLog, the code exhibits the problem described.  And [NSSound play] is supposed to run asynchronously ...
    >

    Asynchronous APIs usually depend on the run loop of the calling thread to be running normally.

    --Kyle Sluder
  • The essential thing you showed in your code is that you went into a loop while waiting for isPlaying to turn to NO, or at least never returned from your action method back to AppKit. I don't hear you saying otherwise.

    If that's what you're doing, bear in mind that other APIs (I'm thinking of Foundation networking; I can all but guarantee that NSSound is the same) are "supposed to run asynchronously," but that means that you can — you _must_ — return from the event that started the network transaction, all the way back to AppKit; and _must not_ block waiting for completion.

    It's the run loop, and not your code, that detects completion. The completion notifications will not arrive unless control returns to an AppKit/UIKit runloop. You get the notification through the delegate method. It's not like spawning a thread and waiting for it to clear a flag.

    — F

    On 22 Jul 2012, at 7:43 PM, Jay Reynolds Freeman wrote:

    > The "sleep" was only for instrumentation; without the sleep and the NSLog, the code exhibits the problem described.  And [NSSound play] is supposed to run asynchronously ...
  • On Jul 22, 2012, at 7:43 PM, Jay Reynolds Freeman wrote:

    > The "sleep" was only for instrumentation; without the sleep and the NSLog, the code exhibits the problem described.  And [NSSound play] is supposed to run asynchronously ...

    You didn't pay attention to what Fritz wrote.  You need to allow flow of execution to leave your code and return to the framework, so that the framework can run the main event loop.  In all probability, NSSound needs to receive an external notification or event to realize that the sound has finished playing.  It probably receives that notification/event via a run-loop source scheduled on the main thread's run loop.

    There's no good reason for you to loop, polling for the sound's completion.  Use the asynchronously-delivered delegate method, as Fritz suggested.

    Regards,
    Ken
  • What Ken, Fritz and Kyle are saying makes sense, but flies a little bit in the face of what "asynchronous" often means.  (I won't quibble if Apple is doing it their own way, they often do.)  It also doesn't explain why the same linked executable behaves one way under Snow Leopard and another way under Lion, and the documentation for [NSSound isPlaying] is written in a way that suggests the caller of that method is talking directly to the receiver (an instance of NSSound), not a message center somewhere; there is no hint that it cannot be used anywhere.)

    I appreciate the advice and will play some more, but I would be obliged if someone at Apple would be willing to comment on what is going on (not clear if Ken, Kyle, or Fritz are Apple folks), and if anyone has any pointers to Apple documentation about what "asynchronous" means to them, I would be obliged.

    Since my app has nothing to do while waiting for the sound to play, a polling loop on "isPlaying" is actually a good deal less code (only two lines) than setting up a delegate with "-sound:didFinishPlaying" implemented.

    --  Jay Reynolds Freeman
    ---------------------
    <Jay_Reynolds_Freeman...>
    http://JayReynoldsFreeman.com (personal web site)
  • On Jul 22, 2012, at 8:43 PM, Jay Reynolds Freeman wrote:

    > What Ken, Fritz and Kyle are saying makes sense, but flies a little bit in the face of what "asynchronous" often means.  (I won't quibble if Apple is doing it their own way, they often do.)  It also doesn't explain why the same linked executable behaves one way under Snow Leopard and another way under Lion, and the documentation for [NSSound isPlaying] is written in a way that suggests the caller of that method is talking directly to the receiver (an instance of NSSound), not a message center somewhere; there is no hint that it cannot be used anywhere.)

    The reason is that you are relying on undefined behavior. When you do that, results may change from release to release.

    > I appreciate the advice and will play some more, but I would be obliged if someone at Apple would be willing to comment on what is going on (not clear if Ken, Kyle, or Fritz are Apple folks), and if anyone has any pointers to Apple documentation about what "asynchronous" means to them, I would be obliged.

    For just about all of Apple’s asynchronous APIs, you can assume that they will require the main event loop to keep running. In general, it is bad UI to beachball the app for seconds at a time anyway.

    > Since my app has nothing to do while waiting for the sound to play, a polling loop on "isPlaying" is actually a good deal less code (only two lines) than setting up a delegate with "-sound:didFinishPlaying" implemented.

    Oh, I dunno about that. It takes one line to set up the delegate:

    [someSound setDelegate:self];

    and then one more line later on in the object (not counting the braces, since you’re not counting those in the sleep loop example anyway):

    - (void)sound:(NSSound *)sound didFinishPlaying:(BOOL)finishedPlaying {
    ...
    }

    Looks about equivalent to me in terms of length.

    Charles
  • On Jul 22, 2012, at 6:43 PM, Jay Reynolds Freeman <jay_reynolds_freeman...> wrote:

    > Since my app has nothing to do while waiting for the sound to play, a polling loop on "isPlaying" is actually a good deal less code (only two lines) than setting up a delegate with "-sound:didFinishPlaying" implemented.

    It may be less code, but it's bad practice. It locks up the thread, preventing the runloop from performing other activities, and it consumes a lot of CPU unless you put in a sleep call.

    If you really have to block, use a loop that spins the runloop, like this:

            while (!readyToStop()) {
                if (![[NSRunLoop currentRunLoop] runMode: someCustomRunLoopMode
                                              beforeDate: [NSDate distantFuture]])
                    break;
            }

    —Jens
  • On Jul 22, 2012, at 9:24 PM, Jens Alfke wrote:

    > On Jul 22, 2012, at 6:43 PM, Jay Reynolds Freeman <jay_reynolds_freeman...> wrote:
    >
    >> Since my app has nothing to do while waiting for the sound to play, a polling loop on "isPlaying" is actually a good deal less code (only two lines) than setting up a delegate with "-sound:didFinishPlaying" implemented.
    >
    > It may be less code, but it's bad practice. It locks up the thread, preventing the runloop from performing other activities, and it consumes a lot of CPU unless you put in a sleep call.
    >
    > If you really have to block, use a loop that spins the runloop, like this:
    >
    > while (!readyToStop()) {
    > if (![[NSRunLoop currentRunLoop] runMode: someCustomRunLoopMode
    > beforeDate: [NSDate distantFuture]])
    > break;
    > }

    Note that this case is not one where you “really have to block.” Don’t use the code above; just use the handy delegate method that Apple’s engineers were thoughtful enough to provide for you. The difference in the length of code is minimal.

    Charles
  • On Jul 22, 2012, at 9:24 PM, Jens Alfke wrote:

    > On Jul 22, 2012, at 6:43 PM, Jay Reynolds Freeman <jay_reynolds_freeman...> wrote:
    >
    >> Since my app has nothing to do while waiting for the sound to play, a polling loop on "isPlaying" is actually a good deal less code (only two lines) than setting up a delegate with "-sound:didFinishPlaying" implemented.
    >
    > It may be less code, but it's bad practice. It locks up the thread, preventing the runloop from performing other activities, and it consumes a lot of CPU unless you put in a sleep call.
    >
    > If you really have to block, use a loop that spins the runloop, like this:
    >
    > while (!readyToStop()) {
    > if (![[NSRunLoop currentRunLoop] runMode: someCustomRunLoopMode
    > beforeDate: [NSDate distantFuture]])
    > break;
    > }

    Actually, this defeats the purpose.  By specifying a custom run loop mode, rather than the default run loop mode, you are *not* providing an opportunity for any framework run loop sources to be serviced.  They could not possibly be scheduled on your custom run loop mode.

    Regards,
    Ken
  • I guess my main problem with all this is that Apple provides [NSSound isPlaying] with no indication that the method is not general-purpose; reading the documentation would have you think that it would return a correct answer no matter when and how used.

    --  Jay Reynolds Freeman
    ---------------------
    <Jay_Reynolds_Freeman...>
    http://JayReynoldsFreeman.com (personal web site)
  • >
    > Since my app has nothing to do while waiting for the sound to play

    I struggled with this when getting into iOS programming and networking;
    everything's asynchronous, and you can't just have your UI locked up.  So
    what is my app to do while it's waiting?  It's a fundamental programming
    problem that you just have to start dealing with.  If you were dependent on
    a network, it would be even more pervasive, because you never know when (or
    if) something's going to finish.

    Your best bet is to tackle this now and get used to planning for it in the
    future.  It does get easier, but can still be tedious.
  • On Jul 22, 2012, at 7:52 PM, Jay Reynolds Freeman <jay_reynolds_freeman...> wrote:

    > I guess my main problem with all this is that Apple provides [NSSound isPlaying] with no indication that the method is not general-purpose; reading the documentation would have you think that it would return a correct answer no matter when and how used.

    It's not an issue with -isPlaying. It's a question of whether the implementation of NSSound can still function properly when the runloop is hung (by your infinite loop). In 10.6 it could, in 10.7 it can't. The details are of course internal to NSSound's implementation, but it's an example of why you shouldn't lock up the runloop when you don't need to.

    —Jens
  • On Jul 22, 2012, at 9:29 PM, Gavin Stokes <stokestack...> wrote:

    > I struggled with this when getting into iOS programming and networking;
    > everything's asynchronous, and you can't just have your UI locked up.  So
    > what is my app to do while it's waiting?

    Nothing. Just return from your event handler or whatever, so the UI can remain responsive. Then when you're told the operation finished, do whatever you need to do when it finishes.

    Asynchronous programming means you'll be called back when things happen, and in the meantime you should return control so other stuff can run. It doesn't mean that you should go into an infinite loop trying to force something async to run synchronously.

    —Jens
  • >
    > Asynchronous programming means you'll be called back when things happen,
    > and in the meantime you should return control so other stuff can run. It
    > doesn't mean that you should go into an infinite loop trying to force
    > something async to run synchronously.
    >

    That question was rhetorical.  The point is that, for a newcomer to this
    kind of programming, it can be perplexing to figure out what the "other
    stuff" is that can run, when in fact the entire state of the application
    may be invalid until some asynchronous process returns a result.

    For example, I have an app that literally will not function unless it can
    make a network connection; it is useless without one.  But I don't freeze
    the whole thing waiting for a response; I display a status message.

    iCloud is another good example, since you really have no idea what it's
    doing or if it will ever get back to you.  Apple says it's a good place to
    store things like what page of an article your user was reading on another
    device, so he can start right there on a different device.  But you have no
    idea when or if that key/value pair will be returned from iCloud, so your
    application must do something in the meantime at startup.

    It seems obvious after you've dealt with this a while, but at first it's a
    hurdle.
  • Using a delegate with the call-back method did indeed solve the problem.  I have filed a bug report about this one, because the documentation for -NSSound isPlaying is simply wrong: That method does not always return a valid answer under 10.7.4.  (And because it did work as documented under 10.6.8, I now have an app in the field that will break when users run it under Lion.  Apple changed functionality in a non-backward compatible manner without even documenting the new behavior.)

    --  Jay Reynolds Freeman
    ---------------------
    <Jay_Reynolds_Freeman...>
    http://JayReynoldsFreeman.com (personal web site)
  • On Jul 23, 2012, at 2:53 AM, Jay Reynolds Freeman wrote:

    > Using a delegate with the call-back method did indeed solve the problem.  I have filed a bug report about this one, because the documentation for -NSSound isPlaying is simply wrong: That method does not always return a valid answer under 10.7.4.  (And because it did work as documented under 10.6.8, I now have an app in the field that will break when users run it under Lion.  Apple changed functionality in a non-backward compatible manner without even documenting the new behavior.)

    If you’re going to write a bug report against every asynchronous Apple API that doesn’t work properly if the main thread is blocked, you’re going to be writing a lot of bug reports.

    Charles
  • On Jul 23, 2012, at 3:27 AM, Jay Reynolds Freeman wrote:

    > If it had never worked properly I would have found and fixed the problem during testing under Snow Leopard.  The problem is that the 10.6 implementation worked *exactly* as the documentation specifies, but the 10.7 one doesn't.  My code didn't rely on undocumented behavior, it relied precisely on what was documented.  :-)

    The documentation doesn’t say that you can block the main thread and still use the async API. In fact, it doesn’t even say that it will spawn a new thread at all. Of course in practice it would probably use a thread, but strictly going by what’s in the documentation, it could theoretically do all the processing in tiny little slivers on each iteration of the run loop or on an NSTimer on the main thread and still follow that API contract to a tee. Your code is making assumptions about what the API is doing, and this is what has come back to bite you as the undocumented behavior has changed.

    If you think about it, it’s not hard to figure out ways that an asynchronous API could fail if the main thread is blocked:

    1. If the async API doesn’t use a thread at all, obviously it will fail if the thread is blocked.

    2. If the async API calls through to some other, lower-level async API, and *that* API spawns the thread, and the higher-level async API uses either a timer or a notification to detect when the lower-level API finishes, then the higher-level API will never detect the process finishing if the main thread is blocked.

    3. If the async API relies on timers or notifications for anything else, it will fail if the main thread is blocked.

    4. If the async API uses dispatch_[a]sync, +[NSOperationQueue mainQueue], -[NSObject performSelectorOnMainThread:withObject:waitUntilDone:], or something similar to use a non-thread-safe API as part of what it does, that won’t happen as long as the main thread is blocked.

    5. I’ve written async APIs which used the techniques in #4 to set their “finished” properties on the main thread at the end, so that the properties can be observed via KVC without causing all kinds of non-main-thread calls to UI elements. If any API does this, it won’t happen if the main thread is blocked.

    Etc., etc. The moral: don’t block the main thread. No Apple asynchronous API is guaranteed to work when you do so. Mac users tend to associate the spinning beachball with a hung app anyways (note how it’s commonly referred to as the beachball of *death*), and if I’m informed correctly, iOS will actually automatically kill any app whose main thread is blocked for a certain amount of time. Either way, it’s bad mojo.

    FWIW, I just built a little test app out of curiosity. In my testing, it works exactly the same on Snow Leopard as it does on Lion — the isPlaying property stays true forever if the main thread is blocked — so perhaps even the fact that it worked for you on 10.6 may have been due to some peculiarity of your system. At any rate, it certainly is not something that can be relied upon.

    Charles
previous month july 2012 next month
MTWTFSS
            1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31          
Go to today